Skip to content

Commit c986f6f

Browse files
committed
docs: add contributor guidelines and architectural documentation
- Add CLAUDE.md to provide architecture, development, and testing guidelines for Claude Code contributors - Outline project structure, configuration loading, and multi-client token storage design with atomic file writes and locking - Describe key OAuth flow, device polling with exponential backoff, and HTTP client settings - Document error handling, security practices, and CI/CD workflow details Signed-off-by: appleboy <appleboy.tw@gmail.com>
1 parent 292184d commit c986f6f

1 file changed

Lines changed: 213 additions & 0 deletions

File tree

CLAUDE.md

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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+
This is an OAuth 2.0 Device Authorization Flow CLI tool (RFC 8628) written in Go. It authenticates with an AuthGate server and manages tokens for headless/SSH environments.
8+
9+
## Development Commands
10+
11+
### Building and Testing
12+
13+
```bash
14+
# Build the binary
15+
make build # Output: bin/device-cli
16+
17+
# Run all tests with coverage
18+
make test
19+
20+
# View test coverage in browser
21+
make coverage
22+
23+
# Run a single test
24+
go test -v -run TestFunctionName ./...
25+
26+
# Run tests for specific file
27+
go test -v ./filelock_test.go filelock.go
28+
```
29+
30+
### Linting and Formatting
31+
32+
```bash
33+
# Run golangci-lint
34+
make lint
35+
36+
# Auto-format code
37+
make fmt
38+
39+
# Check installed tools
40+
make check-tools
41+
```
42+
43+
### Running the Application
44+
45+
```bash
46+
# Build and run locally
47+
make build
48+
./bin/device-cli -client-id=<id> -server-url=http://localhost:8080
49+
50+
# Hot reload during development
51+
make dev # Uses air for live reload
52+
```
53+
54+
### Other Commands
55+
56+
```bash
57+
# Generate files (templ templates if any)
58+
make generate
59+
60+
# Clean build artifacts
61+
make clean
62+
63+
# Cross-platform builds
64+
make build_linux_amd64
65+
make build_linux_arm64
66+
```
67+
68+
## Architecture
69+
70+
### File Structure
71+
72+
- **main.go** (905 lines): Core application logic, contains all OAuth flow, HTTP client, token management, and CLI entry point
73+
- **filelock.go**: File locking mechanism for safe concurrent token file access
74+
- **polling_test.go**: Tests for device code polling with exponential backoff
75+
- **main_test.go**: Core functionality tests including concurrent token writes
76+
77+
### Configuration Loading
78+
79+
Configuration uses a three-tier priority system implemented in `initConfig()`:
80+
81+
1. Command-line flags (highest priority)
82+
2. Environment variables
83+
3. `.env` file / defaults (lowest priority)
84+
85+
The `initConfig()` function is separated from `init()` to avoid conflicts with Go's test flag parsing. Tests manually initialize variables in their own `init()` function.
86+
87+
### Token Storage Architecture
88+
89+
**Multi-client support**: A single JSON file (`TokenStorageMap`) stores tokens for multiple OAuth clients, keyed by `client_id`:
90+
91+
```json
92+
{
93+
"tokens": {
94+
"client-id-1": { "access_token": "...", "refresh_token": "...", ... },
95+
"client-id-2": { "access_token": "...", "refresh_token": "...", ... }
96+
}
97+
}
98+
```
99+
100+
**Atomic writes**: Uses temp file + rename pattern to prevent corruption:
101+
102+
1. Write new data to `<tokenfile>.tmp`
103+
2. Rename tmp file to actual file (atomic operation)
104+
3. On error, clean up temp file
105+
106+
**File locking**: `saveTokens()` acquires an exclusive lock via `acquireFileLock()` to prevent race conditions during concurrent access. Lock files (`<tokenfile>.lock`) have stale lock detection (30s timeout) and automatic cleanup.
107+
108+
### OAuth Flow Implementation
109+
110+
The main flow is orchestrated by `run()`:
111+
112+
1. **Token Loading**: Try to load existing tokens for current `client_id`
113+
2. **Token Validation**: Check if access token is expired (compare `ExpiresAt` with current time)
114+
3. **Refresh Flow**: If expired, call `refreshAccessToken()` with refresh token
115+
4. **Device Flow Fallback**: If no tokens or refresh fails, call `performDeviceFlow()`
116+
5. **Token Verification**: Verify token with server's `/oauth/tokeninfo` endpoint
117+
6. **Auto-refresh Demo**: `makeAPICallWithAutoRefresh()` demonstrates automatic refresh on 401
118+
119+
### Device Code Polling
120+
121+
`pollForTokenWithProgress()` implements RFC 8628 polling with exponential backoff:
122+
123+
- Initial interval from server (default: 5s)
124+
- On `slow_down` error: multiply interval by 1.5, cap at 60s
125+
- Progress dots printed every 2s (UI update interval)
126+
- Two separate tickers: one for polling, one for UI updates
127+
- Handles errors: `authorization_pending`, `slow_down`, `expired_token`, `access_denied`
128+
129+
### HTTP Client Configuration
130+
131+
`retryClient` (initialized in `initConfig()`) wraps a base HTTP client with:
132+
133+
- TLS 1.2+ enforcement
134+
- Connection pooling (MaxIdleConns: 10, IdleConnTimeout: 90s)
135+
- Retry logic via `go-httpretry` package
136+
- Per-operation timeouts (constants at top of main.go):
137+
- Device code request: 10s
138+
- Token exchange: 5s
139+
- Token verification: 10s
140+
- Refresh token: 10s
141+
142+
### Refresh Token Rotation
143+
144+
`refreshAccessToken()` handles two server modes:
145+
146+
1. **Rotation mode**: Server returns new `refresh_token` → use it
147+
2. **Fixed mode**: Server returns empty `refresh_token` → preserve old one
148+
149+
The function checks `ErrRefreshTokenExpired` for `invalid_grant` or `invalid_token` errors, signaling the need for a new device flow.
150+
151+
### Error Handling
152+
153+
- **Context cancellation**: All HTTP operations accept `context.Context` and respect cancellation (Ctrl+C)
154+
- **OAuth errors**: Parsed from JSON error responses (`ErrorResponse` struct)
155+
- **Validation**: `validateTokenResponse()` checks access token length (≥10 chars), expiry, and token type
156+
- **URL validation**: `validateServerURL()` ensures proper scheme (http/https) and host presence
157+
158+
## Testing Patterns
159+
160+
### Test Initialization
161+
162+
Tests use a separate `init()` function that sets defaults without calling `initConfig()`, avoiding flag parsing conflicts.
163+
164+
### Concurrent Testing
165+
166+
`TestSaveTokens_ConcurrentWrites` spawns 10 goroutines to verify file locking works correctly under concurrent access.
167+
168+
### Test Servers
169+
170+
Tests use `httptest` to create mock OAuth servers, returning device codes and tokens.
171+
172+
## Important Notes
173+
174+
### Security Warnings
175+
176+
The CLI automatically warns users when:
177+
178+
- Using HTTP instead of HTTPS (tokens transmitted in plaintext)
179+
- CLIENT_ID is not a valid UUID format
180+
181+
### Token File Security
182+
183+
- Created with `0600` permissions (owner read/write only)
184+
- Should be added to `.gitignore` (already configured)
185+
- Stored at `.authgate-tokens.json` by default
186+
187+
### Version Information
188+
189+
The Makefile supports version injection via git tags:
190+
191+
```bash
192+
VERSION=$(git describe --tags --always)
193+
COMMIT=$(git rev-parse --short HEAD)
194+
```
195+
196+
goreleaser uses these to build versioned binaries with naming pattern:
197+
`authgate-device-cli-{version}-{os}-{arch}`
198+
199+
### CI/CD
200+
201+
- **Testing**: Runs on Ubuntu and macOS with Go 1.25 and 1.26
202+
- **Linting**: Uses golangci-lint v2.9 with config from `.golangci.yml`
203+
- **Release**: goreleaser builds for multiple platforms (see `.goreleaser.yaml`)
204+
- **Generate step**: Always run `make generate` before building (for templ templates)
205+
206+
## Go Modules
207+
208+
Requires Go 1.25+. Key dependencies:
209+
210+
- `golang.org/x/oauth2`: OAuth 2.0 client library
211+
- `github.com/appleboy/go-httpretry`: HTTP retry logic
212+
- `github.com/joho/godotenv`: .env file loading
213+
- `github.com/google/uuid`: UUID validation

0 commit comments

Comments
 (0)