@@ -33,6 +33,9 @@ const (
3333 svchosttest = "redir.nile.workers.dev"
3434 wsMyIp = "https://checkip.windscribe.com/"
3535 wsMyIp2 = "https://checkip.totallyacdn.com/"
36+ // didTokenHeader is the HTTP response/request header for a device-id token
37+ // issued by svchost / svchosttest. Format: "a hextoken:expiryepochsec".
38+ didTokenHeader = "x-rethink-app-did-token"
3639)
3740
3841const (
@@ -711,6 +714,9 @@ type WsEntitlement struct {
711714 AccStatus string `json:"status"` // "valid" | "invalid" | "banned" | "expired" | "unknown"
712715 AllowCrossDevice bool `json:"allowRestore"` // true if this entitlement can be restored
713716 TestDomain bool `json:"test"` // true if this is a test entitlement
717+ // DidToken is the device-id token (x-rethink-app-did-token) issued by svchost,
718+ // format "a hextoken:expiryepochsec".
719+ DidToken string `json:"didtoken,omitempty"`
714720}
715721
716722var _ x.RpnAcc = (* WsClient )(nil )
@@ -1051,6 +1057,45 @@ func authHeader(req *http.Request, t string) {
10511057 req .Header .Set ("Authorization" , "Bearer " + t )
10521058}
10531059
1060+ // didHeader sets the didTokenHeader request header if tok is non-empty.
1061+ func didHeader (req * http.Request , tok string ) {
1062+ if req != nil && len (tok ) > 0 {
1063+ req .Header .Set (didTokenHeader , tok )
1064+ }
1065+ }
1066+
1067+ func logDidToken (tok string ) {
1068+ if len (tok ) <= 0 {
1069+ log .W ("ws: didtoken: empty token" )
1070+ return
1071+ }
1072+ // token format is "a hextoken:expiryepochsec"; parse the epoch to log expiry.
1073+ parts := strings .SplitN (tok , ":" , 2 )
1074+ if len (parts ) < 2 {
1075+ log .W ("ws: didtoken: unknown format" )
1076+ return
1077+ }
1078+ expSec , err := strconv .ParseInt (strings .TrimSpace (parts [1 ]), 10 , 64 )
1079+ if err != nil {
1080+ log .E ("ws: didtoken: cannot parse expiry epoch: %v" , err )
1081+ }
1082+ expTime := time .Unix (expSec , 0 )
1083+ log .I ("ws: didtoken: expiry %s; expired? %t" , fmtTime (expTime ), expTime .Before (time .Now ()))
1084+ }
1085+
1086+ // updateDidTokenIfNeeded reads didTokenHeader from the response, logs its expiry,
1087+ // and – when it differs from ent.DidToken – overwrites it in ent.
1088+ func updateDidTokenIfNeeded (ent * WsEntitlement , res * http.Response ) {
1089+ if ent == nil || res == nil {
1090+ return
1091+ }
1092+ incoming := res .Header .Get (didTokenHeader )
1093+ logDidToken (incoming )
1094+ if len (incoming ) > 0 && ent .DidToken != incoming {
1095+ ent .DidToken = incoming
1096+ }
1097+ }
1098+
10541099func wsErr (res * http.Response , op string ) error {
10551100 _ , err := wsErr2 (res , op )
10561101 return err
@@ -1099,7 +1144,11 @@ func wsRes[T any](res *http.Response, out *T, op string) (*T, error) {
10991144 return out , nil
11001145}
11011146
1102- func getSession (h * http.Client , cid , did , tok string , test bool ) (* WsSession , error ) {
1147+ func getSession (h * http.Client , ent * WsEntitlement ) (* WsSession , error ) {
1148+ if ent == nil {
1149+ return nil , errWsNoSession
1150+ }
1151+ tok := ent .SessionToken
11031152 if len (tok ) <= 0 {
11041153 return nil , errWsNoToken
11051154 }
@@ -1108,12 +1157,13 @@ func getSession(h *http.Client, cid, did, tok string, test bool) (*WsSession, er
11081157 curl -x GET '.../Session'
11091158 -H 'Authorization: Bearer id:typ:epochsec:sig1:sig2'
11101159 */
1111- u := baseurl (test , cid , did ).JoinPath (wssessionpath )
1160+ u := baseurl (ent . TestDomain , ent . Cid , ent . Did ).JoinPath (wssessionpath )
11121161 req , err := http .NewRequest ("GET" , u .String (), nil )
11131162 if err != nil {
11141163 return nil , log .EE ("ws: getsess: make req err: %v" , err )
11151164 }
11161165 authHeader (req , tok )
1166+ didHeader (req , ent .DidToken )
11171167
11181168 if settings .Debug {
11191169 log .V ("ws: getsess: req: %s tok %s" , u .String (), tokst )
@@ -1124,6 +1174,7 @@ func getSession(h *http.Client, cid, did, tok string, test bool) (*WsSession, er
11241174 return nil , log .EE ("ws: getsess: res err (nil? %t / tok? %s): %v" , res == nil , tokst , err )
11251175 }
11261176 defer core .Close (res .Body )
1177+ updateDidTokenIfNeeded (ent , res )
11271178 if res .StatusCode != http .StatusOK {
11281179 return nil , wsErr (res , "getsess/" + tokst )
11291180 }
@@ -1313,6 +1364,7 @@ func getServerList(h *http.Client, sess *WsSession, ent *WsEntitlement) (*WsServ
13131364 if err != nil {
13141365 return nil , log .EE ("ws: wgconfs: req err: %v" , err )
13151366 }
1367+ didHeader (locreq , ent .DidToken )
13161368
13171369 if settings .Debug {
13181370 log .V ("ws: wgconfs: req: %s tok %s" , u .String (), tokenState (bearer ))
@@ -1324,6 +1376,7 @@ func getServerList(h *http.Client, sess *WsSession, ent *WsEntitlement) (*WsServ
13241376 }
13251377
13261378 defer core .Close (locres .Body )
1379+ updateDidTokenIfNeeded (ent , locres )
13271380 if locres .StatusCode != http .StatusOK {
13281381 return nil , wsErr (locres , "wgconfs" )
13291382 }
@@ -1399,6 +1452,7 @@ initagain:
13991452 }
14001453 initreq .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
14011454 authHeader (initreq , bearer )
1455+ didHeader (initreq , ent .DidToken )
14021456
14031457 if settings .Debug {
14041458 log .V ("ws: wgconfs: init req: %s; tok %s; force %s" , u .String (), tokst , force )
@@ -1409,6 +1463,7 @@ initagain:
14091463 if err != nil || initres == nil {
14101464 return nil , nil , log .EE ("ws: wgconfs: res err (nil? %t / tok? %s): %v" , initres == nil , tokst , err )
14111465 }
1466+ updateDidTokenIfNeeded (ent , initres )
14121467
14131468 if initres .StatusCode != http .StatusOK {
14141469 wserr , err := wsErr2 (initres , "wsinit" )
@@ -1478,6 +1533,7 @@ initagain:
14781533 }
14791534 creq .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
14801535 authHeader (creq , sess .SessionToken )
1536+ didHeader (creq , ent .DidToken )
14811537
14821538 if settings .Debug {
14831539 log .V ("ws: wgconfs: connect req: %s tok %s" , u .String (), tokst )
@@ -1487,6 +1543,7 @@ initagain:
14871543 if err != nil || cres == nil {
14881544 return nil , nil , log .EE ("ws: wgconfs: connect res err (nil? %t / tok? %s): %v" , cres == nil , tokst , err )
14891545 }
1546+ updateDidTokenIfNeeded (ent , cres )
14901547 if cres .StatusCode != http .StatusOK {
14911548 wserr , err := wsErr2 (cres , "wsconnect" )
14921549 core .Close (cres .Body )
@@ -1587,7 +1644,7 @@ func makeWsWg(h *http.Client, ent *WsEntitlement) (*WsClient, error) {
15871644 return nil , errWsNoEntitlement
15881645 }
15891646
1590- sess , err := getSession (h , ent . Cid , ent . Did , ent . SessionToken , ent . TestDomain )
1647+ sess , err := getSession (h , ent )
15911648 if err != nil {
15921649 return nil , err
15931650 }
@@ -1684,17 +1741,14 @@ func makeWsWgFrom(h *http.Client, existingConf *WsWgConfig, errOnNoUpdate bool)
16841741 return
16851742 }
16861743
1687- cid := existingEnt .Cid
1688- did := existingEnt .Did
16891744 tokst := existingConf .tokenState ()
16901745 existingToken := existingSess .SessionToken
16911746 existingLocHash := existingSess .LocHash
1692- existingTestDomain := existingEnt .TestDomain
16931747 if existingEnt .SessionToken != existingToken {
16941748 log .W ("ws: make: entitlement does not match session; tok? %s" , tokst )
16951749 }
16961750
1697- newSess , err := getSession (h , cid , did , existingToken , existingTestDomain )
1751+ newSess , err := getSession (h , existingEnt )
16981752 if err == nil {
16991753 existingConf .Session = newSess // update session with the latest info
17001754 refreshedSess = true
0 commit comments