You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/api/oauth/token.go

132 lines
3.6 KiB
Go

// Copyright (C) 2024 veypi <i@veypi.com>
// 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.ErrTokenInvalid
}
if token.Revoked {
return nil, vigo.ErrTokenInvalid
}
// 生成新的访问令牌
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
}