- 更新 gemini_oauth_client.go: 支持 AI Studio OAuth 客户端 - 更新 geminicli_codeassist_client.go: 适配新的认证流程
105 lines
3.0 KiB
Go
105 lines
3.0 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|
"github.com/Wei-Shaw/sub2api/internal/pkg/geminicli"
|
|
"github.com/Wei-Shaw/sub2api/internal/service/ports"
|
|
|
|
"github.com/imroc/req/v3"
|
|
)
|
|
|
|
type geminiOAuthClient struct {
|
|
tokenURL string
|
|
cfg *config.Config
|
|
}
|
|
|
|
func NewGeminiOAuthClient(cfg *config.Config) ports.GeminiOAuthClient {
|
|
return &geminiOAuthClient{
|
|
tokenURL: geminicli.TokenURL,
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
func (c *geminiOAuthClient) ExchangeCode(ctx context.Context, code, codeVerifier, redirectURI, proxyURL string) (*geminicli.TokenResponse, error) {
|
|
client := createGeminiReqClient(proxyURL)
|
|
|
|
// Both Code Assist and AI Studio OAuth use the same token endpoint and OAuth client.
|
|
oauthCfg, err := geminicli.EffectiveOAuthConfig(geminicli.OAuthConfig{
|
|
ClientID: c.cfg.Gemini.OAuth.ClientID,
|
|
ClientSecret: c.cfg.Gemini.OAuth.ClientSecret,
|
|
Scopes: c.cfg.Gemini.OAuth.Scopes,
|
|
}, "code_assist")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
formData := url.Values{}
|
|
formData.Set("grant_type", "authorization_code")
|
|
formData.Set("client_id", oauthCfg.ClientID)
|
|
formData.Set("client_secret", oauthCfg.ClientSecret)
|
|
formData.Set("code", code)
|
|
formData.Set("code_verifier", codeVerifier)
|
|
formData.Set("redirect_uri", redirectURI)
|
|
|
|
var tokenResp geminicli.TokenResponse
|
|
resp, err := client.R().
|
|
SetContext(ctx).
|
|
SetFormDataFromValues(formData).
|
|
SetSuccessResult(&tokenResp).
|
|
Post(c.tokenURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
if !resp.IsSuccessState() {
|
|
return nil, fmt.Errorf("token exchange failed: status %d, body: %s", resp.StatusCode, geminicli.SanitizeBodyForLogs(resp.String()))
|
|
}
|
|
return &tokenResp, nil
|
|
}
|
|
|
|
func (c *geminiOAuthClient) RefreshToken(ctx context.Context, refreshToken, proxyURL string) (*geminicli.TokenResponse, error) {
|
|
client := createGeminiReqClient(proxyURL)
|
|
|
|
// Both Code Assist and AI Studio OAuth use the same token endpoint and OAuth client.
|
|
oauthCfg, err := geminicli.EffectiveOAuthConfig(geminicli.OAuthConfig{
|
|
ClientID: c.cfg.Gemini.OAuth.ClientID,
|
|
ClientSecret: c.cfg.Gemini.OAuth.ClientSecret,
|
|
Scopes: c.cfg.Gemini.OAuth.Scopes,
|
|
}, "code_assist")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
formData := url.Values{}
|
|
formData.Set("grant_type", "refresh_token")
|
|
formData.Set("refresh_token", refreshToken)
|
|
formData.Set("client_id", oauthCfg.ClientID)
|
|
formData.Set("client_secret", oauthCfg.ClientSecret)
|
|
|
|
var tokenResp geminicli.TokenResponse
|
|
resp, err := client.R().
|
|
SetContext(ctx).
|
|
SetFormDataFromValues(formData).
|
|
SetSuccessResult(&tokenResp).
|
|
Post(c.tokenURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("request failed: %w", err)
|
|
}
|
|
if !resp.IsSuccessState() {
|
|
return nil, fmt.Errorf("token refresh failed: status %d, body: %s", resp.StatusCode, geminicli.SanitizeBodyForLogs(resp.String()))
|
|
}
|
|
return &tokenResp, nil
|
|
}
|
|
|
|
func createGeminiReqClient(proxyURL string) *req.Client {
|
|
client := req.C().SetTimeout(60 * time.Second)
|
|
if proxyURL != "" {
|
|
client.SetProxyURL(proxyURL)
|
|
}
|
|
return client
|
|
}
|