@constructive-io/express-context 0.33.6 → 0.34.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.d.ts +2 -0
- package/context.js +6 -3
- package/esm/context.js +6 -3
- package/esm/refusals.js +3 -3
- package/loaders/request-protection.d.ts +2 -2
- package/loaders/types.d.ts +5 -4
- package/package.json +7 -7
- package/refusals.d.ts +2 -0
- package/refusals.js +2 -2
package/context.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface ContextMiddlewareOptions {
|
|
|
24
24
|
loaders?: LoaderRegistry;
|
|
25
25
|
/** Routing-plane schema loaders query (defaults to routing_public) */
|
|
26
26
|
routingSchema?: string;
|
|
27
|
+
/** Role the module loaders run as (`SET LOCAL ROLE`); required with `loaders`. */
|
|
28
|
+
controlRole?: string;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* Build the ConstructiveContext from the current request state.
|
package/context.js
CHANGED
|
@@ -18,6 +18,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
18
18
|
exports.buildContext = buildContext;
|
|
19
19
|
exports.createContextMiddleware = createContextMiddleware;
|
|
20
20
|
const pg_cache_1 = require("pg-cache");
|
|
21
|
+
const pg_query_context_1 = require("pg-query-context");
|
|
21
22
|
const billing_client_1 = require("./billing-client");
|
|
22
23
|
const pg_client_1 = require("./pg-client");
|
|
23
24
|
const pg_settings_1 = require("./pg-settings");
|
|
@@ -64,11 +65,10 @@ function buildContext(req, opts = {}) {
|
|
|
64
65
|
// Build loader context (if registry provided and databaseId known)
|
|
65
66
|
let loaderCtx = null;
|
|
66
67
|
if (opts.loaders && api.databaseId) {
|
|
67
|
-
const routingPool = (0, pg_cache_1.getPgPool)(opts.pg);
|
|
68
68
|
loaderCtx = {
|
|
69
|
-
routingPool,
|
|
69
|
+
routingPool: (0, pg_query_context_1.controlPlanePool)((0, pg_cache_1.getPgPool)(opts.pg), opts.controlRole),
|
|
70
70
|
routingSchema: opts.routingSchema,
|
|
71
|
-
tenantPool,
|
|
71
|
+
tenantPool: (0, pg_query_context_1.controlPlanePool)(tenantPool, opts.controlRole),
|
|
72
72
|
databaseId: api.databaseId,
|
|
73
73
|
apiId: api.apiId,
|
|
74
74
|
dbname: api.dbname
|
|
@@ -143,6 +143,9 @@ function buildContext(req, opts = {}) {
|
|
|
143
143
|
* ```
|
|
144
144
|
*/
|
|
145
145
|
function createContextMiddleware(opts = {}) {
|
|
146
|
+
if (opts.loaders && !opts.controlRole) {
|
|
147
|
+
throw new Error('createContextMiddleware: `loaders` need a `controlRole` to run as');
|
|
148
|
+
}
|
|
146
149
|
return (req, _res, next) => {
|
|
147
150
|
const ctx = buildContext(req, opts);
|
|
148
151
|
if (ctx) {
|
package/esm/context.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* route handler can use for tenant-scoped database operations.
|
|
15
15
|
*/
|
|
16
16
|
import { getPgPool } from 'pg-cache';
|
|
17
|
+
import { controlPlanePool } from 'pg-query-context';
|
|
17
18
|
import { createBillingClient } from './billing-client';
|
|
18
19
|
import { withPgClient as withPgClientFn } from './pg-client';
|
|
19
20
|
import { buildPgSettings } from './pg-settings';
|
|
@@ -60,11 +61,10 @@ export function buildContext(req, opts = {}) {
|
|
|
60
61
|
// Build loader context (if registry provided and databaseId known)
|
|
61
62
|
let loaderCtx = null;
|
|
62
63
|
if (opts.loaders && api.databaseId) {
|
|
63
|
-
const routingPool = getPgPool(opts.pg);
|
|
64
64
|
loaderCtx = {
|
|
65
|
-
routingPool,
|
|
65
|
+
routingPool: controlPlanePool(getPgPool(opts.pg), opts.controlRole),
|
|
66
66
|
routingSchema: opts.routingSchema,
|
|
67
|
-
tenantPool,
|
|
67
|
+
tenantPool: controlPlanePool(tenantPool, opts.controlRole),
|
|
68
68
|
databaseId: api.databaseId,
|
|
69
69
|
apiId: api.apiId,
|
|
70
70
|
dbname: api.dbname
|
|
@@ -139,6 +139,9 @@ export function buildContext(req, opts = {}) {
|
|
|
139
139
|
* ```
|
|
140
140
|
*/
|
|
141
141
|
export function createContextMiddleware(opts = {}) {
|
|
142
|
+
if (opts.loaders && !opts.controlRole) {
|
|
143
|
+
throw new Error('createContextMiddleware: `loaders` need a `controlRole` to run as');
|
|
144
|
+
}
|
|
142
145
|
return (req, _res, next) => {
|
|
143
146
|
const ctx = buildContext(req, opts);
|
|
144
147
|
if (ctx) {
|
package/esm/refusals.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* @module refusals
|
|
21
21
|
*/
|
|
22
22
|
import { Logger } from '@pgpmjs/logger';
|
|
23
|
-
import {
|
|
23
|
+
import { withControlRole } from 'pg-query-context';
|
|
24
24
|
import { BoundedCounter, CounterFlusher } from './usage-counter';
|
|
25
25
|
const log = new Logger('refusals');
|
|
26
26
|
export const REFUSAL_REASONS = [
|
|
@@ -207,8 +207,8 @@ export const createRecordRefusalsSink = (opts) => {
|
|
|
207
207
|
if (rows.length === 0)
|
|
208
208
|
return;
|
|
209
209
|
const claims = await opts.claims();
|
|
210
|
-
await
|
|
210
|
+
await withControlRole(opts.pool, opts.role, async (client) => {
|
|
211
211
|
await client.query(sql, [JSON.stringify(rows)]);
|
|
212
|
-
});
|
|
212
|
+
}, claims);
|
|
213
213
|
};
|
|
214
214
|
};
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* predates these columns must resolve to the platform defaults, not fail every
|
|
15
15
|
* request against it with `column ds.… does not exist`.
|
|
16
16
|
*/
|
|
17
|
-
import type {
|
|
17
|
+
import type { ControlPlaneQueryable } from 'pg-query-context';
|
|
18
18
|
import type { RequestProtection } from '../request-protection';
|
|
19
19
|
import type { ModuleLoader } from './types';
|
|
20
20
|
/** Test seam: forget what a pool's routing plane looked like. */
|
|
21
|
-
export declare const forgetProtectionColumns: (pool:
|
|
21
|
+
export declare const forgetProtectionColumns: (pool: ControlPlaneQueryable) => void;
|
|
22
22
|
export declare const requestProtectionLoader: ModuleLoader<RequestProtection>;
|
package/loaders/types.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* during context building. The result is a typed modules map on
|
|
10
10
|
* `req.constructive.modules`.
|
|
11
11
|
*/
|
|
12
|
-
import type {
|
|
12
|
+
import type { ControlPlaneQueryable } from 'pg-query-context';
|
|
13
13
|
/** Published logical name of the scoped routing plane. */
|
|
14
14
|
export declare const DEFAULT_ROUTING_SCHEMA = "routing_public";
|
|
15
15
|
/**
|
|
@@ -31,15 +31,16 @@ export declare function requireDatabaseId(databaseId: string | undefined, loader
|
|
|
31
31
|
/**
|
|
32
32
|
* Context passed to every loader's resolve function.
|
|
33
33
|
* Provides both pool references so the loader can query whichever
|
|
34
|
-
* database tier it needs.
|
|
34
|
+
* database tier it needs. Both run every query as the control role
|
|
35
|
+
* (`SET LOCAL ROLE` in its own transaction): the login role can read nothing.
|
|
35
36
|
*/
|
|
36
37
|
export interface LoaderContext {
|
|
37
38
|
/** Routing/configuration database pool (for routing-plane lookups) */
|
|
38
|
-
routingPool:
|
|
39
|
+
routingPool: ControlPlaneQueryable;
|
|
39
40
|
/** Routing-plane schema to query (defaults to the published routing_public) */
|
|
40
41
|
routingSchema?: string;
|
|
41
42
|
/** Tenant database pool (for metaschema_modules_public.* lookups) */
|
|
42
|
-
tenantPool:
|
|
43
|
+
tenantPool: ControlPlaneQueryable;
|
|
43
44
|
/** UUID of the database being resolved */
|
|
44
45
|
databaseId: string;
|
|
45
46
|
/** UUID of the API (if resolved from domain/api-name lookup) */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/express-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.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,14 +30,14 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@constructive-io/url-domains": "^2.31.0",
|
|
33
|
-
"@pgpmjs/env": "^2.
|
|
33
|
+
"@pgpmjs/env": "^2.47.0",
|
|
34
34
|
"@pgpmjs/logger": "^2.26.0",
|
|
35
|
-
"@pgpmjs/server-utils": "^3.29.
|
|
36
|
-
"@pgpmjs/types": "^2.
|
|
35
|
+
"@pgpmjs/server-utils": "^3.29.7",
|
|
36
|
+
"@pgpmjs/types": "^2.57.0",
|
|
37
37
|
"lru-cache": "^11.2.7",
|
|
38
38
|
"pg": "^8.21.0",
|
|
39
|
-
"pg-cache": "^3.29.
|
|
40
|
-
"pg-query-context": "^2.
|
|
39
|
+
"pg-cache": "^3.29.7",
|
|
40
|
+
"pg-query-context": "^2.32.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/express": "^5.0.6",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"jwt",
|
|
60
60
|
"pgSettings"
|
|
61
61
|
],
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "2f506a4a4062b4bd92979c02249e0fee14af4043"
|
|
63
63
|
}
|
package/refusals.d.ts
CHANGED
|
@@ -136,6 +136,8 @@ export declare class RefusalRecorder {
|
|
|
136
136
|
export interface RecordRefusalsSinkOptions {
|
|
137
137
|
/** The platform database's pool. */
|
|
138
138
|
pool: Pool;
|
|
139
|
+
/** Role the flush runs as (`SET LOCAL ROLE`); the login role holds nothing. */
|
|
140
|
+
role: string;
|
|
139
141
|
/**
|
|
140
142
|
* The transaction-local claims the flush runs under, resolved by the caller
|
|
141
143
|
* (a service principal plus the platform database id). The function raises
|
package/refusals.js
CHANGED
|
@@ -215,9 +215,9 @@ const createRecordRefusalsSink = (opts) => {
|
|
|
215
215
|
if (rows.length === 0)
|
|
216
216
|
return;
|
|
217
217
|
const claims = await opts.claims();
|
|
218
|
-
await (0, pg_query_context_1.
|
|
218
|
+
await (0, pg_query_context_1.withControlRole)(opts.pool, opts.role, async (client) => {
|
|
219
219
|
await client.query(sql, [JSON.stringify(rows)]);
|
|
220
|
-
});
|
|
220
|
+
}, claims);
|
|
221
221
|
};
|
|
222
222
|
};
|
|
223
223
|
exports.createRecordRefusalsSink = createRecordRefusalsSink;
|