@@ -8,7 +8,7 @@ import { getPgPool } from 'pg-cache';
88
99import errorPage50x from '../errors/50x' ;
1010import errorPage404Message from '../errors/404-message' ;
11- import { ApiConfigResult , ApiError , ApiOptions , ApiStructure , RlsModule } from '../types' ;
11+ import { ApiConfigResult , ApiError , ApiOptions , ApiStructure , AuthSettings , RlsModule } from '../types' ;
1212import './types' ;
1313
1414const log = new Logger ( 'api' ) ;
@@ -86,6 +86,26 @@ const RLS_MODULE_SQL = `
8686 LIMIT 1
8787` ;
8888
89+ /**
90+ * Query auth settings from the tenant DB private schema.
91+ * The table name follows the pattern: {privateSchema}.app_settings_auth
92+ * We select only the server-relevant columns.
93+ */
94+ const AUTH_SETTINGS_SQL = ( privateSchema : string ) => `
95+ SELECT
96+ allowed_origins,
97+ cookie_secure,
98+ cookie_samesite,
99+ cookie_domain,
100+ cookie_httponly,
101+ cookie_max_age,
102+ cookie_path,
103+ enable_captcha,
104+ captcha_site_key
105+ FROM "${ privateSchema } ".app_settings_auth
106+ LIMIT 1
107+ ` ;
108+
89109// =============================================================================
90110// Types
91111// =============================================================================
@@ -111,6 +131,18 @@ interface RlsModuleData {
111131 current_user_agent : string ;
112132}
113133
134+ interface AuthSettingsRow {
135+ allowed_origins : string [ ] | null ;
136+ cookie_secure : boolean ;
137+ cookie_samesite : string ;
138+ cookie_domain : string | null ;
139+ cookie_httponly : boolean ;
140+ cookie_max_age : string | null ;
141+ cookie_path : string ;
142+ enable_captcha : boolean ;
143+ captcha_site_key : string | null ;
144+ }
145+
114146interface RlsModuleRow {
115147 data : RlsModuleData | null ;
116148}
@@ -208,7 +240,22 @@ const toRlsModule = (row: RlsModuleRow | null): RlsModule | undefined => {
208240 } ;
209241} ;
210242
211- const toApiStructure = ( row : ApiRow , opts : ApiOptions , rlsModuleRow ?: RlsModuleRow | null ) : ApiStructure => ( {
243+ const toAuthSettings = ( row : AuthSettingsRow | null ) : AuthSettings | undefined => {
244+ if ( ! row ) return undefined ;
245+ return {
246+ allowedOrigins : row . allowed_origins ,
247+ cookieSecure : row . cookie_secure ,
248+ cookieSamesite : row . cookie_samesite ,
249+ cookieDomain : row . cookie_domain ,
250+ cookieHttponly : row . cookie_httponly ,
251+ cookieMaxAge : row . cookie_max_age ,
252+ cookiePath : row . cookie_path ,
253+ enableCaptcha : row . enable_captcha ,
254+ captchaSiteKey : row . captcha_site_key ,
255+ } ;
256+ } ;
257+
258+ const toApiStructure = ( row : ApiRow , opts : ApiOptions , rlsModuleRow ?: RlsModuleRow | null , authSettingsRow ?: AuthSettingsRow | null ) : ApiStructure => ( {
212259 apiId : row . api_id ,
213260 dbname : row . dbname || opts . pg ?. database || '' ,
214261 anonRole : row . anon_role || 'anon' ,
@@ -219,6 +266,7 @@ const toApiStructure = (row: ApiRow, opts: ApiOptions, rlsModuleRow?: RlsModuleR
219266 domains : [ ] ,
220267 databaseId : row . database_id ,
221268 isPublic : row . is_public ,
269+ authSettings : toAuthSettings ( authSettingsRow ?? null ) ,
222270} ) ;
223271
224272const createAdminStructure = (
@@ -278,6 +326,28 @@ const queryRlsModule = async (pool: Pool, apiId: string): Promise<RlsModuleRow |
278326 return result . rows [ 0 ] ?? null ;
279327} ;
280328
329+ /**
330+ * Load server-relevant auth settings from the tenant DB private schema.
331+ * Fails gracefully if the table doesn't exist yet (pre-migration).
332+ */
333+ const queryAuthSettings = async (
334+ opts : ApiOptions ,
335+ dbname : string ,
336+ rlsModuleRow : RlsModuleRow | null
337+ ) : Promise < AuthSettingsRow | null > => {
338+ if ( ! rlsModuleRow ?. data ?. authenticate_schema ) return null ;
339+ const privateSchema = rlsModuleRow . data . authenticate_schema ;
340+ try {
341+ const tenantPool = getPgPool ( { ...opts . pg , database : dbname } ) ;
342+ const result = await tenantPool . query < AuthSettingsRow > ( AUTH_SETTINGS_SQL ( privateSchema ) ) ;
343+ return result . rows [ 0 ] ?? null ;
344+ } catch ( e : any ) {
345+ // Table may not exist yet if the 2FA migration hasn't been applied
346+ log . debug ( `[auth-settings] Failed to load auth settings from ${ privateSchema } .app_settings_auth: ${ e . message } ` ) ;
347+ return null ;
348+ }
349+ } ;
350+
281351// =============================================================================
282352// Resolution Logic
283353// =============================================================================
@@ -337,8 +407,9 @@ const resolveApiNameHeader = async (ctx: ResolveContext): Promise<ApiStructure |
337407 }
338408
339409 const rlsModule = await queryRlsModule ( pool , row . api_id ) ;
340- log . debug ( `[api-name-lookup] resolved schemas: [${ row . schemas ?. join ( ', ' ) } ], rlsModule: ${ rlsModule ? 'found' : 'none' } ` ) ;
341- return toApiStructure ( row , opts , rlsModule ) ;
410+ const authSettings = await queryAuthSettings ( opts , row . dbname , rlsModule ) ;
411+ log . debug ( `[api-name-lookup] resolved schemas: [${ row . schemas ?. join ( ', ' ) } ], rlsModule: ${ rlsModule ? 'found' : 'none' } , authSettings: ${ authSettings ? 'found' : 'none' } ` ) ;
412+ return toApiStructure ( row , opts , rlsModule , authSettings ) ;
342413} ;
343414
344415const resolveMetaSchemaHeader = (
@@ -362,8 +433,9 @@ const resolveDomainLookup = async (ctx: ResolveContext): Promise<ApiStructure |
362433 }
363434
364435 const rlsModule = await queryRlsModule ( pool , row . api_id ) ;
365- log . debug ( `[domain-lookup] resolved schemas: [${ row . schemas ?. join ( ', ' ) } ], rlsModule: ${ rlsModule ? 'found' : 'none' } ` ) ;
366- return toApiStructure ( row , opts , rlsModule ) ;
436+ const authSettings = await queryAuthSettings ( opts , row . dbname , rlsModule ) ;
437+ log . debug ( `[domain-lookup] resolved schemas: [${ row . schemas ?. join ( ', ' ) } ], rlsModule: ${ rlsModule ? 'found' : 'none' } , authSettings: ${ authSettings ? 'found' : 'none' } ` ) ;
438+ return toApiStructure ( row , opts , rlsModule , authSettings ) ;
367439} ;
368440
369441const buildDevFallbackError = async (
0 commit comments