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