@stratum-hq/control-plane 1.4.0 → 1.5.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/README.md +49 -12
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +27 -0
- package/dist/app.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +22 -2
- package/dist/config.js.map +1 -1
- package/dist/db/connection.d.ts +10 -0
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +41 -12
- package/dist/db/connection.js.map +1 -1
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +29 -6
- package/dist/db/migrate.js.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/middleware/auth.js +1 -1
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/authorize.d.ts.map +1 -1
- package/dist/middleware/authorize.js +4 -3
- package/dist/middleware/authorize.js.map +1 -1
- package/dist/middleware/error-handler.d.ts +6 -0
- package/dist/middleware/error-handler.d.ts.map +1 -1
- package/dist/middleware/error-handler.js +27 -4
- package/dist/middleware/error-handler.js.map +1 -1
- package/dist/middleware/path-params.d.ts +31 -0
- package/dist/middleware/path-params.d.ts.map +1 -0
- package/dist/middleware/path-params.js +78 -0
- package/dist/middleware/path-params.js.map +1 -0
- package/dist/middleware/rate-limit-redis.js +1 -1
- package/dist/middleware/rate-limit-redis.js.map +1 -1
- package/dist/middleware/telemetry.js +3 -3
- package/dist/middleware/telemetry.js.map +1 -1
- package/dist/redis.js +1 -1
- package/dist/redis.js.map +1 -1
- package/dist/routes/abac.js +4 -4
- package/dist/routes/abac.js.map +1 -1
- package/dist/routes/api-keys.js +5 -5
- package/dist/routes/api-keys.js.map +1 -1
- package/dist/routes/audit-logs.js +2 -2
- package/dist/routes/audit-logs.js.map +1 -1
- package/dist/routes/config-diff.d.ts.map +1 -1
- package/dist/routes/config-diff.js +3 -2
- package/dist/routes/config-diff.js.map +1 -1
- package/dist/routes/config.d.ts +11 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +107 -7
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/consent.js +3 -3
- package/dist/routes/consent.js.map +1 -1
- package/dist/routes/health.d.ts.map +1 -1
- package/dist/routes/health.js +14 -9
- package/dist/routes/health.js.map +1 -1
- package/dist/routes/maintenance.js +2 -2
- package/dist/routes/maintenance.js.map +1 -1
- package/dist/routes/permissions.js +4 -4
- package/dist/routes/permissions.js.map +1 -1
- package/dist/routes/regions.js +5 -5
- package/dist/routes/regions.js.map +1 -1
- package/dist/routes/roles.js +7 -7
- package/dist/routes/roles.js.map +1 -1
- package/dist/routes/tenants.d.ts.map +1 -1
- package/dist/routes/tenants.js +20 -17
- package/dist/routes/tenants.js.map +1 -1
- package/dist/routes/webhooks.js +12 -12
- package/dist/routes/webhooks.js.map +1 -1
- package/dist/services/isolation-service.d.ts.map +1 -1
- package/dist/services/isolation-service.js +9 -4
- package/dist/services/isolation-service.js.map +1 -1
- package/package.json +11 -6
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @stratum-hq/control-plane
|
|
2
2
|
|
|
3
|
-
The [Stratum](https://github.com/stratum-hq/Stratum) control plane
|
|
3
|
+
The [Stratum](https://github.com/stratum-hq/Stratum) control plane: a [Fastify](https://fastify.dev) REST API server that exposes tenant, config, permission, API-key, webhook, audit, consent, region, and ABAC management over HTTP. Pair it with `@stratum-hq/sdk` for polyglot stacks or service separation.
|
|
4
|
+
|
|
5
|
+
Read the REST API reference at [docs.stratum-hq.org/api/tenants](https://docs.stratum-hq.org/api/tenants/).
|
|
4
6
|
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
@@ -12,29 +14,64 @@ Most deployments run it as a standalone service rather than importing it. A cont
|
|
|
12
14
|
|
|
13
15
|
## Running
|
|
14
16
|
|
|
17
|
+
The package has no `bin` entry. Start its entry point with Node:
|
|
18
|
+
|
|
15
19
|
```bash
|
|
16
|
-
DATABASE_URL=postgres://
|
|
20
|
+
DATABASE_URL=postgres://stratum_app:stratum_dev@localhost:5432/stratum \
|
|
21
|
+
DATABASE_ADMIN_URL=postgres://stratum_admin:stratum_dev@localhost:5432/stratum \
|
|
17
22
|
JWT_SECRET=your-secret \
|
|
18
|
-
node dist/index.js
|
|
23
|
+
node node_modules/@stratum-hq/control-plane/dist/index.js
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
In a clone of the repository, run `npm run build`, then `node packages/control-plane/dist/index.js`.
|
|
27
|
+
|
|
28
|
+
On startup the server runs database migrations (on `DATABASE_ADMIN_URL` when set), then listens on `PORT` (default `3001`). It handles `SIGTERM`/`SIGINT` for graceful shutdown.
|
|
29
|
+
|
|
30
|
+
The OpenAPI documentation needs no API key: the Swagger UI is at `/api/docs`, and the specification at `/api/docs/json` and `/api/docs/yaml`.
|
|
31
|
+
|
|
32
|
+
## The first admin key
|
|
33
|
+
|
|
34
|
+
Every route except the health check and the OpenAPI documentation needs an API key or a JWT, and creating keys needs the `admin` scope. A JWT always belongs to a tenant, and a new API key has the scopes `read` and `write`, so create the first admin key with the library, on the same database and with the same `STRATUM_API_KEY_HMAC_SECRET` as the server:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { Pool } from "pg";
|
|
38
|
+
import { Stratum } from "@stratum-hq/lib";
|
|
39
|
+
|
|
40
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
|
41
|
+
const adminPool = new Pool({ connectionString: process.env.DATABASE_ADMIN_URL });
|
|
42
|
+
const stratum = new Stratum({ adminPool, pool });
|
|
43
|
+
await stratum.initialize();
|
|
44
|
+
|
|
45
|
+
// A global key (tenant null) with a role that grants the admin scope.
|
|
46
|
+
const role = await stratum.createRole({ name: "operator", scopes: ["admin"] });
|
|
47
|
+
const key = await stratum.createApiKey(null, { name: "bootstrap-admin" });
|
|
48
|
+
await stratum.assignRoleToKey(key.id, role.id);
|
|
49
|
+
console.log(key.plaintext_key); // shown once: store it now
|
|
50
|
+
|
|
51
|
+
await Promise.all([pool.end(), adminPool.end()]);
|
|
19
52
|
```
|
|
20
53
|
|
|
21
|
-
|
|
54
|
+
Run it after the server has started once, so the tables exist. Without `DATABASE_ADMIN_URL`, leave out `adminPool`. Then send the key in the `X-API-Key` header, and create further keys and roles through `/api/v1/api-keys` and `/api/v1/roles`. `stratum generate api-key` from `@stratum-hq/cli` also creates a global key, but with the default `read` and `write` scopes, so it cannot manage keys.
|
|
22
55
|
|
|
23
56
|
## Configuration
|
|
24
57
|
|
|
25
58
|
| Variable | Default | Description |
|
|
26
59
|
|----------|---------|-------------|
|
|
27
60
|
| `PORT` | `3001` | Listen port |
|
|
28
|
-
| `DATABASE_URL` | `postgres://stratum:stratum_dev@localhost:5432/stratum` | PostgreSQL connection string |
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
61
|
+
| `DATABASE_URL` | `postgres://stratum:stratum_dev@localhost:5432/stratum` | PostgreSQL connection string of the application login |
|
|
62
|
+
| `DATABASE_ADMIN_URL` | none | Optional admin login, a member of the control role of `@stratum-hq/lib` migration 032. When set, the migrations, the library (`adminPool`) and tenant schema and database provisioning run on it, both logins are checked at startup, and the health check reports `admin_db`. When unset, everything runs on `DATABASE_URL` as before. See the [hardening guide](https://docs.stratum-hq.org/guides/hardening-roles/) |
|
|
63
|
+
| `STRATUM_CONTROL_ROLE` | none | Name of the control role, when it is not the database's or `stratum_control` |
|
|
64
|
+
| `STRATUM_ALLOW_LEGACY_KEY_HASHES` | library default (`true` in 1.x) | `false` accepts only HMAC API key hashes while `STRATUM_API_KEY_HMAC_SECRET` is set; `true` also accepts legacy SHA-256 hashes and re-hashes them on use |
|
|
65
|
+
| `STRATUM_API_KEY_HMAC_SECRET` | none | Optional HMAC secret for API key hashes (at least 32 bytes whenever `NODE_ENV` is not `development` or `test`) |
|
|
66
|
+
| `JWT_SECRET` | dev fallback | JWT signing secret, **required** whenever `NODE_ENV` is not `development` or `test` (server refuses to start without it). There it must also be at least 32 bytes and not a placeholder such as `change-me-in-production` |
|
|
67
|
+
| `JWT_AUDIENCE` | none | Optional; when set, Bearer tokens must carry this `aud` claim (for example `stratum-control-plane`). Recommended whenever `JWT_SECRET` is shared with another application; the server warns at startup when it is unset and `NODE_ENV` is not `development` or `test` |
|
|
68
|
+
| `JWT_ISSUER` | none | Optional; when set, Bearer tokens must carry this `iss` claim |
|
|
32
69
|
| `ALLOWED_ORIGINS` | `localhost:3000,3300` | Comma-separated CORS allowlist |
|
|
33
70
|
| `RATE_LIMIT_MAX` | `100` | Requests per window |
|
|
34
71
|
| `RATE_LIMIT_WINDOW` | `1 minute` | Rate-limit window |
|
|
35
|
-
| `REDIS_URL` |
|
|
36
|
-
| `STRATUM_ENCRYPTION_KEY` | dev fallback | Field-level encryption key
|
|
37
|
-
| `STRATUM_HKDF_SALT` | dev fallback | Hex-encoded HKDF salt
|
|
72
|
+
| `REDIS_URL` | none | Optional; enables distributed per-key rate limiting. While Redis is unreachable the client keeps reconnecting and per-key limits are counted in memory per process |
|
|
73
|
+
| `STRATUM_ENCRYPTION_KEY` | dev fallback | Field-level encryption key, **required** (at least 32 bytes) whenever `NODE_ENV` is not `development` or `test` |
|
|
74
|
+
| `STRATUM_HKDF_SALT` | dev fallback | Hex-encoded HKDF salt, **required** whenever `NODE_ENV` is not `development` or `test` |
|
|
38
75
|
| `NODE_ENV` | `development` | Environment. Any value other than `development` or `test` (staging and preview included) enforces the `JWT_SECRET` checks, requires real encryption key material, and runs migrations with RLS enforcement (refusing a `BYPASSRLS` role). Unset counts as `development` |
|
|
39
76
|
|
|
40
77
|
Security middleware (`@fastify/helmet`, CORS, rate limiting) is enabled by default.
|
|
@@ -58,7 +95,7 @@ All routes are versioned under `/api/v1`:
|
|
|
58
95
|
| `/api/v1/config` | Config diff |
|
|
59
96
|
| `/api/v1/maintenance` | Retention / purge tasks |
|
|
60
97
|
|
|
61
|
-
|
|
98
|
+
`GET /api/v1/health` needs no API key and reports server, database (and, with `DATABASE_ADMIN_URL`, admin database) and Redis status.
|
|
62
99
|
|
|
63
100
|
## Links
|
|
64
101
|
|
package/dist/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AACA,OAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AA6CnD,wBAAsB,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC,CAqGzD"}
|
package/dist/app.js
CHANGED
|
@@ -12,6 +12,7 @@ const helmet_1 = __importDefault(require("@fastify/helmet"));
|
|
|
12
12
|
const lib_1 = require("@stratum-hq/lib");
|
|
13
13
|
const openapi_js_1 = require("./openapi.js");
|
|
14
14
|
const error_handler_js_1 = require("./middleware/error-handler.js");
|
|
15
|
+
const path_params_js_1 = require("./middleware/path-params.js");
|
|
15
16
|
const auth_js_1 = require("./middleware/auth.js");
|
|
16
17
|
const authorize_js_1 = require("./middleware/authorize.js");
|
|
17
18
|
const tenant_scope_js_1 = require("./middleware/tenant-scope.js");
|
|
@@ -56,10 +57,30 @@ async function buildApp() {
|
|
|
56
57
|
},
|
|
57
58
|
genReqId: () => node_crypto_1.default.randomUUID(),
|
|
58
59
|
});
|
|
60
|
+
// With DATABASE_ADMIN_URL the library runs on the admin login, as the
|
|
61
|
+
// control role of migration 032, and sets no app.bypass_rls. Without it,
|
|
62
|
+
// everything runs on DATABASE_URL as in earlier releases.
|
|
63
|
+
const adminPool = (0, connection_js_1.getAdminPool)();
|
|
59
64
|
const stratum = new lib_1.Stratum({
|
|
60
65
|
pool: (0, connection_js_1.getPool)(),
|
|
66
|
+
adminPool,
|
|
67
|
+
controlRole: config_js_2.config.controlRole,
|
|
68
|
+
allowLegacyKeyHashes: config_js_2.config.allowLegacyKeyHashes,
|
|
61
69
|
keyPrefix: config_js_2.config.nodeEnv === "production" ? "sk_live_" : "sk_test_",
|
|
62
70
|
});
|
|
71
|
+
// Checks the logins against the role model and logs each problem: both
|
|
72
|
+
// logins with DATABASE_ADMIN_URL, else whether the DATABASE_URL login is a
|
|
73
|
+
// member of the control role. Migrations run separately (db/migrate.ts).
|
|
74
|
+
if (adminPool) {
|
|
75
|
+
await stratum.initialize();
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// As before 1.8, the server starts without the admin login even when
|
|
79
|
+
// the check cannot read the database yet.
|
|
80
|
+
await stratum.initialize().catch((err) => {
|
|
81
|
+
app.log.warn(`Could not check the database role model: ${err instanceof Error ? err.message : String(err)}`);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
63
84
|
await app.register(helmet_1.default, {
|
|
64
85
|
contentSecurityPolicy: {
|
|
65
86
|
directives: {
|
|
@@ -83,6 +104,11 @@ async function buildApp() {
|
|
|
83
104
|
await (0, openapi_js_1.registerOpenApi)(app);
|
|
84
105
|
app.addHook("preHandler", (0, auth_js_1.createAuthMiddleware)(stratum));
|
|
85
106
|
app.addHook("preHandler", (0, authorize_js_1.createAuthorizeMiddleware)());
|
|
107
|
+
// A path id or a query-string tenant id that is not a UUID gets 400, after
|
|
108
|
+
// authentication and before any lookup by that id.
|
|
109
|
+
(0, path_params_js_1.registerUuidPathParams)(app);
|
|
110
|
+
app.addHook("preHandler", path_params_js_1.rejectInvalidPathParams);
|
|
111
|
+
app.addHook("preHandler", path_params_js_1.rejectInvalidQueryTenantIds);
|
|
86
112
|
// Default-deny: a route that declares no tenant scope is refused.
|
|
87
113
|
app.addHook("preHandler", (0, tenant_scope_js_1.createTenantScopeEnforcer)(stratum));
|
|
88
114
|
// Use Redis-backed rate limiting when REDIS_URL is configured, otherwise in-memory
|
|
@@ -100,6 +126,7 @@ async function buildApp() {
|
|
|
100
126
|
app.log.info("Per-key rate limiting: in-memory (no REDIS_URL)");
|
|
101
127
|
}
|
|
102
128
|
app.setErrorHandler(error_handler_js_1.errorHandler);
|
|
129
|
+
app.setNotFoundHandler(error_handler_js_1.notFoundHandler);
|
|
103
130
|
await app.register((0, health_js_1.healthRoutes)(redis_js_1.checkRedisHealth));
|
|
104
131
|
await app.register((0, tenants_js_1.createTenantRoutes)(stratum), { prefix: "/api/v1/tenants" });
|
|
105
132
|
await app.register((0, config_js_1.createConfigRoutes)(stratum), { prefix: "/api/v1/tenants/:id/config" });
|
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export declare const config: {
|
|
2
2
|
port: number;
|
|
3
3
|
databaseUrl: string;
|
|
4
|
+
databaseAdminUrl: string | undefined;
|
|
5
|
+
controlRole: string | undefined;
|
|
6
|
+
allowLegacyKeyHashes: boolean | undefined;
|
|
4
7
|
nodeEnv: string;
|
|
5
8
|
jwtSecret: string;
|
|
6
9
|
jwtAudience: string | undefined;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAwDA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;CAmBlB,CAAC"}
|
package/dist/config.js
CHANGED
|
@@ -15,7 +15,7 @@ if (!jwtSecretEnv) {
|
|
|
15
15
|
throw new Error(`FATAL: JWT_SECRET must be set (NODE_ENV=${nodeEnv}). Refusing to start.`);
|
|
16
16
|
}
|
|
17
17
|
else {
|
|
18
|
-
console.warn("[stratum] JWT_SECRET not set
|
|
18
|
+
console.warn("[stratum] JWT_SECRET not set; using dev fallback. Set JWT_SECRET before deploying to production.");
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
// Placeholder secrets published in this repository's examples and scaffolds.
|
|
@@ -40,11 +40,31 @@ if (jwtSecretEnv && enforceSecretHygiene) {
|
|
|
40
40
|
const jwtAudienceEnv = process.env.JWT_AUDIENCE || undefined;
|
|
41
41
|
const jwtIssuerEnv = process.env.JWT_ISSUER || undefined;
|
|
42
42
|
if (!jwtAudienceEnv && enforceSecretHygiene) {
|
|
43
|
-
console.warn("[stratum] JWT_AUDIENCE not set
|
|
43
|
+
console.warn("[stratum] JWT_AUDIENCE not set. Bearer tokens are not bound to the control plane. Set JWT_AUDIENCE (for example stratum-control-plane).");
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* STRATUM_ALLOW_LEGACY_KEY_HASHES: whether API keys stored with the legacy
|
|
47
|
+
* SHA-256 hash still authenticate while STRATUM_API_KEY_HMAC_SECRET is set.
|
|
48
|
+
* Unset keeps the library default (true in 1.x).
|
|
49
|
+
*/
|
|
50
|
+
function parseAllowLegacyKeyHashes(raw) {
|
|
51
|
+
if (raw === undefined || raw === "")
|
|
52
|
+
return undefined;
|
|
53
|
+
if (raw === "true" || raw === "1")
|
|
54
|
+
return true;
|
|
55
|
+
if (raw === "false" || raw === "0")
|
|
56
|
+
return false;
|
|
57
|
+
throw new Error(`FATAL: STRATUM_ALLOW_LEGACY_KEY_HASHES must be true or false, got "${raw}". Refusing to start.`);
|
|
44
58
|
}
|
|
45
59
|
exports.config = {
|
|
46
60
|
port: parseInt(process.env.PORT || "3001"),
|
|
47
61
|
databaseUrl: process.env.DATABASE_URL || "postgres://stratum:stratum_dev@localhost:5432/stratum",
|
|
62
|
+
// The admin login (member of the control role of lib migration 032). Unset
|
|
63
|
+
// keeps the single-pool behavior of earlier releases.
|
|
64
|
+
databaseAdminUrl: process.env.DATABASE_ADMIN_URL || undefined,
|
|
65
|
+
// The control role of migration 032. Unset: the database's, else stratum_control.
|
|
66
|
+
controlRole: process.env.STRATUM_CONTROL_ROLE || undefined,
|
|
67
|
+
allowLegacyKeyHashes: parseAllowLegacyKeyHashes(process.env.STRATUM_ALLOW_LEGACY_KEY_HASHES),
|
|
48
68
|
nodeEnv,
|
|
49
69
|
jwtSecret: jwtSecretEnv || node_crypto_1.default.randomBytes(32).toString("hex"),
|
|
50
70
|
jwtAudience: jwtAudienceEnv,
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;;;;AAAA,8DAAiC;AAEjC,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;AAC5C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,aAAa,CAAC;AACtD,8EAA8E;AAC9E,8EAA8E;AAC9E,MAAM,oBAAoB,GAAG,OAAO,KAAK,aAAa,IAAI,OAAO,KAAK,MAAM,CAAC;AAE7E,IAAI,CAAC,YAAY,EAAE,CAAC;IAClB,IAAI,oBAAoB,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CAAC,2CAA2C,OAAO,uBAAuB,CAAC,CAAC;IAC7F,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/db/connection.d.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import pg from "pg";
|
|
2
|
+
/** The application login's pool, from DATABASE_URL. */
|
|
2
3
|
export declare function getPool(): pg.Pool;
|
|
4
|
+
/**
|
|
5
|
+
* The admin login's pool, from DATABASE_ADMIN_URL, or undefined when it is
|
|
6
|
+
* not set. The admin login is a member of the control role of
|
|
7
|
+
* @stratum-hq/lib migration 032: the library, the migrations and the
|
|
8
|
+
* isolation service run on it, and the library sets no app.bypass_rls.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getAdminPool(): pg.Pool | undefined;
|
|
11
|
+
/** The pool for Stratum's own work: the admin pool when configured, else the application pool. */
|
|
12
|
+
export declare function getStratumPool(): pg.Pool;
|
|
3
13
|
export declare function closePool(): Promise<void>;
|
|
4
14
|
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/db/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/db/connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AAqBpB,uDAAuD;AACvD,wBAAgB,OAAO,IAAI,EAAE,CAAC,IAAI,CAQjC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,IAAI,EAAE,CAAC,IAAI,GAAG,SAAS,CAKlD;AAED,kGAAkG;AAClG,wBAAgB,cAAc,IAAI,EAAE,CAAC,IAAI,CAExC;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAS/C"}
|
package/dist/db/connection.js
CHANGED
|
@@ -4,28 +4,57 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.getPool = getPool;
|
|
7
|
+
exports.getAdminPool = getAdminPool;
|
|
8
|
+
exports.getStratumPool = getStratumPool;
|
|
7
9
|
exports.closePool = closePool;
|
|
8
10
|
const pg_1 = __importDefault(require("pg"));
|
|
9
11
|
const { Pool } = pg_1.default;
|
|
10
12
|
let pool = null;
|
|
13
|
+
let adminPool = null;
|
|
14
|
+
// Note: pg.Pool has no built-in queue depth limit. Under heavy load, excess
|
|
15
|
+
// requests queue in memory until connectionTimeoutMillis elapses. Keep
|
|
16
|
+
// connectionTimeoutMillis short so queued requests fail fast rather than
|
|
17
|
+
// accumulating and exhausting the event loop.
|
|
18
|
+
function createPool(connectionString) {
|
|
19
|
+
return new Pool({
|
|
20
|
+
connectionString,
|
|
21
|
+
max: 20,
|
|
22
|
+
idleTimeoutMillis: 30000,
|
|
23
|
+
connectionTimeoutMillis: 2000,
|
|
24
|
+
allowExitOnIdle: true,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** The application login's pool, from DATABASE_URL. */
|
|
11
28
|
function getPool() {
|
|
12
29
|
if (!pool) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// Keep connectionTimeoutMillis short so queued requests fail fast rather
|
|
16
|
-
// than accumulating and exhausting the event loop.
|
|
17
|
-
pool = new Pool({
|
|
18
|
-
connectionString: process.env.DATABASE_URL ||
|
|
19
|
-
"postgres://stratum_app:stratum_dev@localhost:5432/stratum",
|
|
20
|
-
max: 20,
|
|
21
|
-
idleTimeoutMillis: 30000,
|
|
22
|
-
connectionTimeoutMillis: 2000,
|
|
23
|
-
allowExitOnIdle: true,
|
|
24
|
-
});
|
|
30
|
+
pool = createPool(process.env.DATABASE_URL ||
|
|
31
|
+
"postgres://stratum_app:stratum_dev@localhost:5432/stratum");
|
|
25
32
|
}
|
|
26
33
|
return pool;
|
|
27
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* The admin login's pool, from DATABASE_ADMIN_URL, or undefined when it is
|
|
37
|
+
* not set. The admin login is a member of the control role of
|
|
38
|
+
* @stratum-hq/lib migration 032: the library, the migrations and the
|
|
39
|
+
* isolation service run on it, and the library sets no app.bypass_rls.
|
|
40
|
+
*/
|
|
41
|
+
function getAdminPool() {
|
|
42
|
+
const url = process.env.DATABASE_ADMIN_URL;
|
|
43
|
+
if (!url)
|
|
44
|
+
return undefined;
|
|
45
|
+
if (!adminPool)
|
|
46
|
+
adminPool = createPool(url);
|
|
47
|
+
return adminPool;
|
|
48
|
+
}
|
|
49
|
+
/** The pool for Stratum's own work: the admin pool when configured, else the application pool. */
|
|
50
|
+
function getStratumPool() {
|
|
51
|
+
return getAdminPool() ?? getPool();
|
|
52
|
+
}
|
|
28
53
|
async function closePool() {
|
|
54
|
+
if (adminPool) {
|
|
55
|
+
await adminPool.end();
|
|
56
|
+
adminPool = null;
|
|
57
|
+
}
|
|
29
58
|
if (pool) {
|
|
30
59
|
await pool.end();
|
|
31
60
|
pool = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/db/connection.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/db/connection.ts"],"names":[],"mappings":";;;;;AAsBA,0BAQC;AAQD,oCAKC;AAGD,wCAEC;AAED,8BASC;AA3DD,4CAAoB;AAEpB,MAAM,EAAE,IAAI,EAAE,GAAG,YAAE,CAAC;AAEpB,IAAI,IAAI,GAAmB,IAAI,CAAC;AAChC,IAAI,SAAS,GAAmB,IAAI,CAAC;AAErC,4EAA4E;AAC5E,uEAAuE;AACvE,yEAAyE;AACzE,8CAA8C;AAC9C,SAAS,UAAU,CAAC,gBAAwB;IAC1C,OAAO,IAAI,IAAI,CAAC;QACd,gBAAgB;QAChB,GAAG,EAAE,EAAE;QACP,iBAAiB,EAAE,KAAK;QACxB,uBAAuB,EAAE,IAAI;QAC7B,eAAe,EAAE,IAAI;KACtB,CAAC,CAAC;AACL,CAAC;AAED,uDAAuD;AACvD,SAAgB,OAAO;IACrB,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,GAAG,UAAU,CACf,OAAO,CAAC,GAAG,CAAC,YAAY;YACtB,2DAA2D,CAC9D,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;GAKG;AACH,SAAgB,YAAY;IAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;IAC3C,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,IAAI,CAAC,SAAS;QAAE,SAAS,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC5C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,kGAAkG;AAClG,SAAgB,cAAc;IAC5B,OAAO,YAAY,EAAE,IAAI,OAAO,EAAE,CAAC;AACrC,CAAC;AAEM,KAAK,UAAU,SAAS;IAC7B,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC;QACtB,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;IACD,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;QACjB,IAAI,GAAG,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/db/migrate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":"AASA,iBAAe,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CA6BtC;AAED,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/db/migrate.js
CHANGED
|
@@ -3,14 +3,37 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.migrate = migrate;
|
|
4
4
|
const lib_1 = require("@stratum-hq/lib");
|
|
5
5
|
const connection_js_1 = require("./connection.js");
|
|
6
|
+
async function currentLogin(pool) {
|
|
7
|
+
const res = await pool.query("SELECT current_user::text AS me");
|
|
8
|
+
return res.rows[0].me;
|
|
9
|
+
}
|
|
6
10
|
async function migrate() {
|
|
7
|
-
const pool = (0, connection_js_1.getPool)();
|
|
8
11
|
console.log("Running migrations...");
|
|
9
|
-
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const adminPool = (0, connection_js_1.getAdminPool)();
|
|
13
|
+
// The control role of lib migration 032 (see config.controlRole). Unset:
|
|
14
|
+
// the database's, else stratum_control.
|
|
15
|
+
const controlRole = process.env.STRATUM_CONTROL_ROLE ? { controlRole: process.env.STRATUM_CONTROL_ROLE } : {};
|
|
16
|
+
if (adminPool) {
|
|
17
|
+
// Migration 032 grants the control role to the admin login, so it must
|
|
18
|
+
// not be the application's login.
|
|
19
|
+
const admin = await currentLogin(adminPool);
|
|
20
|
+
if (admin === (await currentLogin((0, connection_js_1.getPool)()))) {
|
|
21
|
+
throw new Error(`DATABASE_ADMIN_URL and DATABASE_URL log in as the same role "${admin}". DATABASE_ADMIN_URL must be a ` +
|
|
22
|
+
"separate login: the migrations make it a member of the control role, which passes every Stratum policy.");
|
|
23
|
+
}
|
|
24
|
+
// The admin login owns the Stratum objects and is a member of the control
|
|
25
|
+
// role. It may have BYPASSRLS, so the RLS check below is about the
|
|
26
|
+
// application login, which Stratum.initialize() checks in buildApp().
|
|
27
|
+
// It is the admin login, so migration 032 may grant it the control role.
|
|
28
|
+
await (0, lib_1.migrate)({ pool: adminPool, applyControlRole: true, ...controlRole });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
// RLS is enforced everywhere except local development and test runs (an
|
|
32
|
+
// unset NODE_ENV counts as development), matching the JWT_SECRET checks.
|
|
33
|
+
const nodeEnv = process.env.NODE_ENV || "development";
|
|
34
|
+
const enforceRls = nodeEnv !== "development" && nodeEnv !== "test";
|
|
35
|
+
await (0, lib_1.migrate)({ pool: (0, connection_js_1.getPool)(), enforceRls, ...controlRole });
|
|
36
|
+
}
|
|
14
37
|
console.log("Migrations complete.");
|
|
15
38
|
}
|
|
16
39
|
//# sourceMappingURL=migrate.js.map
|
package/dist/db/migrate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/db/migrate.ts"],"names":[],"mappings":";;AAwCS,0BAAO;AAxChB,yCAA2D;AAE3D,mDAAwD;AAExD,KAAK,UAAU,YAAY,CAAC,IAAa;IACvC,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,KAAK,CAAiB,iCAAiC,CAAC,CAAC;IAChF,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACxB,CAAC;AAED,KAAK,UAAU,OAAO;IACpB,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACrC,MAAM,SAAS,GAAG,IAAA,4BAAY,GAAE,CAAC;IACjC,yEAAyE;IACzE,wCAAwC;IACxC,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9G,IAAI,SAAS,EAAE,CAAC;QACd,uEAAuE;QACvE,kCAAkC;QAClC,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,KAAK,KAAK,CAAC,MAAM,YAAY,CAAC,IAAA,uBAAO,GAAE,CAAC,CAAC,EAAE,CAAC;YAC9C,MAAM,IAAI,KAAK,CACb,gEAAgE,KAAK,kCAAkC;gBACrG,yGAAyG,CAC5G,CAAC;QACJ,CAAC;QACD,0EAA0E;QAC1E,mEAAmE;QACnE,sEAAsE;QACtE,yEAAyE;QACzE,MAAM,IAAA,aAAa,EAAC,EAAE,IAAI,EAAE,SAAS,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,WAAW,EAAE,CAAC,CAAC;IACnF,CAAC;SAAM,CAAC;QACN,wEAAwE;QACxE,yEAAyE;QACzE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,aAAa,CAAC;QACtD,MAAM,UAAU,GAAG,OAAO,KAAK,aAAa,IAAI,OAAO,KAAK,MAAM,CAAC;QACnE,MAAM,IAAA,aAAa,EAAC,EAAE,IAAI,EAAE,IAAA,uBAAO,GAAE,EAAE,UAAU,EAAE,GAAG,WAAW,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;AACtC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,7 @@ const config_js_1 = require("./config.js");
|
|
|
5
5
|
const migrate_js_1 = require("./db/migrate.js");
|
|
6
6
|
const connection_js_1 = require("./db/connection.js");
|
|
7
7
|
async function main() {
|
|
8
|
-
// Run database migrations
|
|
9
|
-
console.log("Running migrations...");
|
|
8
|
+
// Run database migrations (migrate() reports its own progress)
|
|
10
9
|
await (0, migrate_js_1.migrate)();
|
|
11
10
|
// Build and start the app
|
|
12
11
|
const app = await (0, app_js_1.buildApp)();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,qCAAoC;AACpC,2CAAqC;AACrC,gDAA0C;AAC1C,sDAA+C;AAE/C,KAAK,UAAU,IAAI;IACjB
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAAA,qCAAoC;AACpC,2CAAqC;AACrC,gDAA0C;AAC1C,sDAA+C;AAE/C,KAAK,UAAU,IAAI;IACjB,+DAA+D;IAC/D,MAAM,IAAA,oBAAO,GAAE,CAAC;IAEhB,0BAA0B;IAC1B,MAAM,GAAG,GAAG,MAAM,IAAA,iBAAQ,GAAE,CAAC;IAE7B,MAAM,GAAG,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,kBAAM,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,2CAA2C,kBAAM,CAAC,IAAI,KAAK,kBAAM,CAAC,OAAO,GAAG,CAAC,CAAC;IAE1F,oBAAoB;IACpB,MAAM,QAAQ,GAAG,KAAK,EAAE,MAAc,EAAiB,EAAE;QACvD,OAAO,CAAC,GAAG,CAAC,YAAY,MAAM,oBAAoB,CAAC,CAAC;QACpD,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,IAAA,yBAAS,GAAE,CAAC;YAClB,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;YAClC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,GAAG,CAAC,CAAC;YAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC;IAEF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;IACjD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/middleware/auth.js
CHANGED
|
@@ -46,7 +46,7 @@ function createAuthMiddleware(stratum) {
|
|
|
46
46
|
...(config_js_1.config.jwtAudience ? { audience: config_js_1.config.jwtAudience } : {}),
|
|
47
47
|
...(config_js_1.config.jwtIssuer ? { issuer: config_js_1.config.jwtIssuer } : {}),
|
|
48
48
|
});
|
|
49
|
-
// JWT must contain a tenant_id claim
|
|
49
|
+
// JWT must contain a tenant_id claim; JWTs without one are not allowed
|
|
50
50
|
// global access (only API keys may have null tenant_id).
|
|
51
51
|
if (!payload.tenant_id || typeof payload.tenant_id !== "string") {
|
|
52
52
|
throw new core_1.UnauthorizedError("JWT must contain a valid tenant_id claim");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":";;;;;AAwBA,oDAuFC;AA9GD,gEAA+B;AAC/B,4CAAsC;AACtC,2CAAqD;AAqBrD,SAAgB,oBAAoB,CAAC,OAAgB;IACnD,OAAO,KAAK,UAAU,cAAc,CAClC,OAAuB,EACvB,MAAoB;QAEpB,mDAAmD;QACnD,IACE,OAAO,CAAC,GAAG,KAAK,gBAAgB;YAChC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,iBAAiB,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,CAAC,EACnC,CAAC;YACD,OAAO;QACT,CAAC;QAED,oBAAoB;QACpB,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAClD,IAAI,YAAY,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;YACrD,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;YAC1D,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,wBAAiB,CAAC,4BAA4B,CAAC,CAAC;YAC5D,CAAC;YACD,OAAO,CAAC,MAAM,GAAG;gBACf,EAAE,EAAE,MAAM,CAAC,MAAM;gBACjB,SAAS,EAAE,MAAM,CAAC,SAAS;gBAC3B,QAAQ,EAAE,EAAE;gBACZ,IAAI,EAAE,EAAE;gBACR,UAAU,EAAE,IAAI,IAAI,EAAE;gBACtB,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,cAAc,EAAE,MAAM,CAAC,cAAc;gBACrC,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;aAC5C,CAAC;YACF,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;YAC/B,OAAO;QACT,CAAC;QAED,uBAAuB;QACvB,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QACpD,IAAI,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACrF,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,sBAAG,CAAC,MAAM,CAAC,KAAK,EAAE,kBAAM,CAAC,SAAS,EAAE;oBAClD,UAAU,EAAE,CAAC,OAAO,CAAC;oBACrB,6DAA6D;oBAC7D,GAAG,CAAC,kBAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,kBAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC/D,GAAG,CAAC,kBAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,kBAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC1D,CAAmB,CAAC;gBAErB,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/middleware/authorize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAIxE;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,CAAC;AAEpE,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,oBAAoB;QAC5B,aAAa,CAAC,EAAE,aAAa,CAAC;KAC/B;CACF;AAED;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAChC,aAAa,GACb;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AAMlD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,WAAW,EAAE,wBAAwB,GACpC,IAAI,CAkBN;AAED,wBAAgB,yBAAyB,KAErC,SAAS,cAAc,EACvB,QAAQ,YAAY,KACnB,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/middleware/authorize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAIxE;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,UAAU,CAAC;AAEpE,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,oBAAoB;QAC5B,aAAa,CAAC,EAAE,aAAa,CAAC;KAC/B;CACF;AAED;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAChC,aAAa,GACb;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,KAAK,EAAE,aAAa,CAAA;CAAE,CAAC;AAMlD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,eAAe,EACpB,WAAW,EAAE,wBAAwB,GACpC,IAAI,CAkBN;AAED,wBAAgB,yBAAyB,KAErC,SAAS,cAAc,EACvB,QAAQ,YAAY,KACnB,OAAO,CAAC,IAAI,CAAC,CAuCjB"}
|
|
@@ -46,7 +46,7 @@ function createAuthorizeMiddleware() {
|
|
|
46
46
|
request.url.startsWith("/api/docs")) {
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
// If no apiKey, auth middleware should have rejected
|
|
49
|
+
// If no apiKey, auth middleware should have rejected, so fail closed
|
|
50
50
|
if (!request.apiKey) {
|
|
51
51
|
throw new core_1.UnauthorizedError("Authentication required");
|
|
52
52
|
}
|
|
@@ -63,8 +63,9 @@ function createAuthorizeMiddleware() {
|
|
|
63
63
|
const scopes = request.apiKey.scopes ?? ["read"];
|
|
64
64
|
// Hierarchical scopes: admin implies write implies read. A granted scope
|
|
65
65
|
// satisfies any required scope of equal-or-lower rank.
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
const neededScope = requiredScope === "operator" ? "admin" : requiredScope;
|
|
67
|
+
if (!(0, core_1.scopeSatisfies)(scopes, neededScope)) {
|
|
68
|
+
throw new core_1.ForbiddenError(`Insufficient permissions for this operation: it requires the "${neededScope}" scope`);
|
|
68
69
|
}
|
|
69
70
|
if (requiredScope === "operator") {
|
|
70
71
|
(0, tenant_scope_js_1.assertOperator)(request);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/middleware/authorize.ts"],"names":[],"mappings":";;AAuCA,oDAqBC;AAED,
|
|
1
|
+
{"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/middleware/authorize.ts"],"names":[],"mappings":"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"}
|
|
@@ -38,5 +38,11 @@ export declare function legacyValidationErrorBody(message: string, issues: reado
|
|
|
38
38
|
issues: ValidationIssue[];
|
|
39
39
|
};
|
|
40
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* Answers a request that matches no route with the error envelope of every
|
|
43
|
+
* other error. The global hooks run first, so a caller without credentials
|
|
44
|
+
* gets 401 and only an authenticated caller learns that the route is unknown.
|
|
45
|
+
*/
|
|
46
|
+
export declare function notFoundHandler(request: FastifyRequest, reply: FastifyReply): void;
|
|
41
47
|
export declare function errorHandler(error: FastifyError | Error, request: FastifyRequest, reply: FastifyReply): void;
|
|
42
48
|
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAErE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAErE,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,kFAAkF;AAClF,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,SAAS,eAAe,EAAE,EAClC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;;;;;;;EAYlC;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,eAAe,EAAE;;;;;;;;;;EAG5F;AA4FD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAQlF;AAED,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,GAAG,KAAK,EAC3B,OAAO,EAAE,cAAc,EACvB,KAAK,EAAE,YAAY,GAClB,IAAI,CA0CN"}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.validationErrorBody = validationErrorBody;
|
|
4
4
|
exports.legacyValidationErrorBody = legacyValidationErrorBody;
|
|
5
|
+
exports.notFoundHandler = notFoundHandler;
|
|
5
6
|
exports.errorHandler = errorHandler;
|
|
6
7
|
const core_1 = require("@stratum-hq/core");
|
|
7
|
-
const config_js_1 = require("../config.js");
|
|
8
8
|
/**
|
|
9
9
|
* Returns the body of a 400 VALIDATION_ERROR response.
|
|
10
10
|
* The issues go in `error.details.issues`, next to any other details.
|
|
@@ -94,11 +94,32 @@ function clientErrorCode(error) {
|
|
|
94
94
|
return core_1.ErrorCode.VALIDATION_ERROR;
|
|
95
95
|
return CLIENT_ERROR_CODES[error.statusCode] ?? "BAD_REQUEST";
|
|
96
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* Logs an error that the API answers with a 5xx status, in every environment.
|
|
99
|
+
* The request logger adds the request id (reqId), so a report from a client
|
|
100
|
+
* can be matched to the log line.
|
|
101
|
+
*/
|
|
102
|
+
function logServerError(request, error) {
|
|
103
|
+
request.log.error({ err: error }, "request failed with a server error");
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Answers a request that matches no route with the error envelope of every
|
|
107
|
+
* other error. The global hooks run first, so a caller without credentials
|
|
108
|
+
* gets 401 and only an authenticated caller learns that the route is unknown.
|
|
109
|
+
*/
|
|
110
|
+
function notFoundHandler(request, reply) {
|
|
111
|
+
const path = request.url.split("?")[0];
|
|
112
|
+
reply.status(404).send({
|
|
113
|
+
error: {
|
|
114
|
+
code: "NOT_FOUND",
|
|
115
|
+
message: `Route ${request.method} ${path} not found`,
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
97
119
|
function errorHandler(error, request, reply) {
|
|
98
|
-
if (config_js_1.config.nodeEnv === "development") {
|
|
99
|
-
console.error("[error]", error);
|
|
100
|
-
}
|
|
101
120
|
if (isStratumError(error)) {
|
|
121
|
+
if (error.statusCode >= 500)
|
|
122
|
+
logServerError(request, error);
|
|
102
123
|
const issues = error.details?.issues;
|
|
103
124
|
if (error.code === core_1.ErrorCode.VALIDATION_ERROR && isIssueList(issues)) {
|
|
104
125
|
reply.status(error.statusCode).send(validationErrorBody(error.message, issues, error.details));
|
|
@@ -127,6 +148,8 @@ function errorHandler(error, request, reply) {
|
|
|
127
148
|
});
|
|
128
149
|
return;
|
|
129
150
|
}
|
|
151
|
+
// The client gets no detail of the cause; the log has it, under the request id.
|
|
152
|
+
logServerError(request, error);
|
|
130
153
|
reply.status(500).send({
|
|
131
154
|
error: {
|
|
132
155
|
code: "INTERNAL_SERVER_ERROR",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"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"}
|