session.ts93 lines · main
| 1 | /** |
| 2 | * briven-engine session helpers (Doltgres-native). |
| 3 | */ |
| 4 | |
| 5 | import { |
| 6 | getSessionByHandle, |
| 7 | listSessionHandles, |
| 8 | revokeAllForUser, |
| 9 | revokeEngineSession, |
| 10 | } from './native-session.js'; |
| 11 | import { isAuthCoreInitialized } from './engine.js'; |
| 12 | |
| 13 | export type VerifySessionResult = |
| 14 | | { |
| 15 | ok: true; |
| 16 | session: { |
| 17 | getUserId: () => string; |
| 18 | getHandle: () => string; |
| 19 | getAccessTokenPayload: () => Record<string, unknown>; |
| 20 | }; |
| 21 | } |
| 22 | | { ok: false; reason: string; status?: number }; |
| 23 | |
| 24 | /** |
| 25 | * Resolve session handle from headers/cookies (pure — unit-tested). |
| 26 | * Phase 2: sAccessToken cookie value = session handle. |
| 27 | */ |
| 28 | export function extractSessionHandle(opts: { |
| 29 | headers?: Headers | { get: (n: string) => string | null }; |
| 30 | cookieHeader?: string | null; |
| 31 | }): string | null { |
| 32 | const get = (n: string) => opts.headers?.get(n) ?? null; |
| 33 | let handle = |
| 34 | get('x-briven-session-handle') ?? get('x-session-handle') ?? null; |
| 35 | if (handle === 'cookie') handle = null; |
| 36 | if (handle) return handle; |
| 37 | const cookie = opts.cookieHeader ?? get('cookie') ?? ''; |
| 38 | const m = |
| 39 | /(?:^|;\s*)sAccessToken=([^;]+)/.exec(cookie) ?? |
| 40 | /(?:^|;\s*)briven_session=([^;]+)/.exec(cookie); |
| 41 | if (!m?.[1]) return null; |
| 42 | try { |
| 43 | return decodeURIComponent(m[1]); |
| 44 | } catch { |
| 45 | return m[1]; |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | export async function verifyAuthCoreSession(opts: { |
| 50 | url: string; |
| 51 | method: string; |
| 52 | headers: Headers; |
| 53 | cookieHeader?: string; |
| 54 | }): Promise<VerifySessionResult> { |
| 55 | if (!isAuthCoreInitialized()) { |
| 56 | return { ok: false, reason: 'auth_core_sdk_not_ready', status: 503 }; |
| 57 | } |
| 58 | const handle = extractSessionHandle({ |
| 59 | headers: opts.headers, |
| 60 | cookieHeader: opts.cookieHeader, |
| 61 | }); |
| 62 | if (!handle) { |
| 63 | return { ok: false, reason: 'no_session', status: 401 }; |
| 64 | } |
| 65 | const row = await getSessionByHandle(handle); |
| 66 | if (!row) return { ok: false, reason: 'invalid_session', status: 401 }; |
| 67 | return { |
| 68 | ok: true, |
| 69 | session: { |
| 70 | getUserId: () => row.userId, |
| 71 | getHandle: () => handle, |
| 72 | getAccessTokenPayload: () => ({ |
| 73 | sub: row.userId, |
| 74 | tenantId: row.tenantId, |
| 75 | }), |
| 76 | }, |
| 77 | }; |
| 78 | } |
| 79 | |
| 80 | export async function listSessionsForUser(userId: string): Promise<string[]> { |
| 81 | if (!isAuthCoreInitialized()) return []; |
| 82 | return listSessionHandles(userId); |
| 83 | } |
| 84 | |
| 85 | export async function revokeSession(sessionHandle: string): Promise<boolean> { |
| 86 | if (!isAuthCoreInitialized()) return false; |
| 87 | return revokeEngineSession(sessionHandle); |
| 88 | } |
| 89 | |
| 90 | export async function revokeAllSessionsForUser(userId: string): Promise<number> { |
| 91 | if (!isAuthCoreInitialized()) return 0; |
| 92 | return revokeAllForUser(userId); |
| 93 | } |