@@ -396,6 +396,16 @@ func (h *OAuthWebHandler) pollForToken(ctx context.Context, session *webAuthSess
396396 StartURL : session .startURL ,
397397 }
398398
399+ if _ , errSave := h .saveTokenToFile (tokenData ); errSave != nil {
400+ log .Errorf ("OAuth Web: failed to save token to file: %v" , errSave )
401+ h .mu .Lock ()
402+ session .status = statusFailed
403+ session .error = "Failed to save authentication tokens"
404+ session .completedAt = time .Now ()
405+ h .mu .Unlock ()
406+ return
407+ }
408+
399409 h .mu .Lock ()
400410 session .status = statusSuccess
401411 session .completedAt = time .Now ()
@@ -407,47 +417,51 @@ func (h *OAuthWebHandler) pollForToken(ctx context.Context, session *webAuthSess
407417 h .onTokenObtained (tokenData )
408418 }
409419
410- // Save token to file
411- h .saveTokenToFile (tokenData )
412-
413420 log .Infof ("OAuth Web: authentication successful for %s" , email )
414421 return
415422 }
416423 }
417424}
418425
419- // saveTokenToFile saves the token data to the auth directory
420- func (h * OAuthWebHandler ) saveTokenToFile (tokenData * KiroTokenData ) {
426+ // saveTokenToFile saves the token data to the auth directory.
427+ func (h * OAuthWebHandler ) saveTokenToFile (tokenData * KiroTokenData ) ( string , error ) {
421428 // Get auth directory from config or use default
422429 authDir := ""
423430 if h .cfg != nil && h .cfg .AuthDir != "" {
424431 var err error
425432 authDir , err = util .ResolveAuthDir (h .cfg .AuthDir )
426433 if err != nil {
427- log .Errorf ("OAuth Web: failed to resolve auth directory: %v " , err )
434+ return "" , fmt .Errorf ("failed to resolve auth directory: %w " , err )
428435 }
429436 }
430437
431438 // Fall back to default location
432439 if authDir == "" {
433440 home , err := os .UserHomeDir ()
434441 if err != nil {
435- log .Errorf ("OAuth Web: failed to get home directory: %v" , err )
436- return
442+ return "" , fmt .Errorf ("failed to get home directory: %w" , err )
437443 }
438444 authDir = filepath .Join (home , ".cli-proxy-api" )
439445 }
440446
441447 // Create directory if not exists
442448 if err := os .MkdirAll (authDir , 0700 ); err != nil {
443- log .Errorf ("OAuth Web: failed to create auth directory: %v" , err )
444- return
449+ return "" , fmt .Errorf ("failed to create auth directory: %w" , err )
445450 }
446451
447452 // Generate filename using the unified function
448453 fileName := GenerateTokenFileName (tokenData )
449454
450455 authFilePath := filepath .Join (authDir , fileName )
456+ absAuthDir , errAuthDir := filepath .Abs (authDir )
457+ absAuthFilePath , errAuthFilePath := filepath .Abs (authFilePath )
458+ if errAuthDir != nil || errAuthFilePath != nil {
459+ return "" , fmt .Errorf ("failed to resolve token output path" )
460+ }
461+ authDirPrefix := absAuthDir + string (os .PathSeparator )
462+ if absAuthFilePath != absAuthDir && ! strings .HasPrefix (absAuthFilePath , authDirPrefix ) {
463+ return "" , fmt .Errorf ("invalid token output path" )
464+ }
451465
452466 // Convert to storage format and save
453467 storage := & KiroTokenStorage {
@@ -461,17 +475,18 @@ func (h *OAuthWebHandler) saveTokenToFile(tokenData *KiroTokenData) {
461475 LastRefresh : time .Now ().Format (time .RFC3339 ),
462476 ClientID : tokenData .ClientID ,
463477 ClientSecret : tokenData .ClientSecret ,
478+ ClientIDHash : tokenData .ClientIDHash ,
464479 Region : tokenData .Region ,
465480 StartURL : tokenData .StartURL ,
466481 Email : tokenData .Email ,
467482 }
468483
469484 if err := storage .SaveTokenToFile (authFilePath ); err != nil {
470- log .Errorf ("OAuth Web: failed to save token to file: %v" , err )
471- return
485+ return "" , fmt .Errorf ("failed to save token to file: %w" , err )
472486 }
473487
474488 log .Infof ("OAuth Web: token saved to %s" , authFilePath )
489+ return fileName , nil
475490}
476491
477492func (h * OAuthWebHandler ) ssoClient (session * webAuthSession ) * SSOOIDCClient {
@@ -591,6 +606,17 @@ func (h *OAuthWebHandler) handleSocialCallback(c *gin.Context) {
591606 Region : "us-east-1" ,
592607 }
593608
609+ if _ , errSave := h .saveTokenToFile (tokenData ); errSave != nil {
610+ log .Errorf ("OAuth Web: failed to save social token: %v" , errSave )
611+ h .mu .Lock ()
612+ session .status = statusFailed
613+ session .error = "Failed to save authentication tokens"
614+ session .completedAt = time .Now ()
615+ h .mu .Unlock ()
616+ h .renderError (c , session .error )
617+ return
618+ }
619+
594620 h .mu .Lock ()
595621 session .status = statusSuccess
596622 session .completedAt = time .Now ()
@@ -606,9 +632,6 @@ func (h *OAuthWebHandler) handleSocialCallback(c *gin.Context) {
606632 h .onTokenObtained (tokenData )
607633 }
608634
609- // Save token to file
610- h .saveTokenToFile (tokenData )
611-
612635 log .Infof ("OAuth Web: social authentication successful for %s via %s" , email , provider )
613636 h .renderSuccess (c , session )
614637}
@@ -751,18 +774,50 @@ type ImportTokenRequest struct {
751774 RefreshToken string `json:"refreshToken"`
752775}
753776
754- // handleImportToken handles manual refresh token import from Kiro IDE
777+ // handleImportToken handles manual token imports from Kiro IDE.
755778func (h * OAuthWebHandler ) handleImportToken (c * gin.Context ) {
756- var req ImportTokenRequest
757- if err := c . ShouldBindJSON ( & req ); err != nil {
779+ data , err := c . GetRawData ()
780+ if err != nil {
758781 c .JSON (http .StatusBadRequest , gin.H {
759782 "success" : false ,
760783 "error" : "Invalid request body" ,
761784 })
762785 return
763786 }
764787
765- refreshToken := strings .TrimSpace (req .RefreshToken )
788+ parsed , err := parseImportTokenPayload (data )
789+ if err != nil {
790+ c .JSON (http .StatusBadRequest , gin.H {
791+ "success" : false ,
792+ "error" : err .Error (),
793+ })
794+ return
795+ }
796+ if parsed .rawKiroIDEJSON {
797+ tokenData := parsed .tokenData
798+ fileName , errSave := h .saveTokenToFile (tokenData )
799+ if errSave != nil {
800+ log .Errorf ("OAuth Web: failed to save imported token: %v" , errSave )
801+ c .JSON (http .StatusInternalServerError , gin.H {
802+ "success" : false ,
803+ "error" : "Failed to save token" ,
804+ })
805+ return
806+ }
807+ if h .onTokenObtained != nil {
808+ h .onTokenObtained (tokenData )
809+ }
810+
811+ log .Infof ("OAuth Web: raw Kiro IDE token imported successfully" )
812+ c .JSON (http .StatusOK , gin.H {
813+ "success" : true ,
814+ "message" : "Token imported successfully" ,
815+ "fileName" : fileName ,
816+ })
817+ return
818+ }
819+
820+ refreshToken := strings .TrimSpace (parsed .tokenData .RefreshToken )
766821 if refreshToken == "" {
767822 c .JSON (http .StatusBadRequest , gin.H {
768823 "success" : false ,
@@ -801,17 +856,20 @@ func (h *OAuthWebHandler) handleImportToken(c *gin.Context) {
801856 tokenData .AuthMethod = "social"
802857 tokenData .Provider = "imported"
803858
804- // Notify callback if set
859+ // Save token to file
860+ fileName , errSave := h .saveTokenToFile (tokenData )
861+ if errSave != nil {
862+ log .Errorf ("OAuth Web: failed to save imported token: %v" , errSave )
863+ c .JSON (http .StatusInternalServerError , gin.H {
864+ "success" : false ,
865+ "error" : "Failed to save token" ,
866+ })
867+ return
868+ }
805869 if h .onTokenObtained != nil {
806870 h .onTokenObtained (tokenData )
807871 }
808872
809- // Save token to file
810- h .saveTokenToFile (tokenData )
811-
812- // Generate filename for response using the unified function
813- fileName := GenerateTokenFileName (tokenData )
814-
815873 log .Infof ("OAuth Web: token imported successfully" )
816874 c .JSON (http .StatusOK , gin.H {
817875 "success" : true ,
0 commit comments