feat: Introduce a new monitoring and configuration dashboard with backend API for autopool management and S2A integration.

This commit is contained in:
2026-02-02 07:55:22 +08:00
parent 5a3b3aa8ef
commit 3d026b2010
8 changed files with 722 additions and 68 deletions

View File

@@ -0,0 +1,483 @@
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,
"canSharefunction 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)
var wsResult struct {
ContinueURL string `json:"continue_url"`
}
if err := json.Unmarshal(wsResp, &wsResult); err != nil || wsResult.ContinueURL == "" {
c.logError(StepSelectWorkspace, "未获取到 continue_url")
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
}