Skip to content

Add auth commands with browser-based login flow#455

Merged
stack72 merged 3 commits intomainfrom
feat/auth-commands
Feb 24, 2026
Merged

Add auth commands with browser-based login flow#455
stack72 merged 3 commits intomainfrom
feat/auth-commands

Conversation

@johnrwatson
Copy link
Contributor

Summary

  • Add swamp auth login — browser-based login (default) with stdin fallback (--username/--password or --no-browser)
  • Add swamp auth whoami — verify stored credentials against the server
  • Add swamp auth logout — clear stored credentials from ~/.swamp/auth.json
  • Add SwampClubClient for sign-in, API key creation, and whoami via Authorization: Bearer
  • Add ephemeral localhost callback server for receiving browser login redirect
  • Add cross-platform browser opener (macOS/Linux/Windows)

Depends on systeminit/swamp-club#55 for server-side bearer auth and CLI login flow support.

Test Plan

  • SWAMP_CLUB_URL=http://localhost:5173 deno run -A main.ts auth login — browser opens, sign in, CLI captures token
  • deno run -A main.ts auth whoami — verifies stored API key works
  • deno run -A main.ts auth login --username X --password Y --server http://localhost:5173 — stdin fallback
  • deno run -A main.ts auth login --no-browser — forces stdin flow
  • deno run -A main.ts auth logout — cleans up ~/.swamp/auth.json

🤖 Generated with Claude Code

- Add `swamp auth login` with browser-based flow (default) and
  stdin fallback (--username/--password or --no-browser)
- Add `swamp auth whoami` to verify stored credentials
- Add `swamp auth logout` to clear stored credentials
- Add SwampClubClient for sign-in, API key creation, and whoami
- Add ephemeral callback server for browser login redirect
- Add cross-platform browser opener utility
- Store credentials at ~/.swamp/auth.json
- Use Authorization: Bearer for all API authentication

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Review Summary

This PR adds authentication commands with browser-based login flow. The architecture follows good DDD principles, and the security implementation (CSRF protection, password masking, timeout handling) is solid. However, there are a few blocking issues that need to be addressed.


Blocking Issues

1. Missing Unit Tests

Per CLAUDE.md: "Comprehensive unit test coverage" and "Unit tests live next to source files: foo.tsfoo_test.ts"

The following test files are missing:

  • src/cli/commands/auth_login_test.ts
  • src/cli/commands/auth_logout_test.ts
  • src/cli/commands/auth_whoami_test.ts
  • src/infrastructure/persistence/auth_repository_test.ts
  • src/infrastructure/http/swamp_club_client_test.ts
  • src/infrastructure/http/callback_server_test.ts
  • src/infrastructure/process/browser_test.ts

Additionally, the new getSwampDataDir() function in paths.ts needs tests added to paths_test.ts.

2. Windows Compatibility Bug in getSwampDataDir()

File: src/infrastructure/persistence/paths.ts:170-176

The function only checks the HOME environment variable:

export function getSwampDataDir(): string {
  const home = Deno.env.get("HOME");
  if (!home) {
    throw new Error("HOME environment variable is not set");
  }
  return join(home, ".swamp");
}

On Windows, the home directory is in USERPROFILE, not HOME. Other parts of the codebase handle this correctly (see src/infrastructure/runtime/embedded_deno_runtime.ts:132):

Deno.env.get("USERPROFILE");

This will cause auth commands to fail on Windows. The fix should check both HOME and USERPROFILE.


Suggestions (Non-Blocking)

1. Security Improvement - File Permissions for Auth Credentials

The auth credentials file at ~/.swamp/auth.json contains an API key but is written without restricted file permissions. Consider setting mode: 0o600 when writing to restrict access to the owner only.

In src/infrastructure/persistence/auth_repository.ts:37, the atomicWriteTextFile accepts an options parameter:

await atomicWriteTextFile(
  this.getAuthPath(),
  JSON.stringify(credentials, null, 2) + "\n",
  { mode: 0o600 }  // Restrict to owner read/write only
);

This follows the pattern used for secrets in symlink_repo_index_service.ts:758.


What Looks Good

  • DDD Structure: Clean separation with AuthCredentials as a value object in domain, AuthRepository in infrastructure/persistence, SwampClubClient in infrastructure/http
  • Security: CSRF protection via state parameter, password input masking, request timeouts
  • Code Style: Proper AGPLv3 headers, named exports, follows existing AnyOptions pattern
  • Both output modes supported: Commands properly support "log" and "json" output modes as required

@stack72 stack72 enabled auto-merge (squash) February 24, 2026 22:48
johnrwatson and others added 2 commits February 24, 2026 22:52
- Add tests for AuthRepository (save/load/delete round trip, file permissions)
- Add tests for callback server (valid/invalid state, missing token, 404)
- Add tests for SwampClubClient (sign-in, API key, whoami, error paths)
- Add tests for getSwampDataDir (HOME, USERPROFILE fallback, missing both)
- Add browser.ts smoke test
- Fix Windows compat: getSwampDataDir falls back to USERPROFILE
- Fix response body leaks in SwampClubClient error paths
- Set mode 0o600 on auth.json for restrictive file permissions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Code Review: Auth Commands with Browser-Based Login Flow

This PR adds a well-structured authentication system with browser-based and stdin-based login flows. The code is well-organized and follows existing project patterns.

Security Review ✅

The implementation includes solid security practices:

  • API key storage: Uses restrictive 0o600 permissions (owner read/write only)
  • CSRF protection: Callback server validates state nonce using crypto.randomUUID()
  • URL encoding: Parameters properly escaped with encodeURIComponent()
  • Password input: Correctly disables echo during password entry
  • Command execution: Uses Deno.Command (not shell), preventing command injection
  • Timeouts: 2-minute timeout on callback server prevents indefinite hanging
  • Request timeouts: 15-second timeout on HTTP requests

Domain-Driven Design ✅

The code follows good DDD principles:

  • AuthCredentials value object properly placed in src/domain/auth/
  • AuthRepository in infrastructure layer for persistence
  • SwampClubClient as an anti-corruption layer for external API
  • CLI commands in the application/interface layer

Test Coverage ✅

Tests provided for infrastructure components:

  • callback_server_test.ts - 5 tests covering success, state mismatch, missing token, 404, and port allocation
  • swamp_club_client_test.ts - 8 tests covering sign-in, API key creation, whoami, and error cases
  • auth_repository_test.ts - 5 tests covering save/load, delete, idempotency, and file permissions
  • paths_test.ts - 3 new tests for getSwampDataDir()
  • browser_test.ts - Smoke test (full browser testing not possible in CI)

Code Quality ✅

  • All files have AGPLv3 license headers
  • Named exports used throughout
  • Both log and json output modes supported per CLAUDE.md requirements
  • Error handling uses UserError for user-friendly messages
  • Follows existing AnyOptions pattern for CLI command typing

Suggestions (Non-blocking)

  1. Minor: The AnyOptions pattern works but if Cliffy ever improves its typing, consider defining explicit option interfaces.

  2. Consider: For the browser test, you could add a test that verifies error handling by mocking a command that fails, though the current smoke test is acceptable.

LGTM! Approving this PR.

@stack72 stack72 merged commit ec1a76d into main Feb 24, 2026
1 check passed
@stack72 stack72 deleted the feat/auth-commands branch February 24, 2026 22:57
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Review Summary

This PR adds a well-structured authentication system with browser-based and stdin login flows. The code follows project conventions and demonstrates good security practices.

✅ Strengths

Code Quality:

  • All files include required AGPLv3 license headers
  • Uses named exports as required by CLAUDE.md
  • Properly supports both log and json output modes for all commands

Security:

  • Credentials stored with restrictive permissions (0o600)
  • CSRF protection via state parameter validation in OAuth callback
  • Timeout on callback server prevents hanging indefinitely
  • API key stored instead of session token for persistent auth

Architecture (DDD):

  • Clean separation: domain (AuthCredentials value object), infrastructure (repository, HTTP client, browser opener), and CLI layers
  • AuthRepository correctly abstracts persistence
  • SwampClubClient isolates external API interaction
  • Follows established patterns in the codebase

Testing:

  • Comprehensive tests for CallbackServer, SwampClubClient, AuthRepository, and paths.ts
  • Tests cover success paths, error conditions, and edge cases
  • File permission test for credentials storage

💡 Suggestions (non-blocking)

  1. Command unit tests: The command files (auth_login.ts, auth_logout.ts, auth_whoami.ts) don't have dedicated test files. While integration testing via the CLI is common, unit tests for the flows (especially browserFlow and stdinFlow) could improve coverage.

  2. Schema validation: AuthRepository.load() casts parsed JSON directly to AuthCredentials. Consider adding runtime validation (e.g., Zod) to handle corrupted or manually-edited auth files gracefully.

  3. AnyOptions pattern: The PR uses the existing type AnyOptions = any pattern (54 files in codebase use this). Future work could introduce proper typed options interfaces, but this isn't blocking.

Verdict

The PR implements authentication correctly with good security practices, follows established codebase patterns, and includes solid test coverage for infrastructure components.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants