feat(db): enforce runtime RLS boundaries

This commit is contained in:
2026-03-22 22:49:47 +04:00
parent 7665af0268
commit 97b5edcc0a
24 changed files with 2054 additions and 545 deletions

View File

@@ -23,6 +23,7 @@
"0019_faithful_madame_masque.sql": "38711341799b04a7c47fcc64fd19faf5b26e6f183d6a4c01d492b9929cd63641",
"0020_natural_mauler.sql": "a80a4a0196a3b4931040850089346d1bc99b34a5afca77d6d62478ee4b8902c1",
"0020_silver_payments.sql": "9686235c75453f1eaa016f2f4ab7fce8fe964c76a4e3515987a2b9f90bd7b1ad",
"0021_sharp_payer.sql": "973596e154382984ba7769979ea58298b6d93c5139540854be01e8b283ddb4f1"
"0021_sharp_payer.sql": "973596e154382984ba7769979ea58298b6d93c5139540854be01e8b283ddb4f1",
"0022_harden_rls.sql": "d2e24b3e5b7ec7ef9da7e90c0ddf0e408764f3578af3872f76b9b3198ffbd70e"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -155,6 +155,13 @@
"when": 1774200000000,
"tag": "0021_sharp_payer",
"breakpoints": true
},
{
"idx": 22,
"version": "7",
"when": 1774204831000,
"tag": "0022_harden_rls",
"breakpoints": true
}
]
}

View File

@@ -1,9 +1,34 @@
import postgres from 'postgres'
import { drizzle } from 'drizzle-orm/postgres-js'
export interface DbSessionContext {
telegramUserId?: string
householdId?: string
memberId?: string
isAdmin?: boolean
isWorker?: boolean
}
export interface DbClientOptions {
max?: number
prepare?: boolean
sessionContext?: DbSessionContext
}
function quoteRuntimeOptionValue(value: string): string {
return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`
}
function appendRuntimeOption(
options: string[],
key: string,
value: string | boolean | undefined
): void {
if (value === undefined) {
return
}
options.push(`-c ${key}=${quoteRuntimeOptionValue(String(value))}`)
}
export function createDbClient(databaseUrl: string, options: DbClientOptions = {}) {
@@ -17,7 +42,17 @@ export function createDbClient(databaseUrl: string, options: DbClientOptions = {
url.searchParams.delete('options')
// Set search_path via options parameter (required for PgBouncer compatibility)
url.searchParams.set('options', `-c search_path=${dbSchema}`)
const runtimeOptions = [`-c search_path=${dbSchema}`]
appendRuntimeOption(
runtimeOptions,
'app.telegram_user_id',
options.sessionContext?.telegramUserId
)
appendRuntimeOption(runtimeOptions, 'app.household_id', options.sessionContext?.householdId)
appendRuntimeOption(runtimeOptions, 'app.member_id', options.sessionContext?.memberId)
appendRuntimeOption(runtimeOptions, 'app.is_admin', options.sessionContext?.isAdmin)
appendRuntimeOption(runtimeOptions, 'app.is_worker', options.sessionContext?.isWorker)
url.searchParams.set('options', runtimeOptions.join(' '))
const cleanUrl = url.toString()

View File

@@ -1,2 +1,3 @@
export { createDbClient } from './client'
export type { DbSessionContext } from './client'
export * as schema from './schema'