@@ -24,7 +24,7 @@ import (
2424
2525 tea "charm.land/bubbletea/v2"
2626 "github.com/go-authgate/device-cli/tui"
27- "github.com/go-authgate/sdk-go/tokenstore "
27+ "github.com/go-authgate/sdk-go/credstore "
2828)
2929
3030var (
3838 flagTokenStore * string
3939 configInitialized bool
4040 retryClient * retry.Client
41- tokenStore tokenstore .Store
41+ tokenStore credstore .Store [credstore. Token ]
4242)
4343
4444const defaultKeyringService = "authgate-device-cli"
@@ -185,16 +185,16 @@ func initConfig() {
185185 }
186186
187187 // Initialize token store based on mode
188- fileStore := tokenstore . NewFileStore (tokenFile )
188+ fileStore := credstore . NewTokenFileStore (tokenFile )
189189 switch tokenStoreMode {
190190 case "file" :
191191 tokenStore = fileStore
192192 case "keyring" :
193- tokenStore = tokenstore . NewKeyringStore (defaultKeyringService )
193+ tokenStore = credstore . NewTokenKeyringStore (defaultKeyringService )
194194 case "auto" :
195- kr := tokenstore . NewKeyringStore (defaultKeyringService )
196- tokenStore = tokenstore .NewSecureStore (kr , fileStore )
197- if ! tokenStore .(* tokenstore .SecureStore ).UseKeyring () {
195+ kr := credstore . NewTokenKeyringStore (defaultKeyringService )
196+ tokenStore = credstore .NewSecureStore [credstore. Token ] (kr , fileStore )
197+ if ! tokenStore .(* credstore .SecureStore [credstore. Token ] ).UseKeyring () {
198198 fmt .Fprintln (
199199 os .Stderr ,
200200 "⚠️ OS keyring unavailable, falling back to file-based token storage" ,
@@ -325,17 +325,22 @@ func run(ctx context.Context, d tui.Displayer) error {
325325 ctx , stop := signal .NotifyContext (ctx , os .Interrupt , syscall .SIGTERM )
326326 defer stop ()
327327
328- var storage * tokenstore.Token
328+ var (
329+ storage credstore.Token
330+ hasStorage bool
331+ )
329332
330333 // Try to load existing tokens
331- storage , err := tokenStore .Load (clientID )
334+ loaded , err := tokenStore .Load (clientID )
332335 switch {
333- case err != nil && ! errors .Is (err , tokenstore .ErrNotFound ):
336+ case err != nil && ! errors .Is (err , credstore .ErrNotFound ):
334337 d .Fatal (err )
335338 return err
336339 case err != nil :
337340 d .TokensNotFound ()
338- case storage != nil :
341+ default :
342+ storage = loaded
343+ hasStorage = true
339344 d .TokensFound ()
340345
341346 // Check if access token is still valid
@@ -349,18 +354,16 @@ func run(ctx context.Context, d tui.Displayer) error {
349354 newStorage , err := refreshAccessToken (ctx , storage .RefreshToken , d )
350355 if err != nil {
351356 d .RefreshFailed (err )
352- storage = nil // Force device flow
357+ hasStorage = false // Force device flow
353358 } else {
354359 storage = newStorage
355360 d .RefreshOK ()
356361 }
357362 }
358- default :
359- d .TokensNotFound ()
360363 }
361364
362365 // If no valid tokens, do device flow
363- if storage == nil {
366+ if ! hasStorage {
364367 storage , err = performDeviceFlow (ctx , d )
365368 if err != nil {
366369 d .Fatal (err )
@@ -382,7 +385,7 @@ func run(ctx context.Context, d tui.Displayer) error {
382385 }
383386
384387 // Demonstrate automatic refresh on 401
385- if err := makeAPICallWithAutoRefresh (ctx , storage , d ); err != nil {
388+ if err := makeAPICallWithAutoRefresh (ctx , & storage , d ); err != nil {
386389 // Check if error is due to expired refresh token
387390 if err == ErrRefreshTokenExpired {
388391 d .ReAuthRequired ()
@@ -394,7 +397,7 @@ func run(ctx context.Context, d tui.Displayer) error {
394397
395398 // Retry API call with new tokens
396399 d .TokenRefreshedRetrying ()
397- if err := makeAPICallWithAutoRefresh (ctx , storage , d ); err != nil {
400+ if err := makeAPICallWithAutoRefresh (ctx , & storage , d ); err != nil {
398401 d .Fatal (err )
399402 return err
400403 }
@@ -473,7 +476,7 @@ func requestDeviceCode(ctx context.Context) (*oauth2.DeviceAuthResponse, error)
473476}
474477
475478// performDeviceFlow performs the OAuth device authorization flow
476- func performDeviceFlow (ctx context.Context , d tui.Displayer ) (* tokenstore .Token , error ) {
479+ func performDeviceFlow (ctx context.Context , d tui.Displayer ) (credstore .Token , error ) {
477480 config := & oauth2.Config {
478481 ClientID : clientID ,
479482 Endpoint : oauth2.Endpoint {
@@ -486,7 +489,7 @@ func performDeviceFlow(ctx context.Context, d tui.Displayer) (*tokenstore.Token,
486489 // Step 1: Request device code (with retry logic)
487490 deviceAuth , err := requestDeviceCode (ctx )
488491 if err != nil {
489- return nil , fmt .Errorf ("device code request failed: %w" , err )
492+ return credstore. Token {} , fmt .Errorf ("device code request failed: %w" , err )
490493 }
491494
492495 d .DeviceCodeReady (
@@ -500,21 +503,21 @@ func performDeviceFlow(ctx context.Context, d tui.Displayer) (*tokenstore.Token,
500503 d .WaitingForAuth ()
501504 token , err := pollForTokenWithProgress (ctx , config , deviceAuth , d )
502505 if err != nil {
503- return nil , fmt .Errorf ("token poll failed: %w" , err )
506+ return credstore. Token {} , fmt .Errorf ("token poll failed: %w" , err )
504507 }
505508
506509 d .AuthSuccess ()
507510
508511 // Convert to Token and save
509- storage := & tokenstore .Token {
512+ storage := credstore .Token {
510513 AccessToken : token .AccessToken ,
511514 RefreshToken : token .RefreshToken ,
512515 TokenType : token .Type (),
513516 ExpiresAt : token .Expiry ,
514517 ClientID : clientID ,
515518 }
516519
517- if err := tokenStore .Save (storage ); err != nil {
520+ if err := tokenStore .Save (clientID , storage ); err != nil {
518521 d .TokenSaveFailed (err )
519522 } else {
520523 d .TokenSaved (tokenStore .String ())
@@ -715,7 +718,7 @@ func refreshAccessToken(
715718 ctx context.Context ,
716719 refreshToken string ,
717720 d tui.Displayer ,
718- ) (* tokenstore .Token , error ) {
721+ ) (credstore .Token , error ) {
719722 // Create request with timeout
720723 reqCtx , cancel := context .WithTimeout (ctx , refreshTokenTimeout )
721724 defer cancel ()
@@ -732,38 +735,42 @@ func refreshAccessToken(
732735 strings .NewReader (data .Encode ()),
733736 )
734737 if err != nil {
735- return nil , fmt .Errorf ("failed to create request: %w" , err )
738+ return credstore. Token {} , fmt .Errorf ("failed to create request: %w" , err )
736739 }
737740 req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
738741
739742 // Execute request with retry logic
740743 resp , err := retryClient .DoWithContext (reqCtx , req )
741744 if err != nil {
742- return nil , fmt .Errorf ("refresh request failed: %w" , err )
745+ return credstore. Token {} , fmt .Errorf ("refresh request failed: %w" , err )
743746 }
744747 defer resp .Body .Close ()
745748
746749 body , err := io .ReadAll (resp .Body )
747750 if err != nil {
748- return nil , fmt .Errorf ("failed to read response: %w" , err )
751+ return credstore. Token {} , fmt .Errorf ("failed to read response: %w" , err )
749752 }
750753
751754 if resp .StatusCode != http .StatusOK {
752755 var errResp ErrorResponse
753756 if err := json .Unmarshal (body , & errResp ); err == nil {
754757 // Check if refresh token is expired or invalid
755758 if errResp .Error == oauthErrInvalidGrant || errResp .Error == oauthErrInvalidToken {
756- return nil , ErrRefreshTokenExpired
759+ return credstore. Token {} , ErrRefreshTokenExpired
757760 }
758- return nil , fmt .Errorf ("%s: %s" , errResp .Error , errResp .ErrorDescription )
761+ return credstore. Token {} , fmt .Errorf ("%s: %s" , errResp .Error , errResp .ErrorDescription )
759762 }
760- return nil , fmt .Errorf ("refresh failed with status %d: %s" , resp .StatusCode , string (body ))
763+ return credstore.Token {}, fmt .Errorf (
764+ "refresh failed with status %d: %s" ,
765+ resp .StatusCode ,
766+ string (body ),
767+ )
761768 }
762769
763770 // Parse token response
764771 var tokenResp tokenResponse
765772 if err := json .Unmarshal (body , & tokenResp ); err != nil {
766- return nil , fmt .Errorf ("failed to parse token response: %w" , err )
773+ return credstore. Token {} , fmt .Errorf ("failed to parse token response: %w" , err )
767774 }
768775
769776 // Validate token response
@@ -772,7 +779,7 @@ func refreshAccessToken(
772779 tokenResp .TokenType ,
773780 tokenResp .ExpiresIn ,
774781 ); err != nil {
775- return nil , fmt .Errorf ("invalid token response: %w" , err )
782+ return credstore. Token {} , fmt .Errorf ("invalid token response: %w" , err )
776783 }
777784
778785 // Handle refresh token rotation modes:
@@ -784,7 +791,7 @@ func refreshAccessToken(
784791 newRefreshToken = refreshToken
785792 }
786793
787- storage := & tokenstore .Token {
794+ storage := credstore .Token {
788795 AccessToken : tokenResp .AccessToken ,
789796 RefreshToken : newRefreshToken ,
790797 TokenType : tokenResp .TokenType ,
@@ -793,7 +800,7 @@ func refreshAccessToken(
793800 }
794801
795802 // Save updated tokens
796- if err := tokenStore .Save (storage ); err != nil {
803+ if err := tokenStore .Save (clientID , storage ); err != nil {
797804 d .TokenSaveFailed (err )
798805 }
799806
@@ -803,7 +810,7 @@ func refreshAccessToken(
803810// makeAPICallWithAutoRefresh demonstrates automatic refresh on 401
804811func makeAPICallWithAutoRefresh (
805812 ctx context.Context ,
806- storage * tokenstore .Token ,
813+ storage * credstore .Token ,
807814 d tui.Displayer ,
808815) error {
809816 // Try with current access token
0 commit comments