@@ -79,8 +79,6 @@ type dpopKeyPairEnvelope struct {
7979 JWK * jwkECPrivateKey `json:"jwk"`
8080}
8181
82- const defaultDPoPKeySecret = "passport-dpop-token-generator"
83-
8482type encryptedDpopKeyPair struct {
8583 Data string `json:"data"`
8684 Ciphertext string `json:"ciphertext"`
@@ -162,6 +160,14 @@ func GenerateDPoPToken(in DPoPTokenInput) (*DPoPTokenOutput, error) {
162160 }, nil
163161}
164162
163+ func GenerateDPoPKeyPair () (* ecdsa.PrivateKey , error ) {
164+ key , err := ecdsa .GenerateKey (elliptic .P256 (), rand .Reader )
165+ if err != nil {
166+ return nil , err
167+ }
168+ return validateP256Key (key )
169+ }
170+
165171func ParseJWTPayload (token string , out any ) error {
166172 token = strings .TrimSpace (trimTokenScheme (token ))
167173 parts := strings .Split (token , "." )
@@ -234,7 +240,7 @@ func parseECPrivateKeyJWK(raw string) (*ecdsa.PrivateKey, error) {
234240 return validateP256Key (key )
235241}
236242
237- func parseEncryptedDPoPKeyPair (raw string ) (* ecdsa.PrivateKey , error ) {
243+ func parseEncryptedDPoPKeyPair (raw , secret string ) (* ecdsa.PrivateKey , error ) {
238244 raw = strings .TrimSpace (raw )
239245 if raw == "" {
240246 return nil , errors .New ("empty encrypted key pair" )
@@ -266,9 +272,9 @@ func parseEncryptedDPoPKeyPair(raw string) (*ecdsa.PrivateKey, error) {
266272 return nil , errors .New ("encrypted dpop payload too short" )
267273 }
268274
269- plain , err := decryptDoubaoKeyPair (decoded , defaultDPoPKeySecret )
275+ plain , err := decryptDoubaoKeyPair (decoded , secret )
270276 if err != nil {
271- return nil , fmt .Errorf ("failed to decrypt with default secret: %w" , err )
277+ return nil , fmt .Errorf ("failed to decrypt with secret: %w" , err )
272278 }
273279 return parseECPrivateKeyJWK (string (plain ))
274280}
@@ -381,7 +387,7 @@ func (d *DoubaoNew) resolveAuthorization() string {
381387 return "DPoP " + auth
382388}
383389
384- func shouldRefreshJWT (token string , aheadSeconds int64 ) bool {
390+ func shouldRefreshJWT (token string ) bool {
385391 if token == "" {
386392 return true
387393 }
@@ -392,24 +398,30 @@ func shouldRefreshJWT(token string, aheadSeconds int64) bool {
392398 if payload .Exp <= 0 {
393399 return false
394400 }
395- return payload .Exp <= time .Now ().Unix ()+ aheadSeconds
401+ return payload .Exp <= time .Now ().Unix ()+ defaultAuthRefreshAheadSeconds
396402}
397403
398- func (d * DoubaoNew ) fetchBizAuth (dpop string ) (string , error ) {
404+ func (d * DoubaoNew ) fetchBizAuth (dpop string , public bool ) (string , error ) {
405+ var reqUrl string
399406 client := base .RestyClient .Clone ()
400407 req := client .R ()
401408 req .SetHeader ("accept" , "application/json, text/javascript" )
402409 req .SetHeader ("origin" , DoubaoURL )
403410 req .SetHeader ("referer" , DoubaoURL + "/" )
404411 req .SetHeader ("content-type" , "application/x-www-form-urlencoded" )
405- if d .Cookie != "" {
406- req .SetHeader ("cookie" , d .Cookie )
407- if csrf := strings .TrimSpace (cookie .GetStr (d .Cookie , "passport_csrf_token" )); csrf != "" {
408- req .SetHeader ("x-tt-passport-csrf-token" , csrf )
412+ if public {
413+ reqUrl = DoubaoURL + "/passport/anonymity_user/biz_auth/"
414+ } else {
415+ reqUrl = DoubaoURL + "/passport/user/biz_auth/"
416+ if d .Cookie != "" {
417+ req .SetHeader ("cookie" , d .Cookie )
418+ if csrf := strings .TrimSpace (cookie .GetStr (d .Cookie , "passport_csrf_token" )); csrf != "" {
419+ req .SetHeader ("x-tt-passport-csrf-token" , csrf )
420+ }
421+ }
422+ if oldAuth := d .resolveAuthorization (); oldAuth != "" {
423+ req .SetHeader ("authorization" , oldAuth )
409424 }
410- }
411- if oldAuth := d .resolveAuthorization (); oldAuth != "" {
412- req .SetHeader ("authorization" , oldAuth )
413425 }
414426 if dpop != "" {
415427 req .SetHeader ("dpop" , dpop )
@@ -424,23 +436,22 @@ func (d *DoubaoNew) fetchBizAuth(dpop string) (string, error) {
424436 req .SetQueryParam ("account_sdk_source" , d .AuthSDKSource )
425437 req .SetQueryParam ("sdk_version" , d .AuthSDKVersion )
426438
427- res , err := req .Post (DoubaoURL + "/passport/user/biz_auth/" )
439+ res , err := req .Post (reqUrl )
428440 if err != nil {
429441 return "" , err
430442 }
431443 var resp bizAuthResp
432444 if err = json .Unmarshal (res .Body (), & resp ); err != nil {
433445 return "" , err
434446 }
435- ok := resp .Message != "success" && strings .TrimSpace (resp .Data .AccessToken ) != ""
436- if ! ok {
447+ if resp .Message != "success" || resp .Data .AccessToken == "" {
437448 return "" , fmt .Errorf ("[doubao_new] %s: %s" , resp .Message , resp .Data .Description )
438449 }
439- return strings . TrimSpace ( resp .Data .AccessToken ) , nil
450+ return resp .Data .AccessToken , nil
440451}
441452
442453func (d * DoubaoNew ) refreshAuthorizationWithDPoP (dpop string ) (string , error ) {
443- token , err := d .fetchBizAuth (dpop )
454+ token , err := d .fetchBizAuth (dpop , false )
444455 if err == nil && token != "" {
445456 return token , nil
446457 }
@@ -451,9 +462,9 @@ func (d *DoubaoNew) refreshAuthorizationWithDPoP(dpop string) (string, error) {
451462}
452463
453464func (d * DoubaoNew ) resolveDpopForRequest (method , rawURL string ) (string , error ) {
454- if d .DpopKeyPair != nil {
465+ if d .DPoPKeyPair != nil {
455466 proof , err := GenerateDPoPToken (DPoPTokenInput {
456- KeyPair : d .DpopKeyPair ,
467+ KeyPair : d .DPoPKeyPair ,
457468 HTM : strings .ToUpper (strings .TrimSpace (method )),
458469 HTU : normalizeDPoPURL (rawURL ),
459470 })
@@ -463,37 +474,39 @@ func (d *DoubaoNew) resolveDpopForRequest(method, rawURL string) (string, error)
463474 return proof .DPoPToken , nil
464475 }
465476
466- static := d .Dpop
477+ static := d .DPoP
467478 if static == "" {
468479 return "" , nil
469480 }
470- if payload , err := parseDPoPPayload (static ); err == nil && payload .Exp > 0 {
471- now := time .Now ().Unix ()
472- if payload .Exp <= now + defaultDpopRefreshAheadSeconds {
473- return "" , errors .New ("static dpop token expired or near expiry; configure dpop_key_pair for automatic refresh" )
481+ if ! d .IgnoreJWTCheck {
482+ if payload , err := parseDPoPPayload (static ); err == nil && payload .Exp > 0 {
483+ now := time .Now ().Unix ()
484+ if payload .Exp <= now + defaultDpopRefreshAheadSeconds {
485+ return "" , errors .New ("static dpop token expired or near expiry; configure dpop_key_pair for automatic refresh" )
486+ }
474487 }
475488 }
476489 return static , nil
477490}
478491
492+ func (d * DoubaoNew ) ensureAuthAdditons () bool {
493+ return d .DPoPKeySecret != "" && d .AuthClientID != "" && d .AuthClientType != "" &&
494+ d .AuthScope != "" && d .AuthSDKSource != "" && d .AuthSDKVersion != ""
495+ }
496+
479497func (d * DoubaoNew ) resolveAuthorizationForRequest (method , rawURL string ) (string , error ) {
480- if ! shouldRefreshJWT (d .Authorization , defaultAuthRefreshAheadSeconds ) {
498+ if ! shouldRefreshJWT (d .Authorization ) {
481499 return d .resolveAuthorization (), nil
482500 }
483- // 刷新 Authorization Token 的前置条件:
484- // 1. DPoP 密钥对
485- // 2. Cookie
486- // 3. AuthClientID、AuthClientType、AuthScope、AuthSDKSource、AuthSDKVersion
487- if d .DpopKeyPair == nil || strings .TrimSpace (d .Cookie ) == "" ||
488- d .AuthClientID == "" || d .AuthClientType == "" || d .AuthScope == "" ||
489- d .AuthSDKSource == "" || d .AuthSDKVersion == "" {
501+
502+ if d .DPoPKeyPair == nil || strings .TrimSpace (d .Cookie ) == "" || ! d .ensureAuthAdditons () {
490503 return d .resolveAuthorization (), nil
491504 }
492505
493506 d .authRefreshMu .Lock ()
494507 defer d .authRefreshMu .Unlock ()
495508
496- if ! shouldRefreshJWT (d .Authorization , defaultAuthRefreshAheadSeconds ) {
509+ if ! shouldRefreshJWT (d .Authorization ) {
497510 return d .resolveAuthorization (), nil
498511 }
499512
@@ -513,6 +526,41 @@ func (d *DoubaoNew) resolveAuthorizationForRequest(method, rawURL string) (strin
513526 return d .resolveAuthorization (), nil
514527}
515528
529+ func (d * DoubaoNew ) resolveAuthorizationForPublic () (dpop string , auth string , err error ) {
530+ if d .DPoPPublic != "" && ! shouldRefreshJWT (d .AuthorizationPublic ) {
531+ return d .DPoPPublic , "DPoP " + d .AuthorizationPublic , nil
532+ }
533+
534+ if ! d .ensureAuthAdditons () {
535+ return "" , "" , fmt .Errorf ("[doubao_new] missing auth additions, please fill them all" )
536+ }
537+
538+ d .authRefreshPublicMu .Lock ()
539+ defer d .authRefreshPublicMu .Unlock ()
540+
541+ if d .DPoPPublic != "" && ! shouldRefreshJWT (d .AuthorizationPublic ) {
542+ return d .DPoPPublic , "DPoP " + d .AuthorizationPublic , nil
543+ }
544+
545+ // generate new public dpop
546+ keypair , err := GenerateDPoPKeyPair ()
547+ if err != nil {
548+ return "" , "" , err
549+ }
550+ proof , err := GenerateDPoPToken (DPoPTokenInput {
551+ KeyPair : keypair ,
552+ })
553+ d .DPoPPublic = proof .DPoPToken
554+
555+ // get authorization token
556+ d .AuthorizationPublic , err = d .fetchBizAuth (proof .DPoPToken , true )
557+ if err != nil {
558+ return "" , "" , err
559+ }
560+
561+ return d .DPoPPublic , "DPoP " + d .AuthorizationPublic , nil
562+ }
563+
516564func (d * DoubaoNew ) applyAuthHeaders (req * resty.Request , method , rawURL string ) error {
517565 auth , err := d .resolveAuthorizationForRequest (method , rawURL )
518566 if err != nil {
0 commit comments