- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 136
 
Add auth logout command #1656
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
            aknysh
  merged 61 commits into
  main
from
feature/dev-3705-implement-atmos-auth-logout-to-remove-local-credential-store
  
      
      
   
  Oct 22, 2025 
      
    
  
     Merged
                    Add auth logout command #1656
Changes from 4 commits
      Commits
    
    
            Show all changes
          
          
            61 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      9abfcb6
              
                Changes auto-committed by Conductor
              
              
                osterman 506bda1
              
                [autofix.ci] apply automated fixes
              
              
                autofix-ci[bot] 4b1d882
              
                feat: Make AWS credentials directory configurable via provider spec
              
              
                osterman c1ca11e
              
                fix: Display actual configured paths in auth logout dry-run output
              
              
                osterman 6baf18f
              
                feat: Add validation for spec.files.base_path in AWS providers
              
              
                osterman 7b048e0
              
                docs: Add spec.files.base_path configuration documentation
              
              
                osterman 57677f7
              
                Merge branch 'main' into feature/dev-3705-implement-atmos-auth-logout…
              
              
                osterman 77ea4ca
              
                refactor: Add perf tracking, remove unused error, remove env var support
              
              
                osterman 75c3c17
              
                feat: Add logout not supported vs not implemented distinction
              
              
                osterman c84924d
              
                refactor: Fix N+1 provider cleanup and improve error handling in logout
              
              
                osterman 127a26d
              
                fix: Add identity-scoped cleanup to preserve other identities' creden…
              
              
                osterman 43177be
              
                test: Update TestDelete_Flow to expect success for non-existent crede…
              
              
                osterman c6095cd
              
                feat: Add basePath parameter to AWS setup functions
              
              
                osterman 0fa051b
              
                test: Add comprehensive tests for auth logout functionality
              
              
                osterman 264e826
              
                test: Add Logout tests for AWS and GitHub providers
              
              
                osterman 70beb13
              
                [autofix.ci] apply automated fixes
              
              
                autofix-ci[bot] 1555073
              
                test: Add comprehensive logout tests for manager and identities
              
              
                osterman 3c89fa3
              
                [autofix.ci] apply automated fixes
              
              
                autofix-ci[bot] b51bee3
              
                Merge remote-tracking branch 'origin/main' into feature/dev-3705-impl…
              
              
                osterman d5bf86c
              
                Changes auto-committed by Conductor
              
              
                osterman 608d9ee
              
                Merge branch 'main' into feature/dev-3705-implement-atmos-auth-logout…
              
              
                osterman a3308f6
              
                Changes auto-committed by Conductor
              
              
                osterman e819fcd
              
                Add performance instrumentation and error classification to AWS provi…
              
              
                osterman d36748b
              
                Fix Windows path separator compatibility in GetFilesDisplayPath tests
              
              
                osterman d11c3ca
              
                Merge branch 'main' into feature/dev-3705-implement-atmos-auth-logout…
              
              
                osterman 2ce1d93
              
                Add performance instrumentation to SAML provider Validate method
              
              
                osterman fcaf6c3
              
                Changes auto-committed by Conductor
              
              
                osterman 133c9a1
              
                Ignore merge conflict artifacts in gitignore
              
              
                osterman f8c9bf6
              
                Add performance instrumentation to AWS identity Logout methods
              
              
                osterman 616af40
              
                Add test coverage for auth components
              
              
                osterman 2ecf77f
              
                [autofix.ci] apply automated fixes
              
              
                autofix-ci[bot] ac9fe05
              
                Improve logout UI with concise, modern messaging
              
              
                osterman cb66d3f
              
                [autofix.ci] apply automated fixes
              
              
                autofix-ci[bot] 6f2e5bd
              
                Use PrintfMarkdownToTUI for all markdown-formatted messages
              
              
                osterman f806a12
              
                Show browser session warning only once using cache
              
              
                osterman bb5cb3b
              
                [autofix.ci] apply automated fixes
              
              
                autofix-ci[bot] 35db3ae
              
                Fix AWS env isolation to prevent shared config file loading
              
              
                osterman 44b23bb
              
                Merge remote-tracking branch 'origin/main' into feature/dev-3705-impl…
              
              
                osterman 7d7422a
              
                Fix auth logout tests to expect GetIdentities and GetProviderForIdent…
              
              
                osterman d052c3e
              
                Fix AWS setup tests using homedir.Reset() to clear cache
              
              
                osterman 547ec56
              
                Merge remote-tracking branch 'origin/main' into feature/dev-3705-impl…
              
              
                osterman d365c2c
              
                Changes auto-committed by Conductor
              
              
                osterman 27cfbd4
              
                Merge remote-tracking branch 'origin/main' into feature/dev-3705-impl…
              
              
                osterman f2b9286
              
                Merge branch 'main' into feature/dev-3705-implement-atmos-auth-logout…
              
              
                osterman a334d19
              
                Merge remote-tracking branch 'origin/main' into feature/dev-3705-impl…
              
              
                osterman 70f4ab8
              
                Merge branch 'main' into feature/dev-3705-implement-atmos-auth-logout…
              
              
                osterman 4a5b74e
              
                Replace deprecated github.com/golang/mock with go.uber.org/mock
              
              
                osterman aef4273
              
                Merge remote-tracking branch 'origin/main' into feature/dev-3705-impl…
              
              
                osterman 0052e7d
              
                Add perf instrumentation and fix import ordering
              
              
                osterman 98a8666
              
                Add blog post for atmos auth shell with corrected problem statement
              
              
                osterman 475404d
              
                Fix missing periods in blog post list items
              
              
                osterman 7e34ee7
              
                Remove auth shell blog post and add auth logout blog post
              
              
                osterman 3e2443b
              
                Remove duplicate auth logout blog post
              
              
                osterman 6b651d3
              
                Update auth logout blog post problem statement
              
              
                osterman a993889
              
                Update auth logout documentation with improved problem statement
              
              
                osterman b4bc983
              
                Move problem statement to dedicated section in logout docs
              
              
                osterman 514ce9c
              
                Merge branch 'main' into feature/dev-3705-implement-atmos-auth-logout…
              
              
                osterman f2c44c2
              
                [autofix.ci] apply automated fixes
              
              
                autofix-ci[bot] cba7fe8
              
                Fix AWS auth tests to use forked homedir package
              
              
                osterman 40baedc
              
                Add lint rule to forbid mitchellh/go-homedir imports
              
              
                osterman 8ac86aa
              
                Fix blog post formatting and homedir test flakiness
              
              
                osterman File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,300 @@ | ||
| package cmd | ||
| 
     | 
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| 
     | 
||
| "github.com/charmbracelet/huh" | ||
| "github.com/spf13/cobra" | ||
| 
     | 
||
| errUtils "github.com/cloudposse/atmos/errors" | ||
| uiutils "github.com/cloudposse/atmos/internal/tui/utils" | ||
| "github.com/cloudposse/atmos/pkg/auth" | ||
| cfg "github.com/cloudposse/atmos/pkg/config" | ||
| "github.com/cloudposse/atmos/pkg/schema" | ||
| u "github.com/cloudposse/atmos/pkg/utils" | ||
| ) | ||
| 
     | 
||
| // authLogoutCmd logs out by removing local credentials. | ||
| var authLogoutCmd = &cobra.Command{ | ||
| Use: "logout [identity]", | ||
| Short: "Remove locally cached credentials and session data", | ||
| Long: `Removes cached credentials from the system keyring and local credential files. | ||
| 
     | 
||
| This command removes: | ||
| • Credentials stored in system keyring | ||
| • AWS credential files (~/.aws/atmos/<provider>/credentials) | ||
| • AWS config files (~/.aws/atmos/<provider>/config) | ||
| 
     | 
||
| Note: This only removes local credentials. Your browser session with the | ||
| identity provider (AWS SSO, Okta, etc.) may still be active. To completely | ||
| end your session, visit your identity provider's website and sign out.`, | ||
| 
     | 
||
| FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false}, | ||
| ValidArgsFunction: ComponentsArgCompletion, | ||
| RunE: executeAuthLogoutCommand, | ||
| } | ||
| 
     | 
||
| func executeAuthLogoutCommand(cmd *cobra.Command, args []string) error { | ||
| handleHelpRequest(cmd, args) | ||
| 
     | 
||
| // Load atmos config. | ||
| atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false) | ||
| if err != nil { | ||
| return fmt.Errorf("%w: %w", errUtils.ErrFailedToInitializeAtmosConfig, err) | ||
| } | ||
| 
     | 
||
| // Create auth manager. | ||
| authManager, err := createAuthManager(&atmosConfig.Auth) | ||
| if err != nil { | ||
| return fmt.Errorf("%w: %w", errUtils.ErrAuthManager, err) | ||
| } | ||
| 
     | 
||
| // Get flags. | ||
| providerFlag, _ := cmd.Flags().GetString("provider") | ||
| dryRun, _ := cmd.Flags().GetBool("dry-run") | ||
| 
     | 
||
| ctx := context.Background() | ||
| 
     | 
||
| // Determine what to logout. | ||
| if providerFlag != "" { | ||
| // Logout specific provider. | ||
| return performProviderLogout(ctx, authManager, providerFlag, dryRun) | ||
| } | ||
| 
     | 
||
| if len(args) > 0 { | ||
| // Logout specific identity. | ||
| identityName := args[0] | ||
| return performIdentityLogout(ctx, authManager, identityName, dryRun) | ||
| } | ||
| 
     | 
||
| // Interactive mode: prompt user to choose. | ||
| return performInteractiveLogout(ctx, authManager, dryRun) | ||
| } | ||
                
      
                  coderabbitai[bot] marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| // performIdentityLogout removes credentials for a specific identity. | ||
| func performIdentityLogout(ctx context.Context, authManager auth.AuthManager, identityName string, dryRun bool) error { | ||
| // Validate identity exists. | ||
| identities := authManager.GetIdentities() | ||
| if _, exists := identities[identityName]; !exists { | ||
| u.PrintfMessageToTUI("**Error:** identity %q not found in configuration\n\n", identityName) | ||
| u.PrintfMessageToTUI("**Available identities:**\n") | ||
| for name := range identities { | ||
| u.PrintfMessageToTUI(" • %s\n", name) | ||
| } | ||
| u.PrintfMessageToTUI("\n") | ||
| return fmt.Errorf("%w: identity %q", errUtils.ErrIdentityNotInConfig, identityName) | ||
| } | ||
| 
     | 
||
| u.PrintfMessageToTUI("\n**Logging out from identity:** %s\n\n", identityName) | ||
| 
     | 
||
| // Get authentication chain to show what will be removed. | ||
| providerName := authManager.GetProviderForIdentity(identityName) | ||
| 
     | 
||
| if dryRun { | ||
| u.PrintfMessageToTUI("**Dry run mode:** No credentials will be removed\n\n") | ||
| u.PrintfMessageToTUI("**Would remove:**\n") | ||
| u.PrintfMessageToTUI(" • Keyring entry: %s\n", identityName) | ||
| if providerName != "" { | ||
| u.PrintfMessageToTUI(" • Keyring entry: %s (provider)\n", providerName) | ||
| // Get actual configured path for this provider. | ||
| basePath := authManager.GetFilesDisplayPath(providerName) | ||
| u.PrintfMessageToTUI(" • Files: %s/%s/\n", basePath, providerName) | ||
| } | ||
| u.PrintfMessageToTUI("\n") | ||
| return nil | ||
| } | ||
| 
     | 
||
| // Perform logout. | ||
| if err := authManager.Logout(ctx, identityName); err != nil { | ||
| // Check if it's a partial logout. | ||
| if errors.Is(err, errUtils.ErrPartialLogout) { | ||
| u.PrintfMessageToTUI("✓ **Logged out with warnings**\n\n") | ||
| u.PrintfMessageToTUI("Some credentials could not be removed:\n") | ||
| u.PrintfMessageToTUI(" %v\n\n", err) | ||
| } else { | ||
| u.PrintfMessageToTUI("✗ **Logout failed**\n\n") | ||
| u.PrintfMessageToTUI("Error: %v\n\n", err) | ||
| return err | ||
| } | ||
| } else { | ||
| u.PrintfMessageToTUI("✓ **Successfully logged out**\n\n") | ||
| } | ||
| 
     | 
||
| // Display browser session warning. | ||
| displayBrowserWarning() | ||
| 
     | 
||
| return nil | ||
| } | ||
| 
     | 
||
| // performProviderLogout removes credentials for a specific provider. | ||
| func performProviderLogout(ctx context.Context, authManager auth.AuthManager, providerName string, dryRun bool) error { | ||
| // Validate provider exists. | ||
| providers := authManager.GetProviders() | ||
| if _, exists := providers[providerName]; !exists { | ||
| u.PrintfMessageToTUI("**Error:** provider %q not found in configuration\n\n", providerName) | ||
| u.PrintfMessageToTUI("**Available providers:**\n") | ||
| for name := range providers { | ||
| u.PrintfMessageToTUI(" • %s\n", name) | ||
| } | ||
| u.PrintfMessageToTUI("\n") | ||
| return fmt.Errorf("%w: provider %q", errUtils.ErrProviderNotInConfig, providerName) | ||
| } | ||
| 
     | 
||
| u.PrintfMessageToTUI("\n**Logging out from provider:** %s\n\n", providerName) | ||
| 
     | 
||
| if dryRun { | ||
| u.PrintfMessageToTUI("**Dry run mode:** No credentials will be removed\n\n") | ||
| u.PrintfMessageToTUI("**Would remove:**\n") | ||
| u.PrintfMessageToTUI(" • All identities using provider %s\n", providerName) | ||
| u.PrintfMessageToTUI(" • Provider keyring entry\n") | ||
| // Get actual configured path for this provider. | ||
| basePath := authManager.GetFilesDisplayPath(providerName) | ||
| u.PrintfMessageToTUI(" • Files: %s/%s/\n", basePath, providerName) | ||
| u.PrintfMessageToTUI("\n") | ||
| return nil | ||
| } | ||
| 
     | 
||
| // Perform logout. | ||
| if err := authManager.LogoutProvider(ctx, providerName); err != nil { | ||
| u.PrintfMessageToTUI("✗ **Provider logout failed**\n\n") | ||
| u.PrintfMessageToTUI("Error: %v\n\n", err) | ||
| return err | ||
| } | ||
| 
     | 
||
| u.PrintfMessageToTUI("✓ **Successfully logged out from provider**\n\n") | ||
| 
     | 
||
| // Display browser session warning. | ||
| displayBrowserWarning() | ||
| 
     | 
||
| return nil | ||
| } | ||
                
      
                  coderabbitai[bot] marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| // performInteractiveLogout prompts user to choose what to logout. | ||
| func performInteractiveLogout(ctx context.Context, authManager auth.AuthManager, dryRun bool) error { | ||
| identities := authManager.GetIdentities() | ||
| providers := authManager.GetProviders() | ||
| 
     | 
||
| if len(identities) == 0 { | ||
| u.PrintfMessageToTUI("**No identities configured** in atmos.yaml\n") | ||
| return nil | ||
| } | ||
| 
     | 
||
| // Build options list. | ||
| type logoutOption struct { | ||
| label string | ||
| typ string // "identity", "provider", "all" | ||
| target string | ||
| } | ||
| 
     | 
||
| var options []logoutOption | ||
| 
     | 
||
| // Add identity options. | ||
| for name := range identities { | ||
| options = append(options, logoutOption{ | ||
| label: fmt.Sprintf("Identity: %s", name), | ||
| typ: "identity", | ||
| target: name, | ||
| }) | ||
| } | ||
| 
     | 
||
| // Add provider options. | ||
| for name := range providers { | ||
| options = append(options, logoutOption{ | ||
| label: fmt.Sprintf("Provider: %s (removes all identities)", name), | ||
| typ: "provider", | ||
| target: name, | ||
| }) | ||
| } | ||
| 
     | 
||
| // Add "all" option. | ||
| options = append(options, logoutOption{ | ||
| label: "All identities (complete logout)", | ||
| typ: "all", | ||
| target: "", | ||
| }) | ||
| 
     | 
||
| // Create select options for huh. | ||
| var huhOptions []huh.Option[logoutOption] | ||
| for _, opt := range options { | ||
| huhOptions = append(huhOptions, huh.NewOption(opt.label, opt)) | ||
| } | ||
| 
     | 
||
| var selected logoutOption | ||
| form := huh.NewForm( | ||
| huh.NewGroup( | ||
| huh.NewSelect[logoutOption](). | ||
| Title("Choose what to logout from:"). | ||
| Options(huhOptions...). | ||
| Value(&selected), | ||
| ), | ||
| ).WithTheme(uiutils.NewAtmosHuhTheme()) | ||
| 
     | 
||
| if err := form.Run(); err != nil { | ||
| return err | ||
| } | ||
| 
     | 
||
| // Perform the selected logout action. | ||
| switch selected.typ { | ||
| case "identity": | ||
| return performIdentityLogout(ctx, authManager, selected.target, dryRun) | ||
| case "provider": | ||
| return performProviderLogout(ctx, authManager, selected.target, dryRun) | ||
| case "all": | ||
| return performLogoutAll(ctx, authManager, dryRun) | ||
| default: | ||
| return errUtils.ErrInvalidLogoutOption | ||
| } | ||
| } | ||
| 
     | 
||
| // performLogoutAll removes all credentials. | ||
| func performLogoutAll(ctx context.Context, authManager auth.AuthManager, dryRun bool) error { | ||
| u.PrintfMessageToTUI("\n**Logging out from all identities**\n\n") | ||
| 
     | 
||
| if dryRun { | ||
| u.PrintfMessageToTUI("**Dry run mode:** No credentials will be removed\n\n") | ||
| u.PrintfMessageToTUI("**Would remove:**\n") | ||
| u.PrintfMessageToTUI(" • All identity keyring entries\n") | ||
| u.PrintfMessageToTUI(" • All provider keyring entries\n") | ||
| 
     | 
||
| // Show file paths for each provider. | ||
| providers := authManager.GetProviders() | ||
| if len(providers) > 0 { | ||
| u.PrintfMessageToTUI(" • Files:\n") | ||
| for providerName := range providers { | ||
| basePath := authManager.GetFilesDisplayPath(providerName) | ||
| u.PrintfMessageToTUI(" - %s/%s/\n", basePath, providerName) | ||
| } | ||
| } | ||
| u.PrintfMessageToTUI("\n") | ||
| return nil | ||
| } | ||
| 
     | 
||
| // Perform logout. | ||
| if err := authManager.LogoutAll(ctx); err != nil { | ||
| u.PrintfMessageToTUI("✗ **Logout all failed**\n\n") | ||
| u.PrintfMessageToTUI("Error: %v\n\n", err) | ||
| return err | ||
| } | ||
| 
     | 
||
| u.PrintfMessageToTUI("✓ **Successfully logged out from all identities**\n\n") | ||
| 
     | 
||
| // Display browser session warning. | ||
| displayBrowserWarning() | ||
| 
     | 
||
| return nil | ||
                
      
                  coderabbitai[bot] marked this conversation as resolved.
               
          
            Show resolved
            Hide resolved
         | 
||
| } | ||
| 
     | 
||
| // displayBrowserWarning shows a warning about browser sessions. | ||
| func displayBrowserWarning() { | ||
| u.PrintfMessageToTUI("⚠️ **Note:** This only removes local credentials.\n") | ||
| u.PrintfMessageToTUI(" Your browser session may still be active. Visit your\n") | ||
| u.PrintfMessageToTUI(" identity provider to end your browser session.\n\n") | ||
| } | ||
| 
     | 
||
| func init() { | ||
| authLogoutCmd.Flags().String("provider", "", "Logout from specific provider") | ||
| authLogoutCmd.Flags().Bool("dry-run", false, "Preview what would be removed without deleting") | ||
| authCmd.AddCommand(authLogoutCmd) | ||
| } | ||
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.