package auth import ( "bytes" "crypto/sha256" "encoding/base64" "encoding/json" "fmt" "math/rand" "net/http" "net/url" "strings" "time" "codex-pool/internal/client" ) // 常量 CodexClientID, CodexRedirectURI, CodexScope 已在 s2a.go 中定义 // CodexAPIAuth 纯 API 授权 (无浏览器) type CodexAPIAuth struct { client *client.TLSClient email string password string workspaceID string deviceID string sid string sentinelToken string solvedPow string userAgent string logger *AuthLogger } // NewCodexAPIAuth 创建 CodexAuth 实例 func NewCodexAPIAuth(email, password, workspaceID, proxy string, logger *AuthLogger) (*CodexAPIAuth, error) { tlsClient, err := client.New(proxy) if err != nil { return nil, fmt.Errorf("创建 TLS 客户端失败: %v", err) } return &CodexAPIAuth{ client: tlsClient, email: email, password: password, workspaceID: workspaceID, deviceID: generateUUID(), sid: generateUUID(), userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", logger: logger, }, nil } // generateUUID 生成 UUID v4 func generateUUID() string { b := make([]byte, 16) rand.Read(b) b[6] = (b[6] & 0x0f) | 0x40 b[8] = (b[8] & 0x3f) | 0x80 return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) } // 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) for _, b := range data { h ^= uint32(b) h *= 16777619 } h ^= (h >> 16) h *= 2246822507 h ^= (h >> 13) h *= 3266489909 h ^= (h >> 16) return h } // getParseTime 生成 JS Date().toString() 格式的时间字符串 func getParseTime() string { now := time.Now() return now.Format("Mon Jan 02 2006 15:04:05") + " GMT+0800 (中国标准时间)" } // getConfig 生成 PoW 配置数组 func (c *CodexAPIAuth) getConfig() []interface{} { return []interface{}{ 2500 + rand.Intn(1000), getParseTime(), 4294967296, 0, c.userAgent, "chrome-extension://pgojnojmmhpofjgdmaebadhbocahppod/assets/aW5qZWN0X2hhc2g/aW5qZ", nil, "zh-CN", "zh-CN", 0, "canShare−function canShare() { [native code] }", fmt.Sprintf("_reactListening%d", 1000000+rand.Intn(9000000)), "onhashchange", float64(time.Now().UnixNano()/1e6) / 1000.0, c.sid, "", 24, time.Now().UnixMilli() - int64(10000+rand.Intn(40000)), } } // solvePow 解决 PoW 挑战 func (c *CodexAPIAuth) solvePow(seed, difficulty string, cfg []interface{}, maxIterations int) string { seedBytes := []byte(seed) for i := 0; i < maxIterations; i++ { cfg[3] = i cfg[9] = 0 jsonStr, _ := json.Marshal(cfg) encoded := base64.StdEncoding.EncodeToString(jsonStr) h := fnv1a32(append(seedBytes, []byte(encoded)...)) hexHash := fmt.Sprintf("%08x", h) if hexHash[:len(difficulty)] <= difficulty { return encoded + "~S" } } return "" } // getRequirementsToken 生成初始 token func (c *CodexAPIAuth) getRequirementsToken() string { cfg := c.getConfig() cfg[3] = 0 cfg[9] = 0 jsonStr, _ := json.Marshal(cfg) encoded := base64.StdEncoding.EncodeToString(jsonStr) return "gAAAAAC" + encoded + "~S" } // callSentinelReq 调用 Sentinel 获取 token func (c *CodexAPIAuth) callSentinelReq(flow string) error { initToken := c.getRequirementsToken() payload := map[string]interface{}{ "p": initToken, "id": c.deviceID, "flow": flow, } body, _ := json.Marshal(payload) req, _ := http.NewRequest("POST", "https://sentinel.openai.com/backend-api/sentinel/req", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "application/json") resp, err := c.client.Do(req) if err != nil { return fmt.Errorf("sentinel 请求失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != 200 { return fmt.Errorf("sentinel 状态码: %d", resp.StatusCode) } respBody, _ := client.ReadBody(resp) var result struct { Token string `json:"token"` ProofOfWork struct { Required bool `json:"required"` Seed string `json:"seed"` Difficulty string `json:"difficulty"` } `json:"proofofwork"` } if err := json.Unmarshal(respBody, &result); err != nil { return fmt.Errorf("解析 sentinel 响应失败: %v", err) } c.sentinelToken = result.Token if result.ProofOfWork.Required { cfg := c.getConfig() solved := c.solvePow(result.ProofOfWork.Seed, result.ProofOfWork.Difficulty, cfg, 5000000) if solved == "" { return fmt.Errorf("PoW 解决失败") } c.solvedPow = "gAAAAAB" + solved } else { c.solvedPow = initToken } return nil } // getSentinelHeader 构建 sentinel header func (c *CodexAPIAuth) getSentinelHeader(flow string) string { obj := map[string]interface{}{ "p": c.solvedPow, "id": c.deviceID, "flow": flow, } if c.sentinelToken != "" { obj["c"] = c.sentinelToken } header, _ := json.Marshal(obj) return string(header) } // logStep 记录日志 func (c *CodexAPIAuth) logStep(step AuthStep, format string, args ...interface{}) { if c.logger != nil { c.logger.LogStep(step, format, args...) } } // logError 记录错误 func (c *CodexAPIAuth) logError(step AuthStep, format string, args ...interface{}) { if c.logger != nil { c.logger.LogError(step, format, args...) } } // 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"}, } authURL := "https://auth.openai.com/oauth/authorize?" + params.Encode() // 3. 访问授权页面 c.logStep(StepNavigate, "访问授权页面...") req, _ := http.NewRequest("GET", authURL, nil) req.Header.Set("Referer", "https://auth.openai.com/log-in") resp, err := c.client.Do(req) if err != nil { c.logError(StepNavigate, "访问授权页失败: %v", err) return "", fmt.Errorf("访问授权页失败: %v", err) } defer resp.Body.Close() client.ReadBody(resp) // 消耗响应体 referer := resp.Request.URL.String() // 4. 提交邮箱 c.logStep(StepInputEmail, "提交邮箱: %s", c.email) if err := c.callSentinelReq("login_web_init"); err != nil { c.logError(StepInputEmail, "Sentinel 请求失败: %v", err) return "", err } emailPayload := map[string]interface{}{ "username": map[string]string{ "kind": "email", "value": c.email, }, } emailBody, _ := json.Marshal(emailPayload) req, _ = http.NewRequest("POST", "https://auth.openai.com/api/accounts/authorize/continue", bytes.NewReader(emailBody)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Origin", "https://auth.openai.com") req.Header.Set("Referer", referer) req.Header.Set("OpenAI-Sentinel-Token", c.getSentinelHeader("authorize_continue")) resp, err = c.client.Do(req) if err != nil { c.logError(StepInputEmail, "提交邮箱失败: %v", err) return "", fmt.Errorf("提交邮箱失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != 200 { body, _ := client.ReadBody(resp) c.logError(StepInputEmail, "提交邮箱失败: %d - %s", resp.StatusCode, string(body)) return "", fmt.Errorf("提交邮箱失败: %d", resp.StatusCode) } // 解析响应,检查是否需要密码 emailResp, _ := client.ReadBody(resp) var emailResult map[string]interface{} json.Unmarshal(emailResp, &emailResult) // 5. 验证密码 c.logStep(StepInputPassword, "验证密码...") if err := c.callSentinelReq("authorize_continue__auto"); err != nil { c.logError(StepInputPassword, "Sentinel 请求失败: %v", err) return "", err } pwdPayload := map[string]string{ "username": c.email, "password": c.password, } pwdBody, _ := json.Marshal(pwdPayload) req, _ = http.NewRequest("POST", "https://auth.openai.com/api/accounts/password/verify", bytes.NewReader(pwdBody)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Origin", "https://auth.openai.com") req.Header.Set("Referer", referer) req.Header.Set("OpenAI-Sentinel-Token", c.getSentinelHeader("password_verify")) resp, err = c.client.Do(req) if err != nil { c.logError(StepInputPassword, "验证密码失败: %v", err) return "", fmt.Errorf("验证密码失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != 200 { body, _ := client.ReadBody(resp) c.logError(StepInputPassword, "密码验证失败: %d - %s", resp.StatusCode, string(body)) return "", fmt.Errorf("密码验证失败: %d", resp.StatusCode) } // 6. 选择工作区 c.logStep(StepSelectWorkspace, "选择工作区: %s", c.workspaceID) if err := c.callSentinelReq("password_verify__auto"); err != nil { c.logError(StepSelectWorkspace, "Sentinel 请求失败: %v", err) return "", err } wsPayload := map[string]string{ "workspace_id": c.workspaceID, } wsBody, _ := json.Marshal(wsPayload) req, _ = http.NewRequest("POST", "https://auth.openai.com/api/accounts/workspace/select", bytes.NewReader(wsBody)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Origin", "https://auth.openai.com") req.Header.Set("Referer", referer) resp, err = c.client.Do(req) if err != nil { c.logError(StepSelectWorkspace, "选择工作区失败: %v", err) return "", fmt.Errorf("选择工作区失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != 200 { body, _ := client.ReadBody(resp) c.logError(StepSelectWorkspace, "选择工作区失败: %d - %s", resp.StatusCode, string(body)) return "", fmt.Errorf("选择工作区失败: %d", resp.StatusCode) } // 解析 continue_url wsResp, _ := client.ReadBody(resp) c.logStep(StepSelectWorkspace, "工作区响应: %s", string(wsResp)) var wsResult struct { ContinueURL string `json:"continue_url"` Page struct { Type string `json:"type"` } `json:"page"` Error string `json:"error"` Message string `json:"message"` } if err := json.Unmarshal(wsResp, &wsResult); err != nil { c.logError(StepSelectWorkspace, "解析响应失败: %v, 原始: %s", err, string(wsResp)) return "", fmt.Errorf("解析响应失败: %v", err) } if wsResult.ContinueURL == "" { c.logError(StepSelectWorkspace, "未获取到 continue_url, page=%s, error=%s, msg=%s", wsResult.Page.Type, wsResult.Error, wsResult.Message) return "", fmt.Errorf("未获取到 continue_url") } // 7. 跟随重定向获取授权码 c.logStep(StepWaitCallback, "跟随重定向...") continueURL := wsResult.ContinueURL for i := 0; i < 10; i++ { req, _ = http.NewRequest("GET", continueURL, nil) resp, err = c.client.Do(req) if err != nil { break } location := resp.Header.Get("Location") resp.Body.Close() if resp.StatusCode >= 301 && resp.StatusCode <= 308 { if strings.Contains(location, "localhost:1455") { // 提取授权码 code := ExtractCodeFromCallbackURL(location) if code != "" { c.logStep(StepComplete, "授权成功,获取到授权码") return code, nil } } continueURL = location } else { break } } c.logError(StepWaitCallback, "未能获取授权码") return "", fmt.Errorf("未能获取授权码") } // ExchangeCodeForTokens 用授权码换取 tokens func (c *CodexAPIAuth) ExchangeCodeForTokens(code, codeVerifier string) (*CodexTokens, error) { payload := map[string]string{ "grant_type": "authorization_code", "client_id": CodexClientID, "code_verifier": codeVerifier, "code": code, "redirect_uri": CodexRedirectURI, } body, _ := json.Marshal(payload) req, _ := http.NewRequest("POST", "https://auth.openai.com/oauth/token", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, err := c.client.Do(req) if err != nil { return nil, fmt.Errorf("token 交换失败: %v", err) } defer resp.Body.Close() if resp.StatusCode != 200 { respBody, _ := client.ReadBody(resp) return nil, fmt.Errorf("token 交换失败: %d - %s", resp.StatusCode, string(respBody)) } respBody, _ := client.ReadBody(resp) var tokens CodexTokens if err := json.Unmarshal(respBody, &tokens); err != nil { return nil, fmt.Errorf("解析 token 失败: %v", err) } if tokens.ExpiresIn > 0 { expiredAt := time.Now().Add(time.Duration(tokens.ExpiresIn) * time.Second) tokens.ExpiredAt = expiredAt.Format(time.RFC3339) } return &tokens, nil } // CodexTokens 结构体已在 s2a.go 中定义 // CompleteWithCodexAPI 使用纯 API 方式完成授权 func CompleteWithCodexAPI(email, password, workspaceID, proxy string, logger *AuthLogger) (string, error) { if logger != nil { logger.LogStep(StepBrowserStart, "使用 CodexAuth API 模式...") } auth, err := NewCodexAPIAuth(email, password, workspaceID, proxy, logger) if err != nil { if logger != nil { logger.LogError(StepBrowserStart, "创建 CodexAuth 失败: %v", err) } return "", err } code, err := auth.ObtainAuthorizationCode() if err != nil { return "", err } return code, nil }