package oauth import ( "context" "fmt" "git.0x7f.app/WOJ/woj-server/internal/e" "git.0x7f.app/WOJ/woj-server/internal/model" "git.0x7f.app/WOJ/woj-server/internal/service/user" "github.com/gin-gonic/gin" "net/http" ) // CallbackHandler // @Summary Callback with OAuth2 // @Description Callback endpoint from OAuth2 // @Tags oauth // @Produce json // @Router /oauth/callback [get] func (h *handler) CallbackHandler() gin.HandlerFunc { return func(c *gin.Context) { // Extract key from cookie key, err := c.Cookie(oauthStateCookieName) if err != nil { e.Pong[any](c, e.InvalidParameter, nil) return } // Get state from redis key = fmt.Sprintf(oauthStateKey, key) expected, err := h.cache.Get().Get(context.Background(), key).Result() if err != nil { e.Pong[any](c, e.RedisError, nil) return } // Whether state is valid, delete it h.cache.Get().Unlink(context.Background(), key) // Verify state if c.Query("state") != expected { e.Pong[any](c, e.OAuthStateMismatch, nil) return } // Exchange code for token token, err := h.conf.Exchange(context.Background(), c.Query("code")) if err != nil { e.Pong[any](c, e.OAuthExchangeFailed, nil) return } // Extract the ID Token from OAuth2 token. raw, ok := token.Extra("id_token").(string) if !ok { e.Pong[any](c, e.OAuthExchangeFailed, nil) return } // Parse and verify ID Token payload. idToken, err := h.verifier.Verify(context.Background(), raw) if err != nil { e.Pong[any](c, e.OAuthVerifyFailed, nil) return } // Extract custom claims // TODO: extract role from claims var claims struct { Email string `json:"email"` EmailVerified bool `json:"email_verified"` Nickname string `json:"preferred_username"` Role string `json:"role"` } if err := idToken.Claims(&claims); err != nil { e.Pong[any](c, e.OAuthGetClaimsFailed, nil) return } if !claims.EmailVerified || claims.Email == "" || claims.Nickname == "" { e.Pong[any](c, e.UserInvalid, nil) return } // Check user existence u, status := h.user.ProfileOrCreate(&user.CreateData{Email: claims.Email, NickName: claims.Nickname}) if status != e.Success { e.Pong[any](c, status, nil) return } // Increment user version version, status := h.user.IncrVersion(u.ID) if status != e.Success { e.Pong[any](c, status, nil) return } // Sign JWT token claim := &model.Claim{ UID: u.ID, Role: u.Role, Version: version, } jwt, status := h.jwt.SignClaim(claim) if status != e.Success { e.Pong[any](c, status, nil) return } // TODO: Figure out a better way to cooperate with frontend c.Redirect(http.StatusFound, "/login?redirect_token="+jwt) } }