mirror of https://github.com/veypi/OneAuth.git
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.
132 lines
3.5 KiB
Go
132 lines
3.5 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(time.Hour)
|
|
|
|
// 保存令牌
|
|
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(time.Hour.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(time.Hour)
|
|
|
|
// 使旧令牌失效
|
|
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(time.Hour.Seconds()),
|
|
RefreshToken: newRefreshToken,
|
|
Scope: token.Scope,
|
|
}, nil
|
|
}
|