@constructive-io/express-context 0.12.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/context.js +2 -2
- package/esm/context.js +2 -2
- package/esm/loaders/auth-settings.js +3 -3
- package/esm/loaders/cors.js +12 -12
- package/esm/loaders/database-settings.js +6 -6
- package/esm/loaders/index.js +20 -20
- package/esm/loaders/pubkey.js +10 -10
- package/esm/loaders/rls.js +11 -11
- package/esm/loaders/types.js +1 -1
- package/esm/loaders/webauthn.js +5 -5
- package/esm/pg-settings.js +3 -2
- package/loaders/auth-settings.d.ts +1 -1
- package/loaders/auth-settings.js +3 -3
- package/loaders/cors.d.ts +3 -3
- package/loaders/cors.js +12 -12
- package/loaders/database-settings.js +6 -6
- package/loaders/index.d.ts +12 -12
- package/loaders/index.js +28 -28
- package/loaders/pubkey.d.ts +2 -2
- package/loaders/pubkey.js +10 -10
- package/loaders/rls.d.ts +2 -2
- package/loaders/rls.js +11 -11
- package/loaders/types.d.ts +3 -3
- package/loaders/types.js +1 -1
- package/loaders/webauthn.js +5 -5
- package/package.json +6 -6
- package/pg-settings.js +3 -2
package/context.js
CHANGED
|
@@ -64,9 +64,9 @@ function buildContext(req, opts = {}) {
|
|
|
64
64
|
// Build loader context (if registry provided and databaseId known)
|
|
65
65
|
let loaderCtx = null;
|
|
66
66
|
if (opts.loaders && api.databaseId) {
|
|
67
|
-
const
|
|
67
|
+
const routingPool = (0, pg_cache_1.getPgPool)(opts.pg);
|
|
68
68
|
loaderCtx = {
|
|
69
|
-
|
|
69
|
+
routingPool,
|
|
70
70
|
tenantPool,
|
|
71
71
|
databaseId: api.databaseId,
|
|
72
72
|
apiId: api.apiId,
|
package/esm/context.js
CHANGED
|
@@ -60,9 +60,9 @@ export function buildContext(req, opts = {}) {
|
|
|
60
60
|
// Build loader context (if registry provided and databaseId known)
|
|
61
61
|
let loaderCtx = null;
|
|
62
62
|
if (opts.loaders && api.databaseId) {
|
|
63
|
-
const
|
|
63
|
+
const routingPool = getPgPool(opts.pg);
|
|
64
64
|
loaderCtx = {
|
|
65
|
-
|
|
65
|
+
routingPool,
|
|
66
66
|
tenantPool,
|
|
67
67
|
databaseId: api.databaseId,
|
|
68
68
|
apiId: api.apiId,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 2. Query the discovered <schema>.<table> in the tenant DB
|
|
8
8
|
*
|
|
9
9
|
* This is the pattern for any module whose config lives in the tenant
|
|
10
|
-
* database rather than the
|
|
10
|
+
* database rather than the routing database.
|
|
11
11
|
*/
|
|
12
12
|
import { createModuleLoader } from './create-loader';
|
|
13
13
|
// ─── SQL ────────────────────────────────────────────────────────────────────
|
|
@@ -56,7 +56,7 @@ export const authSettingsLoader = createModuleLoader({
|
|
|
56
56
|
cookiePath: row.cookie_path,
|
|
57
57
|
rememberMeDuration: row.remember_me_duration,
|
|
58
58
|
enableCaptcha: row.enable_captcha,
|
|
59
|
-
captchaSiteKey: row.captcha_site_key
|
|
59
|
+
captchaSiteKey: row.captcha_site_key
|
|
60
60
|
};
|
|
61
|
-
}
|
|
61
|
+
}
|
|
62
62
|
});
|
package/esm/loaders/cors.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CORS Origins Loader
|
|
3
3
|
*
|
|
4
|
-
* Resolves allowed CORS origins for a database+API combination
|
|
5
|
-
* Checks per-API settings first, falls back to
|
|
6
|
-
* then to the
|
|
4
|
+
* Resolves allowed CORS origins for a database+API combination from the
|
|
5
|
+
* scoped routing plane. Checks per-API settings first, falls back to
|
|
6
|
+
* database-level default, then to the api_modules approach.
|
|
7
7
|
*/
|
|
8
8
|
import { createModuleLoader } from './create-loader';
|
|
9
9
|
// ─── SQL ────────────────────────────────────────────────────────────────────
|
|
10
10
|
const CORS_SETTINGS_SQL = `
|
|
11
11
|
SELECT allowed_origins
|
|
12
|
-
FROM
|
|
12
|
+
FROM constructive_routing_public.cors_settings
|
|
13
13
|
WHERE database_id = $1 AND api_id = $2
|
|
14
14
|
LIMIT 1
|
|
15
15
|
`;
|
|
16
16
|
const CORS_SETTINGS_DB_DEFAULT_SQL = `
|
|
17
17
|
SELECT allowed_origins
|
|
18
|
-
FROM
|
|
18
|
+
FROM constructive_routing_public.cors_settings
|
|
19
19
|
WHERE database_id = $1 AND api_id IS NULL
|
|
20
20
|
LIMIT 1
|
|
21
21
|
`;
|
|
22
22
|
const CORS_MODULE_SQL = `
|
|
23
23
|
SELECT data
|
|
24
|
-
FROM
|
|
24
|
+
FROM constructive_routing_public.api_modules
|
|
25
25
|
WHERE api_id = $1 AND name = 'cors'
|
|
26
26
|
LIMIT 1
|
|
27
27
|
`;
|
|
@@ -30,26 +30,26 @@ export const corsLoader = createModuleLoader({
|
|
|
30
30
|
name: 'corsOrigins',
|
|
31
31
|
ttlMs: 5 * 60_000,
|
|
32
32
|
async resolve(ctx) {
|
|
33
|
-
const {
|
|
33
|
+
const { routingPool, databaseId, apiId } = ctx;
|
|
34
34
|
// Try per-API cors_settings first
|
|
35
35
|
try {
|
|
36
36
|
if (apiId) {
|
|
37
|
-
const perApi = await
|
|
37
|
+
const perApi = await routingPool.query(CORS_SETTINGS_SQL, [databaseId, apiId]);
|
|
38
38
|
if (perApi.rows[0])
|
|
39
39
|
return perApi.rows[0].allowed_origins;
|
|
40
40
|
}
|
|
41
|
-
const dbDefault = await
|
|
41
|
+
const dbDefault = await routingPool.query(CORS_SETTINGS_DB_DEFAULT_SQL, [databaseId]);
|
|
42
42
|
if (dbDefault.rows[0])
|
|
43
43
|
return dbDefault.rows[0].allowed_origins;
|
|
44
44
|
}
|
|
45
45
|
catch {
|
|
46
46
|
// Table may not exist yet
|
|
47
47
|
}
|
|
48
|
-
// Fall back to
|
|
48
|
+
// Fall back to api_modules
|
|
49
49
|
if (apiId) {
|
|
50
|
-
const result = await
|
|
50
|
+
const result = await routingPool.query(CORS_MODULE_SQL, [apiId]);
|
|
51
51
|
return result.rows[0]?.data?.urls;
|
|
52
52
|
}
|
|
53
53
|
return undefined;
|
|
54
|
-
}
|
|
54
|
+
}
|
|
55
55
|
});
|
|
@@ -21,8 +21,8 @@ const DATABASE_SETTINGS_SQL = `
|
|
|
21
21
|
COALESCE(aps.enable_realtime, ds.enable_realtime) AS resolved_enable_realtime,
|
|
22
22
|
COALESCE(aps.enable_bulk, ds.enable_bulk) AS resolved_enable_bulk,
|
|
23
23
|
COALESCE(aps.enable_i18n, ds.enable_i18n) AS resolved_enable_i18n
|
|
24
|
-
FROM
|
|
25
|
-
LEFT JOIN
|
|
24
|
+
FROM constructive_routing_public.database_settings ds
|
|
25
|
+
LEFT JOIN constructive_routing_public.api_settings aps ON ds.database_id = aps.database_id AND aps.api_id = $2
|
|
26
26
|
WHERE ds.database_id = $1
|
|
27
27
|
LIMIT 1
|
|
28
28
|
`;
|
|
@@ -31,8 +31,8 @@ export const databaseSettingsLoader = createModuleLoader({
|
|
|
31
31
|
name: 'databaseSettings',
|
|
32
32
|
ttlMs: 5 * 60_000,
|
|
33
33
|
async resolve(ctx) {
|
|
34
|
-
const {
|
|
35
|
-
const result = await
|
|
34
|
+
const { routingPool, databaseId, apiId } = ctx;
|
|
35
|
+
const result = await routingPool.query(DATABASE_SETTINGS_SQL, [databaseId, apiId ?? null]);
|
|
36
36
|
const row = result.rows[0];
|
|
37
37
|
if (!row)
|
|
38
38
|
return undefined;
|
|
@@ -48,7 +48,7 @@ export const databaseSettingsLoader = createModuleLoader({
|
|
|
48
48
|
enableLlm: row.resolved_enable_llm,
|
|
49
49
|
enableRealtime: row.resolved_enable_realtime,
|
|
50
50
|
enableBulk: row.resolved_enable_bulk,
|
|
51
|
-
enableI18n: row.resolved_enable_i18n
|
|
51
|
+
enableI18n: row.resolved_enable_i18n
|
|
52
52
|
};
|
|
53
|
-
}
|
|
53
|
+
}
|
|
54
54
|
});
|
package/esm/loaders/index.js
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* a LoaderRegistry and pass it to createContextMiddleware().
|
|
7
7
|
*
|
|
8
8
|
* Built-in loaders cover the standard Constructive modules:
|
|
9
|
-
* - rlsModule (
|
|
10
|
-
* - corsOrigins (
|
|
11
|
-
* - databaseSettings(
|
|
12
|
-
* - pubkeyChallengeSettings (
|
|
13
|
-
* - webauthnSettings(
|
|
9
|
+
* - rlsModule (constructive_routing_public.rls_settings)
|
|
10
|
+
* - corsOrigins (constructive_routing_public.cors_settings)
|
|
11
|
+
* - databaseSettings(constructive_routing_public.database_settings)
|
|
12
|
+
* - pubkeyChallengeSettings (constructive_routing_public.pubkey_settings)
|
|
13
|
+
* - webauthnSettings(constructive_routing_public.webauthn_settings)
|
|
14
14
|
* - authSettings (metaschema_modules_public.sessions_module → tenant DB)
|
|
15
15
|
*
|
|
16
16
|
* To add a new per-db lookup, implement a ModuleLoader and register it:
|
|
@@ -28,32 +28,32 @@
|
|
|
28
28
|
export { createModuleLoader } from './create-loader';
|
|
29
29
|
export { createLoaderRegistry } from './registry';
|
|
30
30
|
// Built-in loaders
|
|
31
|
-
export {
|
|
32
|
-
export { corsLoader } from './cors';
|
|
33
|
-
export { databaseSettingsLoader } from './database-settings';
|
|
34
|
-
export { pubkeyLoader } from './pubkey';
|
|
35
|
-
export { webauthnLoader } from './webauthn';
|
|
31
|
+
export { agentChatLoader } from './agent-chat';
|
|
36
32
|
export { authSettingsLoader } from './auth-settings';
|
|
37
33
|
export { billingLoader } from './billing';
|
|
34
|
+
export { computeLoader } from './compute';
|
|
35
|
+
export { corsLoader } from './cors';
|
|
36
|
+
export { databaseSettingsLoader } from './database-settings';
|
|
38
37
|
export { inferenceLogLoader } from './inference-log';
|
|
39
|
-
export { agentChatLoader } from './agent-chat';
|
|
40
38
|
export { llmLoader } from './llm';
|
|
41
|
-
export {
|
|
39
|
+
export { pubkeyLoader } from './pubkey';
|
|
40
|
+
export { rlsLoader } from './rls';
|
|
41
|
+
export { webauthnLoader } from './webauthn';
|
|
42
42
|
/**
|
|
43
43
|
* Convenience: create a registry pre-loaded with all built-in loaders.
|
|
44
44
|
*/
|
|
45
|
-
import {
|
|
46
|
-
import { rlsLoader } from './rls';
|
|
47
|
-
import { corsLoader } from './cors';
|
|
48
|
-
import { databaseSettingsLoader } from './database-settings';
|
|
49
|
-
import { pubkeyLoader } from './pubkey';
|
|
50
|
-
import { webauthnLoader } from './webauthn';
|
|
45
|
+
import { agentChatLoader } from './agent-chat';
|
|
51
46
|
import { authSettingsLoader } from './auth-settings';
|
|
52
47
|
import { billingLoader } from './billing';
|
|
48
|
+
import { computeLoader } from './compute';
|
|
49
|
+
import { corsLoader } from './cors';
|
|
50
|
+
import { databaseSettingsLoader } from './database-settings';
|
|
53
51
|
import { inferenceLogLoader } from './inference-log';
|
|
54
|
-
import { agentChatLoader } from './agent-chat';
|
|
55
52
|
import { llmLoader } from './llm';
|
|
56
|
-
import {
|
|
53
|
+
import { pubkeyLoader } from './pubkey';
|
|
54
|
+
import { createLoaderRegistry } from './registry';
|
|
55
|
+
import { rlsLoader } from './rls';
|
|
56
|
+
import { webauthnLoader } from './webauthn';
|
|
57
57
|
export function createDefaultRegistry() {
|
|
58
58
|
const registry = createLoaderRegistry();
|
|
59
59
|
registry.register(rlsLoader);
|
package/esm/loaders/pubkey.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Pubkey Challenge Settings Loader
|
|
3
3
|
*
|
|
4
4
|
* Resolves public-key challenge auth config (crypto network, sign-in/sign-up
|
|
5
|
-
* function names)
|
|
6
|
-
* to the
|
|
5
|
+
* function names) from the scoped routing plane. Tries the pubkey_settings
|
|
6
|
+
* table first, falls back to the api_modules approach.
|
|
7
7
|
*/
|
|
8
8
|
import { createModuleLoader } from './create-loader';
|
|
9
9
|
// ─── SQL ────────────────────────────────────────────────────────────────────
|
|
@@ -15,7 +15,7 @@ const PUBKEY_SETTINGS_SQL = `
|
|
|
15
15
|
sign_in_req_fn.name AS sign_in_request_challenge,
|
|
16
16
|
sign_in_fail_fn.name AS sign_in_record_failure,
|
|
17
17
|
sign_in_fn.name AS sign_in_with_challenge
|
|
18
|
-
FROM
|
|
18
|
+
FROM constructive_routing_public.pubkey_settings ps
|
|
19
19
|
LEFT JOIN metaschema_public.schema s ON ps.schema_id = s.id
|
|
20
20
|
LEFT JOIN metaschema_public.function sign_up_fn ON ps.sign_up_with_key_function_id = sign_up_fn.id
|
|
21
21
|
LEFT JOIN metaschema_public.function sign_in_req_fn ON ps.sign_in_request_challenge_function_id = sign_in_req_fn.id
|
|
@@ -26,7 +26,7 @@ const PUBKEY_SETTINGS_SQL = `
|
|
|
26
26
|
`;
|
|
27
27
|
const PUBKEY_MODULE_SQL = `
|
|
28
28
|
SELECT data
|
|
29
|
-
FROM
|
|
29
|
+
FROM constructive_routing_public.api_modules
|
|
30
30
|
WHERE api_id = $1 AND name = 'pubkey_challenge'
|
|
31
31
|
LIMIT 1
|
|
32
32
|
`;
|
|
@@ -40,7 +40,7 @@ function fromRow(row) {
|
|
|
40
40
|
signUpWithKey: row.sign_up_with_key,
|
|
41
41
|
signInRequestChallenge: row.sign_in_request_challenge,
|
|
42
42
|
signInRecordFailure: row.sign_in_record_failure,
|
|
43
|
-
signInWithChallenge: row.sign_in_with_challenge
|
|
43
|
+
signInWithChallenge: row.sign_in_with_challenge
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
// ─── Loader ─────────────────────────────────────────────────────────────────
|
|
@@ -48,10 +48,10 @@ export const pubkeyLoader = createModuleLoader({
|
|
|
48
48
|
name: 'pubkeyChallengeSettings',
|
|
49
49
|
ttlMs: 5 * 60_000,
|
|
50
50
|
async resolve(ctx) {
|
|
51
|
-
const {
|
|
51
|
+
const { routingPool, databaseId, apiId } = ctx;
|
|
52
52
|
// Try new pubkey_settings table first
|
|
53
53
|
try {
|
|
54
|
-
const result = await
|
|
54
|
+
const result = await routingPool.query(PUBKEY_SETTINGS_SQL, [databaseId]);
|
|
55
55
|
const resolved = fromRow(result.rows[0] ?? null);
|
|
56
56
|
if (resolved)
|
|
57
57
|
return resolved;
|
|
@@ -59,11 +59,11 @@ export const pubkeyLoader = createModuleLoader({
|
|
|
59
59
|
catch {
|
|
60
60
|
// Table may not exist yet
|
|
61
61
|
}
|
|
62
|
-
// Fall back to
|
|
62
|
+
// Fall back to api_modules
|
|
63
63
|
if (apiId) {
|
|
64
|
-
const result = await
|
|
64
|
+
const result = await routingPool.query(PUBKEY_MODULE_SQL, [apiId]);
|
|
65
65
|
return fromRow(result.rows[0]?.data ?? null);
|
|
66
66
|
}
|
|
67
67
|
return undefined;
|
|
68
|
-
}
|
|
68
|
+
}
|
|
69
69
|
});
|
package/esm/loaders/rls.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* RLS Module Loader
|
|
3
3
|
*
|
|
4
4
|
* Resolves RLS authentication function names and schema references for
|
|
5
|
-
* a given database
|
|
6
|
-
* to the
|
|
5
|
+
* a given database from the scoped routing plane. Tries the rls_settings
|
|
6
|
+
* table first, falls back to the api_modules approach.
|
|
7
7
|
*/
|
|
8
8
|
import { createModuleLoader } from './create-loader';
|
|
9
9
|
// ─── SQL ────────────────────────────────────────────────────────────────────
|
|
@@ -17,7 +17,7 @@ const RLS_SETTINGS_SQL = `
|
|
|
17
17
|
role_id_fn.name AS current_role_id,
|
|
18
18
|
ua_fn.name AS current_user_agent,
|
|
19
19
|
ip_fn.name AS current_ip_address
|
|
20
|
-
FROM
|
|
20
|
+
FROM constructive_routing_public.rls_settings rs
|
|
21
21
|
LEFT JOIN metaschema_public.schema auth_schema ON rs.authenticate_schema_id = auth_schema.id
|
|
22
22
|
LEFT JOIN metaschema_public.schema role_schema ON rs.role_schema_id = role_schema.id
|
|
23
23
|
LEFT JOIN metaschema_public.function auth_fn ON rs.authenticate_function_id = auth_fn.id
|
|
@@ -31,7 +31,7 @@ const RLS_SETTINGS_SQL = `
|
|
|
31
31
|
`;
|
|
32
32
|
const RLS_MODULE_SQL = `
|
|
33
33
|
SELECT data
|
|
34
|
-
FROM
|
|
34
|
+
FROM constructive_routing_public.api_modules
|
|
35
35
|
WHERE api_id = $1 AND name = 'rls_module'
|
|
36
36
|
LIMIT 1
|
|
37
37
|
`;
|
|
@@ -49,7 +49,7 @@ function fromSettings(row) {
|
|
|
49
49
|
currentRole: row.current_role,
|
|
50
50
|
currentRoleId: row.current_role_id,
|
|
51
51
|
currentIpAddress: row.current_ip_address,
|
|
52
|
-
currentUserAgent: row.current_user_agent
|
|
52
|
+
currentUserAgent: row.current_user_agent
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
function fromModule(row) {
|
|
@@ -64,7 +64,7 @@ function fromModule(row) {
|
|
|
64
64
|
currentRole: d.current_role,
|
|
65
65
|
currentRoleId: d.current_role_id,
|
|
66
66
|
currentIpAddress: d.current_ip_address,
|
|
67
|
-
currentUserAgent: d.current_user_agent
|
|
67
|
+
currentUserAgent: d.current_user_agent
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
// ─── Loader ─────────────────────────────────────────────────────────────────
|
|
@@ -72,10 +72,10 @@ export const rlsLoader = createModuleLoader({
|
|
|
72
72
|
name: 'rlsModule',
|
|
73
73
|
ttlMs: 5 * 60_000,
|
|
74
74
|
async resolve(ctx) {
|
|
75
|
-
const {
|
|
75
|
+
const { routingPool, databaseId, apiId } = ctx;
|
|
76
76
|
// Try new rls_settings table first
|
|
77
77
|
try {
|
|
78
|
-
const result = await
|
|
78
|
+
const result = await routingPool.query(RLS_SETTINGS_SQL, [databaseId]);
|
|
79
79
|
const resolved = fromSettings(result.rows[0] ?? null);
|
|
80
80
|
if (resolved)
|
|
81
81
|
return resolved;
|
|
@@ -83,11 +83,11 @@ export const rlsLoader = createModuleLoader({
|
|
|
83
83
|
catch {
|
|
84
84
|
// Table may not exist yet
|
|
85
85
|
}
|
|
86
|
-
// Fall back to
|
|
86
|
+
// Fall back to api_modules
|
|
87
87
|
if (apiId) {
|
|
88
|
-
const result = await
|
|
88
|
+
const result = await routingPool.query(RLS_MODULE_SQL, [apiId]);
|
|
89
89
|
return fromModule(result.rows[0] ?? null);
|
|
90
90
|
}
|
|
91
91
|
return undefined;
|
|
92
|
-
}
|
|
92
|
+
}
|
|
93
93
|
});
|
package/esm/loaders/types.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Module Loader Types
|
|
3
3
|
*
|
|
4
4
|
* A ModuleLoader is a per-database cached lookup that resolves config
|
|
5
|
-
* from the
|
|
5
|
+
* from the routing DB or tenant DB. Each loader owns its own LRU cache
|
|
6
6
|
* keyed by databaseId, with independent TTL and eviction.
|
|
7
7
|
*
|
|
8
8
|
* Loaders are registered in a LoaderRegistry and resolved in parallel
|
package/esm/loaders/webauthn.js
CHANGED
|
@@ -19,7 +19,7 @@ const WEBAUTHN_SETTINGS_SQL = `
|
|
|
19
19
|
ws.require_user_verification,
|
|
20
20
|
ws.resident_key,
|
|
21
21
|
ws.challenge_expiry_seconds
|
|
22
|
-
FROM
|
|
22
|
+
FROM constructive_routing_public.webauthn_settings ws
|
|
23
23
|
LEFT JOIN metaschema_public.schema s ON ws.schema_id = s.id
|
|
24
24
|
LEFT JOIN metaschema_public.schema cred_s ON ws.credentials_schema_id = cred_s.id
|
|
25
25
|
LEFT JOIN metaschema_public.schema sess_s ON ws.sessions_schema_id = sess_s.id
|
|
@@ -32,8 +32,8 @@ export const webauthnLoader = createModuleLoader({
|
|
|
32
32
|
name: 'webauthnSettings',
|
|
33
33
|
ttlMs: 5 * 60_000,
|
|
34
34
|
async resolve(ctx) {
|
|
35
|
-
const {
|
|
36
|
-
const result = await
|
|
35
|
+
const { routingPool, databaseId } = ctx;
|
|
36
|
+
const result = await routingPool.query(WEBAUTHN_SETTINGS_SQL, [databaseId]);
|
|
37
37
|
const row = result.rows[0];
|
|
38
38
|
if (!row?.schema)
|
|
39
39
|
return undefined;
|
|
@@ -48,7 +48,7 @@ export const webauthnLoader = createModuleLoader({
|
|
|
48
48
|
attestationType: row.attestation_type,
|
|
49
49
|
requireUserVerification: row.require_user_verification,
|
|
50
50
|
residentKey: row.resident_key,
|
|
51
|
-
challengeExpirySeconds: row.challenge_expiry_seconds
|
|
51
|
+
challengeExpirySeconds: row.challenge_expiry_seconds
|
|
52
52
|
};
|
|
53
|
-
}
|
|
53
|
+
}
|
|
54
54
|
});
|
package/esm/pg-settings.js
CHANGED
|
@@ -39,8 +39,9 @@ export function buildPgSettings(input) {
|
|
|
39
39
|
settings['jwt.claims.database_id'] = api.databaseId;
|
|
40
40
|
}
|
|
41
41
|
// API provenance — which API surface this request arrived through.
|
|
42
|
-
// Derived server-side
|
|
43
|
-
// never taken from client-supplied headers,
|
|
42
|
+
// Derived server-side by resolving the hostname through the scoped routing
|
|
43
|
+
// plane (resolve_route -> api_id); never taken from client-supplied headers,
|
|
44
|
+
// body, or token payload.
|
|
44
45
|
if (api.apiId) {
|
|
45
46
|
settings['jwt.claims.api_id'] = api.apiId;
|
|
46
47
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 2. Query the discovered <schema>.<table> in the tenant DB
|
|
8
8
|
*
|
|
9
9
|
* This is the pattern for any module whose config lives in the tenant
|
|
10
|
-
* database rather than the
|
|
10
|
+
* database rather than the routing database.
|
|
11
11
|
*/
|
|
12
12
|
import type { AuthSettings } from '../types';
|
|
13
13
|
import type { ModuleLoader } from './types';
|
package/loaders/auth-settings.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 2. Query the discovered <schema>.<table> in the tenant DB
|
|
9
9
|
*
|
|
10
10
|
* This is the pattern for any module whose config lives in the tenant
|
|
11
|
-
* database rather than the
|
|
11
|
+
* database rather than the routing database.
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.authSettingsLoader = void 0;
|
|
@@ -59,7 +59,7 @@ exports.authSettingsLoader = (0, create_loader_1.createModuleLoader)({
|
|
|
59
59
|
cookiePath: row.cookie_path,
|
|
60
60
|
rememberMeDuration: row.remember_me_duration,
|
|
61
61
|
enableCaptcha: row.enable_captcha,
|
|
62
|
-
captchaSiteKey: row.captcha_site_key
|
|
62
|
+
captchaSiteKey: row.captcha_site_key
|
|
63
63
|
};
|
|
64
|
-
}
|
|
64
|
+
}
|
|
65
65
|
});
|
package/loaders/cors.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CORS Origins Loader
|
|
3
3
|
*
|
|
4
|
-
* Resolves allowed CORS origins for a database+API combination
|
|
5
|
-
* Checks per-API settings first, falls back to
|
|
6
|
-
* then to the
|
|
4
|
+
* Resolves allowed CORS origins for a database+API combination from the
|
|
5
|
+
* scoped routing plane. Checks per-API settings first, falls back to
|
|
6
|
+
* database-level default, then to the api_modules approach.
|
|
7
7
|
*/
|
|
8
8
|
import type { ModuleLoader } from './types';
|
|
9
9
|
export declare const corsLoader: ModuleLoader<string[]>;
|
package/loaders/cors.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* CORS Origins Loader
|
|
4
4
|
*
|
|
5
|
-
* Resolves allowed CORS origins for a database+API combination
|
|
6
|
-
* Checks per-API settings first, falls back to
|
|
7
|
-
* then to the
|
|
5
|
+
* Resolves allowed CORS origins for a database+API combination from the
|
|
6
|
+
* scoped routing plane. Checks per-API settings first, falls back to
|
|
7
|
+
* database-level default, then to the api_modules approach.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.corsLoader = void 0;
|
|
@@ -12,19 +12,19 @@ const create_loader_1 = require("./create-loader");
|
|
|
12
12
|
// ─── SQL ────────────────────────────────────────────────────────────────────
|
|
13
13
|
const CORS_SETTINGS_SQL = `
|
|
14
14
|
SELECT allowed_origins
|
|
15
|
-
FROM
|
|
15
|
+
FROM constructive_routing_public.cors_settings
|
|
16
16
|
WHERE database_id = $1 AND api_id = $2
|
|
17
17
|
LIMIT 1
|
|
18
18
|
`;
|
|
19
19
|
const CORS_SETTINGS_DB_DEFAULT_SQL = `
|
|
20
20
|
SELECT allowed_origins
|
|
21
|
-
FROM
|
|
21
|
+
FROM constructive_routing_public.cors_settings
|
|
22
22
|
WHERE database_id = $1 AND api_id IS NULL
|
|
23
23
|
LIMIT 1
|
|
24
24
|
`;
|
|
25
25
|
const CORS_MODULE_SQL = `
|
|
26
26
|
SELECT data
|
|
27
|
-
FROM
|
|
27
|
+
FROM constructive_routing_public.api_modules
|
|
28
28
|
WHERE api_id = $1 AND name = 'cors'
|
|
29
29
|
LIMIT 1
|
|
30
30
|
`;
|
|
@@ -33,26 +33,26 @@ exports.corsLoader = (0, create_loader_1.createModuleLoader)({
|
|
|
33
33
|
name: 'corsOrigins',
|
|
34
34
|
ttlMs: 5 * 60_000,
|
|
35
35
|
async resolve(ctx) {
|
|
36
|
-
const {
|
|
36
|
+
const { routingPool, databaseId, apiId } = ctx;
|
|
37
37
|
// Try per-API cors_settings first
|
|
38
38
|
try {
|
|
39
39
|
if (apiId) {
|
|
40
|
-
const perApi = await
|
|
40
|
+
const perApi = await routingPool.query(CORS_SETTINGS_SQL, [databaseId, apiId]);
|
|
41
41
|
if (perApi.rows[0])
|
|
42
42
|
return perApi.rows[0].allowed_origins;
|
|
43
43
|
}
|
|
44
|
-
const dbDefault = await
|
|
44
|
+
const dbDefault = await routingPool.query(CORS_SETTINGS_DB_DEFAULT_SQL, [databaseId]);
|
|
45
45
|
if (dbDefault.rows[0])
|
|
46
46
|
return dbDefault.rows[0].allowed_origins;
|
|
47
47
|
}
|
|
48
48
|
catch {
|
|
49
49
|
// Table may not exist yet
|
|
50
50
|
}
|
|
51
|
-
// Fall back to
|
|
51
|
+
// Fall back to api_modules
|
|
52
52
|
if (apiId) {
|
|
53
|
-
const result = await
|
|
53
|
+
const result = await routingPool.query(CORS_MODULE_SQL, [apiId]);
|
|
54
54
|
return result.rows[0]?.data?.urls;
|
|
55
55
|
}
|
|
56
56
|
return undefined;
|
|
57
|
-
}
|
|
57
|
+
}
|
|
58
58
|
});
|
|
@@ -24,8 +24,8 @@ const DATABASE_SETTINGS_SQL = `
|
|
|
24
24
|
COALESCE(aps.enable_realtime, ds.enable_realtime) AS resolved_enable_realtime,
|
|
25
25
|
COALESCE(aps.enable_bulk, ds.enable_bulk) AS resolved_enable_bulk,
|
|
26
26
|
COALESCE(aps.enable_i18n, ds.enable_i18n) AS resolved_enable_i18n
|
|
27
|
-
FROM
|
|
28
|
-
LEFT JOIN
|
|
27
|
+
FROM constructive_routing_public.database_settings ds
|
|
28
|
+
LEFT JOIN constructive_routing_public.api_settings aps ON ds.database_id = aps.database_id AND aps.api_id = $2
|
|
29
29
|
WHERE ds.database_id = $1
|
|
30
30
|
LIMIT 1
|
|
31
31
|
`;
|
|
@@ -34,8 +34,8 @@ exports.databaseSettingsLoader = (0, create_loader_1.createModuleLoader)({
|
|
|
34
34
|
name: 'databaseSettings',
|
|
35
35
|
ttlMs: 5 * 60_000,
|
|
36
36
|
async resolve(ctx) {
|
|
37
|
-
const {
|
|
38
|
-
const result = await
|
|
37
|
+
const { routingPool, databaseId, apiId } = ctx;
|
|
38
|
+
const result = await routingPool.query(DATABASE_SETTINGS_SQL, [databaseId, apiId ?? null]);
|
|
39
39
|
const row = result.rows[0];
|
|
40
40
|
if (!row)
|
|
41
41
|
return undefined;
|
|
@@ -51,7 +51,7 @@ exports.databaseSettingsLoader = (0, create_loader_1.createModuleLoader)({
|
|
|
51
51
|
enableLlm: row.resolved_enable_llm,
|
|
52
52
|
enableRealtime: row.resolved_enable_realtime,
|
|
53
53
|
enableBulk: row.resolved_enable_bulk,
|
|
54
|
-
enableI18n: row.resolved_enable_i18n
|
|
54
|
+
enableI18n: row.resolved_enable_i18n
|
|
55
55
|
};
|
|
56
|
-
}
|
|
56
|
+
}
|
|
57
57
|
});
|
package/loaders/index.d.ts
CHANGED
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* a LoaderRegistry and pass it to createContextMiddleware().
|
|
7
7
|
*
|
|
8
8
|
* Built-in loaders cover the standard Constructive modules:
|
|
9
|
-
* - rlsModule (
|
|
10
|
-
* - corsOrigins (
|
|
11
|
-
* - databaseSettings(
|
|
12
|
-
* - pubkeyChallengeSettings (
|
|
13
|
-
* - webauthnSettings(
|
|
9
|
+
* - rlsModule (constructive_routing_public.rls_settings)
|
|
10
|
+
* - corsOrigins (constructive_routing_public.cors_settings)
|
|
11
|
+
* - databaseSettings(constructive_routing_public.database_settings)
|
|
12
|
+
* - pubkeyChallengeSettings (constructive_routing_public.pubkey_settings)
|
|
13
|
+
* - webauthnSettings(constructive_routing_public.webauthn_settings)
|
|
14
14
|
* - authSettings (metaschema_modules_public.sessions_module → tenant DB)
|
|
15
15
|
*
|
|
16
16
|
* To add a new per-db lookup, implement a ModuleLoader and register it:
|
|
@@ -30,15 +30,15 @@ export type { CreateLoaderOptions } from './create-loader';
|
|
|
30
30
|
export { createModuleLoader } from './create-loader';
|
|
31
31
|
export type { LoaderRegistry } from './registry';
|
|
32
32
|
export { createLoaderRegistry } from './registry';
|
|
33
|
-
export {
|
|
34
|
-
export { corsLoader } from './cors';
|
|
35
|
-
export { databaseSettingsLoader } from './database-settings';
|
|
36
|
-
export { pubkeyLoader } from './pubkey';
|
|
37
|
-
export { webauthnLoader } from './webauthn';
|
|
33
|
+
export { agentChatLoader } from './agent-chat';
|
|
38
34
|
export { authSettingsLoader } from './auth-settings';
|
|
39
35
|
export { billingLoader } from './billing';
|
|
36
|
+
export { computeLoader } from './compute';
|
|
37
|
+
export { corsLoader } from './cors';
|
|
38
|
+
export { databaseSettingsLoader } from './database-settings';
|
|
40
39
|
export { inferenceLogLoader } from './inference-log';
|
|
41
|
-
export { agentChatLoader } from './agent-chat';
|
|
42
40
|
export { llmLoader } from './llm';
|
|
43
|
-
export {
|
|
41
|
+
export { pubkeyLoader } from './pubkey';
|
|
42
|
+
export { rlsLoader } from './rls';
|
|
43
|
+
export { webauthnLoader } from './webauthn';
|
|
44
44
|
export declare function createDefaultRegistry(): import("./registry").LoaderRegistry;
|
package/loaders/index.js
CHANGED
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
* a LoaderRegistry and pass it to createContextMiddleware().
|
|
8
8
|
*
|
|
9
9
|
* Built-in loaders cover the standard Constructive modules:
|
|
10
|
-
* - rlsModule (
|
|
11
|
-
* - corsOrigins (
|
|
12
|
-
* - databaseSettings(
|
|
13
|
-
* - pubkeyChallengeSettings (
|
|
14
|
-
* - webauthnSettings(
|
|
10
|
+
* - rlsModule (constructive_routing_public.rls_settings)
|
|
11
|
+
* - corsOrigins (constructive_routing_public.cors_settings)
|
|
12
|
+
* - databaseSettings(constructive_routing_public.database_settings)
|
|
13
|
+
* - pubkeyChallengeSettings (constructive_routing_public.pubkey_settings)
|
|
14
|
+
* - webauthnSettings(constructive_routing_public.webauthn_settings)
|
|
15
15
|
* - authSettings (metaschema_modules_public.sessions_module → tenant DB)
|
|
16
16
|
*
|
|
17
17
|
* To add a new per-db lookup, implement a ModuleLoader and register it:
|
|
@@ -27,50 +27,50 @@
|
|
|
27
27
|
* registry.register(myLoader);
|
|
28
28
|
*/
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.
|
|
30
|
+
exports.webauthnLoader = exports.rlsLoader = exports.pubkeyLoader = exports.llmLoader = exports.inferenceLogLoader = exports.databaseSettingsLoader = exports.corsLoader = exports.computeLoader = exports.billingLoader = exports.authSettingsLoader = exports.agentChatLoader = exports.createLoaderRegistry = exports.createModuleLoader = void 0;
|
|
31
31
|
exports.createDefaultRegistry = createDefaultRegistry;
|
|
32
32
|
var create_loader_1 = require("./create-loader");
|
|
33
33
|
Object.defineProperty(exports, "createModuleLoader", { enumerable: true, get: function () { return create_loader_1.createModuleLoader; } });
|
|
34
34
|
var registry_1 = require("./registry");
|
|
35
35
|
Object.defineProperty(exports, "createLoaderRegistry", { enumerable: true, get: function () { return registry_1.createLoaderRegistry; } });
|
|
36
36
|
// Built-in loaders
|
|
37
|
-
var
|
|
38
|
-
Object.defineProperty(exports, "
|
|
39
|
-
var cors_1 = require("./cors");
|
|
40
|
-
Object.defineProperty(exports, "corsLoader", { enumerable: true, get: function () { return cors_1.corsLoader; } });
|
|
41
|
-
var database_settings_1 = require("./database-settings");
|
|
42
|
-
Object.defineProperty(exports, "databaseSettingsLoader", { enumerable: true, get: function () { return database_settings_1.databaseSettingsLoader; } });
|
|
43
|
-
var pubkey_1 = require("./pubkey");
|
|
44
|
-
Object.defineProperty(exports, "pubkeyLoader", { enumerable: true, get: function () { return pubkey_1.pubkeyLoader; } });
|
|
45
|
-
var webauthn_1 = require("./webauthn");
|
|
46
|
-
Object.defineProperty(exports, "webauthnLoader", { enumerable: true, get: function () { return webauthn_1.webauthnLoader; } });
|
|
37
|
+
var agent_chat_1 = require("./agent-chat");
|
|
38
|
+
Object.defineProperty(exports, "agentChatLoader", { enumerable: true, get: function () { return agent_chat_1.agentChatLoader; } });
|
|
47
39
|
var auth_settings_1 = require("./auth-settings");
|
|
48
40
|
Object.defineProperty(exports, "authSettingsLoader", { enumerable: true, get: function () { return auth_settings_1.authSettingsLoader; } });
|
|
49
41
|
var billing_1 = require("./billing");
|
|
50
42
|
Object.defineProperty(exports, "billingLoader", { enumerable: true, get: function () { return billing_1.billingLoader; } });
|
|
43
|
+
var compute_1 = require("./compute");
|
|
44
|
+
Object.defineProperty(exports, "computeLoader", { enumerable: true, get: function () { return compute_1.computeLoader; } });
|
|
45
|
+
var cors_1 = require("./cors");
|
|
46
|
+
Object.defineProperty(exports, "corsLoader", { enumerable: true, get: function () { return cors_1.corsLoader; } });
|
|
47
|
+
var database_settings_1 = require("./database-settings");
|
|
48
|
+
Object.defineProperty(exports, "databaseSettingsLoader", { enumerable: true, get: function () { return database_settings_1.databaseSettingsLoader; } });
|
|
51
49
|
var inference_log_1 = require("./inference-log");
|
|
52
50
|
Object.defineProperty(exports, "inferenceLogLoader", { enumerable: true, get: function () { return inference_log_1.inferenceLogLoader; } });
|
|
53
|
-
var agent_chat_1 = require("./agent-chat");
|
|
54
|
-
Object.defineProperty(exports, "agentChatLoader", { enumerable: true, get: function () { return agent_chat_1.agentChatLoader; } });
|
|
55
51
|
var llm_1 = require("./llm");
|
|
56
52
|
Object.defineProperty(exports, "llmLoader", { enumerable: true, get: function () { return llm_1.llmLoader; } });
|
|
57
|
-
var
|
|
58
|
-
Object.defineProperty(exports, "
|
|
53
|
+
var pubkey_1 = require("./pubkey");
|
|
54
|
+
Object.defineProperty(exports, "pubkeyLoader", { enumerable: true, get: function () { return pubkey_1.pubkeyLoader; } });
|
|
55
|
+
var rls_1 = require("./rls");
|
|
56
|
+
Object.defineProperty(exports, "rlsLoader", { enumerable: true, get: function () { return rls_1.rlsLoader; } });
|
|
57
|
+
var webauthn_1 = require("./webauthn");
|
|
58
|
+
Object.defineProperty(exports, "webauthnLoader", { enumerable: true, get: function () { return webauthn_1.webauthnLoader; } });
|
|
59
59
|
/**
|
|
60
60
|
* Convenience: create a registry pre-loaded with all built-in loaders.
|
|
61
61
|
*/
|
|
62
|
-
const
|
|
63
|
-
const rls_2 = require("./rls");
|
|
64
|
-
const cors_2 = require("./cors");
|
|
65
|
-
const database_settings_2 = require("./database-settings");
|
|
66
|
-
const pubkey_2 = require("./pubkey");
|
|
67
|
-
const webauthn_2 = require("./webauthn");
|
|
62
|
+
const agent_chat_2 = require("./agent-chat");
|
|
68
63
|
const auth_settings_2 = require("./auth-settings");
|
|
69
64
|
const billing_2 = require("./billing");
|
|
65
|
+
const compute_2 = require("./compute");
|
|
66
|
+
const cors_2 = require("./cors");
|
|
67
|
+
const database_settings_2 = require("./database-settings");
|
|
70
68
|
const inference_log_2 = require("./inference-log");
|
|
71
|
-
const agent_chat_2 = require("./agent-chat");
|
|
72
69
|
const llm_2 = require("./llm");
|
|
73
|
-
const
|
|
70
|
+
const pubkey_2 = require("./pubkey");
|
|
71
|
+
const registry_2 = require("./registry");
|
|
72
|
+
const rls_2 = require("./rls");
|
|
73
|
+
const webauthn_2 = require("./webauthn");
|
|
74
74
|
function createDefaultRegistry() {
|
|
75
75
|
const registry = (0, registry_2.createLoaderRegistry)();
|
|
76
76
|
registry.register(rls_2.rlsLoader);
|
package/loaders/pubkey.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Pubkey Challenge Settings Loader
|
|
3
3
|
*
|
|
4
4
|
* Resolves public-key challenge auth config (crypto network, sign-in/sign-up
|
|
5
|
-
* function names)
|
|
6
|
-
* to the
|
|
5
|
+
* function names) from the scoped routing plane. Tries the pubkey_settings
|
|
6
|
+
* table first, falls back to the api_modules approach.
|
|
7
7
|
*/
|
|
8
8
|
import type { PubkeyChallengeSettings } from '../types';
|
|
9
9
|
import type { ModuleLoader } from './types';
|
package/loaders/pubkey.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Pubkey Challenge Settings Loader
|
|
4
4
|
*
|
|
5
5
|
* Resolves public-key challenge auth config (crypto network, sign-in/sign-up
|
|
6
|
-
* function names)
|
|
7
|
-
* to the
|
|
6
|
+
* function names) from the scoped routing plane. Tries the pubkey_settings
|
|
7
|
+
* table first, falls back to the api_modules approach.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.pubkeyLoader = void 0;
|
|
@@ -18,7 +18,7 @@ const PUBKEY_SETTINGS_SQL = `
|
|
|
18
18
|
sign_in_req_fn.name AS sign_in_request_challenge,
|
|
19
19
|
sign_in_fail_fn.name AS sign_in_record_failure,
|
|
20
20
|
sign_in_fn.name AS sign_in_with_challenge
|
|
21
|
-
FROM
|
|
21
|
+
FROM constructive_routing_public.pubkey_settings ps
|
|
22
22
|
LEFT JOIN metaschema_public.schema s ON ps.schema_id = s.id
|
|
23
23
|
LEFT JOIN metaschema_public.function sign_up_fn ON ps.sign_up_with_key_function_id = sign_up_fn.id
|
|
24
24
|
LEFT JOIN metaschema_public.function sign_in_req_fn ON ps.sign_in_request_challenge_function_id = sign_in_req_fn.id
|
|
@@ -29,7 +29,7 @@ const PUBKEY_SETTINGS_SQL = `
|
|
|
29
29
|
`;
|
|
30
30
|
const PUBKEY_MODULE_SQL = `
|
|
31
31
|
SELECT data
|
|
32
|
-
FROM
|
|
32
|
+
FROM constructive_routing_public.api_modules
|
|
33
33
|
WHERE api_id = $1 AND name = 'pubkey_challenge'
|
|
34
34
|
LIMIT 1
|
|
35
35
|
`;
|
|
@@ -43,7 +43,7 @@ function fromRow(row) {
|
|
|
43
43
|
signUpWithKey: row.sign_up_with_key,
|
|
44
44
|
signInRequestChallenge: row.sign_in_request_challenge,
|
|
45
45
|
signInRecordFailure: row.sign_in_record_failure,
|
|
46
|
-
signInWithChallenge: row.sign_in_with_challenge
|
|
46
|
+
signInWithChallenge: row.sign_in_with_challenge
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
// ─── Loader ─────────────────────────────────────────────────────────────────
|
|
@@ -51,10 +51,10 @@ exports.pubkeyLoader = (0, create_loader_1.createModuleLoader)({
|
|
|
51
51
|
name: 'pubkeyChallengeSettings',
|
|
52
52
|
ttlMs: 5 * 60_000,
|
|
53
53
|
async resolve(ctx) {
|
|
54
|
-
const {
|
|
54
|
+
const { routingPool, databaseId, apiId } = ctx;
|
|
55
55
|
// Try new pubkey_settings table first
|
|
56
56
|
try {
|
|
57
|
-
const result = await
|
|
57
|
+
const result = await routingPool.query(PUBKEY_SETTINGS_SQL, [databaseId]);
|
|
58
58
|
const resolved = fromRow(result.rows[0] ?? null);
|
|
59
59
|
if (resolved)
|
|
60
60
|
return resolved;
|
|
@@ -62,11 +62,11 @@ exports.pubkeyLoader = (0, create_loader_1.createModuleLoader)({
|
|
|
62
62
|
catch {
|
|
63
63
|
// Table may not exist yet
|
|
64
64
|
}
|
|
65
|
-
// Fall back to
|
|
65
|
+
// Fall back to api_modules
|
|
66
66
|
if (apiId) {
|
|
67
|
-
const result = await
|
|
67
|
+
const result = await routingPool.query(PUBKEY_MODULE_SQL, [apiId]);
|
|
68
68
|
return fromRow(result.rows[0]?.data ?? null);
|
|
69
69
|
}
|
|
70
70
|
return undefined;
|
|
71
|
-
}
|
|
71
|
+
}
|
|
72
72
|
});
|
package/loaders/rls.d.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* RLS Module Loader
|
|
3
3
|
*
|
|
4
4
|
* Resolves RLS authentication function names and schema references for
|
|
5
|
-
* a given database
|
|
6
|
-
* to the
|
|
5
|
+
* a given database from the scoped routing plane. Tries the rls_settings
|
|
6
|
+
* table first, falls back to the api_modules approach.
|
|
7
7
|
*/
|
|
8
8
|
import type { RlsModule } from '../types';
|
|
9
9
|
import type { ModuleLoader } from './types';
|
package/loaders/rls.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* RLS Module Loader
|
|
4
4
|
*
|
|
5
5
|
* Resolves RLS authentication function names and schema references for
|
|
6
|
-
* a given database
|
|
7
|
-
* to the
|
|
6
|
+
* a given database from the scoped routing plane. Tries the rls_settings
|
|
7
|
+
* table first, falls back to the api_modules approach.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.rlsLoader = void 0;
|
|
@@ -20,7 +20,7 @@ const RLS_SETTINGS_SQL = `
|
|
|
20
20
|
role_id_fn.name AS current_role_id,
|
|
21
21
|
ua_fn.name AS current_user_agent,
|
|
22
22
|
ip_fn.name AS current_ip_address
|
|
23
|
-
FROM
|
|
23
|
+
FROM constructive_routing_public.rls_settings rs
|
|
24
24
|
LEFT JOIN metaschema_public.schema auth_schema ON rs.authenticate_schema_id = auth_schema.id
|
|
25
25
|
LEFT JOIN metaschema_public.schema role_schema ON rs.role_schema_id = role_schema.id
|
|
26
26
|
LEFT JOIN metaschema_public.function auth_fn ON rs.authenticate_function_id = auth_fn.id
|
|
@@ -34,7 +34,7 @@ const RLS_SETTINGS_SQL = `
|
|
|
34
34
|
`;
|
|
35
35
|
const RLS_MODULE_SQL = `
|
|
36
36
|
SELECT data
|
|
37
|
-
FROM
|
|
37
|
+
FROM constructive_routing_public.api_modules
|
|
38
38
|
WHERE api_id = $1 AND name = 'rls_module'
|
|
39
39
|
LIMIT 1
|
|
40
40
|
`;
|
|
@@ -52,7 +52,7 @@ function fromSettings(row) {
|
|
|
52
52
|
currentRole: row.current_role,
|
|
53
53
|
currentRoleId: row.current_role_id,
|
|
54
54
|
currentIpAddress: row.current_ip_address,
|
|
55
|
-
currentUserAgent: row.current_user_agent
|
|
55
|
+
currentUserAgent: row.current_user_agent
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
function fromModule(row) {
|
|
@@ -67,7 +67,7 @@ function fromModule(row) {
|
|
|
67
67
|
currentRole: d.current_role,
|
|
68
68
|
currentRoleId: d.current_role_id,
|
|
69
69
|
currentIpAddress: d.current_ip_address,
|
|
70
|
-
currentUserAgent: d.current_user_agent
|
|
70
|
+
currentUserAgent: d.current_user_agent
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
// ─── Loader ─────────────────────────────────────────────────────────────────
|
|
@@ -75,10 +75,10 @@ exports.rlsLoader = (0, create_loader_1.createModuleLoader)({
|
|
|
75
75
|
name: 'rlsModule',
|
|
76
76
|
ttlMs: 5 * 60_000,
|
|
77
77
|
async resolve(ctx) {
|
|
78
|
-
const {
|
|
78
|
+
const { routingPool, databaseId, apiId } = ctx;
|
|
79
79
|
// Try new rls_settings table first
|
|
80
80
|
try {
|
|
81
|
-
const result = await
|
|
81
|
+
const result = await routingPool.query(RLS_SETTINGS_SQL, [databaseId]);
|
|
82
82
|
const resolved = fromSettings(result.rows[0] ?? null);
|
|
83
83
|
if (resolved)
|
|
84
84
|
return resolved;
|
|
@@ -86,11 +86,11 @@ exports.rlsLoader = (0, create_loader_1.createModuleLoader)({
|
|
|
86
86
|
catch {
|
|
87
87
|
// Table may not exist yet
|
|
88
88
|
}
|
|
89
|
-
// Fall back to
|
|
89
|
+
// Fall back to api_modules
|
|
90
90
|
if (apiId) {
|
|
91
|
-
const result = await
|
|
91
|
+
const result = await routingPool.query(RLS_MODULE_SQL, [apiId]);
|
|
92
92
|
return fromModule(result.rows[0] ?? null);
|
|
93
93
|
}
|
|
94
94
|
return undefined;
|
|
95
|
-
}
|
|
95
|
+
}
|
|
96
96
|
});
|
package/loaders/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Module Loader Types
|
|
3
3
|
*
|
|
4
4
|
* A ModuleLoader is a per-database cached lookup that resolves config
|
|
5
|
-
* from the
|
|
5
|
+
* from the routing DB or tenant DB. Each loader owns its own LRU cache
|
|
6
6
|
* keyed by databaseId, with independent TTL and eviction.
|
|
7
7
|
*
|
|
8
8
|
* Loaders are registered in a LoaderRegistry and resolved in parallel
|
|
@@ -16,8 +16,8 @@ import type { Pool } from 'pg';
|
|
|
16
16
|
* database tier it needs.
|
|
17
17
|
*/
|
|
18
18
|
export interface LoaderContext {
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/** Routing/configuration database pool (for constructive_routing_public.* lookups) */
|
|
20
|
+
routingPool: Pool;
|
|
21
21
|
/** Tenant database pool (for metaschema_modules_public.* lookups) */
|
|
22
22
|
tenantPool: Pool;
|
|
23
23
|
/** UUID of the database being resolved */
|
package/loaders/types.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Module Loader Types
|
|
4
4
|
*
|
|
5
5
|
* A ModuleLoader is a per-database cached lookup that resolves config
|
|
6
|
-
* from the
|
|
6
|
+
* from the routing DB or tenant DB. Each loader owns its own LRU cache
|
|
7
7
|
* keyed by databaseId, with independent TTL and eviction.
|
|
8
8
|
*
|
|
9
9
|
* Loaders are registered in a LoaderRegistry and resolved in parallel
|
package/loaders/webauthn.js
CHANGED
|
@@ -22,7 +22,7 @@ const WEBAUTHN_SETTINGS_SQL = `
|
|
|
22
22
|
ws.require_user_verification,
|
|
23
23
|
ws.resident_key,
|
|
24
24
|
ws.challenge_expiry_seconds
|
|
25
|
-
FROM
|
|
25
|
+
FROM constructive_routing_public.webauthn_settings ws
|
|
26
26
|
LEFT JOIN metaschema_public.schema s ON ws.schema_id = s.id
|
|
27
27
|
LEFT JOIN metaschema_public.schema cred_s ON ws.credentials_schema_id = cred_s.id
|
|
28
28
|
LEFT JOIN metaschema_public.schema sess_s ON ws.sessions_schema_id = sess_s.id
|
|
@@ -35,8 +35,8 @@ exports.webauthnLoader = (0, create_loader_1.createModuleLoader)({
|
|
|
35
35
|
name: 'webauthnSettings',
|
|
36
36
|
ttlMs: 5 * 60_000,
|
|
37
37
|
async resolve(ctx) {
|
|
38
|
-
const {
|
|
39
|
-
const result = await
|
|
38
|
+
const { routingPool, databaseId } = ctx;
|
|
39
|
+
const result = await routingPool.query(WEBAUTHN_SETTINGS_SQL, [databaseId]);
|
|
40
40
|
const row = result.rows[0];
|
|
41
41
|
if (!row?.schema)
|
|
42
42
|
return undefined;
|
|
@@ -51,7 +51,7 @@ exports.webauthnLoader = (0, create_loader_1.createModuleLoader)({
|
|
|
51
51
|
attestationType: row.attestation_type,
|
|
52
52
|
requireUserVerification: row.require_user_verification,
|
|
53
53
|
residentKey: row.resident_key,
|
|
54
|
-
challengeExpirySeconds: row.challenge_expiry_seconds
|
|
54
|
+
challengeExpirySeconds: row.challenge_expiry_seconds
|
|
55
55
|
};
|
|
56
|
-
}
|
|
56
|
+
}
|
|
57
57
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/express-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Extractable Express middleware for Constructive tenant context — domain resolution, JWT auth, pgSettings, and withPgClient",
|
|
5
5
|
"author": "Constructive <developers@constructive.io>",
|
|
6
6
|
"homepage": "https://github.com/constructive-io/constructive",
|
|
@@ -30,13 +30,13 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@constructive-io/url-domains": "^2.19.0",
|
|
33
|
-
"@pgpmjs/env": "^2.
|
|
33
|
+
"@pgpmjs/env": "^2.29.0",
|
|
34
34
|
"@pgpmjs/logger": "^2.14.0",
|
|
35
|
-
"@pgpmjs/server-utils": "^3.15.
|
|
36
|
-
"@pgpmjs/types": "^2.
|
|
35
|
+
"@pgpmjs/server-utils": "^3.15.4",
|
|
36
|
+
"@pgpmjs/types": "^2.36.0",
|
|
37
37
|
"lru-cache": "^11.2.7",
|
|
38
38
|
"pg": "^8.21.0",
|
|
39
|
-
"pg-cache": "^3.15.
|
|
39
|
+
"pg-cache": "^3.15.3",
|
|
40
40
|
"pg-query-context": "^2.19.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"jwt",
|
|
60
60
|
"pgSettings"
|
|
61
61
|
],
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "39db6d576bce7bc1de8123849f237b1695779d88"
|
|
63
63
|
}
|
package/pg-settings.js
CHANGED
|
@@ -42,8 +42,9 @@ function buildPgSettings(input) {
|
|
|
42
42
|
settings['jwt.claims.database_id'] = api.databaseId;
|
|
43
43
|
}
|
|
44
44
|
// API provenance — which API surface this request arrived through.
|
|
45
|
-
// Derived server-side
|
|
46
|
-
// never taken from client-supplied headers,
|
|
45
|
+
// Derived server-side by resolving the hostname through the scoped routing
|
|
46
|
+
// plane (resolve_route -> api_id); never taken from client-supplied headers,
|
|
47
|
+
// body, or token payload.
|
|
47
48
|
if (api.apiId) {
|
|
48
49
|
settings['jwt.claims.api_id'] = api.apiId;
|
|
49
50
|
}
|