-213
.claude/CLAUDE.md
-213
.claude/CLAUDE.md
···
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 Deno-compatible AT Protocol OAuth client built specifically for handle-based authentication. This is **NOT a drop-in replacement** for `@atproto/oauth-client-node` - it's an opinionated, handle-focused alternative built on Web Crypto API.
8
-
9
-
**Key Design Decisions:**
10
-
11
-
- Handle-only inputs (e.g., `alice.bsky.social`) - no DIDs or URLs accepted
12
-
- Slingshot resolver as default with fallbacks (Bluesky API → direct resolution)
13
-
- Web Crypto API exclusively for cross-platform compatibility
14
-
- Built for Deno runtime, not Node.js
15
-
16
-
## Commands
17
-
18
-
### Development
19
-
20
-
```bash
21
-
# Type checking
22
-
deno task check
23
-
24
-
# Format code
25
-
deno task fmt # Check formatting
26
-
deno task fmt:fix # Auto-fix formatting
27
-
28
-
# Linting
29
-
deno task lint
30
-
31
-
# Run all checks (CI simulation)
32
-
deno task ci
33
-
```
34
-
35
-
### Testing
36
-
37
-
```bash
38
-
# Run all tests
39
-
deno task test
40
-
41
-
# Run specific test file
42
-
deno test tests/session_test.ts --allow-net --allow-read
43
-
44
-
# Run tests with coverage
45
-
deno test --coverage=coverage --allow-net --allow-read
46
-
```
47
-
48
-
### Publishing
49
-
50
-
```bash
51
-
# Publish to JSR (requires proper version in deno.json)
52
-
deno publish
53
-
```
54
-
55
-
## Architecture
56
-
57
-
### Core Flow: OAuth Authorization
58
-
59
-
1. **Handle Resolution** (`src/resolvers.ts`)
60
-
- `SlingshotResolver` (default): Uses Slingshot's `resolveMiniDoc` endpoint for fast DID+PDS lookup
61
-
- Fallback chain: Slingshot standard → Bluesky API → Direct `.well-known/atproto-did` lookup
62
-
- `DirectoryResolver`: Bluesky API only (no Slingshot)
63
-
- `CustomResolver`: User-provided resolution logic
64
-
65
-
2. **OAuth Endpoint Discovery** (`src/resolvers.ts`)
66
-
- Discover auth server from PDS: `/.well-known/oauth-protected-resource`
67
-
- Discover OAuth endpoints from auth server: `/.well-known/oauth-authorization-server`
68
-
- Fallback: Try PDS directly if auth server discovery fails
69
-
70
-
3. **Authorization Flow** (`src/client.ts`)
71
-
- Generate PKCE parameters (code_verifier, code_challenge)
72
-
- Store PKCE data in storage with 10-minute TTL (`pkce:{state}`)
73
-
- Push Authorization Request (PAR) to get request_uri
74
-
- Return authorization URL for user redirect
75
-
76
-
4. **Token Exchange** (`src/client.ts`)
77
-
- Validate state parameter and retrieve PKCE data
78
-
- Generate DPoP ES256 key pair (Web Crypto API)
79
-
- Exchange authorization code for tokens with DPoP proof
80
-
- Handle DPoP nonce challenges (retry with nonce on 400 status)
81
-
- Create and return authenticated session
82
-
83
-
5. **DPoP Authentication** (`src/dpop.ts`)
84
-
- ES256 (ECDSA P-256) key generation using Web Crypto API
85
-
- JWT creation with `jsr:@panva/jose` (NOT npm:jose)
86
-
- DPoP proof includes: jti, htm, htu, iat, exp, optional ath (access token hash), optional nonce
87
-
- Automatic nonce handling: retry on 401 with `DPoP-Nonce` header
88
-
89
-
### Key Components
90
-
91
-
**`OAuthClient` (src/client.ts)**
92
-
93
-
- Main entry point for OAuth operations
94
-
- Methods: `authorize()`, `callback()`, `store()`, `restore()`, `refresh()`, `signOut()`
95
-
- Manages PKCE flow, token exchange, and session lifecycle
96
-
97
-
**`Session` (src/session.ts)**
98
-
99
-
- Represents authenticated user session
100
-
- Properties: `did`, `handle`, `pdsUrl`, `accessToken`, `refreshToken`, `isExpired`
101
-
- `makeRequest()`: Makes DPoP-authenticated HTTP requests with automatic nonce handling
102
-
- Serializable via `toJSON()` / `fromJSON()` for storage
103
-
104
-
**Storage Implementations (src/storage.ts)**
105
-
106
-
- `MemoryStorage`: In-memory with TTL support (development/testing)
107
-
- `SQLiteStorage`: Example SQLite backend (reference implementation)
108
-
- `LocalStorage`: Browser localStorage wrapper
109
-
- All implement `OAuthStorage` interface: `get()`, `set()`, `delete()`
110
-
111
-
**Error Hierarchy (src/errors.ts)**
112
-
113
-
- Base: `OAuthError` (all OAuth errors inherit from this)
114
-
- Handle errors: `InvalidHandleError`, `HandleResolutionError`
115
-
- Discovery errors: `PDSDiscoveryError`, `AuthServerDiscoveryError`
116
-
- Flow errors: `TokenExchangeError`, `AuthorizationError`, `InvalidStateError`
117
-
- Auth errors: `DPoPError`, `SessionError`
118
-
119
-
### Critical Implementation Details
120
-
121
-
**Web Crypto API vs Node.js crypto**
122
-
123
-
- MUST use `crypto.subtle.generateKey()` with explicit `namedCurve: "P-256"`
124
-
- MUST use `jsr:@panva/jose` NOT `npm:jose` or Node.js jose packages
125
-
- DPoP key generation MUST set `extractable: true` for JWK export
126
-
- Private key import MUST clean JWK (remove conflicting `key_ops` from exportJWK)
127
-
128
-
**DPoP Nonce Handling**
129
-
130
-
- AT Protocol uses 400 status (not 401) for initial nonce challenges during token exchange
131
-
- Token refresh uses 401 status for nonce challenges
132
-
- Always check `DPoP-Nonce` header and retry with nonce if present
133
-
- Nonce included in JWT payload, not header
134
-
135
-
**Handle Resolution Strategy**
136
-
137
-
- Default: Slingshot with multi-level fallbacks
138
-
- `resolveMiniDoc` returns both DID + PDS in one request (preferred)
139
-
- Standard resolution requires two requests: handle→DID, then DID document→PDS
140
-
- PDS URL extracted from DID document's `AtprotoPersonalDataServer` service
141
-
142
-
**Session Storage Pattern**
143
-
144
-
- PKCE data stored with `pkce:{state}` prefix, 10-minute TTL
145
-
- Sessions stored with `session:{sessionId}` prefix
146
-
- Auto-refresh on restore if token expires within 5 minutes
147
-
- `isExpired` uses 5-minute buffer to prevent edge cases
148
-
149
-
## Testing Patterns
150
-
151
-
Tests use Deno's built-in test framework with the following patterns:
152
-
153
-
**Mock/Fake Pattern** (per user's global CLAUDE.md)
154
-
155
-
- Tests must NOT rely on external services
156
-
- Use injection patterns for all dependencies
157
-
- Mock storage, resolvers, and network calls in tests
158
-
- Test files: `tests/*_test.ts`
159
-
160
-
**Test File Structure**
161
-
162
-
- `errors_test.ts`: Error class behavior and messages
163
-
- `session_test.ts`: Session management, token refresh, serialization
164
-
- `storage_test.ts`: Storage implementations with TTL
165
-
- `utils_test.ts`: Utility functions (PKCE, DPoP, etc.)
166
-
167
-
## Security & OAuth Best Practices
168
-
169
-
**CRITICAL: No OAuth Workarounds** (per user's global CLAUDE.md)
170
-
171
-
- Always follow OAuth 2.0, AT Protocol, and DPoP specs exactly
172
-
- No shortcuts or "good enough" solutions for auth flows
173
-
- Properly validate state parameters (CSRF protection)
174
-
- Use secure PKCE (S256, not plain)
175
-
- DPoP proof must include all required claims
176
-
177
-
**Token Management**
178
-
179
-
- Store refresh tokens securely in storage backend
180
-
- Never log tokens or sensitive cryptographic material
181
-
- Clean up PKCE data after use (success or failure)
182
-
- Revoke tokens on sign out (best effort)
183
-
184
-
## Common Development Patterns
185
-
186
-
**Adding a new storage backend:**
187
-
188
-
1. Implement `OAuthStorage` interface from `src/types.ts`
189
-
2. Implement `get<T>()`, `set<T>()`, `delete()` with TTL support
190
-
3. Handle TTL expiration in `get()` (return null if expired)
191
-
4. Add tests following pattern in `tests/storage_test.ts`
192
-
193
-
**Adding a new resolver:**
194
-
195
-
1. Implement `HandleResolver` interface from `src/types.ts`
196
-
2. Implement `resolve(handle)` returning `{ did: string; pdsUrl: string }`
197
-
3. Throw `HandleResolutionError` on failure
198
-
4. Consider fallback mechanisms like `SlingshotResolver`
199
-
200
-
**Error handling:**
201
-
202
-
- Catch and re-throw with appropriate error class
203
-
- Preserve error cause chain for debugging
204
-
- All OAuth errors extend `OAuthError` base class
205
-
- Use specific error types for different failure modes
206
-
207
-
## Important Constraints
208
-
209
-
- **Handle-only inputs**: Client only accepts AT Protocol handles, not DIDs or URLs
210
-
- **Deno runtime**: Built for Deno, uses Web Standards APIs exclusively
211
-
- **No Node.js crypto**: Cannot use Node.js crypto modules (incompatible with Deno)
212
-
- **Slingshot dependency**: Default resolver uses third-party Slingshot service (can be configured)
213
-
- **ES256 only**: DPoP uses ECDSA P-256 (ES256), not RS256 or other algorithms