diff --git a/internal/register/flow.go b/internal/register/flow.go index 85c0e13..16547ca 100644 --- a/internal/register/flow.go +++ b/internal/register/flow.go @@ -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 { diff --git a/internal/sentinel/challenge.go b/internal/sentinel/challenge.go new file mode 100644 index 0000000..d487516 --- /dev/null +++ b/internal/sentinel/challenge.go @@ -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 +} diff --git a/internal/sentinel/fnv.go b/internal/sentinel/fnv.go new file mode 100644 index 0000000..9d29b8c --- /dev/null +++ b/internal/sentinel/fnv.go @@ -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) +} diff --git a/internal/sentinel/generator.go b/internal/sentinel/generator.go new file mode 100644 index 0000000..47ed7df --- /dev/null +++ b/internal/sentinel/generator.go @@ -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 +}