Skip to content

Conversation

jamlo
Copy link
Contributor

@jamlo jamlo commented Mar 20, 2025

Add SSO Logout Command and Improve SSO Authentication Structure

This PR enhances the SSO authentication functionality by adding a logout command and improving the overall command structure. The changes provide users with a complete authentication lifecycle, allowing them to both login and logout from their SSO sessions.

Changes

  1. Added new logout command to SSO authentication
  2. Restructured SSO commands under auth sso namespace
  3. Added force flag option for logout to skip confirmation
  4. Added comprehensive unit tests for logout functionality

New Commands

  • bacalhau auth sso login - Login using SSO (existing command, moved)
  • bacalhau auth sso logout - Logout from current SSO session (new command)
    • Optional -f, --force flag to skip confirmation prompt

Usage Examples

# Login using SSO
bacalhau auth sso login

# Logout with confirmation prompt
bacalhau auth sso logout

# Force logout without confirmation
bacalhau auth sso logout --force

Checklist

  • Added new logout command
  • Implemented force flag option
  • Added comprehensive unit tests
  • Tested manually

Linear: https://linear.app/expanso/issue/ENG-714/add-logout-option-for-sso

Summary by CodeRabbit

  • New Features
    • Introduced an SSO logout command with a force option to bypass confirmation prompts.
    • Enhanced the SSO login functionality with clearer, more specific command labels.
    • Added a root command for SSO authentication, consolidating login and logout functionalities.
  • Refactor
    • Streamlined the overall SSO authentication command structure to provide a unified and intuitive user experience.

Copy link
Contributor

coderabbitai bot commented Mar 20, 2025

Walkthrough

This pull request restructures the SSO command functionality. The main auth command now sources its SSO subcommand from a newly introduced SSO root command. Refactoring in the SSO login logic includes renaming types, functions, and tests to focus on login-specific functionality. Additionally, a new SSO logout feature has been implemented with a corresponding suite of tests. These changes involve package renaming from "auth" to "sso", updates to command usage, and the addition of confirmation and force flag logic for logout.

Changes

Files Change Summary
cmd/cli/auth/root.go & cmd/cli/auth/sso/root.go Updated main auth command to use sso.NewSSORootCmd(); introduced a new root command that aggregates SSO login and logout subcommands.
cmd/cli/auth/sso/login.go & cmd/cli/auth/sso/login_test.go Renamed SSO-related types and functions (e.g., SSOOptionsSSOLoginOptions, NewSSOCmdNewSSOLoginCmd); updated command usage and test assertions to reflect the shift from a generic SSO to a login-specific command.
cmd/cli/auth/sso/logout.go & cmd/cli/auth/sso/logout_test.go Added new logout functionality with a LogoutOptions struct, a logout command (NewSSOLogoutCmd), confirmation prompt logic, force flag handling, and accompanying unit tests.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant C as SSO Logout Cmd
    participant O as LogoutOptions
    participant S as Util/Token Handler

    U->>C: Trigger logout command (with/without --force)
    C->>S: Call SetupRepoConfig to initialize repo
    alt Force flag not set
      C->>U: Prompt for confirmation
      U-->>C: Return confirmation (Yes/No)
    end
    C->>O: Execute runSSOLogout()
    O->>S: Retrieve token file path and invoke WriteToken to clear token
    S-->>O: Return success/failure response
    O-->>C: Return logout outcome
    C->>U: Display logout confirmation or error
Loading

Assessment against linked issues

Objective Addressed Explanation
Add logout option for SSO (ENG-714)

Suggested reviewers

  • markkuhn
  • wdbaruni

Poem

I'm a rabbit in the codewood, hopping with glee,
New logout commands now set my spirit free.
With login renamed and tokens cleared with grace,
Our code burrow's a safer, brighter place.
Hopping through changes with a twitch and a smile! 🐰✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 golangci-lint (1.64.8)

{"Issues":[{"FromLinter":"typecheck","Text":"pattern build/: no matching files found","Severity":"","SourceLines":["//go:embed build/"],"Pos":{"Filename":"webui/webui.go","Offset":0,"Line":23,"Column":12},"ExpectNoLint":false,"ExpectedNoLintLinter":""}],"Report":{"Linters":[{"Name":"asasalint"},{"Name":"asciicheck"},{"Name":"bidichk"},{"Name":"bodyclose","Enabled":true},{"Name":"canonicalheader"},{"Name":"containedctx"},{"Name":"contextcheck"},{"Name":"copyloopvar","Enabled":true},{"Name":"cyclop"},{"Name":"decorder"},{"Name":"deadcode"},{"Name":"depguard","Enabled":true},{"Name":"dogsled","Enabled":true},{"Name":"dupl"},{"Name":"dupword"},{"Name":"durationcheck"},{"Name":"errcheck","Enabled":true,"EnabledByDefault":true},{"Name":"errchkjson"},{"Name":"errname"},{"Name":"errorlint"},{"Name":"execinquery"},{"Name":"exhaustive"},{"Name":"exhaustivestruct"},{"Name":"exhaustruct"},{"Name":"exportloopref"},{"Name":"exptostd"},{"Name":"forbidigo","Enabled":true},{"Name":"forcetypeassert"},{"Name":"fatcontext"},{"Name":"funlen","Enabled":true},{"Name":"gci"},{"Name":"ginkgolinter"},{"Name":"gocheckcompilerdirectives"},{"Name":"gochecknoglobals"},{"Name":"gochecknoinits","Enabled":true},{"Name":"gochecksumtype"},{"Name":"gocognit"},{"Name":"goconst","Enabled":true},{"Name":"gocritic"},{"Name":"gocyclo","Enabled":true},{"Name":"godot"},{"Name":"godox"},{"Name":"err113"},{"Name":"gofmt","Enabled":true},{"Name":"gofumpt"},{"Name":"goheader"},{"Name":"goimports","Enabled":true},{"Name":"golint"},{"Name":"mnd","Enabled":true},{"Name":"gomnd"},{"Name":"gomoddirectives"},{"Name":"gomodguard"},{"Name":"goprintffuncname","Enabled":true},{"Name":"gosec","Enabled":true},{"Name":"gosimple","Enabled":true,"EnabledByDefault":true},{"Name":"gosmopolitan"},{"Name":"govet","Enabled":true,"EnabledByDefault":true},{"Name":"grouper"},{"Name":"ifshort"},{"Name":"iface"},{"Name":"importas"},{"Name":"inamedparam"},{"Name":"ineffassign","Enabled":true,"EnabledByDefault":true},{"Name":"interfacebloat"},{"Name":"interfacer"},{"Name":"intrange"},{"Name":"ireturn"},{"Name":"lll","Enabled":true},{"Name":"loggercheck"},{"Name":"maintidx"},{"Name":"makezero"},{"Name":"maligned"},{"Name":"mirror"},{"Name":"misspell"},{"Name":"musttag"},{"Name":"nakedret","Enabled":true},{"Name":"nestif"},{"Name":"nilerr"},{"Name":"nilnesserr"},{"Name":"nilnil"},{"Name":"nlreturn"},{"Name":"noctx","Enabled":true},{"Name":"nonamedreturns"},{"Name":"nosnakecase"},{"Name":"nosprintfhostport"},{"Name":"paralleltest"},{"Name":"perfsprint"},{"Name":"prealloc"},{"Name":"predeclared"},{"Name":"promlinter"},{"Name":"protogetter"},{"Name":"reassign"},{"Name":"recvcheck"},{"Name":"revive"},{"Name":"rowserrcheck"},{"Name":"sloglint"},{"Name":"scopelint"},{"Name":"sqlclosecheck"},{"Name":"spancheck"},{"Name":"staticcheck","Enabled":true,"EnabledByDefault":true},{"Name":"structcheck"},{"Name":"stylecheck","Enabled":true},{"Name":"tagalign"},{"Name":"tagliatelle"},{"Name":"tenv"},{"Name":"testableexamples"},{"Name":"testifylint"},{"Name":"testpackage"},{"Name":"thelper"},{"Name":"tparallel"},{"Name":"typecheck","Enabled":true,"EnabledByDefault":true},{"Name":"unconvert","Enabled":true},{"Name":"unparam"},{"Name":"unused","Enabled":true,"EnabledByDefault":true},{"Name":"usestdlibvars"},{"Name":"usetesting"},{"Name":"varcheck"},{"Name":"varnamelen"},{"Name":"wastedassign"},{"Name":"whitespace","Enabled":true},{"Name":"wrapcheck"},{"Name":"wsl"},{"Name":"zerologlint"},{"Name":"nolintlint","Enabled":true}]}}


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@jamlo jamlo marked this pull request as ready for review March 20, 2025 17:03
@jamlo jamlo requested a review from wdbaruni March 20, 2025 17:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
cmd/cli/auth/sso/login_test.go (1)

18-18: Fix comment inconsistency in assertion.

The assertion correctly checks for "login" as the command use, but the comment still refers to "sso".

-	assert.Equal(t, "login", cmd.Use, "Command use should be 'sso'")
+	assert.Equal(t, "login", cmd.Use, "Command use should be 'login'")
cmd/cli/auth/sso/logout.go (2)

49-49: Use the underscore variable for unused return value

The urlScheme returned by ConstructAPIEndpoint is not used in this function.

-	apiURL, _ := util.ConstructAPIEndpoint(cfg.API)
+	apiURL, _ := util.ConstructAPIEndpoint(cfg.API)

63-70: Add check for existing token before logout

The current implementation doesn't check if a token exists before attempting to delete it. It would be more user-friendly to provide different messaging based on whether a token actually exists.

	authTokenPath, err := cfg.JWTTokensPath()
	if err != nil {
		return bacerrors.New("unable to find local SSO session file")
	}

+	// Check if token exists
+	existingCred, readErr := util.ReadToken(authTokenPath, apiURL)
+	if readErr != nil {
+		return bacerrors.New("unable to read SSO session credentials")
+	}
+
+	if existingCred == nil {
+		fmt.Fprintf(cmd.OutOrStdout(), "\nNo active session found for %s\n", apiURL)
+		return nil
+	}

	if err = util.WriteToken(authTokenPath, apiURL, nil); err != nil {
		return bacerrors.New("unable to delete SSO session credentials")
	}
cmd/cli/auth/sso/logout_test.go (1)

81-101: Simplify input simulation in the test

The test uses two different mechanisms to simulate user input: setting the command's input with cmd.SetIn(inBuf) and manipulating os.Stdin directly with a pipe. This is redundant and potentially confusing.

Choose one approach for consistency - preferably using just cmd.SetIn() as it's cleaner:

	// Create a buffer for input simulation with "y" response
	inBuf := bytes.NewBufferString("y\n")
	outBuf := new(bytes.Buffer)

	// Set up the command's input and output
	cmd.SetIn(inBuf)
	cmd.SetOut(outBuf)

-	// Create a new reader for Scanf
-	oldStdin := os.Stdin
-	defer func() { os.Stdin = oldStdin }()
-
-	// Create a pipe and use it for stdin
-	r, w, _ := os.Pipe()
-	os.Stdin = r
-
-	// Write "y" to the pipe
-	go func() {
-		w.Write([]byte("y\n"))
-		w.Close()
-	}()
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 608499a and deae9ac.

📒 Files selected for processing (6)
  • cmd/cli/auth/root.go (2 hunks)
  • cmd/cli/auth/sso/login.go (4 hunks)
  • cmd/cli/auth/sso/login_test.go (2 hunks)
  • cmd/cli/auth/sso/logout.go (1 hunks)
  • cmd/cli/auth/sso/logout_test.go (1 hunks)
  • cmd/cli/auth/sso/root.go (1 hunks)
🧰 Additional context used
🧬 Code Definitions (4)
cmd/cli/auth/root.go (1)
cmd/cli/auth/sso/root.go (1) (1)
  • NewSSORootCmd (7-16)
cmd/cli/auth/sso/root.go (2)
cmd/cli/auth/sso/login.go (1) (1)
  • NewSSOLoginCmd (39-66)
cmd/cli/auth/sso/logout.go (1) (1)
  • NewSSOLogoutCmd (25-45)
cmd/cli/auth/sso/login_test.go (1)
cmd/cli/auth/sso/login.go (1) (1)
  • NewSSOLoginCmd (39-66)
cmd/cli/auth/sso/logout.go (1)
cmd/cli/auth/sso/login.go (1) (1)
  • o (69-156)
🔇 Additional comments (13)
cmd/cli/auth/sso/root.go (1)

1-16: Well-structured SSO root command implementation.

This is a clean implementation of the SSO root command that properly organizes login and logout as subcommands. The hierarchy follows good CLI design patterns.

cmd/cli/auth/root.go (2)

4-4: LGTM: Appropriate import for the new SSO package.

The import statement is correctly added to access the new SSO root command implementation.


17-17: LGTM: Command structure properly updated.

The command hierarchy has been successfully updated to use the new SSO root command, which will improve CLI organization.

cmd/cli/auth/sso/login_test.go (1)

1-1: LGTM: Package name and test function properly updated.

The package name and test function have been correctly updated to match the new structure and naming conventions.

Also applies to: 13-15

cmd/cli/auth/sso/login.go (3)

1-1: LGTM: Package and type names effectively refactored.

The package has been correctly renamed to "sso" and the types have been renamed to more clearly indicate their specific purpose for login functionality.

Also applies to: 25-37


39-66: LGTM: Command structure and function properly updated.

The command creation function has been successfully refactored to reflect its specific login purpose, with the appropriate use of "login" instead of "sso".


68-156: LGTM: Method correctly renamed for clarity.

The method has been appropriately renamed from runSSO to runSSOLogin to better reflect its specific functionality, maintaining consistency with the overall refactoring approach.

cmd/cli/auth/sso/logout.go (2)

13-23: Excellent struct design and initialization

The LogoutOptions struct and its constructor are well-designed, with clear documentation and appropriate default values.


25-45: Well-structured command implementation

The command structure follows Cobra best practices with clear usage descriptions, appropriate argument validation, and proper flag setup.

cmd/cli/auth/sso/logout_test.go (4)

15-35: Well-designed test helpers

The test helper functions setupTestCmd and createTempTokenFile are well-designed and facilitate clean, isolated test cases.


37-65: Comprehensive force flag test

The TestLogout_ForceFlag function thoroughly tests the force flag behavior, including verifying the output message and token file updates.


119-148: Excellent test for cancellation scenario

The TestLogout_ConfirmNo function properly tests the cancellation path, ensuring the token remains unchanged and the correct message is displayed.


150-162: Thorough command structure verification

The TestNewSSOLogoutCmd function effectively verifies all aspects of the command structure, including the force flag configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
cmd/cli/auth/sso/logout_test.go (1)

82-101: Redundant input simulation in TestLogout_ConfirmYes

There appears to be redundant input simulation. The test sets both cmd.SetIn(inBuf) (line 87) and also manipulates os.Stdin directly (lines 90-101). This is unnecessary and could lead to inconsistent behavior.

-	// Create a buffer for input simulation with "y" response
-	inBuf := bytes.NewBufferString("y\n")
-	outBuf := new(bytes.Buffer)
-
-	// Set up the command's input and output
-	cmd.SetIn(inBuf)
-	cmd.SetOut(outBuf)
-
-	// Create a new reader for Scanf
-	oldStdin := os.Stdin
-	defer func() { os.Stdin = oldStdin }()
-
-	// Create a pipe and use it for stdin
-	r, w, _ := os.Pipe()
-	os.Stdin = r
-
-	// Write "y" to the pipe
-	go func() {
-		w.Write([]byte("y\n"))
-		w.Close()
-	}()
+	// Create input/output buffers
+	inBuf := bytes.NewBufferString("y\n")
+	outBuf := new(bytes.Buffer)
+
+	// Set up the command's input and output
+	cmd.SetIn(inBuf)
+	cmd.SetOut(outBuf)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro (Legacy)

📥 Commits

Reviewing files that changed from the base of the PR and between deae9ac and ea87a68.

📒 Files selected for processing (2)
  • cmd/cli/auth/sso/logout.go (1 hunks)
  • cmd/cli/auth/sso/logout_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/cli/auth/sso/logout.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: testcontainers-suite / tests
🔇 Additional comments (7)
cmd/cli/auth/sso/logout_test.go (7)

1-13: Clean and well-structured imports

The package declaration and imports are organized correctly, with a clear separation between standard library, third-party, and internal packages.


15-35: Well-designed test helper functions

Good practice implementing reusable helper functions for test setup. The createTempTokenFile function correctly handles cleanup with the returned function, ensuring resources are properly managed.


37-65: Comprehensive test for force logout functionality

The test correctly validates the force logout scenario, including:

  • Proper setup with a mock token file
  • Verification of command output
  • Confirmation that the token file is properly updated

This ensures the force flag works as expected.


67-81: Well-structured confirmation test

The test correctly validates the confirmation scenario with a "yes" response, including proper cleanup and token verification.

Also applies to: 102-117


119-148: Complete negative test case for logout cancellation

Good test coverage for the case when a user cancels the logout with "n". The test properly verifies:

  • The cancellation message is displayed
  • The token file remains unchanged

This ensures the command respects user choice.


150-179: Edge case handling for empty confirmation input

Excellent coverage of the edge case when a user just presses Enter. The test properly treats empty input as cancellation, which is a good UX practice.


181-193: Verify command structure and flags

The test properly validates the structural aspects of the command:

  • Command name and description
  • Force flag properties (shorthand and default value)

This helps ensure the command interface remains consistent.

@jamlo jamlo merged commit 29fccae into main Mar 20, 2025
14 checks passed
@jamlo jamlo deleted the jamlo/auth-commands-restructure branch March 20, 2025 17:58
@jamlo jamlo restored the jamlo/auth-commands-restructure branch March 20, 2025 18:16
@jamlo jamlo deleted the jamlo/auth-commands-restructure branch March 20, 2025 18:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

2 participants