Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
269a6592
Unverified
Commit
269a6592
authored
Jan 28, 2026
by
Wesley Liddick
Committed by
GitHub
Jan 28, 2026
Browse files
Merge pull request #406 from geminiwen/main
fix(openai-oauth): 改进错误处理和代理支持
parents
2c31bf46
8f6639f8
Changes
5
Hide whitespace changes
Inline
Side-by-side
backend/cmd/server/VERSION
View file @
269a6592
0.1.
4
6
0.1.6
1
backend/internal/pkg/response/response.go
View file @
269a6592
...
...
@@ -2,6 +2,7 @@
package
response
import
(
"log"
"math"
"net/http"
...
...
@@ -74,6 +75,12 @@ func ErrorFrom(c *gin.Context, err error) bool {
}
statusCode
,
status
:=
infraerrors
.
ToHTTP
(
err
)
// Log internal errors with full details for debugging
if
statusCode
>=
500
&&
c
.
Request
!=
nil
{
log
.
Printf
(
"[ERROR] %s %s
\n
Error: %s"
,
c
.
Request
.
Method
,
c
.
Request
.
URL
.
Path
,
err
.
Error
())
}
ErrorWithDetails
(
c
,
statusCode
,
status
.
Message
,
status
.
Reason
,
status
.
Metadata
)
return
true
}
...
...
backend/internal/repository/openai_oauth_service.go
View file @
269a6592
...
...
@@ -2,11 +2,11 @@ package repository
import
(
"context"
"
fmt
"
"
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"
...
...
@@ -22,7 +22,7 @@ type openaiOAuthService struct {
}
func
(
s
*
openaiOAuthService
)
ExchangeCode
(
ctx
context
.
Context
,
code
,
codeVerifier
,
redirectURI
,
proxyURL
string
)
(
*
openai
.
TokenResponse
,
error
)
{
client
:=
createOpenAIReqClient
(
s
.
tokenURL
,
proxyURL
)
client
:=
createOpenAIReqClient
(
proxyURL
)
if
redirectURI
==
""
{
redirectURI
=
openai
.
DefaultRedirectURI
...
...
@@ -39,23 +39,24 @@ func (s *openaiOAuthService) ExchangeCode(ctx context.Context, code, codeVerifie
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
,
fmt
.
Errorf
(
"request failed: %
w
"
,
err
)
return
nil
,
infraerrors
.
Newf
(
http
.
StatusBadGateway
,
"OPENAI_OAUTH_REQUEST_FAILED"
,
"request failed: %
v
"
,
err
)
}
if
!
resp
.
IsSuccessState
()
{
return
nil
,
fmt
.
Errorf
(
"token exchange failed: status %d, body: %s"
,
resp
.
StatusCode
,
resp
.
String
())
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
)
{
client
:=
createOpenAIReqClient
(
s
.
tokenURL
,
proxyURL
)
client
:=
createOpenAIReqClient
(
proxyURL
)
formData
:=
url
.
Values
{}
formData
.
Set
(
"grant_type"
,
"refresh_token"
)
...
...
@@ -67,29 +68,25 @@ func (s *openaiOAuthService) RefreshToken(ctx context.Context, refreshToken, pro
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
,
fmt
.
Errorf
(
"request failed: %
w
"
,
err
)
return
nil
,
infraerrors
.
Newf
(
http
.
StatusBadGateway
,
"OPENAI_OAUTH_REQUEST_FAILED"
,
"request failed: %
v
"
,
err
)
}
if
!
resp
.
IsSuccessState
()
{
return
nil
,
fmt
.
Errorf
(
"token refresh failed: status %d, body: %s"
,
resp
.
StatusCode
,
resp
.
String
())
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
(
tokenURL
,
proxyURL
string
)
*
req
.
Client
{
forceHTTP2
:=
false
if
parsedURL
,
err
:=
url
.
Parse
(
tokenURL
);
err
==
nil
{
forceHTTP2
=
strings
.
EqualFold
(
parsedURL
.
Scheme
,
"https"
)
}
func
createOpenAIReqClient
(
proxyURL
string
)
*
req
.
Client
{
return
getSharedReqClient
(
reqClientOptions
{
ProxyURL
:
proxyURL
,
Timeout
:
120
*
time
.
Second
,
ForceHTTP2
:
forceHTTP2
,
ProxyURL
:
proxyURL
,
Timeout
:
120
*
time
.
Second
,
})
}
backend/internal/repository/req_client_pool_test.go
View file @
269a6592
...
...
@@ -77,21 +77,9 @@ func TestGetSharedReqClient_ImpersonateAndProxy(t *testing.T) {
require
.
Equal
(
t
,
"http://proxy.local:8080|4s|true|false"
,
buildReqClientKey
(
opts
))
}
func
TestCreateOpenAIReqClient_ForceHTTP2Enabled
(
t
*
testing
.
T
)
{
sharedReqClients
=
sync
.
Map
{}
client
:=
createOpenAIReqClient
(
"https://auth.openai.com/oauth/token"
,
"http://proxy.local:8080"
)
require
.
Equal
(
t
,
"2"
,
forceHTTPVersion
(
t
,
client
))
}
func
TestCreateOpenAIReqClient_ForceHTTP2DisabledForHTTP
(
t
*
testing
.
T
)
{
sharedReqClients
=
sync
.
Map
{}
client
:=
createOpenAIReqClient
(
"http://localhost/oauth/token"
,
"http://proxy.local:8080"
)
require
.
Equal
(
t
,
""
,
forceHTTPVersion
(
t
,
client
))
}
func
TestCreateOpenAIReqClient_Timeout120Seconds
(
t
*
testing
.
T
)
{
sharedReqClients
=
sync
.
Map
{}
client
:=
createOpenAIReqClient
(
"https://auth.openai.com/oauth/token"
,
"http://proxy.local:8080"
)
client
:=
createOpenAIReqClient
(
"http://proxy.local:8080"
)
require
.
Equal
(
t
,
120
*
time
.
Second
,
client
.
GetClient
()
.
Timeout
)
}
...
...
backend/internal/service/openai_oauth_service.go
View file @
269a6592
...
...
@@ -2,9 +2,10 @@ package service
import
(
"context"
"
fmt
"
"
net/http
"
"time"
infraerrors
"github.com/Wei-Shaw/sub2api/internal/pkg/errors"
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
)
...
...
@@ -35,12 +36,12 @@ func (s *OpenAIOAuthService) GenerateAuthURL(ctx context.Context, proxyID *int64
// Generate PKCE values
state
,
err
:=
openai
.
GenerateState
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to generate state: %
w
"
,
err
)
return
nil
,
infraerrors
.
Newf
(
http
.
StatusInternalServerError
,
"OPENAI_OAUTH_STATE_FAILED"
,
"failed to generate state: %
v
"
,
err
)
}
codeVerifier
,
err
:=
openai
.
GenerateCodeVerifier
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to generate code verifier: %
w
"
,
err
)
return
nil
,
infraerrors
.
Newf
(
http
.
StatusInternalServerError
,
"OPENAI_OAUTH_VERIFIER_FAILED"
,
"failed to generate code verifier: %
v
"
,
err
)
}
codeChallenge
:=
openai
.
GenerateCodeChallenge
(
codeVerifier
)
...
...
@@ -48,14 +49,17 @@ func (s *OpenAIOAuthService) GenerateAuthURL(ctx context.Context, proxyID *int64
// Generate session ID
sessionID
,
err
:=
openai
.
GenerateSessionID
()
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to generate session ID: %
w
"
,
err
)
return
nil
,
infraerrors
.
Newf
(
http
.
StatusInternalServerError
,
"OPENAI_OAUTH_SESSION_FAILED"
,
"failed to generate session ID: %
v
"
,
err
)
}
// Get proxy URL if specified
var
proxyURL
string
if
proxyID
!=
nil
{
proxy
,
err
:=
s
.
proxyRepo
.
GetByID
(
ctx
,
*
proxyID
)
if
err
==
nil
&&
proxy
!=
nil
{
if
err
!=
nil
{
return
nil
,
infraerrors
.
Newf
(
http
.
StatusBadRequest
,
"OPENAI_OAUTH_PROXY_NOT_FOUND"
,
"proxy not found: %v"
,
err
)
}
if
proxy
!=
nil
{
proxyURL
=
proxy
.
URL
()
}
}
...
...
@@ -110,14 +114,17 @@ func (s *OpenAIOAuthService) ExchangeCode(ctx context.Context, input *OpenAIExch
// Get session
session
,
ok
:=
s
.
sessionStore
.
Get
(
input
.
SessionID
)
if
!
ok
{
return
nil
,
fmt
.
Errorf
(
"session not found or expired"
)
return
nil
,
infraerrors
.
New
(
http
.
StatusBadRequest
,
"OPENAI_OAUTH_SESSION_NOT_FOUND"
,
"session not found or expired"
)
}
// Get proxy URL
// Get proxy URL
: prefer input.ProxyID, fallback to session.ProxyURL
proxyURL
:=
session
.
ProxyURL
if
input
.
ProxyID
!=
nil
{
proxy
,
err
:=
s
.
proxyRepo
.
GetByID
(
ctx
,
*
input
.
ProxyID
)
if
err
==
nil
&&
proxy
!=
nil
{
if
err
!=
nil
{
return
nil
,
infraerrors
.
Newf
(
http
.
StatusBadRequest
,
"OPENAI_OAUTH_PROXY_NOT_FOUND"
,
"proxy not found: %v"
,
err
)
}
if
proxy
!=
nil
{
proxyURL
=
proxy
.
URL
()
}
}
...
...
@@ -131,7 +138,7 @@ func (s *OpenAIOAuthService) ExchangeCode(ctx context.Context, input *OpenAIExch
// Exchange code for token
tokenResp
,
err
:=
s
.
oauthClient
.
ExchangeCode
(
ctx
,
input
.
Code
,
session
.
CodeVerifier
,
redirectURI
,
proxyURL
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"failed to exchange code: %w"
,
err
)
return
nil
,
err
}
// Parse ID token to get user info
...
...
@@ -201,12 +208,12 @@ func (s *OpenAIOAuthService) RefreshToken(ctx context.Context, refreshToken stri
// RefreshAccountToken refreshes token for an OpenAI account
func
(
s
*
OpenAIOAuthService
)
RefreshAccountToken
(
ctx
context
.
Context
,
account
*
Account
)
(
*
OpenAITokenInfo
,
error
)
{
if
!
account
.
IsOpenAI
()
{
return
nil
,
fmt
.
Errorf
(
"account is not an OpenAI account"
)
return
nil
,
infraerrors
.
New
(
http
.
StatusBadRequest
,
"OPENAI_OAUTH_INVALID_ACCOUNT"
,
"account is not an OpenAI account"
)
}
refreshToken
:=
account
.
GetOpenAIRefreshToken
()
if
refreshToken
==
""
{
return
nil
,
fmt
.
Errorf
(
"no refresh token available"
)
return
nil
,
infraerrors
.
New
(
http
.
StatusBadRequest
,
"OPENAI_OAUTH_NO_REFRESH_TOKEN"
,
"no refresh token available"
)
}
var
proxyURL
string
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment