docs/src/lib/kv-stores.ts 1.9 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
9
type SerializedStateData = Omit<InternalStateData, "dpopKey"> & {
10
	dpopJwk: Record<string, unknown>;
11
};
12
13
type SerializedSession = Omit<Parameters<SessionStore["set"]>[1], "dpopKey"> & {
14
	dpopJwk: Record<string, unknown>;
15
};
16
17
function serializeKey(key: Key): Record<string, unknown> {
18
	const jwk = key.privateJwk;
19
	if (!jwk) throw new Error("Private DPoP JWK is missing");
20
	return jwk as Record<string, unknown>;
21
}
22
23
async function deserializeKey(jwk: Record<string, unknown>): Promise<Key> {
24
	return JoseKey.fromJWK(jwk);
25
}
26
27
export function createStateStore(kv: KVNamespace, ttl = 600): StateStore {
28
	return {
29
		async set(key, { dpopKey, ...rest }) {
30
			const data: SerializedStateData = {
31
				...rest,
32
				dpopJwk: serializeKey(dpopKey),
33
			};
34
			await kv.put(`oauth_state:${key}`, JSON.stringify(data), {
35
				expirationTtl: ttl,
36
			});
37
		},
38
		async get(key) {
39
			const raw = await kv.get(`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
			await kv.delete(`oauth_state:${key}`);
47
		},
48
	};
49
}
50
51
export function createSessionStore(
52
	kv: KVNamespace,
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
			await kv.put(`oauth_session:${sub}`, JSON.stringify(data), {
62
				expirationTtl: ttl,
63
			});
64
		},
65
		async get(sub) {
66
			const raw = await kv.get(`oauth_session:${sub}`);
67
			if (!raw) return undefined;
68
			const { dpopJwk, ...rest }: SerializedSession = JSON.parse(raw);
69
			const dpopKey = await deserializeKey(dpopJwk);
70
			return { ...rest, dpopKey };
71
		},
72
		async del(sub) {
73
			await kv.delete(`oauth_session:${sub}`);
74
		},
75
	};
76
}