Sifa professional network API (Fastify, AT Protocol, Jetstream) sifa.id/
at main 59 lines 2.7 kB view raw
1import { JoseKey, NodeOAuthClient } from '@atproto/oauth-client-node'; 2import type { DidCache, HandleCache } from '@atproto/oauth-client'; 3import type { Env } from '../config.js'; 4import type { Database } from '../db/index.js'; 5import { DbSessionStore } from './session-store.js'; 6import { ValkeyStateStore, type ValkeyClient } from './state-store.js'; 7import { ValkeyIdentityCache } from './identity-cache.js'; 8import { loadPrivateKey } from './keys.js'; 9 10/** 11 * Creates and configures the ATproto NodeOAuthClient with: 12 * - Database-backed session persistence (PostgreSQL via Drizzle) 13 * - Valkey-backed ephemeral state storage (10 min TTL) 14 * - Client metadata matching the /oauth/client-metadata.json endpoint 15 * 16 * The private key is loaded from disk (path derived from OAUTH_JWKS_PATH by 17 * replacing "jwks" with "private-key"). This key is used for private_key_jwt 18 * token endpoint authentication and DPoP proof signing. 19 * 20 * Async because JoseKey.fromJWK() needs to import the key material. 21 */ 22export async function createOAuthClient( 23 config: Env, 24 db: Database, 25 valkey: ValkeyClient, 26): Promise<NodeOAuthClient> { 27 const privateKeyPath = config.OAUTH_JWKS_PATH.replace('jwks', 'private-key'); 28 const privateKeyJwk = loadPrivateKey(privateKeyPath); 29 const key = await JoseKey.fromJWK(privateKeyJwk as Record<string, unknown>); 30 31 // Valkey-backed identity caches for the OAuth client. 32 // AT Protocol OAuth spec recommends <10 min cache for auth flows. 33 // Separate prefix from the shared resolver (which uses longer TTLs for non-auth paths). 34 // Fail-open: Valkey errors are treated as cache misses. 35 const didCache: DidCache = new ValkeyIdentityCache(valkey, 'oauth-did', 600); // 10 min TTL (spec) 36 const handleCache: HandleCache = new ValkeyIdentityCache(valkey, 'oauth-handle', 600); // 10 min TTL (spec) 37 38 return new NodeOAuthClient({ 39 didCache, 40 handleCache, 41 clientMetadata: { 42 client_id: `${config.PUBLIC_URL}/oauth/client-metadata.json`, 43 client_name: 'Sifa', 44 client_uri: config.PUBLIC_URL, 45 response_types: ['code'], 46 grant_types: ['authorization_code', 'refresh_token'], 47 scope: 48 'atproto repo:id.sifa.profile.self repo:id.sifa.profile.position repo:id.sifa.profile.education repo:id.sifa.profile.skill repo:id.sifa.profile.externalAccount repo:id.sifa.graph.follow', 49 redirect_uris: [`${config.PUBLIC_URL}/oauth/callback`], 50 dpop_bound_access_tokens: true, 51 token_endpoint_auth_method: 'private_key_jwt', 52 token_endpoint_auth_signing_alg: 'ES256', 53 jwks_uri: `${config.PUBLIC_URL}/oauth/jwks.json`, 54 }, 55 keyset: [key], 56 stateStore: new ValkeyStateStore(valkey), 57 sessionStore: new DbSessionStore(db), 58 }); 59}