2024-01-03 00:55:41 +08:00
|
|
|
package oauth
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2024-01-03 14:10:44 +08:00
|
|
|
"fmt"
|
2024-01-03 00:55:41 +08:00
|
|
|
"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"
|
2024-01-05 15:47:29 +08:00
|
|
|
"net/http"
|
2024-01-03 00:55:41 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
// CallbackHandler
|
|
|
|
// @Summary Callback with OAuth2
|
|
|
|
// @Description Callback endpoint from OAuth2
|
|
|
|
// @Tags oauth
|
|
|
|
// @Produce json
|
|
|
|
// @Router /oauth/callback [get]
|
2024-01-05 00:57:43 +08:00
|
|
|
func (h *handler) CallbackHandler() gin.HandlerFunc {
|
2024-01-03 00:55:41 +08:00
|
|
|
return func(c *gin.Context) {
|
2024-01-03 14:10:44 +08:00
|
|
|
// Extract key from cookie
|
|
|
|
key, err := c.Cookie(oauthStateCookieName)
|
2024-01-03 00:55:41 +08:00
|
|
|
if err != nil {
|
|
|
|
e.Pong[any](c, e.InvalidParameter, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-03 14:10:44 +08:00
|
|
|
// Get state from redis
|
|
|
|
key = fmt.Sprintf(oauthStateKey, key)
|
2024-01-05 00:57:43 +08:00
|
|
|
expected, err := h.cache.Get().Get(context.Background(), key).Result()
|
2024-01-03 14:10:44 +08:00
|
|
|
if err != nil {
|
|
|
|
e.Pong[any](c, e.RedisError, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Whether state is valid, delete it
|
2024-01-05 00:57:43 +08:00
|
|
|
h.cache.Get().Unlink(context.Background(), key)
|
2024-01-03 14:10:44 +08:00
|
|
|
|
|
|
|
// Verify state
|
|
|
|
if c.Query("state") != expected {
|
2024-01-03 00:55:41 +08:00
|
|
|
e.Pong[any](c, e.OAuthStateMismatch, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exchange code for token
|
2024-01-05 00:57:43 +08:00
|
|
|
token, err := h.conf.Exchange(context.Background(), c.Query("code"))
|
2024-01-03 00:55:41 +08:00
|
|
|
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.
|
2024-01-05 00:57:43 +08:00
|
|
|
idToken, err := h.verifier.Verify(context.Background(), raw)
|
2024-01-03 00:55:41 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2024-01-03 14:10:44 +08:00
|
|
|
// Check user existence
|
2024-01-05 14:31:09 +08:00
|
|
|
u, status := h.user.ProfileOrCreate(&user.CreateData{Email: claims.Email, NickName: claims.Nickname})
|
2024-01-03 00:55:41 +08:00
|
|
|
if status != e.Success {
|
|
|
|
e.Pong[any](c, status, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-03 14:10:44 +08:00
|
|
|
// Increment user version
|
2024-01-05 00:57:43 +08:00
|
|
|
version, status := h.user.IncrVersion(u.ID)
|
2024-01-03 00:55:41 +08:00
|
|
|
if status != e.Success {
|
|
|
|
e.Pong[any](c, status, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-03 14:10:44 +08:00
|
|
|
// Sign JWT token
|
2024-01-03 00:55:41 +08:00
|
|
|
claim := &model.Claim{
|
|
|
|
UID: u.ID,
|
|
|
|
Role: u.Role,
|
|
|
|
Version: version,
|
|
|
|
}
|
2024-01-05 00:57:43 +08:00
|
|
|
jwt, status := h.jwt.SignClaim(claim)
|
2024-01-03 00:55:41 +08:00
|
|
|
if status != e.Success {
|
|
|
|
e.Pong[any](c, status, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-01-05 15:47:29 +08:00
|
|
|
// TODO: Figure out a better way to cooperate with frontend
|
|
|
|
c.Redirect(http.StatusFound, "/login?redirect_token="+jwt)
|
2024-01-03 00:55:41 +08:00
|
|
|
}
|
|
|
|
}
|