Graphical PDS migrator for AT Protocol
1import { SessionOptions as BaseSessionOptions } from "npm:iron-session";
2
3/**
4 * The session options.
5 * @type {SessionOptions}
6 * @implements {BaseSessionOptions}
7 */
8interface SessionOptions extends BaseSessionOptions {
9 lockFn?: (key: string) => Promise<() => Promise<void>>;
10}
11
12/**
13 * Create a lock using Deno KV.
14 * @param key - The key to lock
15 * @param db - The Deno KV instance for the database
16 * @returns The unlock function
17 */
18async function createLock(
19 key: string,
20 db: Deno.Kv,
21): Promise<() => Promise<void>> {
22 const lockKey = ["session_lock", key];
23 const lockValue = Date.now();
24
25 // Try to acquire lock
26 const result = await db.atomic()
27 .check({ key: lockKey, versionstamp: null }) // Only if key doesn't exist
28 .set(lockKey, lockValue, { expireIn: 5000 }) // 5 second TTL
29 .commit();
30
31 if (!result.ok) {
32 throw new Error("Failed to acquire lock");
33 }
34
35 // Return unlock function
36 return async () => {
37 await db.delete(lockKey);
38 };
39}
40
41/**
42 * The OAuth session.
43 * @type {OauthSession}
44 */
45export interface OauthSession {
46 did: string;
47}
48
49/**
50 * The credential session.
51 * @type {CredentialSession}
52 */
53export interface CredentialSession {
54 did: string;
55 handle: string;
56 service: string;
57 password: string;
58 accessJwt?: string;
59 recoveryKey?: string;
60 recoveryKeyDid?: string;
61 credentials?: {
62 rotationKeys: string[];
63 [key: string]: unknown;
64 };
65}
66
67let db: Deno.Kv;
68
69/**
70 * Create the session options.
71 * @param cookieName - The name of the iron session cookie
72 * @returns The session options for iron session
73 */
74export const createSessionOptions = async (
75 cookieName: string,
76): Promise<SessionOptions> => {
77 const cookieSecret = Deno.env.get("COOKIE_SECRET");
78 if (!cookieSecret) {
79 throw new Error("COOKIE_SECRET is not set");
80 }
81
82 if (!db) {
83 db = await Deno.openKv();
84 }
85
86 return {
87 cookieName: cookieName,
88 password: cookieSecret,
89 cookieOptions: {
90 secure: Deno.env.get("NODE_ENV") === "production" ||
91 Deno.env.get("NODE_ENV") === "staging",
92 httpOnly: true,
93 sameSite: "lax",
94 path: "/",
95 domain: undefined,
96 },
97 lockFn: (key: string) => createLock(key, db),
98 };
99};