// Copyright (C) 2024 veypi // 2025-03-04 16:08:06 // Distributed under terms of the MIT license. package oauth import ( "time" "github.com/google/uuid" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/cache" "github.com/veypi/vbase/models" "github.com/veypi/vigo" ) type TokenRequest struct { GrantType string `json:"grant_type" src:"form" desc:"授权类型"` Code string `json:"code" src:"form" desc:"授权码"` RefreshToken string `json:"refresh_token" src:"form" desc:"刷新令牌"` ClientID string `json:"client_id" src:"form" desc:"客户端ID"` ClientSecret string `json:"client_secret" src:"form" desc:"客户端密钥"` } type TokenResponse struct { AccessToken string `json:"access_token"` TokenType string `json:"token_type"` ExpiresIn int `json:"expires_in"` RefreshToken string `json:"refresh_token,omitempty"` Scope string `json:"scope,omitempty"` } func token(x *vigo.X, req *TokenRequest) (*TokenResponse, error) { switch req.GrantType { case "authorization_code": return handleAuthorizationCode(req) case "refresh_token": return handleRefreshToken(req) default: return nil, vigo.ErrInvalidArg.WithString("unsupported grant type") } } func handleAuthorizationCode(req *TokenRequest) (*TokenResponse, error) { // 验证客户端 var client models.OAuthClient if err := cfg.DB().First(&client, "client_id = ?", req.ClientID).Error; err != nil { return nil, vigo.ErrUnauthorized.WithString("invalid client") } // 验证授权码 var authData map[string]any if err := cache.GetObject(cache.OAuthCodeKey(req.Code), &authData); err != nil { return nil, vigo.ErrUnauthorized.WithString("invalid or expired code") } // 删除已使用的授权码 cache.Delete(cache.OAuthCodeKey(req.Code)) userID := authData["user_id"].(string) scope := authData["scope"].(string) // 生成访问令牌 accessToken := uuid.New().String() refreshToken := uuid.New().String() expiresAt := time.Now().Add(cfg.Config.OAuth.AccessExpiry) // 保存令牌 token := &models.OAuthToken{ ClientID: req.ClientID, UserID: userID, AccessToken: accessToken, RefreshToken: refreshToken, Scope: scope, ExpiresAt: expiresAt, } if err := cfg.DB().Create(token).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } return &TokenResponse{ AccessToken: accessToken, TokenType: "Bearer", ExpiresIn: int(cfg.Config.OAuth.AccessExpiry.Seconds()), RefreshToken: refreshToken, Scope: scope, }, nil } func handleRefreshToken(req *TokenRequest) (*TokenResponse, error) { // 查找刷新令牌 var token models.OAuthToken if err := cfg.DB().First(&token, "refresh_token = ?", req.RefreshToken).Error; err != nil { return nil, vigo.ErrUnauthorized.WithString("invalid refresh token") } if token.Revoked { return nil, vigo.ErrUnauthorized.WithString("token has been revoked") } // 生成新的访问令牌 accessToken := uuid.New().String() newRefreshToken := uuid.New().String() expiresAt := time.Now().Add(cfg.Config.OAuth.AccessExpiry) // 使旧令牌失效 cfg.DB().Model(&token).Updates(map[string]any{ "revoked": true, }) // 保存新令牌 newToken := &models.OAuthToken{ ClientID: token.ClientID, UserID: token.UserID, AccessToken: accessToken, RefreshToken: newRefreshToken, Scope: token.Scope, ExpiresAt: expiresAt, } if err := cfg.DB().Create(newToken).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } return &TokenResponse{ AccessToken: accessToken, TokenType: "Bearer", ExpiresIn: int(cfg.Config.OAuth.AccessExpiry.Seconds()), RefreshToken: newRefreshToken, Scope: token.Scope, }, nil }