feat(backend): 添加 OAuth 能力查询接口,改进 OAuth 客户端选择逻辑
Handler 改进: - 添加 GET /api/v1/admin/gemini/oauth/capabilities 接口 - 简化 GenerateAuthURL,redirect_uri 由服务层决定 Repository 改进: - ExchangeCode/RefreshToken 根据 oauthType 选择正确的 OAuth 客户端 - Code Assist 始终使用内置客户端,AI Studio 使用用户配置的客户端
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/geminicli"
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
|
||||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
|
||||||
@@ -19,6 +18,12 @@ func NewGeminiOAuthHandler(geminiOAuthService *service.GeminiOAuthService) *Gemi
|
|||||||
return &GeminiOAuthHandler{geminiOAuthService: geminiOAuthService}
|
return &GeminiOAuthHandler{geminiOAuthService: geminiOAuthService}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET /api/v1/admin/gemini/oauth/capabilities
|
||||||
|
func (h *GeminiOAuthHandler) GetCapabilities(c *gin.Context) {
|
||||||
|
cfg := h.geminiOAuthService.GetOAuthConfig()
|
||||||
|
response.Success(c, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
type GeminiGenerateAuthURLRequest struct {
|
type GeminiGenerateAuthURLRequest struct {
|
||||||
ProxyID *int64 `json:"proxy_id"`
|
ProxyID *int64 `json:"proxy_id"`
|
||||||
ProjectID string `json:"project_id"`
|
ProjectID string `json:"project_id"`
|
||||||
@@ -46,12 +51,9 @@ func (h *GeminiOAuthHandler) GenerateAuthURL(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always pass the "hosted" callback URI; the OAuth service may override it depending on
|
||||||
|
// oauth_type and whether the built-in Gemini CLI OAuth client is used.
|
||||||
redirectURI := deriveGeminiRedirectURI(c)
|
redirectURI := deriveGeminiRedirectURI(c)
|
||||||
if oauthType == "ai_studio" {
|
|
||||||
// AI Studio OAuth uses a localhost redirect URI to support the "copy/paste callback URL"
|
|
||||||
// flow (no server-side callback endpoint needed).
|
|
||||||
redirectURI = geminicli.AIStudioOAuthRedirectURI
|
|
||||||
}
|
|
||||||
result, err := h.geminiOAuthService.GenerateAuthURL(c.Request.Context(), req.ProxyID, redirectURI, req.ProjectID, oauthType)
|
result, err := h.geminiOAuthService.GenerateAuthURL(c.Request.Context(), req.ProxyID, redirectURI, req.ProjectID, oauthType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := err.Error()
|
msg := err.Error()
|
||||||
|
|||||||
@@ -25,15 +25,23 @@ func NewGeminiOAuthClient(cfg *config.Config) service.GeminiOAuthClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *geminiOAuthClient) ExchangeCode(ctx context.Context, code, codeVerifier, redirectURI, proxyURL string) (*geminicli.TokenResponse, error) {
|
func (c *geminiOAuthClient) ExchangeCode(ctx context.Context, oauthType, code, codeVerifier, redirectURI, proxyURL string) (*geminicli.TokenResponse, error) {
|
||||||
client := createGeminiReqClient(proxyURL)
|
client := createGeminiReqClient(proxyURL)
|
||||||
|
|
||||||
// Both Code Assist and AI Studio OAuth use the same token endpoint and OAuth client.
|
// Use different OAuth clients based on oauthType:
|
||||||
oauthCfg, err := geminicli.EffectiveOAuthConfig(geminicli.OAuthConfig{
|
// - code_assist: always use built-in Gemini CLI OAuth client (public)
|
||||||
|
// - ai_studio: requires a user-provided OAuth client
|
||||||
|
oauthCfgInput := geminicli.OAuthConfig{
|
||||||
ClientID: c.cfg.Gemini.OAuth.ClientID,
|
ClientID: c.cfg.Gemini.OAuth.ClientID,
|
||||||
ClientSecret: c.cfg.Gemini.OAuth.ClientSecret,
|
ClientSecret: c.cfg.Gemini.OAuth.ClientSecret,
|
||||||
Scopes: c.cfg.Gemini.OAuth.Scopes,
|
Scopes: c.cfg.Gemini.OAuth.Scopes,
|
||||||
}, "code_assist")
|
}
|
||||||
|
if oauthType == "code_assist" {
|
||||||
|
oauthCfgInput.ClientID = ""
|
||||||
|
oauthCfgInput.ClientSecret = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
oauthCfg, err := geminicli.EffectiveOAuthConfig(oauthCfgInput, oauthType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -61,15 +69,20 @@ func (c *geminiOAuthClient) ExchangeCode(ctx context.Context, code, codeVerifier
|
|||||||
return &tokenResp, nil
|
return &tokenResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *geminiOAuthClient) RefreshToken(ctx context.Context, refreshToken, proxyURL string) (*geminicli.TokenResponse, error) {
|
func (c *geminiOAuthClient) RefreshToken(ctx context.Context, oauthType, refreshToken, proxyURL string) (*geminicli.TokenResponse, error) {
|
||||||
client := createGeminiReqClient(proxyURL)
|
client := createGeminiReqClient(proxyURL)
|
||||||
|
|
||||||
// Both Code Assist and AI Studio OAuth use the same token endpoint and OAuth client.
|
oauthCfgInput := geminicli.OAuthConfig{
|
||||||
oauthCfg, err := geminicli.EffectiveOAuthConfig(geminicli.OAuthConfig{
|
|
||||||
ClientID: c.cfg.Gemini.OAuth.ClientID,
|
ClientID: c.cfg.Gemini.OAuth.ClientID,
|
||||||
ClientSecret: c.cfg.Gemini.OAuth.ClientSecret,
|
ClientSecret: c.cfg.Gemini.OAuth.ClientSecret,
|
||||||
Scopes: c.cfg.Gemini.OAuth.Scopes,
|
Scopes: c.cfg.Gemini.OAuth.Scopes,
|
||||||
}, "code_assist")
|
}
|
||||||
|
if oauthType == "code_assist" {
|
||||||
|
oauthCfgInput.ClientID = ""
|
||||||
|
oauthCfgInput.ClientSecret = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
oauthCfg, err := geminicli.EffectiveOAuthConfig(oauthCfgInput, oauthType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ func registerRoutes(r *gin.Engine, h *handler.Handlers, s *service.Services, rep
|
|||||||
// Gemini OAuth routes
|
// Gemini OAuth routes
|
||||||
gemini := admin.Group("/gemini")
|
gemini := admin.Group("/gemini")
|
||||||
{
|
{
|
||||||
|
gemini.GET("/oauth/capabilities", h.Admin.GeminiOAuth.GetCapabilities)
|
||||||
gemini.POST("/oauth/auth-url", h.Admin.GeminiOAuth.GenerateAuthURL)
|
gemini.POST("/oauth/auth-url", h.Admin.GeminiOAuth.GenerateAuthURL)
|
||||||
gemini.POST("/oauth/exchange-code", h.Admin.GeminiOAuth.ExchangeCode)
|
gemini.POST("/oauth/exchange-code", h.Admin.GeminiOAuth.ExchangeCode)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user