ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
16
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: migrate to kysely schema

byarielm.fyi e0e224d1 5fdc39be

verified
+227
+102
packages/api/src/infrastructure/oauth/stores/SessionStore.ts
··· 1 + import { db } from "../../../db/client"; 2 + import { SessionData } from "../types"; 3 + import { 4 + encryptToken, 5 + decryptToken, 6 + isEncryptionConfigured, 7 + } from "../../../utils/encryption.utils"; 8 + 9 + interface EncryptedSessionData { 10 + encrypted: true; 11 + dpopJwk: unknown; 12 + authMethod: string; 13 + tokenSet: string; // Encrypted tokenSet 14 + } 15 + 16 + /** 17 + * PostgreSQL-backed session store for OAuth sessions 18 + * Encrypts token sets at rest for security 19 + */ 20 + export class PostgresSessionStore { 21 + private encryptionEnabled = isEncryptionConfigured(); 22 + 23 + async get(key: string): Promise<SessionData | undefined> { 24 + const result = await db 25 + .selectFrom("oauth_sessions") 26 + .select("session_data") 27 + .where("did", "=", key) 28 + .executeTakeFirst(); 29 + 30 + if (!result) return undefined; 31 + 32 + const stored = result.session_data as unknown; 33 + 34 + // Handle encrypted format 35 + if ( 36 + this.encryptionEnabled && 37 + typeof stored === "object" && 38 + stored !== null && 39 + "encrypted" in stored && 40 + stored.encrypted === true 41 + ) { 42 + try { 43 + const encryptedData = stored as EncryptedSessionData; 44 + // Decrypt tokenSet and reconstruct with dpopJwk 45 + const decryptedTokenSet = decryptToken<SessionData["tokenSet"]>( 46 + encryptedData.tokenSet, 47 + ); 48 + 49 + return { 50 + dpopJwk: encryptedData.dpopJwk, 51 + tokenSet: decryptedTokenSet, 52 + authMethod: encryptedData.authMethod, 53 + }; 54 + } catch (error) { 55 + console.error( 56 + "[SessionStore] Failed to decrypt session token set:", 57 + error, 58 + ); 59 + return undefined; 60 + } 61 + } 62 + 63 + // Fallback for unencrypted format 64 + return stored as SessionData; 65 + } 66 + 67 + async set(key: string, value: SessionData): Promise<void> { 68 + let dataToStore: Record<string, unknown>; 69 + 70 + if (this.encryptionEnabled) { 71 + // Encrypt only tokenSet, keep dpopJwk and authMethod as-is 72 + dataToStore = { 73 + encrypted: true, 74 + dpopJwk: value.dpopJwk, 75 + authMethod: value.authMethod, 76 + tokenSet: encryptToken(value.tokenSet), 77 + }; 78 + } else { 79 + // Store as-is if encryption disabled 80 + dataToStore = value as unknown as Record<string, unknown>; 81 + } 82 + 83 + await db 84 + .insertInto("oauth_sessions") 85 + .values({ 86 + did: key, 87 + session_data: dataToStore, 88 + }) 89 + .onConflict((oc) => 90 + oc.column("did").doUpdateSet({ 91 + session_data: dataToStore, 92 + }), 93 + ) 94 + .execute(); 95 + } 96 + 97 + async del(key: string): Promise<void> { 98 + await db.deleteFrom("oauth_sessions").where("did", "=", key).execute(); 99 + } 100 + } 101 + 102 + export const sessionStore = new PostgresSessionStore();
+45
packages/api/src/infrastructure/oauth/stores/StateStore.ts
··· 1 + import { db } from "../../../db/client"; 2 + import { StateData } from "../types"; 3 + 4 + /** 5 + * PostgreSQL-backed state store for OAuth flow 6 + * Stores ephemeral state data with automatic expiry (1 hour via cleanup job) 7 + */ 8 + export class PostgresStateStore { 9 + async get(key: string): Promise<StateData | undefined> { 10 + const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); 11 + 12 + const result = await db 13 + .selectFrom("oauth_states") 14 + .select("data") 15 + .where("state", "=", key) 16 + .where("created_at", ">", oneHourAgo) 17 + .executeTakeFirst(); 18 + 19 + if (!result) return undefined; 20 + 21 + // State data contains dpopKey which must remain as JWK object 22 + return result.data as unknown as StateData; 23 + } 24 + 25 + async set(key: string, value: StateData): Promise<void> { 26 + await db 27 + .insertInto("oauth_states") 28 + .values({ 29 + state: key, 30 + data: value as unknown as Record<string, unknown>, 31 + }) 32 + .onConflict((oc) => 33 + oc.column("state").doUpdateSet({ 34 + data: value as unknown as Record<string, unknown>, 35 + }), 36 + ) 37 + .execute(); 38 + } 39 + 40 + async del(key: string): Promise<void> { 41 + await db.deleteFrom("oauth_states").where("state", "=", key).execute(); 42 + } 43 + } 44 + 45 + export const stateStore = new PostgresStateStore();
+77
packages/api/src/infrastructure/oauth/stores/UserSessionStore.ts
··· 1 + import { db } from "../../../db/client"; 2 + import { UserSessionData } from "../types"; 3 + 4 + const SESSION_EXPIRY = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds 5 + 6 + /** 7 + * PostgreSQL-backed user session store 8 + * Maps session IDs to user DIDs for authenticated requests 9 + */ 10 + export class PostgresUserSessionStore { 11 + async get(sessionId: string): Promise<UserSessionData | undefined> { 12 + const fetchSession = async () => { 13 + const now = new Date(); 14 + 15 + const result = await db 16 + .selectFrom("user_sessions") 17 + .select("did") 18 + .where("session_id", "=", sessionId) 19 + .where("expires_at", ">", now) 20 + .executeTakeFirst(); 21 + 22 + return result ? { did: result.did } : undefined; 23 + }; 24 + 25 + // Try once, if not found retry twice with small delays 26 + // This handles eventual consistency in database replication 27 + let session = await fetchSession(); 28 + 29 + if (!session) { 30 + console.log( 31 + `[UserSessionStore] Session ${sessionId} not found, retrying...`, 32 + ); 33 + await new Promise((resolve) => setTimeout(resolve, 100)); 34 + session = await fetchSession(); 35 + } 36 + 37 + if (!session) { 38 + console.log( 39 + `[UserSessionStore] Session ${sessionId} still not found, final retry...`, 40 + ); 41 + await new Promise((resolve) => setTimeout(resolve, 300)); 42 + session = await fetchSession(); 43 + } 44 + 45 + return session; 46 + } 47 + 48 + async set(sessionId: string, data: UserSessionData): Promise<void> { 49 + const expiresAt = new Date(Date.now() + SESSION_EXPIRY); 50 + 51 + await db 52 + .insertInto("user_sessions") 53 + .values({ 54 + session_id: sessionId, 55 + did: data.did, 56 + fingerprint: JSON.stringify(data.fingerprint || {}), 57 + expires_at: expiresAt, 58 + }) 59 + .onConflict((oc) => 60 + oc.column("session_id").doUpdateSet({ 61 + did: data.did, 62 + fingerprint: JSON.stringify(data.fingerprint || {}), 63 + expires_at: expiresAt, 64 + }), 65 + ) 66 + .execute(); 67 + } 68 + 69 + async del(sessionId: string): Promise<void> { 70 + await db 71 + .deleteFrom("user_sessions") 72 + .where("session_id", "=", sessionId) 73 + .execute(); 74 + } 75 + } 76 + 77 + export const userSessionStore = new PostgresUserSessionStore();
+3
packages/api/src/infrastructure/oauth/stores/index.ts
··· 1 + export * from "./StateStore"; 2 + export * from "./SessionStore"; 3 + export * from "./UserSessionStore";