Add auth commands with browser-based login flow#455
Conversation
- 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>
There was a problem hiding this comment.
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.ts → foo_test.ts"
The following test files are missing:
src/cli/commands/auth_login_test.tssrc/cli/commands/auth_logout_test.tssrc/cli/commands/auth_whoami_test.tssrc/infrastructure/persistence/auth_repository_test.tssrc/infrastructure/http/swamp_club_client_test.tssrc/infrastructure/http/callback_server_test.tssrc/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
AuthCredentialsas a value object in domain,AuthRepositoryin infrastructure/persistence,SwampClubClientin infrastructure/http - Security: CSRF protection via state parameter, password input masking, request timeouts
- Code Style: Proper AGPLv3 headers, named exports, follows existing
AnyOptionspattern - Both output modes supported: Commands properly support
"log"and"json"output modes as required
- 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>
There was a problem hiding this comment.
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
0o600permissions (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:
AuthCredentialsvalue object properly placed insrc/domain/auth/AuthRepositoryin infrastructure layer for persistenceSwampClubClientas 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 allocationswamp_club_client_test.ts- 8 tests covering sign-in, API key creation, whoami, and error casesauth_repository_test.ts- 5 tests covering save/load, delete, idempotency, and file permissionspaths_test.ts- 3 new tests forgetSwampDataDir()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
logandjsonoutput modes supported per CLAUDE.md requirements - Error handling uses
UserErrorfor user-friendly messages - Follows existing
AnyOptionspattern for CLI command typing
Suggestions (Non-blocking)
-
Minor: The
AnyOptionspattern works but if Cliffy ever improves its typing, consider defining explicit option interfaces. -
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.
There was a problem hiding this comment.
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
logandjsonoutput 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 (
AuthCredentialsvalue object), infrastructure (repository, HTTP client, browser opener), and CLI layers AuthRepositorycorrectly abstracts persistenceSwampClubClientisolates external API interaction- Follows established patterns in the codebase
Testing:
- Comprehensive tests for
CallbackServer,SwampClubClient,AuthRepository, andpaths.ts - Tests cover success paths, error conditions, and edge cases
- File permission test for credentials storage
💡 Suggestions (non-blocking)
-
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 (especiallybrowserFlowandstdinFlow) could improve coverage. -
Schema validation:
AuthRepository.load()casts parsed JSON directly toAuthCredentials. Consider adding runtime validation (e.g., Zod) to handle corrupted or manually-edited auth files gracefully. -
AnyOptionspattern: The PR uses the existingtype AnyOptions = anypattern (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.
Summary
swamp auth login— browser-based login (default) with stdin fallback (--username/--passwordor--no-browser)swamp auth whoami— verify stored credentials against the serverswamp auth logout— clear stored credentials from~/.swamp/auth.jsonSwampClubClientfor sign-in, API key creation, and whoami viaAuthorization: BearerDepends 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 tokendeno run -A main.ts auth whoami— verifies stored API key worksdeno run -A main.ts auth login --username X --password Y --server http://localhost:5173— stdin fallbackdeno run -A main.ts auth login --no-browser— forces stdin flowdeno run -A main.ts auth logout— cleans up~/.swamp/auth.json🤖 Generated with Claude Code