Skip to content

Commit 7b92e48

Browse files
committed
docs: document CLI OAuth tool design, testing, and architecture
- Add detailed guidance for Claude on development, testing, and architecture of the CLI OAuth tool - Document the core flow including token management, OAuth with PKCE, callback lifecycle, and file locking - Explain configuration precedence, token storage format, error handling, and security measures - Summarize dependencies, file organization, and common patterns for modifications and extension - Provide best practices for testing, including usage of mock servers and table-driven tests Signed-off-by: appleboy <appleboy.tw@gmail.com>
1 parent a7aae56 commit 7b92e48

1 file changed

Lines changed: 168 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
A CLI tool that implements the OAuth 2.0 Authorization Code Flow with PKCE for authenticating with an AuthGate server. Opens the browser for user authorization, receives the callback via a local HTTP server, exchanges the code for tokens, and manages token lifecycle (storage, refresh, reuse).
8+
9+
Supports both public clients (PKCE-only) and confidential clients (PKCE + client secret).
10+
11+
## Development Commands
12+
13+
Build the binary:
14+
15+
```bash
16+
make build # Builds to bin/oauth-cli
17+
go run . # Run directly without building
18+
```
19+
20+
Testing:
21+
22+
```bash
23+
make test # Run all tests with coverage
24+
make coverage # View coverage in browser
25+
go test ./... -v # Run tests verbose
26+
go test -run TestSpecificFunction # Run a single test
27+
```
28+
29+
Linting and formatting:
30+
31+
```bash
32+
make lint # Run golangci-lint
33+
make fmt # Format code with golangci-lint
34+
```
35+
36+
Development workflow:
37+
38+
```bash
39+
make dev # Hot reload with air (installs air if needed)
40+
```
41+
42+
Other commands:
43+
44+
```bash
45+
make clean # Remove build artifacts and coverage files
46+
make rebuild # Clean then build
47+
make help # Show all available make targets
48+
```
49+
50+
## Architecture
51+
52+
### File Organization
53+
54+
All code is in the main package at the repository root. No subdirectories or internal packages.
55+
56+
Key files:
57+
58+
- `main.go` - Entry point, config, token lifecycle, OAuth flow orchestration
59+
- `callback.go` - Local HTTP server for OAuth callback handling
60+
- `pkce.go` - PKCE code verifier/challenge generation (RFC 7636)
61+
- `filelock.go` - File locking for concurrent token file access
62+
- `browser.go` - Cross-platform browser opening
63+
64+
### Core Flow
65+
66+
1. **Initialization** (`initConfig`): Parse flags, load `.env`, validate config, create HTTP retry client with TLS 1.2+
67+
2. **Token Check**: Try to load existing tokens from disk
68+
- Valid token → use immediately
69+
- Expired token → attempt refresh
70+
- No token or refresh fails → start Authorization Code Flow
71+
3. **Authorization Code Flow** (`performAuthCodeFlow`):
72+
- Generate PKCE verifier/challenge + state (CSRF protection)
73+
- Open browser to `/oauth/authorize` with all params
74+
- Start local callback server on port 8888 (default)
75+
- Wait for OAuth callback with authorization code
76+
- Exchange code for tokens (PKCE verifier + client secret if confidential)
77+
- Save tokens to file with atomic write
78+
4. **Token Exchange in Callback**: The token exchange happens **inside the HTTP callback handler** so the browser tab shows the true outcome (success/failure) rather than a premature success page
79+
5. **Token Storage**: Multi-client JSON file with file locking for concurrent safety
80+
81+
### Key Design Patterns
82+
83+
**Context Propagation**: All HTTP requests and long-running operations accept `context.Context`. The main function uses `signal.NotifyContext` to handle SIGINT/SIGTERM gracefully.
84+
85+
**PKCE Always Enabled**: Even confidential clients use PKCE (defense in depth). Both `code_verifier` and `client_secret` are sent during token exchange for confidential clients.
86+
87+
**Token Refresh**: The `refreshAccessToken` function handles refresh token rotation (preserves old refresh token if server doesn't return a new one).
88+
89+
**Callback Server Lifecycle**:
90+
91+
- Starts before opening browser
92+
- Validates state parameter (CSRF protection)
93+
- Holds HTTP response open during token exchange (browser sees true outcome)
94+
- Uses `sync.Once` to ensure exchange happens exactly once even if browser retries
95+
- Shuts down after first callback or context cancellation
96+
97+
**File Locking**: Uses a separate `.lock` file to coordinate concurrent access to the token file. Implements stale lock detection (removes locks older than 30 seconds).
98+
99+
**HTTP Client**: Uses `github.com/appleboy/go-httpretry` for automatic retries with exponential backoff. TLS 1.2+ enforced. Warns when using HTTP (not HTTPS) for development.
100+
101+
### Configuration Precedence
102+
103+
Flag > Environment Variable > Default
104+
105+
All config is initialized once via `initConfig()` which sets global variables. The `configInitialized` flag prevents double initialization.
106+
107+
### Token Storage Format
108+
109+
JSON file with per-client-id tokens:
110+
111+
```json
112+
{
113+
"tokens": {
114+
"client-id-uuid-here": {
115+
"access_token": "...",
116+
"refresh_token": "...",
117+
"token_type": "Bearer",
118+
"expires_at": "2026-02-19T12:00:00Z",
119+
"client_id": "..."
120+
}
121+
}
122+
}
123+
```
124+
125+
File written with 0600 permissions via atomic rename (write to `.tmp` then rename).
126+
127+
### Error Handling
128+
129+
- OAuth errors (e.g., `access_denied`, `invalid_grant`) are parsed from JSON responses
130+
- `ErrRefreshTokenExpired` signals when a refresh token is invalid → triggers full re-auth
131+
- Context cancellation checked after all blocking operations (graceful shutdown)
132+
- Exit codes: 0 (success), 1 (error), 130 (interrupted via SIGINT)
133+
134+
### Security Notes
135+
136+
- PKCE (RFC 7636) always enabled — code verifier never leaves the client
137+
- State parameter validated on every callback (CSRF protection)
138+
- TLS 1.2+ enforced for all HTTPS connections
139+
- Token file written with 0600 permissions
140+
- Warns when SERVER_URL uses plain HTTP
141+
- Client ID validated as UUID format (warning only)
142+
143+
## Testing Patterns
144+
145+
Tests use table-driven tests with subtests (`t.Run`). Mock HTTP servers created with `httptest.NewServer` for testing OAuth endpoints.
146+
147+
Key test files:
148+
149+
- `main_test.go` - Token storage, refresh, config validation
150+
- `callback_test.go` - Callback server behavior, state validation, concurrent requests
151+
- `pkce_test.go` - PKCE generation, verifier/challenge encoding
152+
- `filelock_test.go` - Concurrent access, stale lock detection
153+
154+
## External Dependencies
155+
156+
- `github.com/joho/godotenv` - Load `.env` files
157+
- `github.com/google/uuid` - UUID validation
158+
- `github.com/appleboy/go-httpretry` - HTTP client with retry and backoff
159+
160+
## Common Modifications
161+
162+
**Changing callback port**: Update both `-port` flag and Redirect URI in AuthGate Admin (must match).
163+
164+
**Adding new OAuth endpoints**: Follow the existing pattern in `main.go` — create request with context timeout, use `retryClient.DoWithContext`, check for OAuth error responses in JSON.
165+
166+
**Token storage changes**: Modify `TokenStorage` struct and update `loadTokens`/`saveTokens`. The atomic write pattern (temp file + rename) should be preserved.
167+
168+
**Browser opening**: Platform-specific logic is in `browser.go`. Uses `xdg-open` (Linux), `open` (macOS), `rundll32` (Windows).

0 commit comments

Comments
 (0)