Skip to content

Commit ca53cae

Browse files
committed
Merge pull request #272 from lingoox/main
feat: account management system + session token + external access token + fingerprint isolation
2 parents b2d7954 + 0e0374d commit ca53cae

9 files changed

Lines changed: 132 additions & 28 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# internal/chatgpt 包指纹审计 — 已知问题存档
2+
3+
**日期:** 2026-07-10
4+
**状态:** 已确认,已修复
5+
6+
## 问题:header 使用全局 deviceID/sessionID
7+
8+
所有 header 构造函数的 deviceID 和 sessionID 都来自全局变量,而非 `account.Fingerprint`
9+
10+
### 全局变量(request.go:69-70)
11+
12+
```
13+
oaiDeviceID = uuid.NewString() // 进程级别,所有账号共享
14+
oaiSessionID = uuid.NewString() // 进程级别,所有账号共享
15+
```
16+
17+
### 修复内容
18+
19+
2026-07-14: #272 修复了所有 header 函数,现在优先使用 `account.Fingerprint.OaiDeviceID/SessionID/UserAgent`
20+
回退到全局变量。新增 `baseHeaderFromAccount()` 辅助函数用于那些已有 account 参数的调用点。
21+
22+
### 受影响的调用点(已修复)
23+
24+
| 函数 | 文件 | 修复方式 |
25+
|------|------|---------|
26+
| sentinelHeaderWithState | headers.go | `account.Fingerprint.OaiDeviceID/SessionID` |
27+
| conversationHeadersWithState | headers.go | `account.Fingerprint.OaiDeviceID/SessionID` |
28+
| imageConversationHeadersWithState | headers.go | `account.Fingerprint.OaiDeviceID/SessionID` |
29+
| conversationFetchHeaders | headers.go | `baseHeaderFromAccount(account)` |
30+
| buildSentinelReqToken | sentinel.go | `account.Fingerprint.OaiDeviceID` |
31+
| getURLAttribution | common.go | `baseHeaderFromAccount(account)` |
32+
| getTTSBlobFromURL | tts.go | `baseHeaderFromAccount(account)` |
33+
| RemoveConversation | tts.go | `baseHeaderFromAccount(account)` |
34+
| createUpload | files.go | `baseHeaderFromAccount(account)` |
35+
| confirmUpload | files.go | `baseHeaderFromAccount(account)` |
36+
| TranscribeAudio | transcribe.go | `baseHeaderFromAccount(account)` |
37+
38+
---
39+
40+
*本文件是审计存档,记录修复前后的状态*

internal/chatgpt/common.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,25 @@ func createBaseHeaderForState(state *ChatClientState) httpclient.AuroraHeaders {
135135
return headerbuilder.NewBaseHeaderWithState(conversationID, deviceID, sessionID, ua)
136136
}
137137

138+
// baseHeaderFromAccount 返回带 account.Fingerprint 设备标识的基础 headers。
139+
func baseHeaderFromAccount(account *accounts.Account) httpclient.AuroraHeaders {
140+
h := createBaseHeader()
141+
if account != nil {
142+
if account.Fingerprint.OaiDeviceID != "" {
143+
h.Set("Oai-Device-Id", account.Fingerprint.OaiDeviceID)
144+
} else {
145+
h.Set("Oai-Device-Id", oaiDeviceID)
146+
}
147+
if account.Fingerprint.OaiSessionID != "" {
148+
h.Set("Oai-Session-Id", account.Fingerprint.OaiSessionID)
149+
}
150+
if account.Fingerprint.UserAgent != "" {
151+
h.Set("User-Agent", account.Fingerprint.UserAgent)
152+
}
153+
}
154+
return h
155+
}
156+
138157
// readResponseSnippet 读取响应体的前 limit 字节用于错误报告。
139158
func readResponseSnippet(body io.Reader, limit int64) string {
140159
if limit <= 0 {
@@ -165,7 +184,7 @@ func setTeamAccountHeader(header httpclient.AuroraHeaders, account *accounts.Acc
165184
func getURLAttribution(client httpclient.AuroraHttpClient, account *accounts.Account, url string) string {
166185
requestURL := BaseURL + "/attributions"
167186
payload := bytes.NewBuffer([]byte(`{"urls":["` + url + `"]}`))
168-
header := createBaseHeader()
187+
header := baseHeaderFromAccount(account)
169188
if account != nil && account.PUID != "" {
170189
header.Set("Cookie", "_puid="+account.PUID+";")
171190
}

internal/chatgpt/files.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ func createUpload(client httpclient.AuroraHttpClient, account *accounts.Account,
139139
if err != nil {
140140
return uploadMetaResponse{}, http.StatusInternalServerError, err
141141
}
142-
header := createBaseHeader()
142+
header := baseHeaderFromAccount(account)
143143
header.Set("Accept", "application/json")
144144
header.Set("Content-Type", "application/json")
145145
if account.Token != "" {
@@ -191,7 +191,7 @@ func putUpload(client httpclient.AuroraHttpClient, uploadURL, contentType string
191191
}
192192

193193
func confirmUpload(client httpclient.AuroraHttpClient, account *accounts.Account, fileID string) (int, error) {
194-
header := createBaseHeader()
194+
header := baseHeaderFromAccount(account)
195195
header.Set("Accept", "application/json")
196196
header.Set("Content-Type", "application/json")
197197
if account.Token != "" {

internal/chatgpt/headers.go

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,15 @@ func conversationHeaders(account *accounts.Account, chatToken *TurnStile, accept
3434
// conversationHeadersWithState 创建对话请求的 header(带 state)。
3535
func conversationHeadersWithState(account *accounts.Account, chatToken *TurnStile, accept, targetPath, conduitToken, turnTraceID string, state *ChatClientState) httpclient.AuroraHeaders {
3636
conversationID := ""
37-
deviceID := oaiDeviceID
38-
sessionID := oaiSessionID
39-
ua := ""
37+
deviceID := account.Fingerprint.OaiDeviceID
38+
if deviceID == "" {
39+
deviceID = oaiDeviceID
40+
}
41+
sessionID := account.Fingerprint.OaiSessionID
42+
if sessionID == "" {
43+
sessionID = oaiSessionID
44+
}
45+
ua := account.Fingerprint.UserAgent
4046
if state != nil {
4147
if state.ConversationID != "" {
4248
conversationID = state.ConversationID
@@ -89,9 +95,15 @@ func sentinelHeader(account *accounts.Account, targetPath string) httpclient.Aur
8995
// sentinelHeaderWithState 创建 sentinel 请求的 header(带 state)。
9096
func sentinelHeaderWithState(account *accounts.Account, targetPath string, state *ChatClientState) httpclient.AuroraHeaders {
9197
conversationID := ""
92-
deviceID := oaiDeviceID
93-
sessionID := oaiSessionID
94-
ua := ""
98+
deviceID := account.Fingerprint.OaiDeviceID
99+
if deviceID == "" {
100+
deviceID = oaiDeviceID
101+
}
102+
sessionID := account.Fingerprint.OaiSessionID
103+
if sessionID == "" {
104+
sessionID = oaiSessionID
105+
}
106+
ua := account.Fingerprint.UserAgent
95107
if state != nil {
96108
if state.ConversationID != "" {
97109
conversationID = state.ConversationID
@@ -126,9 +138,15 @@ func imageConversationHeaders(account *accounts.Account, turnStile *TurnStile, c
126138
// imageConversationHeadersWithState 创建图片对话请求的 header(带 state)。
127139
func imageConversationHeadersWithState(account *accounts.Account, turnStile *TurnStile, conduitToken, accept string, state *ChatClientState) httpclient.AuroraHeaders {
128140
conversationID := ""
129-
deviceID := oaiDeviceID
130-
sessionID := oaiSessionID
131-
ua := ""
141+
deviceID := account.Fingerprint.OaiDeviceID
142+
if deviceID == "" {
143+
deviceID = oaiDeviceID
144+
}
145+
sessionID := account.Fingerprint.OaiSessionID
146+
if sessionID == "" {
147+
sessionID = oaiSessionID
148+
}
149+
ua := account.Fingerprint.UserAgent
132150
if state != nil {
133151
if state.ConversationID != "" {
134152
conversationID = state.ConversationID
@@ -170,7 +188,7 @@ func imageConversationHeadersWithState(account *accounts.Account, turnStile *Tur
170188

171189
// conversationFetchHeaders 创建查询对话的 header。
172190
func conversationFetchHeaders(account *accounts.Account) httpclient.AuroraHeaders {
173-
header := createBaseHeader()
191+
header := baseHeaderFromAccount(account)
174192
header.Set("Accept", "application/json")
175193
header.Set("Content-Type", "application/json")
176194
if account.Token != "" {

internal/chatgpt/sentinel.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -448,9 +448,12 @@ func POSTSentinelFinalizeWithState(client httpclient.AuroraHttpClient, account *
448448
}
449449

450450
// buildSentinelReqToken 为 /sentinel/req 端点生成指纹 token (nonce=2)。
451-
func buildSentinelReqToken(state *ChatClientState) string {
451+
func buildSentinelReqToken(state *ChatClientState, account *accounts.Account) string {
452452
ua := defaultUserAgent()
453453
deviceID := oaiDeviceID
454+
if account != nil && account.Fingerprint.OaiDeviceID != "" {
455+
deviceID = account.Fingerprint.OaiDeviceID
456+
}
454457
if state != nil {
455458
if state.UserAgent != "" {
456459
ua = state.UserAgent
@@ -503,7 +506,7 @@ func POSTSentinelReq(client httpclient.AuroraHttpClient, account *accounts.Accou
503506
if flow == "" {
504507
flow = "conversation"
505508
}
506-
reqToken := buildSentinelReqToken(state)
509+
reqToken := buildSentinelReqToken(state, account)
507510
apiUrl, targetPath := sentinelURL(account, "/sentinel/req")
508511
bodyJSON, err := json.Marshal(map[string]string{
509512
"p": reqToken,

internal/chatgpt/transcribe.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func TranscribeAudio(client httpclient.AuroraHttpClient, account *accounts.Accou
6565
}
6666

6767
// 请求头
68-
header := createBaseHeader()
68+
header := baseHeaderFromAccount(account)
6969
header.Set("Accept", "application/json")
7070
header.Set("Content-Type", w.FormDataContentType())
7171
if account.Token != "" {

internal/chatgpt/tts.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ func HandlerTTS(response *http.Response, input string) (string, string) {
101101
}
102102

103103
func getTTSBlobFromURL(client httpclient.AuroraHttpClient, account *accounts.Account, reqURL string) ([]byte, int, error) {
104-
header := createBaseHeader()
104+
header := baseHeaderFromAccount(account)
105105
header.Set("Accept", "audio/*,*/*")
106106
if !(account.Type == accounts.TypeNoAuth) && account.Token != "" {
107107
header.Set("Authorization", "Bearer "+account.Token)
@@ -184,7 +184,7 @@ func RemoveConversation(client httpclient.AuroraHttpClient, account *accounts.Ac
184184
} else {
185185
url = BaseURL + "/conversation/" + id
186186
}
187-
header := createBaseHeader()
187+
header := baseHeaderFromAccount(account)
188188
header.Set("Content-Type", "application/json")
189189
if !(account.Type == accounts.TypeNoAuth) && account.Token != "" {
190190
header.Set("Authorization", "Bearer "+account.Token)

internal/conversationflow/flow.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,13 @@ func (f *FlowOrchestrator) ExecuteConversation(c *gin.Context, req ExecuteReques
6969
// 2. 获取 account
7070
account := f.resolveSecret(c, req)
7171

72-
client := bogdanfinn.NewStdClient()
72+
// 使用 account 绑定的 Client(有指纹 + 代理);不存在则新建
73+
var client *bogdanfinn.TlsClient
74+
if c, ok := account.Client.(*bogdanfinn.TlsClient); ok && c != nil {
75+
client = c
76+
} else {
77+
client = bogdanfinn.NewStdClient()
78+
}
7379

7480
// 3. 初始化 turnstile + WebSocket
7581
response, wsConn, turnStile, status, err := f.postConversationOrder(
@@ -183,7 +189,13 @@ func (f *FlowOrchestrator) HandleToolCalling(c *gin.Context, req ExecuteRequest)
183189

184190
proxyURL := f.Proxy.Allocate()
185191
account := f.resolveSecretForTool(c, req)
186-
client := bogdanfinn.NewStdClient()
192+
// 使用 account 绑定的 Client(有指纹 + 代理);不存在则新建
193+
var client *bogdanfinn.TlsClient
194+
if c, ok := account.Client.(*bogdanfinn.TlsClient); ok && c != nil {
195+
client = c
196+
} else {
197+
client = bogdanfinn.NewStdClient()
198+
}
187199
clientState := f.resolveClientState(req.TranslatedRequest)
188200

189201
var lastText, lastConversationID string

internal/handler/chat_handler.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -454,10 +454,16 @@ func (h *ChatHandler) Files(c *gin.Context) {
454454

455455
contentType := formFile.Header.Get("Content-Type")
456456

457-
client := bogdanfinn.NewStdClient()
458-
client.SetCookies("https://chatgpt.com", chatgpt.BasicCookies)
457+
// 使用 account 绑定的 Client(有指纹 + 代理);不存在则新建
458+
var fileClient *bogdanfinn.TlsClient
459+
if c, ok := account.Client.(*bogdanfinn.TlsClient); ok && c != nil {
460+
fileClient = c
461+
} else {
462+
fileClient = bogdanfinn.NewStdClient()
463+
fileClient.SetCookies("https://chatgpt.com", chatgpt.BasicCookies)
464+
}
459465

460-
uploaded, status, err := chatgpt.UploadFile(client, account, account.Proxy, formFile.Filename, contentType, data)
466+
uploaded, status, err := chatgpt.UploadFile(fileClient, account, account.Proxy, formFile.Filename, contentType, data)
461467
if err != nil {
462468
c.JSON(status, gin.H{"error": gin.H{
463469
"message": err.Error(),
@@ -605,11 +611,17 @@ func (h *ChatHandler) ChatGPTConversation(c *gin.Context) {
605611
return
606612
}
607613

608-
client := bogdanfinn.NewStdClient()
609-
if account.Proxy != "" {
610-
client.SetProxy(account.Proxy)
614+
// 使用 account 绑定的 Client(有指纹 + 代理);不存在则新建
615+
var convClient *bogdanfinn.TlsClient
616+
if c, ok := account.Client.(*bogdanfinn.TlsClient); ok && c != nil {
617+
convClient = c
618+
} else {
619+
convClient = bogdanfinn.NewStdClient()
620+
if account.Proxy != "" {
621+
convClient.SetProxy(account.Proxy)
622+
}
611623
}
612-
turnStile, status, err := chatgpt.InitSentinel(client, account, account.Proxy, 0)
624+
turnStile, status, err := chatgpt.InitSentinel(convClient, account, account.Proxy, 0)
613625
if err != nil {
614626
if status == http.StatusUnauthorized {
615627
h.accountPool.ReportFailure(account)
@@ -623,7 +635,7 @@ func (h *ChatHandler) ChatGPTConversation(c *gin.Context) {
623635
return
624636
}
625637

626-
response, err := chatgpt.POSTconversation(client, original_request, account, turnStile, account.Proxy)
638+
response, err := chatgpt.POSTconversation(convClient, original_request, account, turnStile, account.Proxy)
627639
if err != nil {
628640
c.JSON(500, gin.H{"error": "error sending request"})
629641
return

0 commit comments

Comments
 (0)