A Deno-compatible AT Protocol OAuth client that serves as a drop-in replacement for @atproto/oauth-client-node

docs: add Ko-fi support badge and project documentation

- Add Ko-fi badge at top of README
- Add Support Development section with Ko-fi link
- Create .claude/CLAUDE.md with architecture and development guidance

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