@astram/guard 0.2.0 → 0.3.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/dist/auth/account.controller.d.ts +245 -0
- package/dist/auth/account.controller.d.ts.map +1 -0
- package/dist/auth/account.controller.js +673 -0
- package/dist/auth/account.controller.js.map +1 -0
- package/dist/auth/auth.controller.d.ts +219 -5
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +790 -12
- package/dist/auth/auth.controller.js.map +1 -0
- package/dist/auth/auth.module.d.ts +100 -2
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +299 -7
- package/dist/auth/auth.module.js.map +1 -0
- package/dist/auth/connections.js.map +1 -0
- package/dist/auth/dto.d.ts +16 -6
- package/dist/auth/dto.d.ts.map +1 -1
- package/dist/auth/dto.js.map +1 -0
- package/dist/auth/pending-mfa-store.js.map +1 -0
- package/dist/auth/persistence.module.js.map +1 -0
- package/dist/auth/ports/audit-sink.d.ts +47 -0
- package/dist/auth/ports/audit-sink.d.ts.map +1 -0
- package/dist/auth/ports/audit-sink.js +20 -0
- package/dist/auth/ports/audit-sink.js.map +1 -0
- package/dist/auth/ports/credential-writer.d.ts +23 -0
- package/dist/auth/ports/credential-writer.d.ts.map +1 -0
- package/dist/auth/ports/credential-writer.js +2 -0
- package/dist/auth/ports/credential-writer.js.map +1 -0
- package/dist/auth/ports/email-sender.d.ts +14 -0
- package/dist/auth/ports/email-sender.d.ts.map +1 -0
- package/dist/auth/ports/email-sender.js +2 -0
- package/dist/auth/ports/email-sender.js.map +1 -0
- package/dist/auth/ports/mfa-device-store.d.ts +45 -0
- package/dist/auth/ports/mfa-device-store.d.ts.map +1 -0
- package/dist/auth/ports/mfa-device-store.js +13 -0
- package/dist/auth/ports/mfa-device-store.js.map +1 -0
- package/dist/auth/ports/profile-writer.d.ts +12 -0
- package/dist/auth/ports/profile-writer.d.ts.map +1 -0
- package/dist/auth/ports/profile-writer.js +2 -0
- package/dist/auth/ports/profile-writer.js.map +1 -0
- package/dist/auth/ports/recovery-code-store.d.ts +10 -0
- package/dist/auth/ports/recovery-code-store.d.ts.map +1 -0
- package/dist/auth/ports/recovery-code-store.js +2 -0
- package/dist/auth/ports/recovery-code-store.js.map +1 -0
- package/dist/auth/ports/reset-token-store.d.ts +12 -0
- package/dist/auth/ports/reset-token-store.d.ts.map +1 -0
- package/dist/auth/ports/reset-token-store.js +2 -0
- package/dist/auth/ports/reset-token-store.js.map +1 -0
- package/dist/auth/ports/session-facts-projector.d.ts +44 -0
- package/dist/auth/ports/session-facts-projector.d.ts.map +1 -0
- package/dist/auth/ports/session-facts-projector.js +30 -0
- package/dist/auth/ports/session-facts-projector.js.map +1 -0
- package/dist/auth/ports/session-sweeper.d.ts +12 -0
- package/dist/auth/ports/session-sweeper.d.ts.map +1 -0
- package/dist/auth/ports/session-sweeper.js +2 -0
- package/dist/auth/ports/session-sweeper.js.map +1 -0
- package/dist/auth/profile-resolver.d.ts +73 -0
- package/dist/auth/profile-resolver.d.ts.map +1 -0
- package/dist/auth/profile-resolver.js +28 -0
- package/dist/auth/profile-resolver.js.map +1 -0
- package/dist/auth/rate-limit/attempt-limiter.d.ts +91 -0
- package/dist/auth/rate-limit/attempt-limiter.d.ts.map +1 -0
- package/dist/auth/rate-limit/attempt-limiter.js +133 -0
- package/dist/auth/rate-limit/attempt-limiter.js.map +1 -0
- package/dist/auth/tokens.d.ts +174 -0
- package/dist/auth/tokens.d.ts.map +1 -1
- package/dist/auth/tokens.js +190 -1
- package/dist/auth/tokens.js.map +1 -0
- package/dist/auth/totp-enrollment.d.ts +42 -0
- package/dist/auth/totp-enrollment.d.ts.map +1 -0
- package/dist/auth/totp-enrollment.js +96 -0
- package/dist/auth/totp-enrollment.js.map +1 -0
- package/dist/authentication/argon2id-password-hasher.js.map +1 -0
- package/dist/authentication/mariadb-credential-resolver.d.ts +38 -24
- package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
- package/dist/authentication/mariadb-credential-resolver.js +42 -28
- package/dist/authentication/mariadb-credential-resolver.js.map +1 -0
- package/dist/authz/cross-tenant-grant-writer.d.ts +259 -0
- package/dist/authz/cross-tenant-grant-writer.d.ts.map +1 -0
- package/dist/authz/cross-tenant-grant-writer.js +397 -0
- package/dist/authz/cross-tenant-grant-writer.js.map +1 -0
- package/dist/authz/manifest-operation-authority-adapter.d.ts +65 -0
- package/dist/authz/manifest-operation-authority-adapter.d.ts.map +1 -0
- package/dist/authz/manifest-operation-authority-adapter.js +108 -0
- package/dist/authz/manifest-operation-authority-adapter.js.map +1 -0
- package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts +90 -0
- package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -0
- package/dist/authz/mariadb-cross-tenant-grant-adapter.js +211 -0
- package/dist/authz/mariadb-cross-tenant-grant-adapter.js.map +1 -0
- package/dist/authz/mariadb-record-rule-adapter.d.ts +60 -0
- package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -0
- package/dist/authz/mariadb-record-rule-adapter.js +154 -0
- package/dist/authz/mariadb-record-rule-adapter.js.map +1 -0
- package/dist/authz/mariadb-record-share-adapter.d.ts +75 -0
- package/dist/authz/mariadb-record-share-adapter.d.ts.map +1 -0
- package/dist/authz/mariadb-record-share-adapter.js +106 -0
- package/dist/authz/mariadb-record-share-adapter.js.map +1 -0
- package/dist/authz/record-share-cascade.d.ts +52 -0
- package/dist/authz/record-share-cascade.d.ts.map +1 -0
- package/dist/authz/record-share-cascade.js +49 -0
- package/dist/authz/record-share-cascade.js.map +1 -0
- package/dist/authz/record-share-writer.d.ts +175 -0
- package/dist/authz/record-share-writer.d.ts.map +1 -0
- package/dist/authz/record-share-writer.js +225 -0
- package/dist/authz/record-share-writer.js.map +1 -0
- package/dist/authz/wire-record-authorization.d.ts +66 -0
- package/dist/authz/wire-record-authorization.d.ts.map +1 -0
- package/dist/authz/wire-record-authorization.js +34 -0
- package/dist/authz/wire-record-authorization.js.map +1 -0
- package/dist/boot/boot-sequence.js.map +1 -0
- package/dist/boot/boot.module.js.map +1 -0
- package/dist/completeness/catalog-reconciler.d.ts +21 -0
- package/dist/completeness/catalog-reconciler.d.ts.map +1 -0
- package/dist/completeness/catalog-reconciler.js +74 -0
- package/dist/completeness/catalog-reconciler.js.map +1 -0
- package/dist/completeness/operation-inventory-scanner.d.ts +17 -0
- package/dist/completeness/operation-inventory-scanner.d.ts.map +1 -0
- package/dist/completeness/operation-inventory-scanner.js +79 -0
- package/dist/completeness/operation-inventory-scanner.js.map +1 -0
- package/dist/completeness/ports/catalog-store.d.ts +33 -0
- package/dist/completeness/ports/catalog-store.d.ts.map +1 -0
- package/dist/completeness/ports/catalog-store.js +2 -0
- package/dist/completeness/ports/catalog-store.js.map +1 -0
- package/dist/config/app-config.js.map +1 -0
- package/dist/config/config.module.js.map +1 -0
- package/dist/enforcement/classification.decorator.d.ts +33 -14
- package/dist/enforcement/classification.decorator.d.ts.map +1 -1
- package/dist/enforcement/classification.decorator.js +34 -14
- package/dist/enforcement/classification.decorator.js.map +1 -0
- package/dist/enforcement/core-seam.js.map +1 -0
- package/dist/enforcement/enforcement.module.js.map +1 -0
- package/dist/enforcement/global-enforcement.guard.d.ts +29 -19
- package/dist/enforcement/global-enforcement.guard.d.ts.map +1 -1
- package/dist/enforcement/global-enforcement.guard.js +108 -25
- package/dist/enforcement/global-enforcement.guard.js.map +1 -0
- package/dist/enforcement/reenroll-allowlist.d.ts +49 -0
- package/dist/enforcement/reenroll-allowlist.d.ts.map +1 -0
- package/dist/enforcement/reenroll-allowlist.js +77 -0
- package/dist/enforcement/reenroll-allowlist.js.map +1 -0
- package/dist/index.d.ts +35 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +159 -7
- package/dist/index.js.map +1 -0
- package/dist/module/shield-tokens.d.ts +15 -0
- package/dist/module/shield-tokens.d.ts.map +1 -0
- package/dist/module/shield-tokens.js +17 -0
- package/dist/module/shield-tokens.js.map +1 -0
- package/dist/module/shield.module.d.ts +153 -9
- package/dist/module/shield.module.d.ts.map +1 -1
- package/dist/module/shield.module.js +181 -24
- package/dist/module/shield.module.js.map +1 -0
- package/dist/request-auth/request-principal.d.ts +42 -0
- package/dist/request-auth/request-principal.d.ts.map +1 -0
- package/dist/request-auth/request-principal.js +87 -0
- package/dist/request-auth/request-principal.js.map +1 -0
- package/dist/request-auth/session-facts-als.d.ts +65 -0
- package/dist/request-auth/session-facts-als.d.ts.map +1 -0
- package/dist/request-auth/session-facts-als.js +57 -0
- package/dist/request-auth/session-facts-als.js.map +1 -0
- package/dist/request-auth/session-token-binder.d.ts +130 -0
- package/dist/request-auth/session-token-binder.d.ts.map +1 -0
- package/dist/request-auth/session-token-binder.js +277 -0
- package/dist/request-auth/session-token-binder.js.map +1 -0
- package/dist/request-auth/tokens.d.ts +14 -0
- package/dist/request-auth/tokens.d.ts.map +1 -0
- package/dist/request-auth/tokens.js +16 -0
- package/dist/request-auth/tokens.js.map +1 -0
- package/dist/session/crypto-adapters.js.map +1 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/mariadb-session-record-store.js.map +1 -0
- package/dist/session/redis-hot-session-store.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cross-tenant-grant-writer.js","sourceRoot":"","sources":["../../src/authz/cross-tenant-grant-writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ManifestOperationAuthorityAdapter` — the concrete {@link OperationAuthorityPort} for
|
|
3
|
+
* `@astram/guard`, backed by a DECLARED operation manifest (ADR-0008). It answers
|
|
4
|
+
* `resolveDelegatedAuthority(operationKey, stepKey)`: what scoped, non-wildcard authority does
|
|
5
|
+
* a trusted operation's step carry?
|
|
6
|
+
*
|
|
7
|
+
* ── WHAT DELEGATED AUTHORITY IS (ADR-0008 / gate 7): one server operation performs an internal
|
|
8
|
+
* side-effect on an object type the CALLER may not touch directly (e.g. "create user" also
|
|
9
|
+
* writes an onboarding record in a separate object type). That internal step must run under
|
|
10
|
+
* the OPERATION'S OWN declared, scoped, least-privilege authority — NOT the caller's grants,
|
|
11
|
+
* and NEVER a wildcard "skip all checks". This adapter resolves THAT declared authority.
|
|
12
|
+
*
|
|
13
|
+
* ── PURE, NO DB (ADR-0008). This is DECLARED metadata — the operation's own manifest, known at
|
|
14
|
+
* build/registration time. So the lookup is SYNCHRONOUS and in-memory (no store, no I/O). The
|
|
15
|
+
* port is contracted synchronous; a manifest is the natural backing.
|
|
16
|
+
*
|
|
17
|
+
* ── THE MANIFEST: `${operationKey}:${stepKey}` → {@link DelegatedAuthority}. The consumer
|
|
18
|
+
* DECLARES it by constructing the adapter with a map (or `[key, authority]` entries). An
|
|
19
|
+
* UNKNOWN key ⇒ `undefined` (pass-through: NO delegation — evaluate under the caller — the
|
|
20
|
+
* common case).
|
|
21
|
+
*
|
|
22
|
+
* ── FAIL-CLOSED ON A MALFORMED / WILDCARD ENTRY. Core's gate-7 overlay re-checks scope with
|
|
23
|
+
* strict membership, but this adapter NEVER hands back a wildcard or malformed authority: a
|
|
24
|
+
* declared entry whose `objectTypeKeys` / `allowedActions` is not a non-empty array of
|
|
25
|
+
* non-empty strings, OR contains a wildcard `'*'`, is treated as UNDEFINED (no delegation).
|
|
26
|
+
* An operation cannot accidentally (or maliciously) declare a `'*'` and gain a root
|
|
27
|
+
* reach — the malformed declaration simply grants no delegated authority.
|
|
28
|
+
*
|
|
29
|
+
* ── EMPTY MANIFEST = every lookup returns `undefined` = pass-through everywhere. A consumer with
|
|
30
|
+
* no trusted operations yet wires this with an empty manifest — identical runtime behavior to
|
|
31
|
+
* the interim `emptyOperationAuthority`, but now a REAL, declared surface ready for the first
|
|
32
|
+
* trusted operation.
|
|
33
|
+
*
|
|
34
|
+
* ── WHY HERE, NOT CORE (ADR-0003): core defines the PORT + the gate-7 overlay that consumes it;
|
|
35
|
+
* the manifest-backed concrete lives in guard and is injected into the wired decision
|
|
36
|
+
* authority. It uses `@astram/core` TYPES only (erased at build).
|
|
37
|
+
*/
|
|
38
|
+
import type { DelegatedAuthority, OperationAuthorityPort } from '@astram/core';
|
|
39
|
+
/** The DI token the adapter is bound under (parity with the other authz-port tokens). */
|
|
40
|
+
export declare const SHIELD_OPERATION_AUTHORITY_PORT: unique symbol;
|
|
41
|
+
/**
|
|
42
|
+
* A single declared operation-step authority. Mirrors core's {@link DelegatedAuthority} (a
|
|
43
|
+
* scoped, NON-wildcard object-type + action ceiling) — declared by the consumer per trusted
|
|
44
|
+
* operation step.
|
|
45
|
+
*/
|
|
46
|
+
export interface OperationManifestEntry {
|
|
47
|
+
/** The trusted operation's stable key (its manifest identity). */
|
|
48
|
+
readonly operationKey: string;
|
|
49
|
+
/** The specific internal step within the operation. */
|
|
50
|
+
readonly stepKey: string;
|
|
51
|
+
/** The declared authority the step carries (scoped, non-wildcard). */
|
|
52
|
+
readonly authority: DelegatedAuthority;
|
|
53
|
+
}
|
|
54
|
+
export declare class ManifestOperationAuthorityAdapter implements OperationAuthorityPort {
|
|
55
|
+
private readonly manifest;
|
|
56
|
+
/**
|
|
57
|
+
* @param entries the declared operation-step authorities. Each malformed / wildcard entry is
|
|
58
|
+
* DROPPED at construction (fail-closed) — it will resolve to `undefined` (no delegation)
|
|
59
|
+
* rather than ever hand back an unsafe authority. A duplicate `${op}:${step}` key keeps the
|
|
60
|
+
* LAST well-formed declaration.
|
|
61
|
+
*/
|
|
62
|
+
constructor(entries?: readonly OperationManifestEntry[]);
|
|
63
|
+
resolveDelegatedAuthority(operationKey: string, stepKey: string): DelegatedAuthority | undefined;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=manifest-operation-authority-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-operation-authority-adapter.d.ts","sourceRoot":"","sources":["../../src/authz/manifest-operation-authority-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE/E,yFAAyF;AACzF,eAAO,MAAM,+BAA+B,eAA4C,CAAC;AAEzF;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,kEAAkE;IAClE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,sEAAsE;IACtE,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC;CACxC;AA6BD,qBAAa,iCAAkC,YAAW,sBAAsB;IAC9E,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAE3D;;;;;OAKG;gBACS,OAAO,GAAE,SAAS,sBAAsB,EAAO;IAuB3D,yBAAyB,CACvB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,GACd,kBAAkB,GAAG,SAAS;CAalC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `ManifestOperationAuthorityAdapter` — the concrete {@link OperationAuthorityPort} for
|
|
4
|
+
* `@astram/guard`, backed by a DECLARED operation manifest (ADR-0008). It answers
|
|
5
|
+
* `resolveDelegatedAuthority(operationKey, stepKey)`: what scoped, non-wildcard authority does
|
|
6
|
+
* a trusted operation's step carry?
|
|
7
|
+
*
|
|
8
|
+
* ── WHAT DELEGATED AUTHORITY IS (ADR-0008 / gate 7): one server operation performs an internal
|
|
9
|
+
* side-effect on an object type the CALLER may not touch directly (e.g. "create user" also
|
|
10
|
+
* writes an onboarding record in a separate object type). That internal step must run under
|
|
11
|
+
* the OPERATION'S OWN declared, scoped, least-privilege authority — NOT the caller's grants,
|
|
12
|
+
* and NEVER a wildcard "skip all checks". This adapter resolves THAT declared authority.
|
|
13
|
+
*
|
|
14
|
+
* ── PURE, NO DB (ADR-0008). This is DECLARED metadata — the operation's own manifest, known at
|
|
15
|
+
* build/registration time. So the lookup is SYNCHRONOUS and in-memory (no store, no I/O). The
|
|
16
|
+
* port is contracted synchronous; a manifest is the natural backing.
|
|
17
|
+
*
|
|
18
|
+
* ── THE MANIFEST: `${operationKey}:${stepKey}` → {@link DelegatedAuthority}. The consumer
|
|
19
|
+
* DECLARES it by constructing the adapter with a map (or `[key, authority]` entries). An
|
|
20
|
+
* UNKNOWN key ⇒ `undefined` (pass-through: NO delegation — evaluate under the caller — the
|
|
21
|
+
* common case).
|
|
22
|
+
*
|
|
23
|
+
* ── FAIL-CLOSED ON A MALFORMED / WILDCARD ENTRY. Core's gate-7 overlay re-checks scope with
|
|
24
|
+
* strict membership, but this adapter NEVER hands back a wildcard or malformed authority: a
|
|
25
|
+
* declared entry whose `objectTypeKeys` / `allowedActions` is not a non-empty array of
|
|
26
|
+
* non-empty strings, OR contains a wildcard `'*'`, is treated as UNDEFINED (no delegation).
|
|
27
|
+
* An operation cannot accidentally (or maliciously) declare a `'*'` and gain a root
|
|
28
|
+
* reach — the malformed declaration simply grants no delegated authority.
|
|
29
|
+
*
|
|
30
|
+
* ── EMPTY MANIFEST = every lookup returns `undefined` = pass-through everywhere. A consumer with
|
|
31
|
+
* no trusted operations yet wires this with an empty manifest — identical runtime behavior to
|
|
32
|
+
* the interim `emptyOperationAuthority`, but now a REAL, declared surface ready for the first
|
|
33
|
+
* trusted operation.
|
|
34
|
+
*
|
|
35
|
+
* ── WHY HERE, NOT CORE (ADR-0003): core defines the PORT + the gate-7 overlay that consumes it;
|
|
36
|
+
* the manifest-backed concrete lives in guard and is injected into the wired decision
|
|
37
|
+
* authority. It uses `@astram/core` TYPES only (erased at build).
|
|
38
|
+
*/
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.ManifestOperationAuthorityAdapter = exports.SHIELD_OPERATION_AUTHORITY_PORT = void 0;
|
|
41
|
+
/** The DI token the adapter is bound under (parity with the other authz-port tokens). */
|
|
42
|
+
exports.SHIELD_OPERATION_AUTHORITY_PORT = Symbol('SHIELD_OPERATION_AUTHORITY_PORT');
|
|
43
|
+
/** Compose the internal manifest map key from an operation + step. */
|
|
44
|
+
function manifestKey(operationKey, stepKey) {
|
|
45
|
+
return `${operationKey}:${stepKey}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether a declared authority is WELL-FORMED and non-wildcard: both `objectTypeKeys` and
|
|
49
|
+
* `allowedActions` must be non-empty arrays of non-empty strings, and NEITHER may contain a
|
|
50
|
+
* wildcard `'*'`. A malformed / wildcard authority is rejected (⇒ treated as no delegation).
|
|
51
|
+
*/
|
|
52
|
+
function isWellFormedAuthority(authority) {
|
|
53
|
+
if (!authority || typeof authority !== 'object')
|
|
54
|
+
return false;
|
|
55
|
+
return (isNonEmptyExplicitStringArray(authority.objectTypeKeys) &&
|
|
56
|
+
isNonEmptyExplicitStringArray(authority.allowedActions));
|
|
57
|
+
}
|
|
58
|
+
/** A non-empty array of non-empty strings, NONE of which is a wildcard `'*'`. */
|
|
59
|
+
function isNonEmptyExplicitStringArray(value) {
|
|
60
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
61
|
+
return false;
|
|
62
|
+
for (const v of value) {
|
|
63
|
+
if (typeof v !== 'string' || v.length === 0 || v === '*')
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
class ManifestOperationAuthorityAdapter {
|
|
69
|
+
manifest;
|
|
70
|
+
/**
|
|
71
|
+
* @param entries the declared operation-step authorities. Each malformed / wildcard entry is
|
|
72
|
+
* DROPPED at construction (fail-closed) — it will resolve to `undefined` (no delegation)
|
|
73
|
+
* rather than ever hand back an unsafe authority. A duplicate `${op}:${step}` key keeps the
|
|
74
|
+
* LAST well-formed declaration.
|
|
75
|
+
*/
|
|
76
|
+
constructor(entries = []) {
|
|
77
|
+
this.manifest = new Map();
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
if (typeof entry.operationKey !== 'string' ||
|
|
80
|
+
entry.operationKey.length === 0 ||
|
|
81
|
+
typeof entry.stepKey !== 'string' ||
|
|
82
|
+
entry.stepKey.length === 0) {
|
|
83
|
+
continue; // malformed key — skip
|
|
84
|
+
}
|
|
85
|
+
if (!isWellFormedAuthority(entry.authority)) {
|
|
86
|
+
continue; // malformed / wildcard authority — skip (fail-closed → undefined)
|
|
87
|
+
}
|
|
88
|
+
// Freeze a defensive copy so a later mutation of the caller's arrays cannot widen it.
|
|
89
|
+
const frozen = {
|
|
90
|
+
objectTypeKeys: Object.freeze([...entry.authority.objectTypeKeys]),
|
|
91
|
+
allowedActions: Object.freeze([...entry.authority.allowedActions]),
|
|
92
|
+
};
|
|
93
|
+
this.manifest.set(manifestKey(entry.operationKey, entry.stepKey), frozen);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
resolveDelegatedAuthority(operationKey, stepKey) {
|
|
97
|
+
if (typeof operationKey !== 'string' ||
|
|
98
|
+
operationKey.length === 0 ||
|
|
99
|
+
typeof stepKey !== 'string' ||
|
|
100
|
+
stepKey.length === 0) {
|
|
101
|
+
return undefined; // malformed lookup key ⇒ pass-through
|
|
102
|
+
}
|
|
103
|
+
// A present entry is already validated + frozen at construction — return it as-is. An
|
|
104
|
+
// absent key ⇒ undefined (pass-through: no delegation, the common case).
|
|
105
|
+
return this.manifest.get(manifestKey(operationKey, stepKey));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.ManifestOperationAuthorityAdapter = ManifestOperationAuthorityAdapter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-operation-authority-adapter.js","sourceRoot":"","sources":["../../src/authz/manifest-operation-authority-adapter.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;;;AAIH,yFAAyF;AAC5E,QAAA,+BAA+B,GAAG,MAAM,CAAC,iCAAiC,CAAC,CAAC;AAgBzF,sEAAsE;AACtE,SAAS,WAAW,CAAC,YAAoB,EAAE,OAAe;IACxD,OAAO,GAAG,YAAY,IAAI,OAAO,EAAE,CAAC;AACtC,CAAC;AAED;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,SAAyC;IACtE,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,CACL,6BAA6B,CAAC,SAAS,CAAC,cAAc,CAAC;QACvD,6BAA6B,CAAC,SAAS,CAAC,cAAc,CAAC,CACxD,CAAC;AACJ,CAAC;AAED,iFAAiF;AACjF,SAAS,6BAA6B,CAAC,KAAwB;IAC7D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9D,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG;YAAE,OAAO,KAAK,CAAC;IACzE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAa,iCAAiC;IAC3B,QAAQ,CAAkC;IAE3D;;;;;OAKG;IACH,YAAY,UAA6C,EAAE;QACzD,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,EAAE,CAAC;QAC1B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IACE,OAAO,KAAK,CAAC,YAAY,KAAK,QAAQ;gBACtC,KAAK,CAAC,YAAY,CAAC,MAAM,KAAK,CAAC;gBAC/B,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ;gBACjC,KAAK,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAC1B,CAAC;gBACD,SAAS,CAAC,uBAAuB;YACnC,CAAC;YACD,IAAI,CAAC,qBAAqB,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC5C,SAAS,CAAC,kEAAkE;YAC9E,CAAC;YACD,sFAAsF;YACtF,MAAM,MAAM,GAAuB;gBACjC,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;gBAClE,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;aACnE,CAAC;YACF,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,YAAY,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IAED,yBAAyB,CACvB,YAAoB,EACpB,OAAe;QAEf,IACE,OAAO,YAAY,KAAK,QAAQ;YAChC,YAAY,CAAC,MAAM,KAAK,CAAC;YACzB,OAAO,OAAO,KAAK,QAAQ;YAC3B,OAAO,CAAC,MAAM,KAAK,CAAC,EACpB,CAAC;YACD,OAAO,SAAS,CAAC,CAAC,sCAAsC;QAC1D,CAAC;QACD,sFAAsF;QACtF,yEAAyE;QACzE,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/D,CAAC;CACF;AAhDD,8EAgDC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MariaDbCrossTenantGrantAdapter` — the concrete {@link CrossTenantGrantPort} for
|
|
3
|
+
* `@astram/guard`, backed by the `cross_tenant_grant` table (ADR-0009). It answers
|
|
4
|
+
* `findActiveGrant`: is there an ACTIVE (accepted, unexpired, unrevoked), consented grant
|
|
5
|
+
* that lets THIS grantee reach THIS owning tenant's object type (+ record) for an action?
|
|
6
|
+
*
|
|
7
|
+
* ── WHAT A CROSS-TENANT GRANT IS (ADR-0009): the ONLY sanctioned way (short of root)
|
|
8
|
+
* a principal reaches a resource owned by a DIFFERENT tenant. It is an explicit,
|
|
9
|
+
* CONSENTED handshake — the OWNING tenant (whose data is reached into) offers it, the
|
|
10
|
+
* GRANTEE tenant accepts it, either side may revoke it, and it always expires. It is NEVER
|
|
11
|
+
* a root bypass: it carries a HARD, non-wildcard action ceiling and a scope
|
|
12
|
+
* (whole object type, or a single record). This adapter only REPORTS an active grant; core's
|
|
13
|
+
* `isCrossTenantGrantApplicable` re-checks the ceiling + scope + expiry (defense-in-depth).
|
|
14
|
+
*
|
|
15
|
+
* ── WHY HERE, NOT CORE (ADR-0003): the `mariadb` driver is a real runtime dependency; core
|
|
16
|
+
* defines only the PORT. This adapter is INJECTED into core's wired decision authority. It
|
|
17
|
+
* uses `@astram/core` TYPES only (erased at build).
|
|
18
|
+
*
|
|
19
|
+
* ── THE MATCH (ADR-0009 — an ACTIVE grant): a row matches when ALL hold —
|
|
20
|
+
* • `ctg_owning_tenant_uuid` = the resource's owning tenant AND `ctg_type_key` = the object
|
|
21
|
+
* type;
|
|
22
|
+
* • the grant is ACTIVE: `ctg_status = 'accepted'` (consent completed) AND
|
|
23
|
+
* `ctg_revoked_at IS NULL` (not revoked by either side) AND `ctg_expires_at_ms > ?` (not
|
|
24
|
+
* expired — bound epoch-ms, timezone-immune) — a revoked/offered/expired grant is NEVER
|
|
25
|
+
* returned, so it can never open access even for a heartbeat;
|
|
26
|
+
* • the grantee is EITHER this user (`ctg_grantee_kind='user'` AND
|
|
27
|
+
* `ctg_grantee_uuid = grantee`) OR one of the grantee's roles (`ctg_grantee_kind='role'`
|
|
28
|
+
* AND `ctg_grantee_uuid IN (roleIds)`);
|
|
29
|
+
* • the action is in the ceiling — the action-ceiling membership check is left to core's
|
|
30
|
+
* grant-evaluator (which re-checks `allowedActions`), but we DO gate scope: an
|
|
31
|
+
* `object_type` grant matches any record of the type; a `record` grant matches ONLY when
|
|
32
|
+
* the query names the exact `ctg_record_uuid` (a record-scoped row is EXCLUDED for an
|
|
33
|
+
* object-type / record-less query so a NULL-compare never spuriously matches).
|
|
34
|
+
* ≥1 matching row ⇒ mapped to a {@link CrossTenantGrant} (first match). No matching row ⇒
|
|
35
|
+
* `null` (a NORMAL data-absence — no active consent — NOT an error, NOT a lockout).
|
|
36
|
+
*
|
|
37
|
+
* ── SCOPE + RECORD-ID HANDLING: `object_type` rows are always eligible; a `record` row is
|
|
38
|
+
* eligible ONLY when `query.recordId` is present AND equals `ctg_record_uuid`. When the
|
|
39
|
+
* query has NO recordId (an object-type-level question), the SQL restricts to
|
|
40
|
+
* `ctg_scope = 'object_type'` so a record row's `ctg_record_uuid` is never compared to a
|
|
41
|
+
* bound NULL (which would never match — but we make the intent explicit and avoid the
|
|
42
|
+
* accidental all-records reach a `record_uuid = NULL` OR-branch could imply).
|
|
43
|
+
*
|
|
44
|
+
* ── ACTION CEILING PARSING: `ctg_allowed_actions` is a JSON column (a JSON array of action-key
|
|
45
|
+
* strings). ⚠️ The mariadb driver AUTO-PARSES a JSON column into a JS ARRAY on read, so the
|
|
46
|
+
* cell arrives already-parsed — `parseAllowedActions` accepts BOTH an already-parsed array
|
|
47
|
+
* (production) and a JSON string (test mocks). A malformed/empty value ⇒ `[]` (an empty
|
|
48
|
+
* ceiling permits nothing — fail-closed; core's ceiling check then denies).
|
|
49
|
+
*
|
|
50
|
+
* ── EXPIRY (MUST-FIX #1 — timezone immunity): `ctg_expires_at_ms` is a BIGINT epoch-ms column
|
|
51
|
+
* (NOT a TIMESTAMP). The active-grant filter compares it `> ?` against a BOUND integer (the
|
|
52
|
+
* adapter's `now()` epoch-ms), so it is immune to host-timezone / `NOW()` wall-clock skew (a
|
|
53
|
+
* TIMESTAMP + `NOW()` comparison silently kept expired grants alive on a non-UTC host). Mirrors
|
|
54
|
+
* the reset-token store's `prt_expires_at_ms` BIGINT precedent.
|
|
55
|
+
*
|
|
56
|
+
* ── EMPTY roleIds: guarded — an empty role list must NOT emit `IN ()` (a MariaDB syntax
|
|
57
|
+
* error). We omit the role branch entirely when there are no roles, so a role-less grantee
|
|
58
|
+
* is matched by its user-grant rows alone.
|
|
59
|
+
*
|
|
60
|
+
* ── ADR-0016 (transient-vs-deny): a genuine store outage THROWS
|
|
61
|
+
* {@link AuthorizationUnavailableError} (→ 503 retryable) — NEVER a silent `null` that
|
|
62
|
+
* would mask an outage as "no grant".
|
|
63
|
+
*
|
|
64
|
+
* ── SAFETY: every value (owningTenantId, typeKey, grantee, each roleId, recordId) is BOUND
|
|
65
|
+
* as a `?` param — NEVER interpolated. A SQL payload in typeKey/recordId is data, not SQL.
|
|
66
|
+
* Mirrors {@link MariaDbRecordShareAdapter}.
|
|
67
|
+
*/
|
|
68
|
+
import type { CrossTenantGrant, CrossTenantGrantPort, CrossTenantGrantQuery } from '@astram/core';
|
|
69
|
+
/** The DI token the adapter is bound under (parity with the share/rule-port tokens). */
|
|
70
|
+
export declare const SHIELD_CROSS_TENANT_GRANT_PORT: unique symbol;
|
|
71
|
+
/** The minimal query surface this adapter needs — a `mariadb` Pool satisfies it. */
|
|
72
|
+
export interface GrantQueryable {
|
|
73
|
+
query(sql: string, params?: readonly unknown[]): Promise<unknown>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The concrete cross-tenant grant adapter. Answers `findActiveGrant` from `cross_tenant_grant`,
|
|
77
|
+
* matching user- and role-granted, ACCEPTED, unexpired, unrevoked grants for the owning tenant
|
|
78
|
+
* + object type (+ record for record-scoped).
|
|
79
|
+
*/
|
|
80
|
+
export declare class MariaDbCrossTenantGrantAdapter implements CrossTenantGrantPort {
|
|
81
|
+
private readonly db;
|
|
82
|
+
private readonly schema;
|
|
83
|
+
private readonly now;
|
|
84
|
+
constructor(db: GrantQueryable, opts?: {
|
|
85
|
+
schema?: string;
|
|
86
|
+
nowMs?: () => number;
|
|
87
|
+
});
|
|
88
|
+
findActiveGrant(query: CrossTenantGrantQuery): Promise<CrossTenantGrant | null>;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=mariadb-cross-tenant-grant-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mariadb-cross-tenant-grant-adapter.d.ts","sourceRoot":"","sources":["../../src/authz/mariadb-cross-tenant-grant-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkEG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EAEtB,MAAM,cAAc,CAAC;AAGtB,wFAAwF;AACxF,eAAO,MAAM,8BAA8B,eAA2C,CAAC;AAKvF,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAsDD;;;;GAIG;AACH,qBAAa,8BAA+B,YAAW,oBAAoB;IACzE,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAiB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,EAAE,EAAE,cAAc,EAAE,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,MAAM,CAAA;KAAO;IAQ9E,eAAe,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;CAuFtF"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `MariaDbCrossTenantGrantAdapter` — the concrete {@link CrossTenantGrantPort} for
|
|
4
|
+
* `@astram/guard`, backed by the `cross_tenant_grant` table (ADR-0009). It answers
|
|
5
|
+
* `findActiveGrant`: is there an ACTIVE (accepted, unexpired, unrevoked), consented grant
|
|
6
|
+
* that lets THIS grantee reach THIS owning tenant's object type (+ record) for an action?
|
|
7
|
+
*
|
|
8
|
+
* ── WHAT A CROSS-TENANT GRANT IS (ADR-0009): the ONLY sanctioned way (short of root)
|
|
9
|
+
* a principal reaches a resource owned by a DIFFERENT tenant. It is an explicit,
|
|
10
|
+
* CONSENTED handshake — the OWNING tenant (whose data is reached into) offers it, the
|
|
11
|
+
* GRANTEE tenant accepts it, either side may revoke it, and it always expires. It is NEVER
|
|
12
|
+
* a root bypass: it carries a HARD, non-wildcard action ceiling and a scope
|
|
13
|
+
* (whole object type, or a single record). This adapter only REPORTS an active grant; core's
|
|
14
|
+
* `isCrossTenantGrantApplicable` re-checks the ceiling + scope + expiry (defense-in-depth).
|
|
15
|
+
*
|
|
16
|
+
* ── WHY HERE, NOT CORE (ADR-0003): the `mariadb` driver is a real runtime dependency; core
|
|
17
|
+
* defines only the PORT. This adapter is INJECTED into core's wired decision authority. It
|
|
18
|
+
* uses `@astram/core` TYPES only (erased at build).
|
|
19
|
+
*
|
|
20
|
+
* ── THE MATCH (ADR-0009 — an ACTIVE grant): a row matches when ALL hold —
|
|
21
|
+
* • `ctg_owning_tenant_uuid` = the resource's owning tenant AND `ctg_type_key` = the object
|
|
22
|
+
* type;
|
|
23
|
+
* • the grant is ACTIVE: `ctg_status = 'accepted'` (consent completed) AND
|
|
24
|
+
* `ctg_revoked_at IS NULL` (not revoked by either side) AND `ctg_expires_at_ms > ?` (not
|
|
25
|
+
* expired — bound epoch-ms, timezone-immune) — a revoked/offered/expired grant is NEVER
|
|
26
|
+
* returned, so it can never open access even for a heartbeat;
|
|
27
|
+
* • the grantee is EITHER this user (`ctg_grantee_kind='user'` AND
|
|
28
|
+
* `ctg_grantee_uuid = grantee`) OR one of the grantee's roles (`ctg_grantee_kind='role'`
|
|
29
|
+
* AND `ctg_grantee_uuid IN (roleIds)`);
|
|
30
|
+
* • the action is in the ceiling — the action-ceiling membership check is left to core's
|
|
31
|
+
* grant-evaluator (which re-checks `allowedActions`), but we DO gate scope: an
|
|
32
|
+
* `object_type` grant matches any record of the type; a `record` grant matches ONLY when
|
|
33
|
+
* the query names the exact `ctg_record_uuid` (a record-scoped row is EXCLUDED for an
|
|
34
|
+
* object-type / record-less query so a NULL-compare never spuriously matches).
|
|
35
|
+
* ≥1 matching row ⇒ mapped to a {@link CrossTenantGrant} (first match). No matching row ⇒
|
|
36
|
+
* `null` (a NORMAL data-absence — no active consent — NOT an error, NOT a lockout).
|
|
37
|
+
*
|
|
38
|
+
* ── SCOPE + RECORD-ID HANDLING: `object_type` rows are always eligible; a `record` row is
|
|
39
|
+
* eligible ONLY when `query.recordId` is present AND equals `ctg_record_uuid`. When the
|
|
40
|
+
* query has NO recordId (an object-type-level question), the SQL restricts to
|
|
41
|
+
* `ctg_scope = 'object_type'` so a record row's `ctg_record_uuid` is never compared to a
|
|
42
|
+
* bound NULL (which would never match — but we make the intent explicit and avoid the
|
|
43
|
+
* accidental all-records reach a `record_uuid = NULL` OR-branch could imply).
|
|
44
|
+
*
|
|
45
|
+
* ── ACTION CEILING PARSING: `ctg_allowed_actions` is a JSON column (a JSON array of action-key
|
|
46
|
+
* strings). ⚠️ The mariadb driver AUTO-PARSES a JSON column into a JS ARRAY on read, so the
|
|
47
|
+
* cell arrives already-parsed — `parseAllowedActions` accepts BOTH an already-parsed array
|
|
48
|
+
* (production) and a JSON string (test mocks). A malformed/empty value ⇒ `[]` (an empty
|
|
49
|
+
* ceiling permits nothing — fail-closed; core's ceiling check then denies).
|
|
50
|
+
*
|
|
51
|
+
* ── EXPIRY (MUST-FIX #1 — timezone immunity): `ctg_expires_at_ms` is a BIGINT epoch-ms column
|
|
52
|
+
* (NOT a TIMESTAMP). The active-grant filter compares it `> ?` against a BOUND integer (the
|
|
53
|
+
* adapter's `now()` epoch-ms), so it is immune to host-timezone / `NOW()` wall-clock skew (a
|
|
54
|
+
* TIMESTAMP + `NOW()` comparison silently kept expired grants alive on a non-UTC host). Mirrors
|
|
55
|
+
* the reset-token store's `prt_expires_at_ms` BIGINT precedent.
|
|
56
|
+
*
|
|
57
|
+
* ── EMPTY roleIds: guarded — an empty role list must NOT emit `IN ()` (a MariaDB syntax
|
|
58
|
+
* error). We omit the role branch entirely when there are no roles, so a role-less grantee
|
|
59
|
+
* is matched by its user-grant rows alone.
|
|
60
|
+
*
|
|
61
|
+
* ── ADR-0016 (transient-vs-deny): a genuine store outage THROWS
|
|
62
|
+
* {@link AuthorizationUnavailableError} (→ 503 retryable) — NEVER a silent `null` that
|
|
63
|
+
* would mask an outage as "no grant".
|
|
64
|
+
*
|
|
65
|
+
* ── SAFETY: every value (owningTenantId, typeKey, grantee, each roleId, recordId) is BOUND
|
|
66
|
+
* as a `?` param — NEVER interpolated. A SQL payload in typeKey/recordId is data, not SQL.
|
|
67
|
+
* Mirrors {@link MariaDbRecordShareAdapter}.
|
|
68
|
+
*/
|
|
69
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
70
|
+
exports.MariaDbCrossTenantGrantAdapter = exports.SHIELD_CROSS_TENANT_GRANT_PORT = void 0;
|
|
71
|
+
const core_1 = require("@astram/core");
|
|
72
|
+
/** The DI token the adapter is bound under (parity with the share/rule-port tokens). */
|
|
73
|
+
exports.SHIELD_CROSS_TENANT_GRANT_PORT = Symbol('SHIELD_CROSS_TENANT_GRANT_PORT');
|
|
74
|
+
/** Schema the `cross_tenant_grant` table lives in (astram3 ships the migration). */
|
|
75
|
+
const DEFAULT_SCHEMA = 'shield_authz';
|
|
76
|
+
/**
|
|
77
|
+
* Parse `ctg_allowed_actions` to a `string[]`. ⚠️ The `ctg_allowed_actions` column is a JSON
|
|
78
|
+
* column, and the mariadb driver AUTO-PARSES it into a JS ARRAY on read — so `raw` is ALREADY an
|
|
79
|
+
* array in production. A test mock, however, may hand back a JSON STRING. This helper accepts
|
|
80
|
+
* BOTH:
|
|
81
|
+
* • already-parsed array ⇒ filter to strings, return directly (NO `JSON.parse` — parsing an
|
|
82
|
+
* array throws, which is exactly the bug that collapsed every ceiling to `[]`);
|
|
83
|
+
* • non-empty string ⇒ `JSON.parse`, then filter to strings;
|
|
84
|
+
* • anything else (null / empty / non-array-non-string / malformed) ⇒ `[]`.
|
|
85
|
+
* Non-string members are dropped (fail-closed — a hostile array can never widen the ceiling); an
|
|
86
|
+
* empty ceiling permits nothing.
|
|
87
|
+
*/
|
|
88
|
+
function parseAllowedActions(raw) {
|
|
89
|
+
// Production path: the driver already parsed the JSON column into an array.
|
|
90
|
+
if (Array.isArray(raw)) {
|
|
91
|
+
return raw.filter((a) => typeof a === 'string');
|
|
92
|
+
}
|
|
93
|
+
// Mock / legacy path: a JSON string. Empty/null ⇒ empty ceiling.
|
|
94
|
+
if (typeof raw === 'string' && raw.length > 0) {
|
|
95
|
+
let parsed;
|
|
96
|
+
try {
|
|
97
|
+
parsed = JSON.parse(raw);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
if (!Array.isArray(parsed))
|
|
103
|
+
return [];
|
|
104
|
+
return parsed.filter((a) => typeof a === 'string');
|
|
105
|
+
}
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
/** Coerce a BIGINT epoch-ms cell (number | bigint | numeric string | null) to a JS number. */
|
|
109
|
+
function toMs(value) {
|
|
110
|
+
if (value === null)
|
|
111
|
+
return 0;
|
|
112
|
+
const ms = typeof value === 'number' ? value : Number(value);
|
|
113
|
+
return Number.isFinite(ms) ? ms : 0;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The concrete cross-tenant grant adapter. Answers `findActiveGrant` from `cross_tenant_grant`,
|
|
117
|
+
* matching user- and role-granted, ACCEPTED, unexpired, unrevoked grants for the owning tenant
|
|
118
|
+
* + object type (+ record for record-scoped).
|
|
119
|
+
*/
|
|
120
|
+
class MariaDbCrossTenantGrantAdapter {
|
|
121
|
+
db;
|
|
122
|
+
schema;
|
|
123
|
+
now;
|
|
124
|
+
constructor(db, opts = {}) {
|
|
125
|
+
this.db = db;
|
|
126
|
+
this.schema = opts.schema ?? DEFAULT_SCHEMA;
|
|
127
|
+
// The expiry filter binds THIS clock's epoch-ms against the BIGINT `ctg_expires_at_ms`
|
|
128
|
+
// column — timezone-immune (no Date/`NOW()` skew). Injectable for deterministic tests.
|
|
129
|
+
this.now = opts.nowMs ?? (() => Date.now());
|
|
130
|
+
}
|
|
131
|
+
async findActiveGrant(query) {
|
|
132
|
+
// Grantee predicate: the user grant OR (only when roles exist) a role grant. The role
|
|
133
|
+
// branch is OMITTED for an empty role list so we NEVER emit `IN ()` (a syntax error).
|
|
134
|
+
let granteeSql = `(ctg_grantee_kind = 'user' AND ctg_grantee_uuid = ?)`;
|
|
135
|
+
const granteeParams = [query.grantee];
|
|
136
|
+
if (query.roleIds.length > 0) {
|
|
137
|
+
const placeholders = query.roleIds.map(() => '?').join(', ');
|
|
138
|
+
granteeSql += ` OR (ctg_grantee_kind = 'role' AND ctg_grantee_uuid IN (${placeholders}))`;
|
|
139
|
+
granteeParams.push(...query.roleIds);
|
|
140
|
+
}
|
|
141
|
+
// Scope predicate. An `object_type` grant always matches this type; a `record` grant
|
|
142
|
+
// matches ONLY when the query names the exact record. When the query has NO recordId, we
|
|
143
|
+
// restrict to object_type grants (a record row's record_uuid must never be compared to a
|
|
144
|
+
// bound NULL — it would not match, but we make the intent explicit + never risk a NULL OR).
|
|
145
|
+
let scopeSql;
|
|
146
|
+
const scopeParams = [];
|
|
147
|
+
if (typeof query.recordId === 'string' && query.recordId.length > 0) {
|
|
148
|
+
scopeSql = `(ctg_scope = 'object_type' OR (ctg_scope = 'record' AND ctg_record_uuid = ?))`;
|
|
149
|
+
scopeParams.push(query.recordId);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
scopeSql = `ctg_scope = 'object_type'`;
|
|
153
|
+
}
|
|
154
|
+
// The expiry bound is a BIGINT epoch-ms comparison against a BOUND integer (this clock's
|
|
155
|
+
// now) — timezone-immune (no `NOW()`/Date wall-clock skew, MUST-FIX #1). Param order matches
|
|
156
|
+
// the SQL below exactly: owning_tenant, type_key, expiry_now, <grantee params>, <scope params>.
|
|
157
|
+
const nowMs = this.now();
|
|
158
|
+
const params = [
|
|
159
|
+
query.owningTenantId,
|
|
160
|
+
query.typeKey,
|
|
161
|
+
nowMs,
|
|
162
|
+
...granteeParams,
|
|
163
|
+
...scopeParams,
|
|
164
|
+
];
|
|
165
|
+
let rows;
|
|
166
|
+
try {
|
|
167
|
+
rows = (await this.db.query(`SELECT ctg_scope, ctg_type_key, ctg_record_uuid, ctg_allowed_actions, ctg_expires_at_ms` +
|
|
168
|
+
` FROM \`${this.schema}\`.\`cross_tenant_grant\`` +
|
|
169
|
+
` WHERE ctg_owning_tenant_uuid = ?` +
|
|
170
|
+
` AND ctg_type_key = ?` +
|
|
171
|
+
` AND ctg_status = 'accepted'` +
|
|
172
|
+
` AND ctg_revoked_at IS NULL` +
|
|
173
|
+
` AND ctg_expires_at_ms > ?` +
|
|
174
|
+
` AND (${granteeSql})` +
|
|
175
|
+
` AND (${scopeSql})` +
|
|
176
|
+
// Prefer a record-scoped row over an object_type row when both match, and the
|
|
177
|
+
// latest-expiring first — deterministic single-match selection.
|
|
178
|
+
` ORDER BY (ctg_scope = 'record') DESC, ctg_expires_at_ms DESC` +
|
|
179
|
+
` LIMIT 1`, params));
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
// Genuine store outage — fail closed as retryable (never mask as "no grant").
|
|
183
|
+
throw new core_1.AuthorizationUnavailableError(`cross_tenant_grant store unavailable for ` +
|
|
184
|
+
`${query.owningTenantId}:${query.typeKey}:${query.action}: ${err.message}`);
|
|
185
|
+
}
|
|
186
|
+
const row = rows[0];
|
|
187
|
+
if (!row) {
|
|
188
|
+
// No active consented grant — a NORMAL data-absence (NOT an error). Core reads this as
|
|
189
|
+
// "no cross-tenant permit" — deny-by-default for the reach.
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
// Map the row → CrossTenantGrant. Core's grant-evaluator re-checks allowedActions + scope
|
|
193
|
+
// + expiry; we still filtered status/expiry/revoke/scope in SQL so a revoked/expired/
|
|
194
|
+
// offered grant is never even returned.
|
|
195
|
+
const scope = row.ctg_scope === 'record' ? 'record' : 'object_type';
|
|
196
|
+
const grant = {
|
|
197
|
+
scope,
|
|
198
|
+
typeKey: row.ctg_type_key,
|
|
199
|
+
allowedActions: parseAllowedActions(row.ctg_allowed_actions),
|
|
200
|
+
// Already epoch-ms (BIGINT column) — just coerce the driver's number/string to a JS number.
|
|
201
|
+
expiresAtMs: toMs(row.ctg_expires_at_ms),
|
|
202
|
+
// `recordId` is only present for a record-scoped grant (exactOptionalPropertyTypes:
|
|
203
|
+
// never write an explicit `undefined` onto the optional field).
|
|
204
|
+
...(scope === 'record' && row.ctg_record_uuid !== null
|
|
205
|
+
? { recordId: row.ctg_record_uuid }
|
|
206
|
+
: {}),
|
|
207
|
+
};
|
|
208
|
+
return grant;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
exports.MariaDbCrossTenantGrantAdapter = MariaDbCrossTenantGrantAdapter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mariadb-cross-tenant-grant-adapter.js","sourceRoot":"","sources":["../../src/authz/mariadb-cross-tenant-grant-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MariaDbRecordPermissionRuleAdapter` — the concrete {@link RecordPermissionRulePort}
|
|
3
|
+
* for `@astram/guard`, backed by the `record_permission_rule` table (design §5.1). It
|
|
4
|
+
* reads ADMIN rules ONLY and evaluates them for a single record+action, returning the
|
|
5
|
+
* `{ allow, explicitDeny }` verdict core's record-permit tier composes with the
|
|
6
|
+
* framework-owned owner BASELINE (design §5.2).
|
|
7
|
+
*
|
|
8
|
+
* ── ADMIN-ONLY BY CONSTRUCTION (not by a WHERE filter): the owner/system baselines are
|
|
9
|
+
* COMPUTED IN CORE (no rows), so this table is only ever WRITTEN admin-authored rules —
|
|
10
|
+
* there is no `provenance` column and every row here is an admin rule. ⚠️ If a future
|
|
11
|
+
* change EVER stores non-admin (e.g. `system`) rows in this table, this SELECT MUST add
|
|
12
|
+
* a `WHERE rpr_provenance = 'admin'` filter — otherwise a system row would be read as an
|
|
13
|
+
* admin rule and could conflict with core's immutable baseline.
|
|
14
|
+
*
|
|
15
|
+
* ── WHY HERE, NOT CORE (ADR-0003): the `mariadb` driver is a real runtime dependency;
|
|
16
|
+
* core defines only the PORT + the immutable owner baseline. This adapter is INJECTED
|
|
17
|
+
* into core's wired decision authority. It uses `@astram/core` TYPES only (erased).
|
|
18
|
+
*
|
|
19
|
+
* ── THE MODEL (design §5):
|
|
20
|
+
* • The OWNER baseline is CORE's job — it is NOT a row here. This table holds ONLY
|
|
21
|
+
* admin-authored rules that WIDEN the baseline (e.g. "Company Admins read all
|
|
22
|
+
* inspection_reports") or add explicit DENIES (deny-wins). There is no
|
|
23
|
+
* `owner`/`system` subject_kind row.
|
|
24
|
+
* • NO applicable admin rule ⇒ `{ allow: false, explicitDeny: false }` — a NORMAL
|
|
25
|
+
* data-absence (not a lockout): core's owner baseline still permits the owner. An
|
|
26
|
+
* empty table ⇒ owner-only access, exactly the safe default.
|
|
27
|
+
* • A matching `allow` rule ⇒ `allow: true`. A matching `deny` rule ⇒
|
|
28
|
+
* `explicitDeny: true` (deny-wins; core short-circuits the share on it).
|
|
29
|
+
*
|
|
30
|
+
* ── ADR-0016 (transient-vs-deny): a genuine store outage THROWS
|
|
31
|
+
* {@link AuthorizationUnavailableError} (→ 503 retryable) — NEVER a silent
|
|
32
|
+
* `{allow:false,explicitDeny:false}` that would mask an outage as a deny.
|
|
33
|
+
*
|
|
34
|
+
* ── SAFETY: every rule row is matched on `(tenant, type_key, action)` with all values
|
|
35
|
+
* BOUND as `?` params (no interpolation). The action match is exact OR the rule's
|
|
36
|
+
* wildcard `*`. Field conditions are evaluated in JS against the record attributes
|
|
37
|
+
* core supplies (design §11.1 builder shape) — the operator set is a fixed
|
|
38
|
+
* allow-list; an unknown operator makes the rule NOT match (fail-closed, never a
|
|
39
|
+
* spurious allow).
|
|
40
|
+
*/
|
|
41
|
+
import type { RecordPermissionRulePort, RulePermit, RulePermitInput } from '@astram/core';
|
|
42
|
+
/** The DI token the adapter is bound under (parity with the session store token). */
|
|
43
|
+
export declare const SHIELD_RECORD_RULE_PORT: unique symbol;
|
|
44
|
+
/** The minimal query surface this adapter needs — a `mariadb` Pool satisfies it. */
|
|
45
|
+
export interface RuleQueryable {
|
|
46
|
+
query(sql: string, params?: readonly unknown[]): Promise<unknown>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The concrete admin-rule adapter. Reads ADMIN rules for `(tenant, type_key, action|*)`
|
|
50
|
+
* and folds them into a `{ allow, explicitDeny }` verdict with deny-wins.
|
|
51
|
+
*/
|
|
52
|
+
export declare class MariaDbRecordPermissionRuleAdapter implements RecordPermissionRulePort {
|
|
53
|
+
private readonly db;
|
|
54
|
+
private readonly schema;
|
|
55
|
+
constructor(db: RuleQueryable, opts?: {
|
|
56
|
+
schema?: string;
|
|
57
|
+
});
|
|
58
|
+
evaluate(input: RulePermitInput): Promise<RulePermit>;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=mariadb-record-rule-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mariadb-record-rule-adapter.d.ts","sourceRoot":"","sources":["../../src/authz/mariadb-record-rule-adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,KAAK,EACV,wBAAwB,EACxB,UAAU,EACV,eAAe,EAChB,MAAM,cAAc,CAAC;AAGtB,qFAAqF;AACrF,eAAO,MAAM,uBAAuB,eAAoC,CAAC;AAKzE,oFAAoF;AACpF,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAsED;;;GAGG;AACH,qBAAa,kCAAmC,YAAW,wBAAwB;IACjF,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAgB;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,EAAE,EAAE,aAAa,EAAE,IAAI,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;IAKvD,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC;CAwC5D"}
|