feat: Implement API-based Codex authentication and add team process API, while removing get_code.go from .gitignore.
This commit is contained in:
@@ -757,8 +757,8 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
|
||||
var code string
|
||||
// 根据全局配置决定授权方式
|
||||
if config.Global.AuthMethod == "api" {
|
||||
// 使用纯 API 模式(CodexAuth)
|
||||
code, err = auth.CompleteWithCodexAPI(memberChild.Email, memberChild.Password, teamID, req.Proxy, authLogger)
|
||||
// 使用纯 API 模式(CodexAuth)- 使用 S2A 生成的授权 URL
|
||||
code, err = auth.CompleteWithCodexAPI(memberChild.Email, memberChild.Password, teamID, s2aResp.Data.AuthURL, s2aResp.Data.SessionID, req.Proxy, authLogger)
|
||||
} else if req.BrowserType == "rod" {
|
||||
code, err = auth.CompleteWithRodLogged(s2aResp.Data.AuthURL, memberChild.Email, memberChild.Password, teamID, req.Headless, req.Proxy, authLogger)
|
||||
} else {
|
||||
@@ -857,8 +857,8 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
|
||||
var code string
|
||||
// 根据全局配置决定授权方式
|
||||
if config.Global.AuthMethod == "api" {
|
||||
// 使用纯 API 模式(CodexAuth)
|
||||
code, err = auth.CompleteWithCodexAPI(owner.Email, owner.Password, teamID, req.Proxy, authLogger)
|
||||
// 使用纯 API 模式(CodexAuth)- 使用 S2A 生成的授权 URL
|
||||
code, err = auth.CompleteWithCodexAPI(owner.Email, owner.Password, teamID, s2aResp.Data.AuthURL, s2aResp.Data.SessionID, req.Proxy, authLogger)
|
||||
} else if req.BrowserType == "rod" {
|
||||
code, err = auth.CompleteWithRodLogged(s2aResp.Data.AuthURL, owner.Email, owner.Password, teamID, req.Headless, req.Proxy, authLogger)
|
||||
} else {
|
||||
|
||||
@@ -3,7 +3,6 @@ package auth
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
@@ -21,6 +20,10 @@ import (
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
// 常量 CodexClientID, CodexRedirectURI, CodexScope 已在 s2a.go 中定义
|
||||
|
||||
// CodexAPIAuth 纯 API 授权 (无浏览器) - 基于 get_code.go 的实现
|
||||
@@ -29,6 +32,8 @@ type CodexAPIAuth struct {
|
||||
email string
|
||||
password string
|
||||
workspaceID string
|
||||
authURL string // S2A 生成的授权 URL
|
||||
sessionID string // S2A 会话 ID
|
||||
deviceID string
|
||||
sid string
|
||||
sentinelToken string
|
||||
@@ -125,7 +130,8 @@ func (rt *utlsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// NewCodexAPIAuth 创建 CodexAuth 实例
|
||||
func NewCodexAPIAuth(email, password, workspaceID, proxy string, logger *AuthLogger) (*CodexAPIAuth, error) {
|
||||
// authURL 和 sessionID 由 S2A 生成
|
||||
func NewCodexAPIAuth(email, password, workspaceID, authURL, sessionID, proxy string, logger *AuthLogger) (*CodexAPIAuth, error) {
|
||||
var proxyURL *url.URL
|
||||
if proxy != "" {
|
||||
var err error
|
||||
@@ -150,6 +156,8 @@ func NewCodexAPIAuth(email, password, workspaceID, proxy string, logger *AuthLog
|
||||
email: email,
|
||||
password: password,
|
||||
workspaceID: workspaceID,
|
||||
authURL: authURL,
|
||||
sessionID: sessionID,
|
||||
deviceID: generateUUID(),
|
||||
sid: generateUUID(),
|
||||
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
|
||||
@@ -166,26 +174,6 @@ func generateUUID() string {
|
||||
b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
|
||||
}
|
||||
|
||||
// generateCodeVerifier 生成 PKCE code_verifier
|
||||
func generateCodeVerifier() string {
|
||||
b := make([]byte, 64)
|
||||
rand.Read(b)
|
||||
return base64.RawURLEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
// generateCodeChallenge 生成 PKCE code_challenge (S256)
|
||||
func generateCodeChallenge(verifier string) string {
|
||||
hash := sha256.Sum256([]byte(verifier))
|
||||
return base64.RawURLEncoding.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// generateState 生成 state
|
||||
func generateState() string {
|
||||
b := make([]byte, 16)
|
||||
rand.Read(b)
|
||||
return base64.RawURLEncoding.EncodeToString(b)
|
||||
}
|
||||
|
||||
// fnv1a32 FNV-1a 32-bit hash
|
||||
func fnv1a32(data []byte) uint32 {
|
||||
h := uint32(2166136261)
|
||||
@@ -380,47 +368,36 @@ func (c *CodexAPIAuth) logError(step AuthStep, format string, args ...interface{
|
||||
}
|
||||
}
|
||||
|
||||
// GetSessionID 获取 S2A 会话 ID(用于后续入库)
|
||||
func (c *CodexAPIAuth) GetSessionID() string {
|
||||
return c.sessionID
|
||||
}
|
||||
|
||||
// ObtainAuthorizationCode 获取授权码
|
||||
func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
|
||||
c.logStep(StepNavigate, "开始 Codex API 授权流程...")
|
||||
|
||||
// 1. 生成 PKCE 参数
|
||||
codeVerifier := generateCodeVerifier()
|
||||
codeChallenge := generateCodeChallenge(codeVerifier)
|
||||
state := generateState()
|
||||
|
||||
// 2. 构建授权 URL
|
||||
params := url.Values{
|
||||
"client_id": {CodexClientID},
|
||||
"scope": {CodexScope},
|
||||
"response_type": {"code"},
|
||||
"redirect_uri": {CodexRedirectURI},
|
||||
"code_challenge": {codeChallenge},
|
||||
"code_challenge_method": {"S256"},
|
||||
"state": {state},
|
||||
"id_token_add_organizations": {"true"},
|
||||
"codex_cli_simplified_flow": {"true"},
|
||||
"originator": {"codex_cli_rs"},
|
||||
// 使用 S2A 生成的授权 URL(不再自己生成 PKCE 参数)
|
||||
if c.authURL == "" {
|
||||
return "", fmt.Errorf("authURL 未设置,请先通过 S2A 生成授权 URL")
|
||||
}
|
||||
|
||||
authURL := "https://auth.openai.com/oauth/authorize?" + params.Encode()
|
||||
|
||||
headers := map[string]string{
|
||||
"Origin": "https://auth.openai.com",
|
||||
"Referer": "https://auth.openai.com/log-in",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
// 3. 访问授权页面并手动跟随重定向
|
||||
// 访问授权页面并手动跟随重定向
|
||||
c.logStep(StepNavigate, "访问授权页面...")
|
||||
resp, _, err := c.doRequest("GET", authURL, nil, headers)
|
||||
resp, _, err := c.doRequest("GET", c.authURL, nil, headers)
|
||||
if err != nil {
|
||||
c.logError(StepNavigate, "访问授权页失败: %v", err)
|
||||
return "", fmt.Errorf("访问授权页失败: %v", err)
|
||||
}
|
||||
|
||||
// 手动跟随重定向
|
||||
currentURL := authURL
|
||||
currentURL := c.authURL
|
||||
for resp.StatusCode >= 300 && resp.StatusCode < 400 {
|
||||
location := resp.Header.Get("Location")
|
||||
if location == "" {
|
||||
@@ -438,7 +415,7 @@ func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
|
||||
}
|
||||
headers["Referer"] = currentURL
|
||||
|
||||
// 4. 提交邮箱
|
||||
// 提交邮箱
|
||||
c.logStep(StepInputEmail, "提交邮箱: %s", c.email)
|
||||
if !c.callSentinelReq("login_web_init") {
|
||||
return "", fmt.Errorf("Sentinel 请求失败")
|
||||
@@ -474,6 +451,8 @@ func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
c.logStep(StepInputPassword, "邮箱提交响应 pageType=%s, 包含password=%v", pageType, strings.Contains(string(body), "password"))
|
||||
|
||||
if pageType == "password" || strings.Contains(string(body), "password") {
|
||||
// 5. 验证密码
|
||||
c.logStep(StepInputPassword, "验证密码...")
|
||||
@@ -497,6 +476,9 @@ func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
|
||||
c.logError(StepInputPassword, "密码验证失败: %d - %s", resp.StatusCode, string(body[:min(200, len(body))]))
|
||||
return "", fmt.Errorf("密码验证失败: %d", resp.StatusCode)
|
||||
}
|
||||
c.logStep(StepInputPassword, "密码验证成功")
|
||||
} else {
|
||||
c.logStep(StepInputPassword, "跳过密码验证步骤 (服务器未要求)")
|
||||
}
|
||||
|
||||
// 6. 选择工作区
|
||||
@@ -591,12 +573,13 @@ func min(a, b int) int {
|
||||
}
|
||||
|
||||
// CompleteWithCodexAPI 使用纯 API 方式完成授权
|
||||
func CompleteWithCodexAPI(email, password, workspaceID, proxy string, logger *AuthLogger) (string, error) {
|
||||
// authURL 和 sessionID 由 S2A 生成
|
||||
func CompleteWithCodexAPI(email, password, workspaceID, authURL, sessionID, proxy string, logger *AuthLogger) (string, error) {
|
||||
if logger != nil {
|
||||
logger.LogStep(StepBrowserStart, "使用 CodexAuth API 模式...")
|
||||
}
|
||||
|
||||
auth, err := NewCodexAPIAuth(email, password, workspaceID, proxy, logger)
|
||||
auth, err := NewCodexAPIAuth(email, password, workspaceID, authURL, sessionID, proxy, logger)
|
||||
if err != nil {
|
||||
if logger != nil {
|
||||
logger.LogError(StepBrowserStart, "创建 CodexAuth 失败: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user