@stratum-hq/lib 1.7.0 → 1.8.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 +29 -2
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +50 -10
- package/dist/crypto.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -1
- package/dist/index.js.map +1 -1
- package/dist/migrate-schemas.d.ts +4 -0
- package/dist/migrate-schemas.d.ts.map +1 -1
- package/dist/migrate-schemas.js +16 -6
- package/dist/migrate-schemas.js.map +1 -1
- package/dist/migrate.d.ts +24 -0
- package/dist/migrate.d.ts.map +1 -1
- package/dist/migrate.js +44 -6
- package/dist/migrate.js.map +1 -1
- package/dist/migration-sql.d.ts +25 -0
- package/dist/migration-sql.d.ts.map +1 -0
- package/dist/migration-sql.js +64 -0
- package/dist/migration-sql.js.map +1 -0
- package/dist/migrations/029_tenant_parent_cycle_guard.sql +2 -2
- package/dist/migrations/031_subtree_read_scope.sql +6 -6
- package/dist/migrations/032_control_role.sql +809 -0
- package/dist/migrations/033_api_keys_tenant_index.sql +14 -0
- package/dist/pinned-query.d.ts +29 -0
- package/dist/pinned-query.d.ts.map +1 -0
- package/dist/pinned-query.js +60 -0
- package/dist/pinned-query.js.map +1 -0
- package/dist/pool-helpers.d.ts +2 -0
- package/dist/pool-helpers.d.ts.map +1 -1
- package/dist/pool-helpers.js +17 -3
- package/dist/pool-helpers.js.map +1 -1
- package/dist/role-model.d.ts +178 -0
- package/dist/role-model.d.ts.map +1 -0
- package/dist/role-model.js +670 -0
- package/dist/role-model.js.map +1 -0
- package/dist/services/api-key-service.d.ts +12 -2
- package/dist/services/api-key-service.d.ts.map +1 -1
- package/dist/services/api-key-service.js +33 -9
- package/dist/services/api-key-service.js.map +1 -1
- package/dist/services/config-service.d.ts +15 -6
- package/dist/services/config-service.d.ts.map +1 -1
- package/dist/services/config-service.js +96 -50
- package/dist/services/config-service.js.map +1 -1
- package/dist/services/tenant-service.d.ts.map +1 -1
- package/dist/services/tenant-service.js +4 -4
- package/dist/services/tenant-service.js.map +1 -1
- package/dist/stratum-policies.d.ts +51 -0
- package/dist/stratum-policies.d.ts.map +1 -0
- package/dist/stratum-policies.js +217 -0
- package/dist/stratum-policies.js.map +1 -0
- package/dist/stratum-tables.d.ts.map +1 -1
- package/dist/stratum-tables.js +1 -0
- package/dist/stratum-tables.js.map +1 -1
- package/dist/stratum.d.ts +52 -7
- package/dist/stratum.d.ts.map +1 -1
- package/dist/stratum.js +75 -14
- package/dist/stratum.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -38,17 +38,44 @@ const permissions = await stratum.resolvePermissions(customer.id);
|
|
|
38
38
|
|
|
39
39
|
The `pool` is **borrowed, not owned**: Stratum never creates or closes it. With `autoMigrate: true`, `initialize()` runs the schema migrations on first start; leave it off and manage migrations yourself via `migrate`.
|
|
40
40
|
|
|
41
|
+
## Admin pool and the control role (1.8, opt-in)
|
|
42
|
+
|
|
43
|
+
Migration 032 adds a NOLOGIN **control role** (`stratum_control` by default). Every Stratum table has a `stratum_control_plane` policy for that role, so a login that is a member of it reaches every row, and a session cannot become a member by setting anything. Give the library its own pool on such a login, and keep `pool` for your application:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const adminPool = new Pool({ connectionString: process.env.DATABASE_ADMIN_URL }); // member of stratum_control
|
|
47
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL }); // the application role
|
|
48
|
+
const stratum = new Stratum({ adminPool, pool, autoMigrate: true, enforceRls: true });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Every library query and `autoMigrate` run on `adminPool`. The admin login needs neither SUPERUSER nor BYPASSRLS.
|
|
52
|
+
- `initialize()` checks both logins: the admin login must be a member of the control role; the application login must not be a superuser, have BYPASSRLS, be a member of the control role, own Stratum tables or functions, write any Stratum table, or read `api_keys`, `webhooks`, `regions` or `stratum_security`. It warns about each problem; with `enforceRls: true` a problem with the application login throws.
|
|
53
|
+
- `bootstrapRolesSql({ adminRole, appRole, controlRole })` returns the idempotent SQL a database administrator runs once (and again after a migration that adds a table): it creates the control role, makes the admin login a member, and limits the application login to `SELECT` on the read-list tables (`APP_READ_TABLES`). `stratum db roles` (`@stratum-hq/cli`) prints it, or applies it with `--apply`. It first stops when the Stratum tables carry objects the migrations did not create (rules, triggers or defaults that call other functions, changed Stratum functions), because such objects would run with the rights of the login that applies it, and later of the admin login.
|
|
54
|
+
- Applying the control role (`stratum_apply_control_role()`, run by migration 032 and by the bootstrap SQL) resets row-level security on every Stratum table: it drops all their policies, enables and forces RLS, and re-creates the canonical policies. `stratumPolicyDrift(pool)` reports how a database differs from that set; `stratum doctor` shows it.
|
|
55
|
+
- `inspectRoleModel({ appPool, adminPool })` returns the role-model checks as data, without logging, for your own health checks. `stratum doctor` and `stratum health` use it.
|
|
56
|
+
- Migration 032 creates and joins the control role itself only with the opt-in `applyControlRole: true` (`migrate()`, `migrateAllSchemas()`; `autoMigrate` sets it when it runs on `adminPool`), or when the migrating login is a superuser or already a member, and only when the migrating role can (CREATEROLE, or ADMIN on an existing role). Set the opt-in only for the admin login: it makes the migrating login a member of the control role. Otherwise 032 still completes: it applies everything that needs no role, skips the control role with a WARNING that prints the bootstrap SQL, and the database keeps the pre-1.8 behavior. `initialize()` and `stratum doctor` then report the hardening as not active. Running the bootstrap SQL as a superuser later applies it through the idempotent `stratum_apply_control_role(role_name, schema)` function that 032 installs.
|
|
57
|
+
- Migrations 029 and 031 created helper functions with `SET app.*` clauses, which PostgreSQL accepts only from a superuser. When the migrating role is not a superuser, `migrate()` drops those clause lines from these two files; 032 re-creates both functions without them when it applies the control role.
|
|
58
|
+
- The control role name: `migrate({ controlRole })`, `migrateAllSchemas({ controlRole })` and `new Stratum({ controlRole })` set it, and so does `ALTER DATABASE ... SET stratum.control_role = '...'` for every session of one database. Without either, the migration reuses the role the database already has, else `stratum_control`. Roles are cluster-wide, so give databases that must stay apart their own name.
|
|
59
|
+
- The legacy `app.bypass_rls` path stays open while `stratum_security.legacy_guc_bypass` is true, the 1.8 default. Once every client of the database uses `adminPool`, close it as a member of the control role with `stratum db lock` (or `UPDATE stratum_security SET legacy_guc_bypass = false`).
|
|
60
|
+
- Known limit: the tenant context is still the session setting `app.current_tenant_id`, so SQL that runs as the application login can choose a tenant id it knows and read that tenant's rows in the read-list tables. The role model keeps credentials and the bypass out of its reach, not a chosen tenant context.
|
|
61
|
+
- Step by step, including managed PostgreSQL: the [hardening guide](https://docs.stratum-hq.org/guides/hardening-roles/).
|
|
62
|
+
- Without `adminPool`, Stratum behaves as before and logs a deprecation warning once. `adminPool` becomes required in 2.0, which also removes the legacy path.
|
|
63
|
+
|
|
64
|
+
### API key hashes
|
|
65
|
+
|
|
66
|
+
Keys created before `STRATUM_API_KEY_HMAC_SECRET` was set carry an unkeyed SHA-256 hash (version 1). In 1.x, `validateApiKey` still accepts them by default (`allowLegacyKeyHashes: true`) and re-hashes each one with HMAC when it authenticates, logging a warning once per process. Set `allowLegacyKeyHashes: false` to accept only HMAC hashes once the secret is set; 2.0 makes that the default. Before upgrading to 2.0, rotate the keys that still have a version 1 hash (`SELECT id, name FROM api_keys WHERE hash_version = 1 AND revoked_at IS NULL`).
|
|
67
|
+
|
|
41
68
|
## API Summary
|
|
42
69
|
|
|
43
70
|
The `Stratum` instance covers the full tenant lifecycle:
|
|
44
71
|
|
|
45
72
|
- **Tenants**: `createTenant`, `getTenant`, `listTenants`, `updateTenant`, `moveTenant`, `getAncestors`, `getDescendants`, `batchCreateTenants`
|
|
46
|
-
- **Config**: `resolveConfig`, `setConfig`, `deleteConfig`, `batchSetConfig`, `diffConfig`
|
|
73
|
+
- **Config**: `resolveConfig`, `setConfig`, `deleteConfig`, `batchSetConfig`, `diffConfig`. A sensitive value inherited from an ancestor resolves masked (`value: null`, `masked: true`); pass `{ revealSensitive: true }` in trusted server code that needs the secret.
|
|
47
74
|
- **Permissions & ABAC**: `resolvePermissions`, `createPermission`, `createAbacPolicy`, `evaluateAbac`
|
|
48
75
|
- **API keys & roles**: `createApiKey`, `validateApiKey`, `rotateApiKey`, `createRole`, `assignRoleToKey`
|
|
49
76
|
- **Webhooks & audit**: `createWebhook`, `testWebhook`, `queryAuditLogs`, `listFailedDeliveries`
|
|
50
77
|
- **GDPR & regions**: `exportTenantData`, `purgeTenant`, `grantConsent`, `createRegion`, `migrateRegion`
|
|
51
|
-
- **Usage metering**: `recordUsage`, `aggregateUsage` (see [docs/usage-metering.md](
|
|
78
|
+
- **Usage metering**: `recordUsage`, `aggregateUsage` (see [docs/usage-metering.md](https://github.com/stratum-hq/Stratum/blob/main/docs/usage-metering.md))
|
|
52
79
|
|
|
53
80
|
Low-level pool helpers are also exported:
|
|
54
81
|
|
package/dist/crypto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AA+HA,qFAAqF;AACrF,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;;2FAG2F;AAC3F,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAkBjD;AAED;;0EAE0E;AAC1E,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvG;AAED;;+EAE+E;AAC/E,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM9G"}
|
package/dist/crypto.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.decrypt = decrypt;
|
|
|
8
8
|
exports.encryptWithKeyMaterial = encryptWithKeyMaterial;
|
|
9
9
|
exports.decryptWithKeyMaterial = decryptWithKeyMaterial;
|
|
10
10
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
11
|
+
const core_1 = require("@stratum-hq/core");
|
|
11
12
|
const ALGORITHM = "aes-256-gcm";
|
|
12
13
|
const IV_LENGTH = 12;
|
|
13
14
|
const AUTH_TAG_LENGTH = 16;
|
|
@@ -25,30 +26,65 @@ function nodeEnvRequiringKeyMaterial() {
|
|
|
25
26
|
// salt is not secret; the key material is. Every other environment requires an
|
|
26
27
|
// explicit STRATUM_HKDF_SALT.
|
|
27
28
|
const NON_PRODUCTION_DEFAULT_SALT = "stratum-non-production-hkdf-salt-v1";
|
|
29
|
+
// Development and test only fallback key material.
|
|
30
|
+
const NON_PRODUCTION_DEFAULT_KEY = "stratum-dev-key";
|
|
31
|
+
// The shortest STRATUM_ENCRYPTION_KEY accepted outside development and test.
|
|
32
|
+
const MIN_KEY_BYTES = 32;
|
|
33
|
+
// The format of STRATUM_HKDF_SALT. Buffer.from(salt, "hex") silently shortens any other string.
|
|
34
|
+
const HEX_SALT = /^(?:[0-9a-fA-F]{2})+$/;
|
|
28
35
|
const HKDF_SALT = (() => {
|
|
29
|
-
if (process.env.STRATUM_HKDF_SALT) {
|
|
30
|
-
return Buffer.from(process.env.STRATUM_HKDF_SALT, "hex");
|
|
31
|
-
}
|
|
32
36
|
const strictEnv = nodeEnvRequiringKeyMaterial();
|
|
37
|
+
const salt = process.env.STRATUM_HKDF_SALT;
|
|
38
|
+
if (salt) {
|
|
39
|
+
if (!HEX_SALT.test(salt)) {
|
|
40
|
+
throw new Error("STRATUM_HKDF_SALT must be a non-empty, even-length hex string");
|
|
41
|
+
}
|
|
42
|
+
const bytes = Buffer.from(salt, "hex");
|
|
43
|
+
if (strictEnv && bytes.equals(Buffer.from(NON_PRODUCTION_DEFAULT_SALT, "utf8"))) {
|
|
44
|
+
throw new Error(`STRATUM_HKDF_SALT is the built-in development salt; set a random salt in ${strictEnv}`);
|
|
45
|
+
}
|
|
46
|
+
return bytes;
|
|
47
|
+
}
|
|
33
48
|
if (strictEnv) {
|
|
34
49
|
throw new Error(`STRATUM_HKDF_SALT must be set in ${strictEnv}`);
|
|
35
50
|
}
|
|
36
51
|
return Buffer.from(NON_PRODUCTION_DEFAULT_SALT, "utf8");
|
|
37
52
|
})();
|
|
53
|
+
// Outside development and test, the key is checked when the module loads, so
|
|
54
|
+
// a deployment without usable key material refuses to start instead of failing
|
|
55
|
+
// on its first sensitive operation.
|
|
56
|
+
(() => {
|
|
57
|
+
const strictEnv = nodeEnvRequiringKeyMaterial();
|
|
58
|
+
if (!strictEnv)
|
|
59
|
+
return;
|
|
60
|
+
const key = process.env.STRATUM_ENCRYPTION_KEY;
|
|
61
|
+
if (!key) {
|
|
62
|
+
throw new Error(`STRATUM_ENCRYPTION_KEY must be set in ${strictEnv}` +
|
|
63
|
+
(process.env.WEBHOOK_ENCRYPTION_KEY
|
|
64
|
+
? ". WEBHOOK_ENCRYPTION_KEY is read only in development and test; set STRATUM_ENCRYPTION_KEY to its value"
|
|
65
|
+
: ""));
|
|
66
|
+
}
|
|
67
|
+
if (key === NON_PRODUCTION_DEFAULT_KEY) {
|
|
68
|
+
throw new Error(`STRATUM_ENCRYPTION_KEY is the built-in development key; set random key material in ${strictEnv}`);
|
|
69
|
+
}
|
|
70
|
+
if (Buffer.byteLength(key, "utf8") < MIN_KEY_BYTES) {
|
|
71
|
+
throw new Error(`STRATUM_ENCRYPTION_KEY must be at least ${MIN_KEY_BYTES} bytes in ${strictEnv}`);
|
|
72
|
+
}
|
|
73
|
+
})();
|
|
38
74
|
function hkdfDeriveKey(keyMaterial, salt = HKDF_SALT, info = "stratum-aes-key") {
|
|
39
75
|
return Buffer.from(node_crypto_1.default.hkdfSync("sha256", Buffer.from(keyMaterial, "utf8"), salt, info, 32));
|
|
40
76
|
}
|
|
41
77
|
function getEncryptionKeyMaterial() {
|
|
42
|
-
const
|
|
78
|
+
const strictEnv = nodeEnvRequiringKeyMaterial();
|
|
79
|
+
// WEBHOOK_ENCRYPTION_KEY is a legacy name, read only in development and test.
|
|
80
|
+
const envKey = process.env.STRATUM_ENCRYPTION_KEY ?? (strictEnv ? undefined : process.env.WEBHOOK_ENCRYPTION_KEY);
|
|
43
81
|
if (envKey) {
|
|
44
82
|
return envKey;
|
|
45
83
|
}
|
|
46
|
-
const strictEnv = nodeEnvRequiringKeyMaterial();
|
|
47
84
|
if (strictEnv) {
|
|
48
85
|
throw new Error(`STRATUM_ENCRYPTION_KEY must be set in ${strictEnv}`);
|
|
49
86
|
}
|
|
50
|
-
|
|
51
|
-
return "stratum-dev-key";
|
|
87
|
+
return NON_PRODUCTION_DEFAULT_KEY;
|
|
52
88
|
}
|
|
53
89
|
function getEncryptionKey() {
|
|
54
90
|
return hkdfDeriveKey(getEncryptionKeyMaterial());
|
|
@@ -76,7 +112,7 @@ function decryptWithKey(encrypted, key) {
|
|
|
76
112
|
[ivHex, authTagHex, ciphertextHex] = parts;
|
|
77
113
|
}
|
|
78
114
|
else {
|
|
79
|
-
throw new
|
|
115
|
+
throw new core_1.DecryptionError("format");
|
|
80
116
|
}
|
|
81
117
|
const iv = Buffer.from(ivHex, "hex");
|
|
82
118
|
const authTag = Buffer.from(authTagHex, "hex");
|
|
@@ -105,10 +141,14 @@ function decrypt(encrypted) {
|
|
|
105
141
|
return decryptWithKey(encrypted, deriveKey(previousKey || getEncryptionKeyMaterial(), previousSalt));
|
|
106
142
|
}
|
|
107
143
|
catch {
|
|
108
|
-
// Both pairs failed:
|
|
144
|
+
// Both pairs failed: report the original error
|
|
109
145
|
}
|
|
110
146
|
}
|
|
111
|
-
|
|
147
|
+
if (err instanceof core_1.DecryptionError)
|
|
148
|
+
throw err;
|
|
149
|
+
// Node reports a failed GCM authentication as "Unsupported state or
|
|
150
|
+
// unable to authenticate data", which does not say what to check.
|
|
151
|
+
throw new core_1.DecryptionError("authentication", { cause: err });
|
|
112
152
|
}
|
|
113
153
|
}
|
|
114
154
|
/** Encrypts a value with the key derived from `keyMaterial` and `saltHex`.
|
package/dist/crypto.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,15 +2,19 @@ export { Stratum, type StratumOptions } from "./stratum.js";
|
|
|
2
2
|
export { migrate, type MigrateOptions } from "./migrate.js";
|
|
3
3
|
export { migrateAllSchemas, type MigrateSchemasOptions, type MigrateSchemasResult, } from "./migrate-schemas.js";
|
|
4
4
|
export { STRATUM_TABLES } from "./stratum-tables.js";
|
|
5
|
+
export { STRATUM_CONTROL_ROLE } from "./migration-sql.js";
|
|
6
|
+
export { PINNED_SEARCH_PATH, pinnedQuery, schemaOfTable, withPinnedSearchPath } from "./pinned-query.js";
|
|
7
|
+
export { stratumPolicyDrift, STRATUM_RLS_TABLES, type PolicyDriftOptions, } from "./stratum-policies.js";
|
|
8
|
+
export { bootstrapRolesSql, inspectRoleModel, APP_READ_TABLES, type BootstrapRolesOptions, type RoleModelReport, type InspectRoleModelOptions, } from "./role-model.js";
|
|
5
9
|
export { withClient, withTransaction } from "./pool-helpers.js";
|
|
6
10
|
export { runScopedJob, type ScopedJobFn, type RunScopedJobOptions, } from "./scoped-job.js";
|
|
7
11
|
export { verifyWebhookSignature, signWebhookPayload, DEFAULT_WEBHOOK_TOLERANCE_SECONDS, type VerifyWebhookOptions, } from "./webhook-signature.js";
|
|
8
12
|
export { traced, getTracer, isTracingEnabled } from "./telemetry.js";
|
|
9
|
-
export { ErrorCode, StratumError, TenantNotFoundError, TenantAlreadyExistsError, TenantHasChildrenError, TenantCycleDetectedError, TenantArchivedError, TenantSuspendedError, TenantPendingError, TenantProvisioningError, InvalidTenantStateError, TenantContextNotFoundError, IsolationViolationError, IsolationStrategyUnsupportedError, PermissionLockedError, PermissionNotFoundError, PermissionRevocationDeniedError, ConfigLockedError, ConfigNotFoundError, ValidationError, UnauthorizedError, ForbiddenError, RegionInUseError, RegionNotActiveError, WebhookNotFoundError, WebhookDeliveryError, WebhookUrlValidationError, RegionNotFoundError, AbacPolicyNotFoundError, InvalidAbacOperatorError, AbacPolicyLockedError, } from "@stratum-hq/core";
|
|
10
|
-
export type { TenantNode, TenantContext, CreateTenantInput, UpdateTenantInput, MoveTenantInput, PaginationInput, PaginatedResult, ConfigEntry, SetConfigInput, BatchSetConfigEntry, ResolvedConfigEntry, ResolvedConfig, PermissionPolicy, CreatePermissionInput, UpdatePermissionInput, ResolvedPermission, } from "@stratum-hq/core";
|
|
13
|
+
export { ErrorCode, StratumError, TenantNotFoundError, TenantAlreadyExistsError, TenantHasChildrenError, TenantCycleDetectedError, TenantArchivedError, TenantSuspendedError, TenantPendingError, TenantProvisioningError, InvalidTenantStateError, TenantContextNotFoundError, IsolationViolationError, IsolationStrategyUnsupportedError, PermissionLockedError, PermissionNotFoundError, PermissionRevocationDeniedError, ConfigLockedError, ConfigNotFoundError, ValidationError, UnauthorizedError, ForbiddenError, RegionInUseError, RegionNotActiveError, WebhookNotFoundError, WebhookDeliveryError, WebhookUrlValidationError, RegionNotFoundError, AbacPolicyNotFoundError, InvalidAbacOperatorError, AbacPolicyLockedError, DecryptionError, } from "@stratum-hq/core";
|
|
14
|
+
export type { TenantNode, TenantContext, CreateTenantInput, UpdateTenantInput, MoveTenantInput, PaginationInput, PaginatedResult, ConfigEntry, SetConfigInput, BatchSetConfigEntry, ResolvedConfigEntry, ResolveConfigOptions, ResolvedConfig, PermissionPolicy, CreatePermissionInput, UpdatePermissionInput, ResolvedPermission, } from "@stratum-hq/core";
|
|
11
15
|
export { RateLimiter, MemoryRateLimitStore } from "./services/rate-limit-service.js";
|
|
12
16
|
export type { RateLimit, RateLimitResult, RateLimitState, RateLimitStore, RateLimitResolver, RateLimiterOptions, } from "./services/rate-limit-service.js";
|
|
13
|
-
export type { ApiKeyRecord, CreatedApiKey, ValidatedApiKey, CreateApiKeyOptions } from "./services/api-key-service.js";
|
|
17
|
+
export type { ApiKeyRecord, CreatedApiKey, ValidatedApiKey, CreateApiKeyOptions, ValidateApiKeyOptions, } from "./services/api-key-service.js";
|
|
14
18
|
export type { BatchCreateResult } from "./services/tenant-service.js";
|
|
15
19
|
export type { KeyRotationResult, KeyRotationSalts, KeyRotationUnreadableRow } from "./services/key-rotation-service.js";
|
|
16
20
|
export type { DeliveryStats } from "./services/event-service.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EACL,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,EACL,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,iCAAiC,EACjC,KAAK,oBAAoB,GAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAKrE,OAAO,EACL,SAAS,EACT,YAAY,EACZ,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,0BAA0B,EAC1B,uBAAuB,EACvB,iCAAiC,EACjC,qBAAqB,EACrB,uBAAuB,EACvB,+BAA+B,EAC/B,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,EACzB,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.WebhookUrlValidationError = exports.WebhookDeliveryError = exports.WebhookNotFoundError = exports.RegionNotActiveError = exports.RegionInUseError = exports.ForbiddenError = exports.UnauthorizedError = exports.ValidationError = exports.ConfigNotFoundError = exports.ConfigLockedError = exports.PermissionRevocationDeniedError = exports.PermissionNotFoundError = exports.PermissionLockedError = exports.IsolationStrategyUnsupportedError = exports.IsolationViolationError = exports.TenantContextNotFoundError = exports.InvalidTenantStateError = exports.TenantProvisioningError = exports.TenantPendingError = exports.TenantSuspendedError = exports.TenantArchivedError = exports.TenantCycleDetectedError = exports.TenantHasChildrenError = exports.TenantAlreadyExistsError = exports.TenantNotFoundError = exports.StratumError = exports.ErrorCode = exports.isTracingEnabled = exports.getTracer = exports.traced = exports.DEFAULT_WEBHOOK_TOLERANCE_SECONDS = exports.signWebhookPayload = exports.verifyWebhookSignature = exports.runScopedJob = exports.withTransaction = exports.withClient = exports.APP_READ_TABLES = exports.inspectRoleModel = exports.bootstrapRolesSql = exports.STRATUM_RLS_TABLES = exports.stratumPolicyDrift = exports.withPinnedSearchPath = exports.schemaOfTable = exports.pinnedQuery = exports.PINNED_SEARCH_PATH = exports.STRATUM_CONTROL_ROLE = exports.STRATUM_TABLES = exports.migrateAllSchemas = exports.migrate = exports.Stratum = void 0;
|
|
4
|
+
exports.noopLogger = exports.defaultLogger = exports.MemoryRateLimitStore = exports.RateLimiter = exports.DecryptionError = exports.AbacPolicyLockedError = exports.InvalidAbacOperatorError = exports.AbacPolicyNotFoundError = exports.RegionNotFoundError = void 0;
|
|
4
5
|
var stratum_js_1 = require("./stratum.js");
|
|
5
6
|
Object.defineProperty(exports, "Stratum", { enumerable: true, get: function () { return stratum_js_1.Stratum; } });
|
|
6
7
|
var migrate_js_1 = require("./migrate.js");
|
|
@@ -9,6 +10,20 @@ var migrate_schemas_js_1 = require("./migrate-schemas.js");
|
|
|
9
10
|
Object.defineProperty(exports, "migrateAllSchemas", { enumerable: true, get: function () { return migrate_schemas_js_1.migrateAllSchemas; } });
|
|
10
11
|
var stratum_tables_js_1 = require("./stratum-tables.js");
|
|
11
12
|
Object.defineProperty(exports, "STRATUM_TABLES", { enumerable: true, get: function () { return stratum_tables_js_1.STRATUM_TABLES; } });
|
|
13
|
+
var migration_sql_js_1 = require("./migration-sql.js");
|
|
14
|
+
Object.defineProperty(exports, "STRATUM_CONTROL_ROLE", { enumerable: true, get: function () { return migration_sql_js_1.STRATUM_CONTROL_ROLE; } });
|
|
15
|
+
var pinned_query_js_1 = require("./pinned-query.js");
|
|
16
|
+
Object.defineProperty(exports, "PINNED_SEARCH_PATH", { enumerable: true, get: function () { return pinned_query_js_1.PINNED_SEARCH_PATH; } });
|
|
17
|
+
Object.defineProperty(exports, "pinnedQuery", { enumerable: true, get: function () { return pinned_query_js_1.pinnedQuery; } });
|
|
18
|
+
Object.defineProperty(exports, "schemaOfTable", { enumerable: true, get: function () { return pinned_query_js_1.schemaOfTable; } });
|
|
19
|
+
Object.defineProperty(exports, "withPinnedSearchPath", { enumerable: true, get: function () { return pinned_query_js_1.withPinnedSearchPath; } });
|
|
20
|
+
var stratum_policies_js_1 = require("./stratum-policies.js");
|
|
21
|
+
Object.defineProperty(exports, "stratumPolicyDrift", { enumerable: true, get: function () { return stratum_policies_js_1.stratumPolicyDrift; } });
|
|
22
|
+
Object.defineProperty(exports, "STRATUM_RLS_TABLES", { enumerable: true, get: function () { return stratum_policies_js_1.STRATUM_RLS_TABLES; } });
|
|
23
|
+
var role_model_js_1 = require("./role-model.js");
|
|
24
|
+
Object.defineProperty(exports, "bootstrapRolesSql", { enumerable: true, get: function () { return role_model_js_1.bootstrapRolesSql; } });
|
|
25
|
+
Object.defineProperty(exports, "inspectRoleModel", { enumerable: true, get: function () { return role_model_js_1.inspectRoleModel; } });
|
|
26
|
+
Object.defineProperty(exports, "APP_READ_TABLES", { enumerable: true, get: function () { return role_model_js_1.APP_READ_TABLES; } });
|
|
12
27
|
var pool_helpers_js_1 = require("./pool-helpers.js");
|
|
13
28
|
Object.defineProperty(exports, "withClient", { enumerable: true, get: function () { return pool_helpers_js_1.withClient; } });
|
|
14
29
|
Object.defineProperty(exports, "withTransaction", { enumerable: true, get: function () { return pool_helpers_js_1.withTransaction; } });
|
|
@@ -57,6 +72,7 @@ Object.defineProperty(exports, "RegionNotFoundError", { enumerable: true, get: f
|
|
|
57
72
|
Object.defineProperty(exports, "AbacPolicyNotFoundError", { enumerable: true, get: function () { return core_1.AbacPolicyNotFoundError; } });
|
|
58
73
|
Object.defineProperty(exports, "InvalidAbacOperatorError", { enumerable: true, get: function () { return core_1.InvalidAbacOperatorError; } });
|
|
59
74
|
Object.defineProperty(exports, "AbacPolicyLockedError", { enumerable: true, get: function () { return core_1.AbacPolicyLockedError; } });
|
|
75
|
+
Object.defineProperty(exports, "DecryptionError", { enumerable: true, get: function () { return core_1.DecryptionError; } });
|
|
60
76
|
var rate_limit_service_js_1 = require("./services/rate-limit-service.js");
|
|
61
77
|
Object.defineProperty(exports, "RateLimiter", { enumerable: true, get: function () { return rate_limit_service_js_1.RateLimiter; } });
|
|
62
78
|
Object.defineProperty(exports, "MemoryRateLimitStore", { enumerable: true, get: function () { return rate_limit_service_js_1.MemoryRateLimitStore; } });
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;AAAA,2CAA4D;AAAnD,qGAAA,OAAO,OAAA;AAChB,2CAA4D;AAAnD,qGAAA,OAAO,OAAA;AAChB,2DAI8B;AAH5B,uHAAA,iBAAiB,OAAA;AAInB,yDAAqD;AAA5C,mHAAA,cAAc,OAAA;AACvB,uDAA0D;AAAjD,wHAAA,oBAAoB,OAAA;AAC7B,qDAAyG;AAAhG,qHAAA,kBAAkB,OAAA;AAAE,8GAAA,WAAW,OAAA;AAAE,gHAAA,aAAa,OAAA;AAAE,uHAAA,oBAAoB,OAAA;AAC7E,6DAI+B;AAH7B,yHAAA,kBAAkB,OAAA;AAClB,yHAAA,kBAAkB,OAAA;AAGpB,iDAOyB;AANvB,kHAAA,iBAAiB,OAAA;AACjB,iHAAA,gBAAgB,OAAA;AAChB,gHAAA,eAAe,OAAA;AAKjB,qDAAgE;AAAvD,6GAAA,UAAU,OAAA;AAAE,kHAAA,eAAe,OAAA;AACpC,iDAIyB;AAHvB,6GAAA,YAAY,OAAA;AAId,+DAKgC;AAJ9B,8HAAA,sBAAsB,OAAA;AACtB,0HAAA,kBAAkB,OAAA;AAClB,yIAAA,iCAAiC,OAAA;AAGnC,+CAAqE;AAA5D,sGAAA,MAAM,OAAA;AAAE,yGAAA,SAAS,OAAA;AAAE,gHAAA,gBAAgB,OAAA;AAE5C,kFAAkF;AAClF,+EAA+E;AAC/E,8EAA8E;AAC9E,yCAiC0B;AAhCxB,iGAAA,SAAS,OAAA;AACT,oGAAA,YAAY,OAAA;AACZ,2GAAA,mBAAmB,OAAA;AACnB,gHAAA,wBAAwB,OAAA;AACxB,8GAAA,sBAAsB,OAAA;AACtB,gHAAA,wBAAwB,OAAA;AACxB,2GAAA,mBAAmB,OAAA;AACnB,4GAAA,oBAAoB,OAAA;AACpB,0GAAA,kBAAkB,OAAA;AAClB,+GAAA,uBAAuB,OAAA;AACvB,+GAAA,uBAAuB,OAAA;AACvB,kHAAA,0BAA0B,OAAA;AAC1B,+GAAA,uBAAuB,OAAA;AACvB,yHAAA,iCAAiC,OAAA;AACjC,6GAAA,qBAAqB,OAAA;AACrB,+GAAA,uBAAuB,OAAA;AACvB,uHAAA,+BAA+B,OAAA;AAC/B,yGAAA,iBAAiB,OAAA;AACjB,2GAAA,mBAAmB,OAAA;AACnB,uGAAA,eAAe,OAAA;AACf,yGAAA,iBAAiB,OAAA;AACjB,sGAAA,cAAc,OAAA;AACd,wGAAA,gBAAgB,OAAA;AAChB,4GAAA,oBAAoB,OAAA;AACpB,4GAAA,oBAAoB,OAAA;AACpB,4GAAA,oBAAoB,OAAA;AACpB,iHAAA,yBAAyB,OAAA;AACzB,2GAAA,mBAAmB,OAAA;AACnB,+GAAA,uBAAuB,OAAA;AACvB,gHAAA,wBAAwB,OAAA;AACxB,6GAAA,qBAAqB,OAAA;AACrB,uGAAA,eAAe,OAAA;AAwBjB,0EAAqF;AAA5E,oHAAA,WAAW,OAAA;AAAE,6HAAA,oBAAoB,OAAA;AAiC1C,yCAAwD;AAA/C,0GAAA,aAAa,OAAA;AAAE,uGAAA,UAAU,OAAA"}
|
|
@@ -4,6 +4,10 @@ export interface MigrateSchemasOptions {
|
|
|
4
4
|
concurrency?: number;
|
|
5
5
|
onProgress?: (schema: string, index: number, total: number) => void;
|
|
6
6
|
enforceRls?: boolean;
|
|
7
|
+
/** The control role for migration 032; see MigrateOptions.controlRole. */
|
|
8
|
+
controlRole?: string;
|
|
9
|
+
/** Lets migration 032 grant the control role to the login of `pool`; see MigrateOptions.applyControlRole. */
|
|
10
|
+
applyControlRole?: boolean;
|
|
7
11
|
}
|
|
8
12
|
export interface MigrateSchemasResult {
|
|
9
13
|
succeeded: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-schemas.d.ts","sourceRoot":"","sources":["../src/migrate-schemas.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"migrate-schemas.d.ts","sourceRoot":"","sources":["../src/migrate-schemas.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,IAAI,CAAC;AAKpB,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6GAA6G;IAC7G,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAA;KAAE,EAAE,CAAC;CAC5C;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAoE/B"}
|
package/dist/migrate-schemas.js
CHANGED
|
@@ -7,6 +7,8 @@ exports.migrateAllSchemas = migrateAllSchemas;
|
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
const pool_helpers_js_1 = require("./pool-helpers.js");
|
|
10
|
+
const migration_sql_js_1 = require("./migration-sql.js");
|
|
11
|
+
const migrate_js_1 = require("./migrate.js");
|
|
10
12
|
/**
|
|
11
13
|
* Discover all tenant schemas (tenants with SCHEMA_PER_TENANT isolation)
|
|
12
14
|
* and run the standard migration set against each one.
|
|
@@ -14,7 +16,12 @@ const pool_helpers_js_1 = require("./pool-helpers.js");
|
|
|
14
16
|
* Continues on error: schemas that fail are collected in result.failed[].
|
|
15
17
|
*/
|
|
16
18
|
async function migrateAllSchemas(options) {
|
|
17
|
-
const { pool, concurrency = 5, onProgress, enforceRls } = options;
|
|
19
|
+
const { pool, concurrency = 5, onProgress, enforceRls, controlRole, applyControlRole } = options;
|
|
20
|
+
if (controlRole !== undefined)
|
|
21
|
+
(0, migration_sql_js_1.assertRoleName)(controlRole, "control role");
|
|
22
|
+
if (enforceRls) {
|
|
23
|
+
await (0, migrate_js_1.assertRoleSubjectToRls)(pool);
|
|
24
|
+
}
|
|
18
25
|
// Discover tenant schemas. The tenants registry is under FORCE RLS, so
|
|
19
26
|
// discovery runs under the control-plane bypass like every other lib read.
|
|
20
27
|
const { rows } = await (0, pool_helpers_js_1.withClient)(pool, (client) => client.query(`SELECT slug FROM tenants WHERE isolation_strategy = 'SCHEMA_PER_TENANT' AND (deleted_at IS NULL) ORDER BY slug`));
|
|
@@ -32,15 +39,16 @@ async function migrateAllSchemas(options) {
|
|
|
32
39
|
.readdirSync(migrationsDir)
|
|
33
40
|
.filter((f) => f.endsWith(".sql"))
|
|
34
41
|
.sort();
|
|
35
|
-
const
|
|
42
|
+
const superuser = await (0, migration_sql_js_1.isSuperuser)(pool);
|
|
43
|
+
const migrations = files.map((file) => ({
|
|
36
44
|
name: file,
|
|
37
|
-
sql: node_fs_1.default.readFileSync(node_path_1.default.join(migrationsDir, file), "utf-8"),
|
|
45
|
+
sql: (0, migration_sql_js_1.migrationSql)(file, node_fs_1.default.readFileSync(node_path_1.default.join(migrationsDir, file), "utf-8"), superuser),
|
|
38
46
|
}));
|
|
39
47
|
// Process schemas in chunks of `concurrency` size
|
|
40
48
|
let completed = 0;
|
|
41
49
|
for (let i = 0; i < schemas.length; i += concurrency) {
|
|
42
50
|
const chunk = schemas.slice(i, i + concurrency);
|
|
43
|
-
const results = await Promise.allSettled(chunk.map((schema) => migrateSchema(pool, schema,
|
|
51
|
+
const results = await Promise.allSettled(chunk.map((schema) => migrateSchema(pool, schema, migrations, enforceRls, controlRole, applyControlRole)));
|
|
44
52
|
for (let j = 0; j < results.length; j++) {
|
|
45
53
|
const schema = chunk[j];
|
|
46
54
|
const outcome = results[j];
|
|
@@ -60,7 +68,7 @@ async function migrateAllSchemas(options) {
|
|
|
60
68
|
}
|
|
61
69
|
return result;
|
|
62
70
|
}
|
|
63
|
-
async function migrateSchema(pool, schema, migrations, enforceRls) {
|
|
71
|
+
async function migrateSchema(pool, schema, migrations, enforceRls, controlRole, applyControlRole) {
|
|
64
72
|
// Use a hash of the schema name for a unique advisory lock key per schema
|
|
65
73
|
const lockKey = hashSchemaLock(schema);
|
|
66
74
|
const quoted = quoteIdent(schema);
|
|
@@ -69,7 +77,7 @@ async function migrateSchema(pool, schema, migrations, enforceRls) {
|
|
|
69
77
|
try {
|
|
70
78
|
await client.query("BEGIN");
|
|
71
79
|
// Advisory lock scoped to this schema
|
|
72
|
-
await client.query(`SELECT pg_advisory_xact_lock($1)`, [lockKey]);
|
|
80
|
+
await client.query(`SELECT pg_catalog.pg_advisory_xact_lock($1::pg_catalog.int8)`, [lockKey]);
|
|
73
81
|
// Set search_path to the tenant schema. public stays on the path because
|
|
74
82
|
// the migration SQL uses extension types and functions installed there
|
|
75
83
|
// (ltree, uuid_generate_v4).
|
|
@@ -93,6 +101,8 @@ async function migrateSchema(pool, schema, migrations, enforceRls) {
|
|
|
93
101
|
if (enforceRls) {
|
|
94
102
|
await client.query("SET LOCAL stratum.enforce_rls = 'on'");
|
|
95
103
|
}
|
|
104
|
+
await (0, migration_sql_js_1.setControlRole)(client, controlRole);
|
|
105
|
+
await (0, migration_sql_js_1.setApplyControlRole)(client, applyControlRole);
|
|
96
106
|
await client.query(migration.sql);
|
|
97
107
|
await client.query(`INSERT INTO ${quoted}._migrations (name) VALUES ($1)`, [
|
|
98
108
|
migration.name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-schemas.js","sourceRoot":"","sources":["../src/migrate-schemas.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"migrate-schemas.js","sourceRoot":"","sources":["../src/migrate-schemas.ts"],"names":[],"mappings":"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"}
|
package/dist/migrate.d.ts
CHANGED
|
@@ -3,6 +3,30 @@ export interface MigrateOptions {
|
|
|
3
3
|
pool: pg.Pool;
|
|
4
4
|
/** When true, SET stratum.enforce_rls = 'on' before running migrations (hard-fail on BYPASSRLS). */
|
|
5
5
|
enforceRls?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* The name of the NOLOGIN control role that migration 032 creates and names
|
|
8
|
+
* in its policies. Default: the stratum.control_role setting of the session
|
|
9
|
+
* (for example from ALTER DATABASE ... SET), then the role the database
|
|
10
|
+
* already uses, then `stratum_control`. Roles are cluster-wide, so set it
|
|
11
|
+
* when several databases on one server need separate control roles.
|
|
12
|
+
*/
|
|
13
|
+
controlRole?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Lets migration 032 create the control role and grant it to the login of
|
|
16
|
+
* `pool`, then apply it. Set it only when `pool` is the library's admin
|
|
17
|
+
* login (the login behind adminPool), never the application's: a member
|
|
18
|
+
* of the control role passes every Stratum policy. Stratum's autoMigrate
|
|
19
|
+
* sets it when it runs on adminPool. Without it, 032 applies the control
|
|
20
|
+
* role only when that grants nothing new (a superuser, or a login that is
|
|
21
|
+
* already a member), and otherwise warns with the SQL to run.
|
|
22
|
+
*/
|
|
23
|
+
applyControlRole?: boolean;
|
|
6
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Throws when the connecting role has BYPASSRLS, so row-level security would
|
|
27
|
+
* not apply to it. Used by every enforceRls entry point, so the check does not
|
|
28
|
+
* depend on migration 001 running.
|
|
29
|
+
*/
|
|
30
|
+
export declare function assertRoleSubjectToRls(pool: pg.Pool): Promise<void>;
|
|
7
31
|
export declare function migrate(options: MigrateOptions): Promise<void>;
|
|
8
32
|
//# sourceMappingURL=migrate.d.ts.map
|
package/dist/migrate.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,IAAI,CAAC;AAIpB,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC;IACd,oGAAoG;IACpG,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAUzE;AAED,wBAAsB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAoFpE"}
|
package/dist/migrate.js
CHANGED
|
@@ -3,14 +3,42 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.assertRoleSubjectToRls = assertRoleSubjectToRls;
|
|
6
7
|
exports.migrate = migrate;
|
|
7
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const migration_sql_js_1 = require("./migration-sql.js");
|
|
11
|
+
const pinned_query_js_1 = require("./pinned-query.js");
|
|
12
|
+
/**
|
|
13
|
+
* Throws when the connecting role has BYPASSRLS, so row-level security would
|
|
14
|
+
* not apply to it. Used by every enforceRls entry point, so the check does not
|
|
15
|
+
* depend on migration 001 running.
|
|
16
|
+
*/
|
|
17
|
+
async function assertRoleSubjectToRls(pool) {
|
|
18
|
+
const { rows } = await pool.query("SELECT current_user AS role, rolbypassrls AS bypass FROM pg_catalog.pg_roles WHERE rolname = current_user");
|
|
19
|
+
if (rows[0]?.bypass) {
|
|
20
|
+
throw new Error(`SECURITY: Application role "${rows[0].role}" has BYPASSRLS privilege. ` +
|
|
21
|
+
"Connect as a dedicated role without BYPASSRLS, or turn enforceRls off for development.");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
9
24
|
async function migrate(options) {
|
|
10
|
-
const { pool, enforceRls } = options;
|
|
25
|
+
const { pool, enforceRls, controlRole, applyControlRole } = options;
|
|
26
|
+
if (controlRole !== undefined)
|
|
27
|
+
(0, migration_sql_js_1.assertRoleName)(controlRole, "control role");
|
|
28
|
+
if (enforceRls) {
|
|
29
|
+
await assertRoleSubjectToRls(pool);
|
|
30
|
+
}
|
|
31
|
+
// The migrations create their objects in the first schema of the search
|
|
32
|
+
// path; the tracking table lives there too, named with that schema.
|
|
33
|
+
const current = await pool.query("SELECT pg_catalog.current_schema() AS schema");
|
|
34
|
+
const schema = current.rows[0]?.schema;
|
|
35
|
+
if (!schema) {
|
|
36
|
+
throw new Error("[stratum] migrate: no schema on the search path of the migrating login to create the Stratum tables in");
|
|
37
|
+
}
|
|
38
|
+
const migrationsTable = `${(0, pinned_query_js_1.quoteIdentifier)(schema)}._migrations`;
|
|
11
39
|
// Create migrations tracking table
|
|
12
40
|
await pool.query(`
|
|
13
|
-
CREATE TABLE IF NOT EXISTS
|
|
41
|
+
CREATE TABLE IF NOT EXISTS ${migrationsTable} (
|
|
14
42
|
id SERIAL PRIMARY KEY,
|
|
15
43
|
name TEXT NOT NULL UNIQUE,
|
|
16
44
|
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
@@ -25,14 +53,22 @@ async function migrate(options) {
|
|
|
25
53
|
.readdirSync(migrationsDir)
|
|
26
54
|
.filter((f) => f.endsWith(".sql"))
|
|
27
55
|
.sort();
|
|
56
|
+
// Migrations 029 and 031 created helper functions with `SET app.*` clauses,
|
|
57
|
+
// which PostgreSQL accepts only from a superuser (or a role granted SET on
|
|
58
|
+
// the parameter). For any other migrating role, migrationSql() drops those
|
|
59
|
+
// clause lines from these two files only; migration 032 then re-creates
|
|
60
|
+
// both functions without them when it applies the control role. Until it
|
|
61
|
+
// does, they see only what their caller sees, which can only narrow what
|
|
62
|
+
// they return. See migration-sql.ts.
|
|
63
|
+
const superuser = await (0, migration_sql_js_1.isSuperuser)(pool);
|
|
28
64
|
for (const file of files) {
|
|
29
65
|
const client = await pool.connect();
|
|
30
66
|
try {
|
|
31
67
|
await client.query("BEGIN");
|
|
32
68
|
// Acquire advisory lock to prevent concurrent migrations
|
|
33
|
-
await client.query("SELECT pg_advisory_xact_lock(8675309)");
|
|
69
|
+
await client.query("SELECT pg_catalog.pg_advisory_xact_lock(8675309::pg_catalog.int8)");
|
|
34
70
|
// Re-check if already applied (after lock, to prevent TOCTOU race)
|
|
35
|
-
const { rows } = await client.query(
|
|
71
|
+
const { rows } = await client.query(`SELECT 1 FROM ${migrationsTable} WHERE name = $1`, [file]);
|
|
36
72
|
if (rows.length > 0) {
|
|
37
73
|
await client.query("COMMIT");
|
|
38
74
|
continue;
|
|
@@ -41,9 +77,11 @@ async function migrate(options) {
|
|
|
41
77
|
if (enforceRls) {
|
|
42
78
|
await client.query("SET LOCAL stratum.enforce_rls = 'on'");
|
|
43
79
|
}
|
|
44
|
-
|
|
80
|
+
await (0, migration_sql_js_1.setControlRole)(client, controlRole);
|
|
81
|
+
await (0, migration_sql_js_1.setApplyControlRole)(client, applyControlRole);
|
|
82
|
+
const sql = (0, migration_sql_js_1.migrationSql)(file, node_fs_1.default.readFileSync(node_path_1.default.join(migrationsDir, file), "utf-8"), superuser);
|
|
45
83
|
await client.query(sql);
|
|
46
|
-
await client.query(
|
|
84
|
+
await client.query(`INSERT INTO ${migrationsTable} (name) VALUES ($1)`, [file]);
|
|
47
85
|
await client.query("COMMIT");
|
|
48
86
|
}
|
|
49
87
|
catch (err) {
|
package/dist/migrate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"migrate.js","sourceRoot":"","sources":["../src/migrate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type pg from "pg";
|
|
2
|
+
/** The default name of the NOLOGIN control role (migration 032). */
|
|
3
|
+
export declare const STRATUM_CONTROL_ROLE = "stratum_control";
|
|
4
|
+
/** A role name Stratum accepts: a plain lowercase identifier. */
|
|
5
|
+
export declare const ROLE_NAME: RegExp;
|
|
6
|
+
/** Throws when `role` is not a plain lowercase identifier. */
|
|
7
|
+
export declare function assertRoleName(role: string, what: string): void;
|
|
8
|
+
/** The SQL to run for one migration file. */
|
|
9
|
+
export declare function migrationSql(name: string, sql: string, superuser: boolean): string;
|
|
10
|
+
/** Whether the role of `client` is a superuser. */
|
|
11
|
+
export declare function isSuperuser(client: pg.PoolClient | pg.Pool): Promise<boolean>;
|
|
12
|
+
/**
|
|
13
|
+
* Sets stratum.control_role for the current transaction, which migration 032
|
|
14
|
+
* reads. Without a name the setting stays as the session has it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function setControlRole(client: pg.PoolClient, controlRole: string | undefined): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Sets stratum.apply_control_role = 'on' for the current transaction when
|
|
19
|
+
* `apply` is true. Migration 032 grants the control role to the migrating
|
|
20
|
+
* login only with this opt-in (or when the login is a superuser or already a
|
|
21
|
+
* member), because that login must be the library's admin login and never
|
|
22
|
+
* the application's.
|
|
23
|
+
*/
|
|
24
|
+
export declare function setApplyControlRole(client: pg.PoolClient, apply: boolean | undefined): Promise<void>;
|
|
25
|
+
//# sourceMappingURL=migration-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migration-sql.d.ts","sourceRoot":"","sources":["../src/migration-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AAEzB,oEAAoE;AACpE,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AAEtD,iEAAiE;AACjE,eAAO,MAAM,SAAS,QAA4B,CAAC;AAEnD,8DAA8D;AAC9D,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAI/D;AAgBD,6CAA6C;AAC7C,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAGlF;AAED,mDAAmD;AACnD,wBAAsB,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAKnF;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1G;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1G"}
|