A CLI for publishing standard.site documents to ATProto
sequoia.pub
standard
site
lexicon
cli
publishing
1import { JoseKey } from "@atproto/jwk-jose";
2import type {
3 Key,
4 InternalStateData,
5 SessionStore,
6 StateStore,
7} from "@atproto/oauth-client";
8
9type SerializedStateData = Omit<InternalStateData, "dpopKey"> & {
10 dpopJwk: Record<string, unknown>;
11};
12
13type SerializedSession = Omit<Parameters<SessionStore["set"]>[1], "dpopKey"> & {
14 dpopJwk: Record<string, unknown>;
15};
16
17function 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
23async function deserializeKey(jwk: Record<string, unknown>): Promise<Key> {
24 return JoseKey.fromJWK(jwk);
25}
26
27export 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
51export 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}