Graphical PDS migrator for AT Protocol
1import { SessionOptions as BaseSessionOptions } from "npm:iron-session";
2
3interface SessionOptions extends BaseSessionOptions {
4 lockFn?: (key: string) => Promise<() => Promise<void>>;
5}
6
7// Helper function to create a lock using Deno KV
8async function createLock(key: string, db: Deno.Kv): Promise<() => Promise<void>> {
9 const lockKey = ["session_lock", key];
10 const lockValue = Date.now();
11
12 // Try to acquire lock
13 const result = await db.atomic()
14 .check({ key: lockKey, versionstamp: null }) // Only if key doesn't exist
15 .set(lockKey, lockValue, { expireIn: 5000 }) // 5 second TTL
16 .commit();
17
18 if (!result.ok) {
19 throw new Error("Failed to acquire lock");
20 }
21
22 // Return unlock function
23 return async () => {
24 await db.delete(lockKey);
25 };
26}
27
28export interface OauthSession {
29 did: string
30}
31
32export interface CredentialSession {
33 did: string;
34 handle: string;
35 service: string;
36 password: string;
37 accessJwt?: string;
38 recoveryKey?: string;
39 recoveryKeyDid?: string;
40 credentials?: {
41 rotationKeys: string[];
42 [key: string]: unknown;
43 };
44}
45
46let db: Deno.Kv;
47
48export const createSessionOptions = async (cookieName: string): Promise<SessionOptions> => {
49 const cookieSecret = Deno.env.get("COOKIE_SECRET");
50 if (!cookieSecret) {
51 throw new Error("COOKIE_SECRET is not set");
52 }
53
54 if (!db) {
55 db = await Deno.openKv();
56 }
57
58 return {
59 cookieName: cookieName,
60 password: cookieSecret,
61 cookieOptions: {
62 secure: Deno.env.get("NODE_ENV") === "production" || Deno.env.get("NODE_ENV") === "staging",
63 httpOnly: true,
64 sameSite: "lax",
65 path: "/",
66 domain: undefined,
67 },
68 lockFn: (key: string) => createLock(key, db)
69 }
70};