diff --git a/backend/internal/handler/admin/antigravity_oauth_handler.go b/backend/internal/handler/admin/antigravity_oauth_handler.go index 185416847..8861ebe01 100644 --- a/backend/internal/handler/admin/antigravity_oauth_handler.go +++ b/backend/internal/handler/admin/antigravity_oauth_handler.go @@ -1,6 +1,9 @@ package admin import ( + "fmt" + "strings" + "github.com/Wei-Shaw/sub2api/internal/pkg/response" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" @@ -27,7 +30,8 @@ func (h *AntigravityOAuthHandler) GenerateAuthURL(c *gin.Context) { return } - result, err := h.antigravityOAuthService.GenerateAuthURL(c.Request.Context(), req.ProxyID) + redirectURI := deriveRedirectURI(c) + result, err := h.antigravityOAuthService.GenerateAuthURL(c.Request.Context(), req.ProxyID, redirectURI) if err != nil { response.InternalError(c, "生成授权链接失败: "+err.Error()) return @@ -65,3 +69,25 @@ func (h *AntigravityOAuthHandler) ExchangeCode(c *gin.Context) { response.Success(c, tokenInfo) } + +func deriveRedirectURI(c *gin.Context) string { + origin := strings.TrimSpace(c.GetHeader("Origin")) + if origin != "" { + return strings.TrimRight(origin, "/") + "/auth/callback" + } + + scheme := "http" + if c.Request.TLS != nil { + scheme = "https" + } + if xfProto := strings.TrimSpace(c.GetHeader("X-Forwarded-Proto")); xfProto != "" { + scheme = strings.TrimSpace(strings.Split(xfProto, ",")[0]) + } + + host := strings.TrimSpace(c.Request.Host) + if xfHost := strings.TrimSpace(c.GetHeader("X-Forwarded-Host")); xfHost != "" { + host = strings.TrimSpace(strings.Split(xfHost, ",")[0]) + } + + return fmt.Sprintf("%s://%s/auth/callback", scheme, host) +} diff --git a/backend/internal/pkg/antigravity/client.go b/backend/internal/pkg/antigravity/client.go index a6279b11b..4ea01f49c 100644 --- a/backend/internal/pkg/antigravity/client.go +++ b/backend/internal/pkg/antigravity/client.go @@ -186,12 +186,12 @@ func shouldFallbackToNextURL(err error, statusCode int) bool { } // ExchangeCode 用 authorization code 交换 token -func (c *Client) ExchangeCode(ctx context.Context, code, codeVerifier string) (*TokenResponse, error) { +func (c *Client) ExchangeCode(ctx context.Context, code, codeVerifier, redirectURI string) (*TokenResponse, error) { params := url.Values{} params.Set("client_id", ClientID) params.Set("client_secret", ClientSecret) params.Set("code", code) - params.Set("redirect_uri", RedirectURI) + params.Set("redirect_uri", redirectURI) params.Set("grant_type", "authorization_code") params.Set("code_verifier", codeVerifier) diff --git a/backend/internal/pkg/antigravity/oauth.go b/backend/internal/pkg/antigravity/oauth.go index c7d657b90..13b2e9110 100644 --- a/backend/internal/pkg/antigravity/oauth.go +++ b/backend/internal/pkg/antigravity/oauth.go @@ -22,8 +22,8 @@ const ( ClientID = "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com" ClientSecret = "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" - // 固定的 redirect_uri(用户需手动复制 code) - RedirectURI = "http://localhost:8085/callback" + // Default RedirectURI (can be overridden) + DefaultRedirectURI = "http://localhost:8085/callback" // OAuth scopes Scopes = "https://www.googleapis.com/auth/cloud-platform " + @@ -133,6 +133,7 @@ type OAuthSession struct { State string `json:"state"` CodeVerifier string `json:"code_verifier"` ProxyURL string `json:"proxy_url,omitempty"` +RedirectURI string `json:"redirect_uri,omitempty"` CreatedAt time.Time `json:"created_at"` } @@ -248,10 +249,13 @@ func base64URLEncode(data []byte) string { } // BuildAuthorizationURL 构建 Google OAuth 授权 URL -func BuildAuthorizationURL(state, codeChallenge string) string { +func BuildAuthorizationURL(state, codeChallenge, redirectURI string) string { + if redirectURI == "" { + redirectURI = DefaultRedirectURI + } params := url.Values{} params.Set("client_id", ClientID) - params.Set("redirect_uri", RedirectURI) + params.Set("redirect_uri", redirectURI) params.Set("response_type", "code") params.Set("scope", Scopes) params.Set("state", state) diff --git a/backend/internal/service/antigravity_oauth_service.go b/backend/internal/service/antigravity_oauth_service.go index fa8379ed9..d3d31e589 100644 --- a/backend/internal/service/antigravity_oauth_service.go +++ b/backend/internal/service/antigravity_oauth_service.go @@ -30,7 +30,7 @@ type AntigravityAuthURLResult struct { } // GenerateAuthURL 生成 Google OAuth 授权链接 -func (s *AntigravityOAuthService) GenerateAuthURL(ctx context.Context, proxyID *int64) (*AntigravityAuthURLResult, error) { +func (s *AntigravityOAuthService) GenerateAuthURL(ctx context.Context, proxyID *int64, redirectURI string) (*AntigravityAuthURLResult, error) { state, err := antigravity.GenerateState() if err != nil { return nil, fmt.Errorf("生成 state 失败: %w", err) @@ -58,12 +58,13 @@ func (s *AntigravityOAuthService) GenerateAuthURL(ctx context.Context, proxyID * State: state, CodeVerifier: codeVerifier, ProxyURL: proxyURL, + RedirectURI: redirectURI, CreatedAt: time.Now(), } s.sessionStore.Set(sessionID, session) codeChallenge := antigravity.GenerateCodeChallenge(codeVerifier) - authURL := antigravity.BuildAuthorizationURL(state, codeChallenge) + authURL := antigravity.BuildAuthorizationURL(state, codeChallenge, redirectURI) return &AntigravityAuthURLResult{ AuthURL: authURL, @@ -103,6 +104,11 @@ func (s *AntigravityOAuthService) ExchangeCode(ctx context.Context, input *Antig return nil, fmt.Errorf("state 无效") } + redirectURI := session.RedirectURI + if redirectURI == "" { + redirectURI = antigravity.DefaultRedirectURI + } + // 确定代理 URL proxyURL := session.ProxyURL if input.ProxyID != nil { @@ -115,7 +121,7 @@ func (s *AntigravityOAuthService) ExchangeCode(ctx context.Context, input *Antig client := antigravity.NewClient(proxyURL) // 交换 token - tokenResp, err := client.ExchangeCode(ctx, input.Code, session.CodeVerifier) + tokenResp, err := client.ExchangeCode(ctx, input.Code, session.CodeVerifier, redirectURI) if err != nil { return nil, fmt.Errorf("token 交换失败: %w", err) }