@@ -9,15 +9,16 @@ package auth
99//
1010// A campaign is claimable when actionType == "CLAIM_BENEFIT" and
1111// claimStatus == "CLAIMABLE"; an already-CLAIMED campaign means "今日已签到".
12- // The request authenticates with the session's `securityOauthToken` as a Bearer
13- // token (not the COSY-signed bearer used by the chat path) plus the machine
14- // identity headers. Cosy-MachineToken/Type/Code come from the genuine desktop
15- // fingerprint when available; Cosy-MachineId stays the session's own random
16- // uuid, so the header set is NOT byte-identical to the desktop client's.
12+ // The request authenticates with a "jt-" OpenAPI bearer (minted via
13+ // /api/v1/jobToken/exchange in both regions) — the COSY securityOauthToken is
14+ // rejected on this endpoint. The six Cosy-Machine* headers are sent only when
15+ // the genuine desktop fingerprint is available: a full set of derived
16+ // (non-native) values marks the caller as an unofficial client and the
17+ // claimable activity is filtered out, so without a fingerprint none of them
18+ // are sent.
1719
1820import (
1921 "context"
20- "crypto/md5"
2122 "crypto/sha256"
2223 "encoding/hex"
2324 "encoding/json"
@@ -104,34 +105,33 @@ func checkinEndpoint(region *RegionConfig) (base, origin string) {
104105// caller is responsible for ensuring the session is fresh; a 401 returned here
105106// is surfaced as *CheckinHTTPError so the bridge can force-refresh and retry.
106107//
107- // This variant sends the PAT-derived device fingerprint. The bridge uses
108- // CheckinWithBearer to send the genuine desktop fingerprint, which the campaign
109- // service requires to expose the daily credit activity .
110- func Checkin (ctx context.Context , sess * SessionContext , pat string , region * RegionConfig ) (CheckinResult , error ) {
111- return CheckinWithRisk (ctx , sess , pat , region , nil )
108+ // This variant sends no machine headers (no genuine desktop fingerprint is
109+ // available). The bridge uses CheckinWithBearer with the desktop fingerprint
110+ // and the jt- OpenAPI bearer .
111+ func Checkin (ctx context.Context , sess * SessionContext , region * RegionConfig ) (CheckinResult , error ) {
112+ return CheckinWithRisk (ctx , sess , region , nil )
112113}
113114
114115// CheckinWithRisk is Checkin with an optional genuine device fingerprint. When
115- // risk is valid, its machineToken/Type/Code replace the PAT-derived values, so
116- // the campaign service treats the request as the official desktop client and
117- // includes the claimable daily credit activity. When risk is nil or partial,
118- // the derived identity is used instead .
116+ // risk is valid, the six machine headers carry its machineToken/Type/Code plus
117+ // the session's MachineId; when risk is nil or partial, no machine headers are
118+ // sent at all — a full set of derived values would be filtered as an
119+ // unofficial client .
119120//
120- // The Bearer token defaults to the session's securityOauthToken. Global
121- // (qoder.sh) rejects that token on the campaign endpoint (401 TOKEN_EXPIRE) and
122- // requires the OpenAPI bearer instead; callers there should use
123- // CheckinWithBearer with the OpenAPI token.
124- func CheckinWithRisk (ctx context.Context , sess * SessionContext , pat string , region * RegionConfig , risk * RiskIdentity ) (CheckinResult , error ) {
121+ // The Bearer token defaults to the session's securityOauthToken; the
122+ // campaigns endpoint rejects that in both regions, so production callers use
123+ // CheckinWithBearer with the jt- OpenAPI token.
124+ func CheckinWithRisk (ctx context.Context , sess * SessionContext , region * RegionConfig , risk * RiskIdentity ) (CheckinResult , error ) {
125125 if sess == nil {
126126 return CheckinResult {}, errors .New ("qoder check-in: no session" )
127127 }
128- return CheckinWithBearer (ctx , sess , pat , region , sess .Identity .SecurityOauthToken , risk )
128+ return CheckinWithBearer (ctx , sess , region , sess .Identity .SecurityOauthToken , risk )
129129}
130130
131131// CheckinWithBearer is the low-level entry: it uses the supplied bearer token
132- // (CN: securityOauthToken; global: OpenAPI token) plus the optional genuine
133- // device fingerprint.
134- func CheckinWithBearer (ctx context.Context , sess * SessionContext , pat string , region * RegionConfig , bearer string , risk * RiskIdentity ) (CheckinResult , error ) {
132+ // (both regions: the jt- OpenAPI token) plus the optional genuine device
133+ // fingerprint.
134+ func CheckinWithBearer (ctx context.Context , sess * SessionContext , region * RegionConfig , bearer string , risk * RiskIdentity ) (CheckinResult , error ) {
135135 if sess == nil {
136136 return CheckinResult {}, errors .New ("qoder check-in: no session" )
137137 }
@@ -142,20 +142,14 @@ func CheckinWithBearer(ctx context.Context, sess *SessionContext, pat string, re
142142
143143 base , origin := checkinEndpoint (region )
144144
145- // Prefer the genuine desktop fingerprint when available: the campaign
146- // service only exposes the claimable daily-credit activity to the official
147- // client's machine identity. Fall back to the PAT-derived identity otherwise.
148- machineID := sess .MachineID
149- machineToken := sess .MachineToken
150- machineType := sess .MachineType
151- machineCode := deriveMachineCode (pat )
152- if risk .Valid () {
153- machineToken = risk .MachineToken
154- machineType = risk .MachineType
155- machineCode = risk .MachineCode
156- }
157- machineOS := machineOSString ()
158- machineHostname := machineHostnameString ()
145+ // Machine headers: real desktop identity => send all six; no native
146+ // identity (typical on Linux/Docker, where runtime-info is unavailable)
147+ // => send none. A full set of derived (non-native) values marks the caller
148+ // as an unofficial client and the claimable daily-credit activity is
149+ // filtered out of the list. UA / Cosy-ClientType / Cosy-Version must still
150+ // be present or the endpoint returns an empty campaign list without any
151+ // error.
152+ sendMachineHeaders := risk .Valid ()
159153
160154 doRequest := func (path , method string ) (map [string ]any , int , error ) {
161155 reqCtx , cancel := context .WithTimeout (ctx , 15 * time .Second )
@@ -170,15 +164,17 @@ func CheckinWithBearer(ctx context.Context, sess *SessionContext, pat string, re
170164 req .Header .Set ("User-Agent" , "Qoder" )
171165 req .Header .Set ("Cosy-ClientType" , "10" )
172166 req .Header .Set ("Cosy-Version" , "0.3.4" )
173- req .Header .Set ("Cosy-MachineId" , machineID )
174- req .Header .Set ("Cosy-MachineToken" , machineToken )
175- req .Header .Set ("Cosy-MachineType" , machineType )
176- req .Header .Set ("Cosy-MachineCode" , machineCode )
177- if machineOS != "" {
178- req .Header .Set ("Cosy-MachineOS" , machineOS )
179- }
180- if machineHostname != "" {
181- req .Header .Set ("Cosy-MachineHostname" , machineHostname )
167+ if sendMachineHeaders {
168+ req .Header .Set ("Cosy-MachineId" , sess .MachineID )
169+ req .Header .Set ("Cosy-MachineToken" , risk .MachineToken )
170+ req .Header .Set ("Cosy-MachineType" , risk .MachineType )
171+ req .Header .Set ("Cosy-MachineCode" , risk .MachineCode )
172+ if machineOS := machineOSString (); machineOS != "" {
173+ req .Header .Set ("Cosy-MachineOS" , machineOS )
174+ }
175+ if host := machineHostnameString (); host != "" {
176+ req .Header .Set ("Cosy-MachineHostname" , host )
177+ }
182178 }
183179 req .Header .Set ("Origin" , origin )
184180 req .Header .Set ("Referer" , base + "/growth-page/activity-iframe" )
@@ -397,17 +393,6 @@ func truncateOneLine(s string) string {
397393 return s
398394}
399395
400- // deriveMachineCode is the fourth machine-identity header the OpenAPI campaigns
401- // endpoint expects (Cosy-MachineCode), alongside Id/Token/Type. The CLI computes
402- // it from real hardware; we derive a stable value from the PAT instead, mirroring
403- // the other derived identities. A partial identity (missing any of the four)
404- // causes the campaigns endpoint to silently drop the claimable credit campaign,
405- // so this MUST be non-empty.
406- func deriveMachineCode (pat string ) string {
407- sum := md5 .Sum ([]byte ("machinecode:" + pat ))
408- return hex .EncodeToString (sum [:])
409- }
410-
411396// machineOSString mirrors the desktop client's machineOS(): "<arch>_<platform>"
412397// (e.g. "aarch64_darwin").
413398func machineOSString () string {
0 commit comments