A CLI for publishing standard.site documents to ATProto
at disable-text-content 77 lines 2.1 kB view raw
1import { JoseKey } from "@atproto/jwk-jose"; 2import type { 3 Key, 4 InternalStateData, 5 SessionStore, 6 StateStore, 7} from "@atproto/oauth-client"; 8import { RedisClient } from "bun"; 9 10type SerializedStateData = Omit<InternalStateData, "dpopKey"> & { 11 dpopJwk: Record<string, unknown>; 12}; 13 14type SerializedSession = Omit<Parameters<SessionStore["set"]>[1], "dpopKey"> & { 15 dpopJwk: Record<string, unknown>; 16}; 17 18function serializeKey(key: Key): Record<string, unknown> { 19 const jwk = key.privateJwk; 20 if (!jwk) throw new Error("Private DPoP JWK is missing"); 21 return jwk as Record<string, unknown>; 22} 23 24async function deserializeKey(jwk: Record<string, unknown>): Promise<Key> { 25 return JoseKey.fromJWK(jwk) as unknown as Key; 26} 27 28export function createStateStore(redis: RedisClient, ttl = 600): StateStore { 29 return { 30 async set(key, { dpopKey, ...rest }) { 31 const data: SerializedStateData = { 32 ...rest, 33 dpopJwk: serializeKey(dpopKey), 34 }; 35 const redisKey = `oauth_state:${key}`; 36 await redis.set(redisKey, JSON.stringify(data)); 37 await redis.expire(redisKey, ttl); 38 }, 39 async get(key) { 40 const raw = await redis.get(`oauth_state:${key}`); 41 if (!raw) return undefined; 42 const { dpopJwk, ...rest }: SerializedStateData = JSON.parse(raw); 43 const dpopKey = await deserializeKey(dpopJwk); 44 return { ...rest, dpopKey }; 45 }, 46 async del(key) { 47 await redis.del(`oauth_state:${key}`); 48 }, 49 }; 50} 51 52export function createSessionStore( 53 redis: RedisClient, 54 ttl = 60 * 60 * 24 * 14, 55): SessionStore { 56 return { 57 async set(sub, { dpopKey, ...rest }) { 58 const data: SerializedSession = { 59 ...rest, 60 dpopJwk: serializeKey(dpopKey), 61 }; 62 const redisKey = `oauth_session:${sub}`; 63 await redis.set(redisKey, JSON.stringify(data)); 64 await redis.expire(redisKey, ttl); 65 }, 66 async get(sub) { 67 const raw = await redis.get(`oauth_session:${sub}`); 68 if (!raw) return undefined; 69 const { dpopJwk, ...rest }: SerializedSession = JSON.parse(raw); 70 const dpopKey = await deserializeKey(dpopJwk); 71 return { ...rest, dpopKey }; 72 }, 73 async del(sub) { 74 await redis.del(`oauth_session:${sub}`); 75 }, 76 }; 77}