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