Skip to content

Commit 61d8569

Browse files
appleboyclaude
andcommitted
docs(readme): sync documentation with current codebase
- Update Go version requirement from 1.21 to 1.25 - Add token-store parameter and keyring storage backend documentation - Correct backoff strategy from multiplicative to additive per RFC 8628 - Update binary paths, timeouts, and development commands - Remove references to deleted filelock.go and nonexistent make targets - Bump golangci-lint install version to v2.11.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ddd4bd7 commit 61d8569

4 files changed

Lines changed: 105 additions & 84 deletions

File tree

.goreleaser.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ version: 2
22
project_name: authgate-device-cli
33
before:
44
hooks:
5-
- make generate
65
- go mod tidy
76

87
builds:

CLAUDE.md

Lines changed: 26 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ make coverage
2424
go test -v -run TestFunctionName ./...
2525

2626
# Run tests for specific file
27-
go test -v ./filelock_test.go filelock.go
27+
go test -v ./main_test.go main.go
2828
```
2929

3030
### Linting and Formatting
@@ -47,8 +47,6 @@ make check-tools
4747
make build
4848
./bin/device-cli -client-id=<id> -server-url=http://localhost:8080
4949

50-
# Hot reload during development
51-
make dev # Uses air for live reload
5250
```
5351

5452
### Other Commands
@@ -66,10 +64,12 @@ make build_linux_arm64
6664

6765
### File Structure
6866

69-
- **main.go** (905 lines): Core application logic, contains all OAuth flow, HTTP client, token management, and CLI entry point
70-
- **filelock.go**: File locking mechanism for safe concurrent token file access
71-
- **polling_test.go**: Tests for device code polling with exponential backoff
72-
- **main_test.go**: Core functionality tests including concurrent token writes
67+
- **main.go**: Core application logic — OAuth flow, HTTP client, token management, and CLI entry point
68+
- **tui/displayer.go**: Output abstraction layer (`Displayer` interface) with `PlainDisplayer` (non-TTY), `ProgramDisplayer` (BubbleTea TUI), and `NoopDisplayer` (tests)
69+
- **tui/model.go**: BubbleTea model for interactive terminal UI
70+
- **tui/messages.go**: BubbleTea message types for TUI state transitions
71+
- **polling_test.go**: Tests for device code polling with additive backoff
72+
- **main_test.go**: Core functionality tests
7373

7474
### Configuration Loading
7575

@@ -83,24 +83,13 @@ The `initConfig()` function is separated from `init()` to avoid conflicts with G
8383

8484
### Token Storage Architecture
8585

86-
**Multi-client support**: A single JSON file (`TokenStorageMap`) stores tokens for multiple OAuth clients, keyed by `client_id`:
86+
Token storage is handled by the `credstore` package from `github.com/go-authgate/sdk-go`. The `-token-store` flag controls the backend:
8787

88-
```json
89-
{
90-
"tokens": {
91-
"client-id-1": { "access_token": "...", "refresh_token": "...", ... },
92-
"client-id-2": { "access_token": "...", "refresh_token": "...", ... }
93-
}
94-
}
95-
```
96-
97-
**Atomic writes**: Uses temp file + rename pattern to prevent corruption:
98-
99-
1. Write new data to `<tokenfile>.tmp`
100-
2. Rename tmp file to actual file (atomic operation)
101-
3. On error, clean up temp file
88+
- **`auto`** (default): `SecureStore` wraps keyring with file fallback. Warns if keyring is unavailable.
89+
- **`keyring`**: Uses OS keyring via `TokenKeyringStore` (macOS Keychain, GNOME Keyring, Windows Credential Manager)
90+
- **`file`**: Uses `TokenFileStore` with JSON file at the configured path
10291

103-
**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.
92+
**Multi-client support**: All backends store tokens keyed by `client_id`. The `credstore.Store[credstore.Token]` interface provides `Load(clientID)` and `Save(clientID, token)` methods.
10493

10594
### OAuth Flow Implementation
10695

@@ -115,12 +104,12 @@ The main flow is orchestrated by `run()`:
115104

116105
### Device Code Polling
117106

118-
`pollForTokenWithProgress()` implements RFC 8628 polling with exponential backoff:
107+
`pollForTokenWithProgress()` implements RFC 8628 polling with additive backoff:
119108

120109
- Initial interval from server (default: 5s)
121-
- On `slow_down` error: multiply interval by 1.5, cap at 60s
122-
- Progress dots printed every 2s (UI update interval)
123-
- Two separate tickers: one for polling, one for UI updates
110+
- On `slow_down` error: adds 5s to interval per RFC 8628 §3.5, capped at 60s
111+
- Progress reported via `Displayer` interface (TUI or plain text)
112+
- Single ticker for polling
124113
- Handles errors: `authorization_pending`, `slow_down`, `expired_token`, `access_denied`
125114

126115
### HTTP Client Configuration
@@ -158,10 +147,6 @@ The function checks `ErrRefreshTokenExpired` for `invalid_grant` or `invalid_tok
158147

159148
Tests use a separate `init()` function that sets defaults without calling `initConfig()`, avoiding flag parsing conflicts.
160149

161-
### Concurrent Testing
162-
163-
`TestSaveTokens_ConcurrentWrites` spawns 10 goroutines to verify file locking works correctly under concurrent access.
164-
165150
### Test Servers
166151

167152
Tests use `httptest` to create mock OAuth servers, returning device codes and tokens.
@@ -175,11 +160,12 @@ The CLI automatically warns users when:
175160
- Using HTTP instead of HTTPS (tokens transmitted in plaintext)
176161
- CLIENT_ID is not a valid UUID format
177162

178-
### Token File Security
163+
### Token Storage Security
179164

180-
- Created with `0600` permissions (owner read/write only)
181-
- Should be added to `.gitignore` (already configured)
182-
- Stored at `.authgate-tokens.json` by default
165+
- Keyring backend: OS-level credential encryption (preferred)
166+
- File backend: created with `0600` permissions (owner read/write only)
167+
- Token files should be added to `.gitignore` (already configured)
168+
- Default file path: `.authgate-tokens.json`
183169

184170
### Version Information
185171

@@ -196,7 +182,7 @@ goreleaser uses these to build versioned binaries with naming pattern:
196182
### CI/CD
197183

198184
- **Testing**: Runs on Ubuntu and macOS with Go 1.25 and 1.26
199-
- **Linting**: Uses golangci-lint v2.9 with config from `.golangci.yml`
185+
- **Linting**: Uses golangci-lint v2.11 with config from `.golangci.yml`
200186
- **Release**: goreleaser builds for multiple platforms (see `.goreleaser.yaml`)
201187

202188
## Go Modules
@@ -207,3 +193,7 @@ Requires Go 1.25+. Key dependencies:
207193
- `github.com/appleboy/go-httpretry`: HTTP retry logic
208194
- `github.com/joho/godotenv`: .env file loading
209195
- `github.com/google/uuid`: UUID validation
196+
- `github.com/go-authgate/sdk-go/credstore`: Token storage abstraction (file, keyring, auto)
197+
- `charm.land/bubbletea/v2`: Terminal UI framework
198+
- `charm.land/lipgloss/v2`: Terminal styling
199+
- `charm.land/bubbles/v2`: TUI components

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ coverage: test
3737

3838
## install-golangci-lint: install golangci-lint if not present
3939
install-golangci-lint:
40-
@command -v golangci-lint >/dev/null 2>&1 || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $$($(GO) env GOPATH)/bin v2.7.2
40+
@command -v golangci-lint >/dev/null 2>&1 || curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $$($(GO) env GOPATH)/bin v2.11.0
4141

4242
## fmt: format go files using golangci-lint
4343
fmt: install-golangci-lint

README.md

Lines changed: 78 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ A CLI tool for authenticating with AuthGate server via OAuth 2.0 Device Authoriz
4040

4141
## Prerequisites
4242

43-
- **Go 1.21+** — required to build from source
43+
- **Go 1.25+** — required to build from source
4444
- **AuthGate server** — must be running and accessible ([AuthGate Documentation](../../README.md))
4545
- **Default server address**: `http://localhost:8080`
4646

@@ -52,13 +52,13 @@ A CLI tool for authenticating with AuthGate server via OAuth 2.0 Device Authoriz
5252
# 1. Clone and build
5353
git clone <repository-url>
5454
cd device-cli
55-
go build -o authgate-device-cli
55+
make build
5656

5757
# 2. Get your Client ID from the AuthGate server startup logs:
5858
# Client ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
5959

6060
# 3. Run
61-
./authgate-device-cli -client-id=<your-client-id>
61+
./bin/device-cli -client-id=<your-client-id>
6262
```
6363

6464
---
@@ -67,22 +67,30 @@ go build -o authgate-device-cli
6767

6868
Priority order: **Flag > Environment Variable > `.env` file > default**
6969

70-
| Parameter | Flag | Environment Variable | Default |
71-
| ---------- | ------------- | -------------------- | ----------------------- |
72-
| Client ID | `-client-id` | `CLIENT_ID` | _(required)_ |
73-
| Server URL | `-server-url` | `SERVER_URL` | `http://localhost:8080` |
74-
| Token File | `-token-file` | `TOKEN_FILE` | `.authgate-tokens.json` |
70+
| Parameter | Flag | Environment Variable | Default |
71+
| ----------- | --------------- | -------------------- | ----------------------- |
72+
| Client ID | `-client-id` | `CLIENT_ID` | _(required)_ |
73+
| Server URL | `-server-url` | `SERVER_URL` | `http://localhost:8080` |
74+
| Token File | `-token-file` | `TOKEN_FILE` | `.authgate-tokens.json` |
75+
| Token Store | `-token-store` | `TOKEN_STORE` | `auto` |
76+
77+
**Token Store modes:**
78+
79+
- `auto` (default): Tries OS keyring first, falls back to file if keyring is unavailable
80+
- `keyring`: Uses OS keyring only (macOS Keychain, GNOME Keyring, Windows Credential Manager)
81+
- `file`: Uses JSON file only
7582

7683
**Example `.env` file:**
7784

7885
```env
7986
CLIENT_ID=abc-123
8087
SERVER_URL=http://localhost:8080
8188
TOKEN_FILE=.authgate-tokens.json
89+
TOKEN_STORE=auto
8290
```
8391

8492
```bash
85-
./authgate-device-cli -h # view all options
93+
./bin/device-cli -h # view all options
8694
```
8795

8896
---
@@ -157,7 +165,17 @@ Tokens are saved locally after first login. The CLI will:
157165

158166
## Token Storage
159167

160-
Tokens are stored in `.authgate-tokens.json` (configurable). A single file supports **multiple Client IDs**.
168+
Tokens are managed by the `credstore` package from [`sdk-go`](https://github.com/go-authgate/sdk-go), which supports multiple storage backends:
169+
170+
| Backend | Description |
171+
| -------- | ------------------------------------------------------------------ |
172+
| `auto` | Tries OS keyring first, falls back to file (default) |
173+
| `keyring`| OS keyring: macOS Keychain, GNOME Keyring, Windows Credential Manager |
174+
| `file` | JSON file at `.authgate-tokens.json` (configurable) |
175+
176+
A single store supports **multiple Client IDs**.
177+
178+
When using file-based storage, the JSON format is:
161179

162180
```json
163181
{
@@ -182,33 +200,39 @@ Tokens are stored in `.authgate-tokens.json` (configurable). A single file suppo
182200

183201
**Security properties:**
184202

185-
- Created with `0600` permissions (owner read/write only)
186-
- Written atomically (temp file + rename) to prevent corruption
187-
- File locking prevents race conditions with concurrent processes
203+
- File-based storage: created with `0600` permissions (owner read/write only)
204+
- Keyring storage: uses OS-level credential encryption
205+
- Automatic fallback warning when OS keyring is unavailable
188206

189-
> **Never commit this file to version control.** Add `.authgate-tokens.json` to `.gitignore`.
207+
> **Never commit token files to version control.** Add `.authgate-tokens.json` to `.gitignore`.
190208
191209
---
192210

193211
## Usage Examples
194212

195213
```bash
196214
# First run — prompts for browser authorization
197-
./authgate-device-cli -client-id=abc-123
215+
./bin/device-cli -client-id=abc-123
198216

199217
# Subsequent runs — reuses saved tokens automatically
200-
./authgate-device-cli -client-id=abc-123
218+
./bin/device-cli -client-id=abc-123
201219

202220
# Custom server URL
203-
./authgate-device-cli -client-id=abc-123 -server-url=https://auth.example.com
221+
./bin/device-cli -client-id=abc-123 -server-url=https://auth.example.com
204222

205-
# Multiple clients — stored in same file by default
206-
./authgate-device-cli -client-id=abc-123
207-
./authgate-device-cli -client-id=xyz-789
223+
# Use OS keyring for token storage
224+
./bin/device-cli -client-id=abc-123 -token-store=keyring
208225

209-
# Multiple clients — separate token files
210-
./authgate-device-cli -client-id=abc-123 -token-file=./work-tokens.json
211-
./authgate-device-cli -client-id=xyz-789 -token-file=./personal-tokens.json
226+
# Use file-only storage
227+
./bin/device-cli -client-id=abc-123 -token-store=file
228+
229+
# Multiple clients — stored in same backend by default
230+
./bin/device-cli -client-id=abc-123
231+
./bin/device-cli -client-id=xyz-789
232+
233+
# Multiple clients — separate token files (file mode)
234+
./bin/device-cli -client-id=abc-123 -token-store=file -token-file=./work-tokens.json
235+
./bin/device-cli -client-id=xyz-789 -token-store=file -token-file=./personal-tokens.json
212236
```
213237

214238
---
@@ -220,7 +244,7 @@ The CLI handles all OAuth 2.0 Device Authorization Grant error codes defined in
220244
| Error | Meaning | CLI Behaviour |
221245
| ----------------------- | ----------------------------------------- | ------------------------------------------------------- |
222246
| `authorization_pending` | User has not authorized yet | Continues polling, shows progress dots |
223-
| `slow_down` | Server requests slower polling | Triggers exponential backoff (×1.5 multiplier, max 60s) |
247+
| `slow_down` | Server requests slower polling | Adds 5s to polling interval per RFC 8628 §3.5 (max 60s) |
224248
| `expired_token` | Device code expired (default: 30 minutes) | Stops polling, prompts to restart authentication |
225249
| `access_denied` | User explicitly denied authorization | Stops and displays denial message |
226250
| Other errors | Unexpected server errors | Stops and displays detailed error information |
@@ -229,26 +253,26 @@ The CLI handles all OAuth 2.0 Device Authorization Grant error codes defined in
229253

230254
## Advanced Features
231255

232-
### Polling with Exponential Backoff
256+
### Polling with Additive Backoff
233257

234258
- **Initial interval**: 5 seconds (set by server)
235-
- **Progress indicator**: dots printed every 2 seconds; newline every 50 dots
236-
- **`slow_down` backoff**: interval multiplied by 1.5 on each signal, capped at 60s
259+
- **TUI progress**: interactive terminal UI when running in a TTY; plain text output otherwise
260+
- **`slow_down` backoff**: adds 5 seconds per signal (per RFC 8628 §3.5), capped at 60s
237261

238262
```txt
239-
Initial: 5.000s
240-
1st slow_down: 7.500s
241-
2nd slow_down: 11.250s
242-
3rd slow_down: 16.875s
263+
Initial: 5s
264+
1st slow_down: 10s
265+
2nd slow_down: 15s
266+
3rd slow_down: 20s
243267
...
244-
Maximum: 60.000s
268+
Maximum: 60s
245269
```
246270

247271
### Context and Cancellation
248272

249273
- All operations respect Go context cancellation
250274
- Graceful shutdown on `Ctrl+C`
251-
- Request timeout: 30 seconds per HTTP call
275+
- Per-operation timeouts: device code 10s, token exchange 5s, verification 10s, refresh 10s
252276

253277
---
254278

@@ -258,7 +282,7 @@ Maximum: 60.000s
258282

259283
| Protection | Detail |
260284
| ------------------- | --------------------------------------------------- |
261-
| Request timeout | 30 seconds (prevents indefinite hangs) |
285+
| Request timeout | Per-operation: 5–10 seconds (see Context section) |
262286
| Minimum TLS version | TLS 1.2 |
263287
| HTTP warning | Warns automatically when server URL uses plain HTTP |
264288
| Connection pooling | Idle connection limits to manage resources |
@@ -268,11 +292,11 @@ Maximum: 60.000s
268292
- **`SERVER_URL`**: Validates URL format and scheme (`http`/`https` only)
269293
- **`CLIENT_ID`**: Warns if value is not a valid UUID format
270294

271-
### Token File
295+
### Token Storage Security
272296

273-
- Permissions: `0600` (owner-only)
274-
- Atomic writes: temp file + rename pattern
275-
- File locking: prevents data corruption with concurrent access
297+
- **Keyring** (default when available): OS-level credential encryption
298+
- **File fallback**: `0600` permissions (owner-only)
299+
- CLI warns when OS keyring is unavailable and file storage is used
276300

277301
### Error Handling
278302

@@ -308,7 +332,7 @@ The token was revoked or is invalid. Delete the token file and re-authenticate:
308332

309333
```bash
310334
rm .authgate-tokens.json
311-
./authgate-device-cli -client-id=<your-id>
335+
./bin/device-cli -client-id=<your-id>
312336
```
313337

314338
**`refresh failed`**
@@ -318,21 +342,29 @@ The CLI will automatically start a new device flow. Follow the browser authoriza
318342
Normal behavior — the server returned a `slow_down` signal. The CLI has automatically increased its polling interval. See [Error Reference](#error-reference).
319343

320344
**`context deadline exceeded`**
321-
A request timed out (30s limit). Check your network connection and server availability.
345+
A request timed out. Check your network connection and server availability.
322346

323347
---
324348

325349
## Development
326350

327351
```bash
328-
# Run tests
329-
go test ./...
352+
# Run tests with coverage
353+
make test
354+
355+
# Build binary (output: bin/device-cli)
356+
make build
357+
358+
# Lint and format
359+
make lint
360+
make fmt
330361

331-
# Build binary
332-
go build -o authgate-device-cli
362+
# Cross-platform builds
363+
make build_linux_amd64
364+
make build_linux_arm64
333365

334-
# Build with version info
335-
go build -ldflags="-X main.version=1.0.0" -o authgate-device-cli
366+
# Clean build artifacts
367+
make clean
336368
```
337369

338370
---

0 commit comments

Comments
 (0)