111 lines
3.4 KiB
Go
111 lines
3.4 KiB
Go
package repository
|
||
|
||
import (
|
||
"context"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
"time"
|
||
|
||
infraerrors "github.com/Wei-Shaw/sub2api/internal/pkg/errors"
|
||
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
|
||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||
"github.com/imroc/req/v3"
|
||
)
|
||
|
||
// NewOpenAIOAuthClient creates a new OpenAI OAuth client
|
||
func NewOpenAIOAuthClient() service.OpenAIOAuthClient {
|
||
return &openaiOAuthService{tokenURL: openai.TokenURL}
|
||
}
|
||
|
||
type openaiOAuthService struct {
|
||
tokenURL string
|
||
}
|
||
|
||
func (s *openaiOAuthService) ExchangeCode(ctx context.Context, code, codeVerifier, redirectURI, proxyURL, clientID string) (*openai.TokenResponse, error) {
|
||
client := createOpenAIReqClient(proxyURL)
|
||
|
||
if redirectURI == "" {
|
||
redirectURI = openai.DefaultRedirectURI
|
||
}
|
||
clientID = strings.TrimSpace(clientID)
|
||
if clientID == "" {
|
||
clientID = openai.ClientID
|
||
}
|
||
|
||
formData := url.Values{}
|
||
formData.Set("grant_type", "authorization_code")
|
||
formData.Set("client_id", clientID)
|
||
formData.Set("code", code)
|
||
formData.Set("redirect_uri", redirectURI)
|
||
formData.Set("code_verifier", codeVerifier)
|
||
|
||
var tokenResp openai.TokenResponse
|
||
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
SetHeader("User-Agent", "codex-cli/0.91.0").
|
||
SetFormDataFromValues(formData).
|
||
SetSuccessResult(&tokenResp).
|
||
Post(s.tokenURL)
|
||
|
||
if err != nil {
|
||
return nil, infraerrors.Newf(http.StatusBadGateway, "OPENAI_OAUTH_REQUEST_FAILED", "request failed: %v", err)
|
||
}
|
||
|
||
if !resp.IsSuccessState() {
|
||
return nil, infraerrors.Newf(http.StatusBadGateway, "OPENAI_OAUTH_TOKEN_EXCHANGE_FAILED", "token exchange failed: status %d, body: %s", resp.StatusCode, resp.String())
|
||
}
|
||
|
||
return &tokenResp, nil
|
||
}
|
||
|
||
func (s *openaiOAuthService) RefreshToken(ctx context.Context, refreshToken, proxyURL string) (*openai.TokenResponse, error) {
|
||
return s.RefreshTokenWithClientID(ctx, refreshToken, proxyURL, "")
|
||
}
|
||
|
||
func (s *openaiOAuthService) RefreshTokenWithClientID(ctx context.Context, refreshToken, proxyURL string, clientID string) (*openai.TokenResponse, error) {
|
||
// 调用方应始终传入正确的 client_id;为兼容旧数据,未指定时默认使用 OpenAI ClientID
|
||
clientID = strings.TrimSpace(clientID)
|
||
if clientID == "" {
|
||
clientID = openai.ClientID
|
||
}
|
||
return s.refreshTokenWithClientID(ctx, refreshToken, proxyURL, clientID)
|
||
}
|
||
|
||
func (s *openaiOAuthService) refreshTokenWithClientID(ctx context.Context, refreshToken, proxyURL, clientID string) (*openai.TokenResponse, error) {
|
||
client := createOpenAIReqClient(proxyURL)
|
||
|
||
formData := url.Values{}
|
||
formData.Set("grant_type", "refresh_token")
|
||
formData.Set("refresh_token", refreshToken)
|
||
formData.Set("client_id", clientID)
|
||
formData.Set("scope", openai.RefreshScopes)
|
||
|
||
var tokenResp openai.TokenResponse
|
||
|
||
resp, err := client.R().
|
||
SetContext(ctx).
|
||
SetHeader("User-Agent", "codex-cli/0.91.0").
|
||
SetFormDataFromValues(formData).
|
||
SetSuccessResult(&tokenResp).
|
||
Post(s.tokenURL)
|
||
|
||
if err != nil {
|
||
return nil, infraerrors.Newf(http.StatusBadGateway, "OPENAI_OAUTH_REQUEST_FAILED", "request failed: %v", err)
|
||
}
|
||
|
||
if !resp.IsSuccessState() {
|
||
return nil, infraerrors.Newf(http.StatusBadGateway, "OPENAI_OAUTH_TOKEN_REFRESH_FAILED", "token refresh failed: status %d, body: %s", resp.StatusCode, resp.String())
|
||
}
|
||
|
||
return &tokenResp, nil
|
||
}
|
||
|
||
func createOpenAIReqClient(proxyURL string) *req.Client {
|
||
return getSharedReqClient(reqClientOptions{
|
||
ProxyURL: proxyURL,
|
||
Timeout: 120 * time.Second,
|
||
})
|
||
}
|