packages/server/src/lib/stores.ts 2.0 K raw
1
import { JoseKey } from "@atproto/jwk-jose";
2
import type {
3
	Key,
4
	InternalStateData,
5
	SessionStore,
6
	StateStore,
7
} from "@atproto/oauth-client";
8
import type { Database } from "bun:sqlite";
9
import { kvGet, kvSet, kvDel } from "./db";
10
11
type SerializedStateData = Omit<InternalStateData, "dpopKey"> & {
12
	dpopJwk: Record<string, unknown>;
13
};
14
15
type SerializedSession = Omit<Parameters<SessionStore["set"]>[1], "dpopKey"> & {
16
	dpopJwk: Record<string, unknown>;
17
};
18
19
function 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
25
async function deserializeKey(jwk: Record<string, unknown>): Promise<Key> {
26
	return JoseKey.fromJWK(jwk) as unknown as Key;
27
}
28
29
export 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
51
export 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
}