feat: add sentinel token generation for account creation

This commit is contained in:
verssache
2026-04-06 10:59:58 +07:00
parent d9c6645ce3
commit 28a4607d98
4 changed files with 252 additions and 0 deletions

View File

@@ -11,6 +11,7 @@ import (
http "github.com/bogdanfinn/fhttp"
"github.com/verssache/chatgpt-creator/internal/email"
"github.com/verssache/chatgpt-creator/internal/sentinel"
"github.com/verssache/chatgpt-creator/internal/util"
)
@@ -228,11 +229,17 @@ func (c *Client) createAccount(name, birthdate string) (int, map[string]interfac
}
jsonPayload, _ := json.Marshal(payload)
sentinelCreateAccount, err := sentinel.BuildSentinelToken(c.session, c.deviceID, "create_account", c.ua, c.secChUA, c.impersonate)
if err != nil {
return 0, nil, fmt.Errorf("failed to get sentinel auth: %v", err)
}
req, _ := http.NewRequest("POST", createURL, strings.NewReader(string(jsonPayload)))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("Referer", authURL+"/about-you")
req.Header.Set("Origin", authURL)
req.Header.Set("openai-sentinel-token", sentinelCreateAccount)
traceHeaders := util.MakeTraceHeaders()
for k, v := range traceHeaders {

View File

@@ -0,0 +1,107 @@
package sentinel
import (
"bytes"
"encoding/json"
"fmt"
"io"
fhttp "github.com/bogdanfinn/fhttp"
tls_client "github.com/bogdanfinn/tls-client"
)
func FetchSentinelChallenge(session tls_client.HttpClient, deviceID, flow, ua, secChUA, impersonate string) (map[string]any, error) {
generator := NewGenerator(deviceID, ua)
reqBody := map[string]any{
"p": generator.GenerateRequirementsToken(),
"id": deviceID,
"flow": flow,
}
bodyBytes, err := json.Marshal(reqBody)
if err != nil {
return nil, err
}
req, err := fhttp.NewRequest("POST", "https://sentinel.openai.com/backend-api/sentinel/req", bytes.NewBuffer(bodyBytes))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "text/plain;charset=UTF-8")
req.Header.Set("Referer", "https://sentinel.openai.com/sentinel/20260124ceb8/frame.html")
req.Header.Set("Origin", "https://sentinel.openai.com")
req.Header.Set("User-Agent", ua)
if secChUA != "" {
req.Header.Set("sec-ch-ua", secChUA)
} else {
req.Header.Set("sec-ch-ua", "\"Not:A-Brand\";v=\"99\", \"Google Chrome\";v=\"145\", \"Chromium\";v=\"145\"")
}
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", "\"Windows\"")
req.Header.Set("oai-device-id", deviceID)
req.Header.Set("oai-language", "en-US")
resp, err := session.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != fhttp.StatusOK {
return nil, fmt.Errorf("sentinel challenge request failed with status: %d", resp.StatusCode)
}
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var result map[string]any
if err := json.Unmarshal(respBody, &result); err != nil {
return nil, err
}
return result, nil
}
func BuildSentinelToken(session tls_client.HttpClient, deviceID, flow, ua, secChUA, impersonate string) (string, error) {
challenge, err := FetchSentinelChallenge(session, deviceID, flow, ua, secChUA, impersonate)
if err != nil {
return "", err
}
cValue, ok := challenge["token"].(string)
if !ok || cValue == "" {
return "", fmt.Errorf("invalid sentinel challenge token")
}
var pValue string
generator := NewGenerator(deviceID, ua)
powData, _ := challenge["proofofwork"].(map[string]any)
required, _ := powData["required"].(bool)
seed, _ := powData["seed"].(string)
if required && seed != "" {
difficulty, _ := powData["difficulty"].(string)
pValue = generator.GenerateToken(seed, difficulty)
} else {
pValue = generator.GenerateRequirementsToken()
}
tokenData := map[string]string{
"p": pValue,
"t": "",
"c": cValue,
"id": deviceID,
"flow": flow,
}
resultBytes, err := json.Marshal(tokenData)
if err != nil {
return "", err
}
return string(resultBytes), nil
}

21
internal/sentinel/fnv.go Normal file
View File

@@ -0,0 +1,21 @@
package sentinel
import "fmt"
// fnv1a32 computes a 32-bit FNV-1a hash with avalanche finalizer.
// This is a custom implementation independent of hash/fnv, matching
// the Python reference implementation exactly.
func FNV1a32(text string) string {
var h uint32 = 2166136261
for _, c := range text {
h ^= uint32(c)
h *= 16777619
}
// Avalanche finalizer (murmur3-style)
h ^= h >> 16
h *= 2246822507
h ^= h >> 13
h *= 3266489909
h ^= h >> 16
return fmt.Sprintf("%08x", h)
}

View File

@@ -0,0 +1,117 @@
package sentinel
import (
"encoding/base64"
"encoding/json"
"fmt"
"math/rand"
"time"
"github.com/google/uuid"
)
type SentinelTokenGenerator struct {
DeviceID string
UserAgent string
RequirementsSeed string
SID string
}
func NewGenerator(deviceID, ua string) *SentinelTokenGenerator {
if deviceID == "" {
deviceID = uuid.New().String()
}
if ua == "" {
ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"
}
return &SentinelTokenGenerator{
DeviceID: deviceID,
UserAgent: ua,
RequirementsSeed: fmt.Sprintf("%f", rand.Float64()),
SID: uuid.New().String(),
}
}
func (g *SentinelTokenGenerator) getConfig() []any {
now := time.Now().UTC()
nowStr := now.Format("Mon Jan 02 2006 15:04:05 GMT+0000 (Coordinated Universal Time)")
perfNow := rand.Float64()*49000 + 1000
timeOrigin := float64(now.UnixNano()/1e6) - perfNow
navProps := []string{
"vendorSub", "productSub", "vendor", "maxTouchPoints",
"scheduling", "userActivation", "doNotTrack", "geolocation",
"connection", "plugins", "mimeTypes", "pdfViewerEnabled",
"webkitTemporaryStorage", "webkitPersistentStorage",
"hardwareConcurrency", "cookieEnabled", "credentials",
"mediaDevices", "permissions", "locks", "ink",
}
navProp := navProps[rand.Intn(len(navProps))]
navVal := fmt.Sprintf("%s-undefined", navProp)
screenRes := "1920x1080"
sdkJS := "https://sentinel.openai.com/sentinel/20260124ceb8/sdk.js"
return []any{
screenRes,
nowStr,
4294705152,
0, // nonce placeholder
g.UserAgent,
sdkJS,
nil,
nil,
"en-US",
"en-US,en",
rand.Float64(),
navVal,
[]string{"location", "implementation", "URL", "documentURI", "compatMode"}[rand.Intn(5)],
[]string{"Object", "Function", "Array", "Number", "parseFloat", "undefined"}[rand.Intn(6)],
perfNow,
g.SID,
"",
[]int{4, 8, 12, 16}[rand.Intn(4)],
timeOrigin,
}
}
func (g *SentinelTokenGenerator) base64Encode(data any) string {
raw, _ := json.Marshal(data)
return base64.StdEncoding.EncodeToString(raw)
}
func (g *SentinelTokenGenerator) GenerateToken(seed string, difficulty string) string {
if seed == "" {
seed = g.RequirementsSeed
}
if difficulty == "" {
difficulty = "0"
}
startTime := time.Now()
config := g.getConfig()
for i := 0; i < 500000; i++ {
config[3] = i
elapsed := time.Since(startTime).Milliseconds()
config[9] = elapsed
data := g.base64Encode(config)
hashHex := FNV1a32(seed + data)
if hashHex[:len(difficulty)] <= difficulty {
return "gAAAAAB" + data + "~S"
}
}
// Fallback error token (simplified)
return "gAAAAAB" + "wQ8Lk5FbGpA2NcR9dShT6gYjU7VxZ4D" + g.base64Encode("None")
}
func (g *SentinelTokenGenerator) GenerateRequirementsToken() string {
config := g.getConfig()
config[3] = 1
config[9] = rand.Intn(45) + 5
data := g.base64Encode(config)
return "gAAAAAC" + data
}