@astram/guard 0.2.0 → 0.4.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.
Files changed (120) hide show
  1. package/dist/auth/account.controller.d.ts +245 -0
  2. package/dist/auth/account.controller.d.ts.map +1 -0
  3. package/dist/auth/account.controller.js +673 -0
  4. package/dist/auth/auth.controller.d.ts +219 -5
  5. package/dist/auth/auth.controller.d.ts.map +1 -1
  6. package/dist/auth/auth.controller.js +790 -12
  7. package/dist/auth/auth.module.d.ts +101 -3
  8. package/dist/auth/auth.module.d.ts.map +1 -1
  9. package/dist/auth/auth.module.js +300 -8
  10. package/dist/auth/dto.d.ts +16 -6
  11. package/dist/auth/dto.d.ts.map +1 -1
  12. package/dist/auth/ports/audit-sink.d.ts +47 -0
  13. package/dist/auth/ports/audit-sink.d.ts.map +1 -0
  14. package/dist/auth/ports/audit-sink.js +20 -0
  15. package/dist/auth/ports/credential-writer.d.ts +23 -0
  16. package/dist/auth/ports/credential-writer.d.ts.map +1 -0
  17. package/dist/auth/ports/credential-writer.js +2 -0
  18. package/dist/auth/ports/email-sender.d.ts +14 -0
  19. package/dist/auth/ports/email-sender.d.ts.map +1 -0
  20. package/dist/auth/ports/email-sender.js +2 -0
  21. package/dist/auth/ports/mfa-device-store.d.ts +45 -0
  22. package/dist/auth/ports/mfa-device-store.d.ts.map +1 -0
  23. package/dist/auth/ports/mfa-device-store.js +13 -0
  24. package/dist/auth/ports/profile-writer.d.ts +12 -0
  25. package/dist/auth/ports/profile-writer.d.ts.map +1 -0
  26. package/dist/auth/ports/profile-writer.js +2 -0
  27. package/dist/auth/ports/recovery-code-store.d.ts +10 -0
  28. package/dist/auth/ports/recovery-code-store.d.ts.map +1 -0
  29. package/dist/auth/ports/recovery-code-store.js +2 -0
  30. package/dist/auth/ports/reset-token-store.d.ts +12 -0
  31. package/dist/auth/ports/reset-token-store.d.ts.map +1 -0
  32. package/dist/auth/ports/reset-token-store.js +2 -0
  33. package/dist/auth/ports/session-facts-projector.d.ts +44 -0
  34. package/dist/auth/ports/session-facts-projector.d.ts.map +1 -0
  35. package/dist/auth/ports/session-facts-projector.js +30 -0
  36. package/dist/auth/ports/session-sweeper.d.ts +12 -0
  37. package/dist/auth/ports/session-sweeper.d.ts.map +1 -0
  38. package/dist/auth/ports/session-sweeper.js +2 -0
  39. package/dist/auth/profile-resolver.d.ts +73 -0
  40. package/dist/auth/profile-resolver.d.ts.map +1 -0
  41. package/dist/auth/profile-resolver.js +28 -0
  42. package/dist/auth/rate-limit/attempt-limiter.d.ts +91 -0
  43. package/dist/auth/rate-limit/attempt-limiter.d.ts.map +1 -0
  44. package/dist/auth/rate-limit/attempt-limiter.js +133 -0
  45. package/dist/auth/tokens.d.ts +174 -0
  46. package/dist/auth/tokens.d.ts.map +1 -1
  47. package/dist/auth/tokens.js +190 -1
  48. package/dist/auth/totp-enrollment.d.ts +42 -0
  49. package/dist/auth/totp-enrollment.d.ts.map +1 -0
  50. package/dist/auth/totp-enrollment.js +96 -0
  51. package/dist/authentication/mariadb-credential-resolver.d.ts +38 -24
  52. package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
  53. package/dist/authentication/mariadb-credential-resolver.js +42 -28
  54. package/dist/authz/cross-tenant-grant-writer.d.ts +259 -0
  55. package/dist/authz/cross-tenant-grant-writer.d.ts.map +1 -0
  56. package/dist/authz/cross-tenant-grant-writer.js +397 -0
  57. package/dist/authz/manifest-operation-authority-adapter.d.ts +65 -0
  58. package/dist/authz/manifest-operation-authority-adapter.d.ts.map +1 -0
  59. package/dist/authz/manifest-operation-authority-adapter.js +108 -0
  60. package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts +90 -0
  61. package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -0
  62. package/dist/authz/mariadb-cross-tenant-grant-adapter.js +211 -0
  63. package/dist/authz/mariadb-record-rule-adapter.d.ts +60 -0
  64. package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -0
  65. package/dist/authz/mariadb-record-rule-adapter.js +154 -0
  66. package/dist/authz/mariadb-record-share-adapter.d.ts +75 -0
  67. package/dist/authz/mariadb-record-share-adapter.d.ts.map +1 -0
  68. package/dist/authz/mariadb-record-share-adapter.js +106 -0
  69. package/dist/authz/record-share-cascade.d.ts +52 -0
  70. package/dist/authz/record-share-cascade.d.ts.map +1 -0
  71. package/dist/authz/record-share-cascade.js +49 -0
  72. package/dist/authz/record-share-writer.d.ts +175 -0
  73. package/dist/authz/record-share-writer.d.ts.map +1 -0
  74. package/dist/authz/record-share-writer.js +225 -0
  75. package/dist/authz/wire-record-authorization.d.ts +66 -0
  76. package/dist/authz/wire-record-authorization.d.ts.map +1 -0
  77. package/dist/authz/wire-record-authorization.js +34 -0
  78. package/dist/completeness/catalog-reconciler.d.ts +21 -0
  79. package/dist/completeness/catalog-reconciler.d.ts.map +1 -0
  80. package/dist/completeness/catalog-reconciler.js +74 -0
  81. package/dist/completeness/operation-inventory-scanner.d.ts +17 -0
  82. package/dist/completeness/operation-inventory-scanner.d.ts.map +1 -0
  83. package/dist/completeness/operation-inventory-scanner.js +79 -0
  84. package/dist/completeness/ports/catalog-store.d.ts +33 -0
  85. package/dist/completeness/ports/catalog-store.d.ts.map +1 -0
  86. package/dist/completeness/ports/catalog-store.js +2 -0
  87. package/dist/config/app-config.d.ts +18 -16
  88. package/dist/config/app-config.d.ts.map +1 -1
  89. package/dist/config/app-config.js +68 -35
  90. package/dist/enforcement/classification.decorator.d.ts +33 -14
  91. package/dist/enforcement/classification.decorator.d.ts.map +1 -1
  92. package/dist/enforcement/classification.decorator.js +34 -14
  93. package/dist/enforcement/global-enforcement.guard.d.ts +29 -19
  94. package/dist/enforcement/global-enforcement.guard.d.ts.map +1 -1
  95. package/dist/enforcement/global-enforcement.guard.js +108 -25
  96. package/dist/enforcement/reenroll-allowlist.d.ts +49 -0
  97. package/dist/enforcement/reenroll-allowlist.d.ts.map +1 -0
  98. package/dist/enforcement/reenroll-allowlist.js +77 -0
  99. package/dist/index.d.ts +35 -3
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +159 -7
  102. package/dist/module/shield-tokens.d.ts +15 -0
  103. package/dist/module/shield-tokens.d.ts.map +1 -0
  104. package/dist/module/shield-tokens.js +17 -0
  105. package/dist/module/shield.module.d.ts +154 -10
  106. package/dist/module/shield.module.d.ts.map +1 -1
  107. package/dist/module/shield.module.js +181 -24
  108. package/dist/request-auth/request-principal.d.ts +42 -0
  109. package/dist/request-auth/request-principal.d.ts.map +1 -0
  110. package/dist/request-auth/request-principal.js +87 -0
  111. package/dist/request-auth/session-facts-als.d.ts +65 -0
  112. package/dist/request-auth/session-facts-als.d.ts.map +1 -0
  113. package/dist/request-auth/session-facts-als.js +57 -0
  114. package/dist/request-auth/session-token-binder.d.ts +130 -0
  115. package/dist/request-auth/session-token-binder.d.ts.map +1 -0
  116. package/dist/request-auth/session-token-binder.js +277 -0
  117. package/dist/request-auth/tokens.d.ts +14 -0
  118. package/dist/request-auth/tokens.d.ts.map +1 -0
  119. package/dist/request-auth/tokens.js +16 -0
  120. package/package.json +2 -2
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /**
3
+ * Phase 4 MFA-enrollment — the **pure TOTP-enrollment helpers**: generate a fresh
4
+ * base32 TOTP secret, compose the `otpauth://` provisioning URI an authenticator app
5
+ * scans, and mint one-time recovery codes.
6
+ *
7
+ * ── WHY THIS LIVES IN GUARD (not core) ──────────────────────────────────────
8
+ * Core owns the TOTP *verifier* (`createTotpFactorVerifier`, RFC 6238) but is
9
+ * ZERO-dep + node-type-free (ADR-0003), so it cannot mint secrets (needs
10
+ * `node:crypto.randomBytes`) nor build a URI. Enrollment is the *provisioning* side
11
+ * — it produces the secret the verifier later checks against. Guard owns node
12
+ * builtins, so the generation lives here. NO core change: the secret this emits is a
13
+ * plain base32 string, exactly what `createTotpFactorVerifier` decodes.
14
+ *
15
+ * ── SECURITY ─────────────────────────────────────────────────────────────────
16
+ * The secret + recovery codes are credential material: never logged, returned to the
17
+ * user exactly ONCE at enrollment, and (recovery codes) stored only Argon2-hashed.
18
+ * These functions are PURE (no I/O, no storage) — the caller decides what to persist.
19
+ *
20
+ * ADR-0003: guard owns `node:crypto`; this file has NO framework/HTTP/DB coupling.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.generateTotpSecret = generateTotpSecret;
24
+ exports.buildOtpauthUri = buildOtpauthUri;
25
+ exports.generateRecoveryCodes = generateRecoveryCodes;
26
+ const node_crypto_1 = require("node:crypto");
27
+ /** RFC 4648 base32 alphabet — the standard TOTP secret encoding (no padding here). */
28
+ const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
29
+ /**
30
+ * Bytes of entropy behind a generated TOTP secret. 20 bytes = 160 bits → exactly
31
+ * 32 base32 characters (20 × 8 / 5 = 32, no remainder → no padding), the RFC 4226
32
+ * recommended HMAC-SHA1 key length.
33
+ */
34
+ const SECRET_BYTES = 20;
35
+ /** How many recovery codes to mint by default (industry-standard batch of 10). */
36
+ const DEFAULT_RECOVERY_CODE_COUNT = 10;
37
+ /** Bytes per recovery code → 5 bytes = 10 lowercase hex chars, split into two groups. */
38
+ const RECOVERY_CODE_BYTES = 5;
39
+ /**
40
+ * Encode raw bytes into an RFC 4648 base32 string (no padding). Used to turn the
41
+ * random secret into the base32 string an authenticator app + the core verifier both
42
+ * expect. Pure bit-packing: 5 bits per output char, MSB-first.
43
+ */
44
+ function base32Encode(bytes) {
45
+ let bits = 0;
46
+ let value = 0;
47
+ let out = '';
48
+ for (const byte of bytes) {
49
+ value = (value << 8) | byte;
50
+ bits += 8;
51
+ while (bits >= 5) {
52
+ bits -= 5;
53
+ out += BASE32_ALPHABET[(value >>> bits) & 0x1f];
54
+ }
55
+ }
56
+ if (bits > 0) {
57
+ // Flush the remaining <5 bits, left-padded with zeros to a full 5-bit group.
58
+ out += BASE32_ALPHABET[(value << (5 - bits)) & 0x1f];
59
+ }
60
+ return out;
61
+ }
62
+ /**
63
+ * Generate a fresh, cryptographically-random base32 TOTP secret (160 bits → 32
64
+ * chars). This is what the user's authenticator app is provisioned with and what the
65
+ * core verifier later checks presented codes against.
66
+ */
67
+ function generateTotpSecret() {
68
+ return base32Encode((0, node_crypto_1.randomBytes)(SECRET_BYTES));
69
+ }
70
+ /**
71
+ * Build the `otpauth://totp/...` provisioning URI an authenticator app scans (or the
72
+ * user enters manually). `label` identifies the account (shown in the app), `issuer`
73
+ * names the service. All three URI components are percent-encoded so a label/issuer
74
+ * with spaces or `:`/`?`/`&` cannot break the URI. Fixed to the defaults the core
75
+ * verifier uses (SHA-1, 6 digits, 30s period).
76
+ */
77
+ function buildOtpauthUri(secret, label, issuer) {
78
+ const encIssuer = encodeURIComponent(issuer);
79
+ const encLabel = encodeURIComponent(label);
80
+ return (`otpauth://totp/${encIssuer}:${encLabel}` +
81
+ `?secret=${secret}&issuer=${encIssuer}&digits=6&period=30`);
82
+ }
83
+ /**
84
+ * Mint `count` unique one-time recovery codes (each `xxxxx-xxxxx`, 10 lowercase hex
85
+ * chars grouped for readability). Returned RAW to the caller ONCE at enrollment; the
86
+ * store persists them Argon2-hashed. Uniqueness is enforced (a hex collision at this
87
+ * entropy is astronomically unlikely, but the loop guarantees `count` distinct codes).
88
+ */
89
+ function generateRecoveryCodes(count = DEFAULT_RECOVERY_CODE_COUNT) {
90
+ const codes = new Set();
91
+ while (codes.size < count) {
92
+ const hex = (0, node_crypto_1.randomBytes)(RECOVERY_CODE_BYTES).toString('hex'); // 10 hex chars
93
+ codes.add(`${hex.slice(0, 5)}-${hex.slice(5)}`);
94
+ }
95
+ return [...codes];
96
+ }
@@ -1,20 +1,30 @@
1
1
  /**
2
- * `MariaDbCredentialResolver` — the concrete {@link CredentialResolver} (core
3
- * port) backed by the `shield_identity.identity_credential` table. Resolves an
4
- * `identityHint` (username / email) → a {@link ResolvedCredential} the
5
- * Authentication engine verifies, or `null` for an unknown hint.
2
+ * `MariaDbCredentialResolver` — the framework-DEFAULT concrete
3
+ * {@link CredentialResolver} (core port) backed by the single
4
+ * `shield_identity.identity_credential` table. Resolves an `identityHint`
5
+ * (username / email) → a {@link ResolvedCredential} the Authentication engine
6
+ * verifies, or `null` for an unknown hint.
6
7
  *
7
- * ⛔⛔ PHASE 0b PRELUDE SKELETON — the CONTRACT is frozen here; **Team A fills the
8
- * body**. `resolve()` currently THROWS "not implemented" so a missing Team-A impl
9
- * fails CLOSED (a broken resolver can NEVER silently authenticate). Do not change
10
- * the constructor shape (it takes the `SHIELD_DB_POOL`) or the return type — the
11
- * composition (Team B) and the engine depend on them.
8
+ * ⛔ PASSWORD-ONLY BY DESIGN. This default resolver reads ONLY the columns the
9
+ * framework's shipped seed (`infra/mariadb/init/30-identity-credential.sql`)
10
+ * actually creates: `ic_password_hash, ic_principal_id, ic_tenant_id, ic_roles`.
11
+ * It therefore ALWAYS returns `requiredFactors: []` — it does NOT provide an
12
+ * MFA/second-factor challenge. There is no TOTP column here: the multi-device MFA
13
+ * model moved authenticator secrets out of `identity_credential` into a separate
14
+ * `mfa_device` table, which the framework ships only as an UNIMPLEMENTED port.
15
+ *
16
+ * MFA / required-factors are a CONSUMER concern, delivered by plugging a custom
17
+ * `credentialResolver` (+ a concrete `MfaDeviceStore`) via `ShieldModule.forRoot`
18
+ * — NOT by this default. See astram3's `IdentityCredentialResolver`, which reads
19
+ * this same table AND folds in the consumer's confirmed authenticators via
20
+ * `listConfirmedSecrets(mfa_device)` to populate `requiredFactors`. That seam is
21
+ * the supported way to add MFA-at-login on top of this password-only default.
12
22
  *
13
23
  * ─────────────────────────────────────────────────────────────────────────────
14
- * THE TABLE CONTRACT Team A must create (infra migration/seed
15
- * `infra/mariadb/init/30-identity-credential.sql`, in `shield_identity`; the
16
- * runtime `shield_core` account already has DML there — no new grant). Columns
17
- * per spec §3 (prefix `ic_` to match the repo's session-table convention):
24
+ * THE TABLE CONTRACT (infra seed `infra/mariadb/init/30-identity-credential.sql`,
25
+ * in `shield_identity`; the runtime `shield_core` account already has DML there —
26
+ * no new grant). Columns per spec §3 (prefix `ic_` to match the repo's
27
+ * session-table convention):
18
28
  *
19
29
  * identity_credential
20
30
  * ┌────────────────────────┬──────────────┬────────────────────────────────────┐
@@ -25,16 +35,16 @@
25
35
  * │ ic_principal_id │ VARCHAR(255) │ → ResolvedCredential.claims.id │
26
36
  * │ ic_tenant_id │ VARCHAR(255) │ → claims.tenantId │
27
37
  * │ ic_roles │ JSON / TEXT │ role-id array → claims.roleIds │
28
- * │ ic_totp_secret │ VARCHAR(255) │ NULLABLE — base32 TOTP secret; │
29
- * │ │ │ present ⇒ requiredFactors:[totp] │
30
38
  * │ ic_created_at_ms │ BIGINT │ UTC epoch ms (platform sets UTC) │
31
39
  * │ ic_updated_at_ms │ BIGINT │ UTC epoch ms │
32
40
  * └────────────────────────┴──────────────┴────────────────────────────────────┘
33
41
  * PRIMARY KEY / UNIQUE on ic_identity_hint (single-row resolve by hint).
42
+ * (No `ic_totp_secret` column — MFA secrets live in the consumer's `mfa_device`
43
+ * table, resolved by a consumer-plugged resolver, not by this default.)
34
44
  *
35
45
  * The resolver's SELECT must:
36
- * 1. `SELECT ic_password_hash, ic_principal_id, ic_tenant_id, ic_roles,
37
- * ic_totp_secret FROM shield_identity.identity_credential
46
+ * 1. `SELECT ic_password_hash, ic_principal_id, ic_tenant_id, ic_roles
47
+ * FROM shield_identity.identity_credential
38
48
  * WHERE ic_identity_hint = ? LIMIT 1` (parameterized — NEVER string-built).
39
49
  * 2. Return `null` when NO row matches — the ONLY "not found" signal
40
50
  * (anti-enumeration; MUST NOT throw for an unknown hint; the engine burns a
@@ -43,16 +53,16 @@
43
53
  * passwordHash = ic_password_hash
44
54
  * claims = { id: ic_principal_id, tenantId: ic_tenant_id,
45
55
  * roleIds: <parsed ic_roles> } // canonical Internal claims
46
- * requiredFactors = ic_totp_secret ? [{ factor:'totp', secret: ic_totp_secret }] : []
47
- * 4. NEVER log the hash, the TOTP secret, or the raw row.
56
+ * requiredFactors = [] // ALWAYS empty — password-only default (see above)
57
+ * 4. NEVER log the hash or the raw row.
48
58
  * 5. A store outage (connection/query error) MAY throw — the engine treats a
49
59
  * resolver throw as fail-closed `authentication_unavailable` (never a leak).
50
- * Team A does NOT need to swallow it, but MUST NOT convert it into a `null`
51
- * (that would make an outage look like "unknown user").
60
+ * It MUST NOT convert a throw into a `null` (that would make an outage look
61
+ * like "unknown user").
52
62
  *
53
63
  * (`claims.kind` is optional — the Internal adapter defaults an absent kind; add
54
64
  * an `ic_kind` column later if a deployment needs non-`user` principals. Do NOT
55
- * put any superuser flag in the row — superuser is the broker's server-side
65
+ * put any root flag in the row — root is the broker's server-side
56
66
  * allowlist, never a claim, per core/identity.)
57
67
  * ─────────────────────────────────────────────────────────────────────────────
58
68
  *
@@ -88,8 +98,12 @@ export declare class MariaDbCredentialResolver implements CredentialResolver {
88
98
  * unknown user; the engine treats a resolver throw as fail-closed
89
99
  * `authentication_unavailable`. We never convert an error into `null` (that
90
100
  * would mask an outage AND make it look like "no such user").
91
- * - The password hash and TOTP secret are NEVER logged; the raw row is never
92
- * logged. On success they are returned to the engine as verify inputs only.
101
+ * - The password hash is NEVER logged; the raw row is never logged. On success
102
+ * it is returned to the engine as a verify input only.
103
+ * - **Password-only:** `requiredFactors` is ALWAYS `[]`. This default provides
104
+ * no second-factor challenge; MFA-at-login is delivered by a consumer-plugged
105
+ * `credentialResolver` (+ `MfaDeviceStore`) — see the class doc header and
106
+ * astram3's `IdentityCredentialResolver`.
93
107
  */
94
108
  resolve(identityHint: string): Promise<ResolvedCredential | null>;
95
109
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mariadb-credential-resolver.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-credential-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EACV,kBAAkB,EAElB,kBAAkB,EACnB,MAAM,cAAc,CAAC;AAEtB,kFAAkF;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAwD/D;;;;;;;GAOG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IACtD,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAEvC;;;;;;;;;;;;;;;;OAgBG;IACG,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;CAgCxE"}
1
+ {"version":3,"file":"mariadb-credential-resolver.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-credential-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EACV,kBAAkB,EAElB,kBAAkB,EACnB,MAAM,cAAc,CAAC;AAEtB,kFAAkF;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAqD/D;;;;;;;GAOG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IACtD,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAEvC;;;;;;;;;;;;;;;;;;;;OAoBG;IACG,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;CAgCxE"}
@@ -1,21 +1,31 @@
1
1
  "use strict";
2
2
  /**
3
- * `MariaDbCredentialResolver` — the concrete {@link CredentialResolver} (core
4
- * port) backed by the `shield_identity.identity_credential` table. Resolves an
5
- * `identityHint` (username / email) → a {@link ResolvedCredential} the
6
- * Authentication engine verifies, or `null` for an unknown hint.
3
+ * `MariaDbCredentialResolver` — the framework-DEFAULT concrete
4
+ * {@link CredentialResolver} (core port) backed by the single
5
+ * `shield_identity.identity_credential` table. Resolves an `identityHint`
6
+ * (username / email) → a {@link ResolvedCredential} the Authentication engine
7
+ * verifies, or `null` for an unknown hint.
7
8
  *
8
- * ⛔⛔ PHASE 0b PRELUDE SKELETON — the CONTRACT is frozen here; **Team A fills the
9
- * body**. `resolve()` currently THROWS "not implemented" so a missing Team-A impl
10
- * fails CLOSED (a broken resolver can NEVER silently authenticate). Do not change
11
- * the constructor shape (it takes the `SHIELD_DB_POOL`) or the return type — the
12
- * composition (Team B) and the engine depend on them.
9
+ * ⛔ PASSWORD-ONLY BY DESIGN. This default resolver reads ONLY the columns the
10
+ * framework's shipped seed (`infra/mariadb/init/30-identity-credential.sql`)
11
+ * actually creates: `ic_password_hash, ic_principal_id, ic_tenant_id, ic_roles`.
12
+ * It therefore ALWAYS returns `requiredFactors: []` — it does NOT provide an
13
+ * MFA/second-factor challenge. There is no TOTP column here: the multi-device MFA
14
+ * model moved authenticator secrets out of `identity_credential` into a separate
15
+ * `mfa_device` table, which the framework ships only as an UNIMPLEMENTED port.
16
+ *
17
+ * MFA / required-factors are a CONSUMER concern, delivered by plugging a custom
18
+ * `credentialResolver` (+ a concrete `MfaDeviceStore`) via `ShieldModule.forRoot`
19
+ * — NOT by this default. See astram3's `IdentityCredentialResolver`, which reads
20
+ * this same table AND folds in the consumer's confirmed authenticators via
21
+ * `listConfirmedSecrets(mfa_device)` to populate `requiredFactors`. That seam is
22
+ * the supported way to add MFA-at-login on top of this password-only default.
13
23
  *
14
24
  * ─────────────────────────────────────────────────────────────────────────────
15
- * THE TABLE CONTRACT Team A must create (infra migration/seed
16
- * `infra/mariadb/init/30-identity-credential.sql`, in `shield_identity`; the
17
- * runtime `shield_core` account already has DML there — no new grant). Columns
18
- * per spec §3 (prefix `ic_` to match the repo's session-table convention):
25
+ * THE TABLE CONTRACT (infra seed `infra/mariadb/init/30-identity-credential.sql`,
26
+ * in `shield_identity`; the runtime `shield_core` account already has DML there —
27
+ * no new grant). Columns per spec §3 (prefix `ic_` to match the repo's
28
+ * session-table convention):
19
29
  *
20
30
  * identity_credential
21
31
  * ┌────────────────────────┬──────────────┬────────────────────────────────────┐
@@ -26,16 +36,16 @@
26
36
  * │ ic_principal_id │ VARCHAR(255) │ → ResolvedCredential.claims.id │
27
37
  * │ ic_tenant_id │ VARCHAR(255) │ → claims.tenantId │
28
38
  * │ ic_roles │ JSON / TEXT │ role-id array → claims.roleIds │
29
- * │ ic_totp_secret │ VARCHAR(255) │ NULLABLE — base32 TOTP secret; │
30
- * │ │ │ present ⇒ requiredFactors:[totp] │
31
39
  * │ ic_created_at_ms │ BIGINT │ UTC epoch ms (platform sets UTC) │
32
40
  * │ ic_updated_at_ms │ BIGINT │ UTC epoch ms │
33
41
  * └────────────────────────┴──────────────┴────────────────────────────────────┘
34
42
  * PRIMARY KEY / UNIQUE on ic_identity_hint (single-row resolve by hint).
43
+ * (No `ic_totp_secret` column — MFA secrets live in the consumer's `mfa_device`
44
+ * table, resolved by a consumer-plugged resolver, not by this default.)
35
45
  *
36
46
  * The resolver's SELECT must:
37
- * 1. `SELECT ic_password_hash, ic_principal_id, ic_tenant_id, ic_roles,
38
- * ic_totp_secret FROM shield_identity.identity_credential
47
+ * 1. `SELECT ic_password_hash, ic_principal_id, ic_tenant_id, ic_roles
48
+ * FROM shield_identity.identity_credential
39
49
  * WHERE ic_identity_hint = ? LIMIT 1` (parameterized — NEVER string-built).
40
50
  * 2. Return `null` when NO row matches — the ONLY "not found" signal
41
51
  * (anti-enumeration; MUST NOT throw for an unknown hint; the engine burns a
@@ -44,16 +54,16 @@
44
54
  * passwordHash = ic_password_hash
45
55
  * claims = { id: ic_principal_id, tenantId: ic_tenant_id,
46
56
  * roleIds: <parsed ic_roles> } // canonical Internal claims
47
- * requiredFactors = ic_totp_secret ? [{ factor:'totp', secret: ic_totp_secret }] : []
48
- * 4. NEVER log the hash, the TOTP secret, or the raw row.
57
+ * requiredFactors = [] // ALWAYS empty — password-only default (see above)
58
+ * 4. NEVER log the hash or the raw row.
49
59
  * 5. A store outage (connection/query error) MAY throw — the engine treats a
50
60
  * resolver throw as fail-closed `authentication_unavailable` (never a leak).
51
- * Team A does NOT need to swallow it, but MUST NOT convert it into a `null`
52
- * (that would make an outage look like "unknown user").
61
+ * It MUST NOT convert a throw into a `null` (that would make an outage look
62
+ * like "unknown user").
53
63
  *
54
64
  * (`claims.kind` is optional — the Internal adapter defaults an absent kind; add
55
65
  * an `ic_kind` column later if a deployment needs non-`user` principals. Do NOT
56
- * put any superuser flag in the row — superuser is the broker's server-side
66
+ * put any root flag in the row — root is the broker's server-side
57
67
  * allowlist, never a claim, per core/identity.)
58
68
  * ─────────────────────────────────────────────────────────────────────────────
59
69
  *
@@ -123,14 +133,18 @@ class MariaDbCredentialResolver {
123
133
  * unknown user; the engine treats a resolver throw as fail-closed
124
134
  * `authentication_unavailable`. We never convert an error into `null` (that
125
135
  * would mask an outage AND make it look like "no such user").
126
- * - The password hash and TOTP secret are NEVER logged; the raw row is never
127
- * logged. On success they are returned to the engine as verify inputs only.
136
+ * - The password hash is NEVER logged; the raw row is never logged. On success
137
+ * it is returned to the engine as a verify input only.
138
+ * - **Password-only:** `requiredFactors` is ALWAYS `[]`. This default provides
139
+ * no second-factor challenge; MFA-at-login is delivered by a consumer-plugged
140
+ * `credentialResolver` (+ `MfaDeviceStore`) — see the class doc header and
141
+ * astram3's `IdentityCredentialResolver`.
128
142
  */
129
143
  async resolve(identityHint) {
130
144
  // A failed query rejects out of here unchanged (fail-closed): we deliberately
131
145
  // do NOT wrap this in a try/catch that returns null — only a genuine "no row"
132
146
  // maps to null (below), never a store outage.
133
- const rows = (await this.pool.query(`SELECT ic_password_hash, ic_principal_id, ic_tenant_id, ic_roles, ic_totp_secret
147
+ const rows = (await this.pool.query(`SELECT ic_password_hash, ic_principal_id, ic_tenant_id, ic_roles
134
148
  FROM shield_identity.identity_credential
135
149
  WHERE ic_identity_hint = ?
136
150
  LIMIT 1`, [identityHint]));
@@ -139,9 +153,9 @@ class MariaDbCredentialResolver {
139
153
  if (!row) {
140
154
  return null;
141
155
  }
142
- const requiredFactors = row.ic_totp_secret
143
- ? [{ factor: 'totp', secret: row.ic_totp_secret }]
144
- : [];
156
+ // Password-only default: no second factor is derived here. MFA is a consumer
157
+ // concern (custom resolver + MfaDeviceStore), so requiredFactors is always [].
158
+ const requiredFactors = [];
145
159
  return {
146
160
  passwordHash: row.ic_password_hash,
147
161
  claims: {
@@ -0,0 +1,259 @@
1
+ /**
2
+ * `CrossTenantGrantWriter` — the framework-owned WHO-MAY-GRANT write path (ADR-0009, the KEY
3
+ * NEW INVARIANT of Phase 4). Cross-tenant grant LIFECYCLE — offer / accept / revoke — is
4
+ * framework-governed here so a grant can NEVER be minted by the party that BENEFITS from it.
5
+ *
6
+ * ⛔ THIS IS NOT A CORE PORT. Grant EVALUATION is a core port ({@link CrossTenantGrantPort});
7
+ * grant LIFECYCLE is a guard WRITE path (it needs the `mariadb` driver + the consumer's
8
+ * identity schema to answer "is X an admin of tenant T", neither of which core may touch —
9
+ * ADR-0003). It lives in guard and is exposed as a provider the consumer app's grant surface
10
+ * calls (the invitation-email / inbox UX is DEFERRED — ADR-0009's Admin-Console follow-up).
11
+ *
12
+ * ── THE CONSENT MODEL (ADR-0009). A cross-tenant grant is TENANT B (the OWNING tenant, whose
13
+ * data is reached into) consenting to let TENANT A's principal in. The handshake:
14
+ * offer → an owner/ADMIN in the OWNING tenant (or root) declares the grant
15
+ * (object type, scope, action ceiling, expiry) → row created in 'offered' status.
16
+ * accept → the GRANTEE tenant must ACCEPT → row moves to 'accepted' → the grant ACTIVATES
17
+ * (only now does {@link CrossTenantGrantPort.findActiveGrant} return it).
18
+ * revoke → EITHER side (owning tenant admin OR grantee tenant member) may revoke → row
19
+ * moves to 'revoked' + `ctg_revoked_at` stamped → immediately inactive.
20
+ * ⛔ THE #1 SECURITY CRUX: the grantee's OWN tenant can NEVER unilaterally mint a grant. A
21
+ * grant is consent FROM the owning tenant; only an owning-tenant admin may OFFER.
22
+ *
23
+ * ── THE BOUNDS (enforced fail-closed, IN ORDER, BEFORE any write):
24
+ * • offer:
25
+ * 1. The offerer MUST hold `'share'` authority in the OWNING tenant (`ctg_owning_tenant_uuid`)
26
+ * — verified SERVER-SIDE via the injected {@link GrantAuthorizeFn} (the app's `decide()`);
27
+ * rejected otherwise. ⛔ `offeredBy` + `owningTenantId` come from the AUTHENTICATED
28
+ * principal context — the caller supplies them from the request principal, NEVER from
29
+ * a user-controlled body field for the authority decision.
30
+ * 2. `allowedActions` MUST be non-empty AND MUST NOT contain a wildcard (`'*'`) — the
31
+ * ceiling is a hard, explicit, enumerated set (ADR-0009: "non-wildcard, non-empty").
32
+ * 3. `expiresAtMs` MUST be a finite FUTURE timestamp — no perpetual / absent / past
33
+ * expiry (ADR-0009: "expiry mandatory").
34
+ * 4. scope='record' REQUIRES a non-empty `recordId`; scope='object_type' FORBIDS one.
35
+ * On pass ⇒ INSERT a fresh row in 'offered' status.
36
+ * • accept:
37
+ * Only a principal who holds `'accept'` authority in the GRANTEE tenant (`ctg_grantee_tenant_uuid`)
38
+ * may accept — verified via {@link GrantAuthorizeFn} (the app's `decide()`, which implies
39
+ * grantee-tenant membership); and only a grant
40
+ * currently in 'offered' status (not already accepted/revoked). On pass ⇒ UPDATE to
41
+ * 'accepted' + stamp `ctg_accepted_at`. Re-accepting an already-'accepted' grant is a
42
+ * benign no-op (idempotent). Accepting a 'revoked' grant is REJECTED (a revoked grant is
43
+ * dead — it must be re-offered).
44
+ * • revoke:
45
+ * EITHER an admin of the OWNING tenant OR a member of the GRANTEE tenant may revoke
46
+ * (ADR-0009: "either side may revoke immediately"). A stranger (neither) is REJECTED. On
47
+ * pass ⇒ UPDATE to 'revoked' + stamp `ctg_revoked_at`. Re-revoking an already-'revoked'
48
+ * grant is a benign no-op (idempotent).
49
+ *
50
+ * ── AUDIT ON BOTH TENANTS (ADR-0009: "audited on BOTH tenants as cross-tenant"). Every
51
+ * offer/accept/revoke emits ONE {@link GrantAuditEvent} through the injected
52
+ * {@link GrantAuditSink} carrying BOTH `owningTenantId` and `granteeTenantId` in STRUCTURED
53
+ * fields — so the consumer's sink can record it under each tenant's audit trail. NO raw uuid
54
+ * appears in any human-readable MESSAGE (ids ride in structured attributes only). Best-effort
55
+ * — a sink throw never fails the lifecycle operation.
56
+ *
57
+ * ── FETCH-THEN-GATE (mirrors {@link RecordShareWriter}). The authority decisions (is-admin /
58
+ * is-member) are resolved SERVER-SIDE via the injected resolver — NEVER trusted from caller
59
+ * input. accept/revoke FETCH the grant's owning+grantee tenants from the row (not from the
60
+ * caller) before deciding who may act.
61
+ */
62
+ import type { GrantQueryable } from './mariadb-cross-tenant-grant-adapter.js';
63
+ /** The DI token the writer is bound under. */
64
+ export declare const SHIELD_CROSS_TENANT_GRANT_WRITER: unique symbol;
65
+ /** Who a grant is granted to (the grantee principal kind). Mirrors `ctg_grantee_kind`. */
66
+ export type GrantGranteeKind = 'user' | 'role';
67
+ /** The two scopes a grant may span (mirrors core's `CrossTenantGrantScope`). */
68
+ export type GrantScope = 'object_type' | 'record';
69
+ /**
70
+ * The ONE authority question the writer asks: "may this principal perform `action` within
71
+ * `tenantId`?" — answered by the CONSUMER over its OWN identity + authorization model, so guard
72
+ * never imports `@astram/engine` or the app (ADR-0003 layering).
73
+ *
74
+ * This REPLACES the former `GrantAuthorityResolver` port (the bespoke `isTenantAdmin` /
75
+ * `isTenantMember` pair). Cross-tenant sharing is NOT a special authority question — it is the
76
+ * SAME "may this person do this?" question every other action asks. So the app resolves it the
77
+ * SAME way it resolves everything else: through `decide()`. The app's closure builds the
78
+ * principal (id → roleIds for `tenantId`, via its in-effect membership→posting read) and calls
79
+ * its own `decide(principal, {key: action}, {level:'object_type', typeKey:'cross_tenant_grant',
80
+ * tenantId}, ...)`. Because a principal's permissions are resolved THROUGH their membership,
81
+ * one `decide()` answers BOTH "are you in this tenant" AND "do you hold the permission" — so no
82
+ * separate membership check is needed.
83
+ *
84
+ * • offer → `authorize(offeredBy, owningTenantId, 'share')` (act in your own tenant A)
85
+ * • accept → `authorize(acceptedBy, granteeTenantId, 'accept')` (act in your own tenant B)
86
+ * • revoke → `authorize(revokedBy, owningTenantId, 'revoke')` OR (…granteeTenantId, 'revoke')
87
+ *
88
+ * Returns a genuine boolean; the writer gates on strict `=== true`. A store/authority outage
89
+ * SHOULD throw (the writer surfaces it as {@link GrantStoreUnavailableError}, a retryable 503) —
90
+ * never a silent `false` that would mask an outage as "not authorized".
91
+ */
92
+ export type GrantAuthorizeFn = (principalId: string, tenantId: string, action: 'share' | 'accept' | 'revoke') => Promise<boolean>;
93
+ /** A grant lifecycle audit event (structured — ids in fields, NEVER in a rendered message). */
94
+ export interface GrantAuditEvent {
95
+ /** Always `'cross_tenant_grant'`. */
96
+ readonly type: 'cross_tenant_grant';
97
+ /** The specific lifecycle event. */
98
+ readonly action: 'grant.offered' | 'grant.accepted' | 'grant.revoked';
99
+ /** Epoch ms the event occurred. */
100
+ readonly occurredAtMs: number;
101
+ /** The principal who performed the action (offerer / accepter / revoker). */
102
+ readonly actorId: string;
103
+ /**
104
+ * The grant's OWNING tenant (whose data is reached into / who consents). Present so the
105
+ * consumer's sink records the event under this tenant's audit trail too (audit-on-both).
106
+ */
107
+ readonly owningTenantId: string;
108
+ /** The grant's GRANTEE tenant (reaching in). Present so the sink records it under BOTH. */
109
+ readonly granteeTenantId: string;
110
+ /** Structured, UUID-free-message detail (the ids live HERE, not in a message string). */
111
+ readonly attributes: {
112
+ readonly grantUuid: string;
113
+ readonly typeKey: string;
114
+ readonly scope: GrantScope;
115
+ readonly recordId?: string;
116
+ readonly granteeKind: GrantGranteeKind;
117
+ readonly granteeUuid: string;
118
+ readonly allowedActions: readonly string[];
119
+ };
120
+ }
121
+ /** The audit seam the writer emits through. Best-effort — a throw never fails the write. */
122
+ export interface GrantAuditSink {
123
+ record(event: GrantAuditEvent): Promise<void>;
124
+ }
125
+ /** Input to {@link CrossTenantGrantWriter.offer}. */
126
+ export interface OfferGrantInput {
127
+ /**
128
+ * ⛔ The OWNING tenant — whose data is reached into / who consents. Supplied from the
129
+ * AUTHENTICATED principal's context, NEVER a user-controlled body field for the authority
130
+ * decision. The offerer MUST be an admin of THIS tenant.
131
+ */
132
+ readonly owningTenantId: string;
133
+ /** The GRANTEE tenant — the tenant reaching in. */
134
+ readonly granteeTenantId: string;
135
+ /** Who in the grantee tenant the grant is for (a specific user or role). */
136
+ readonly granteeKind: GrantGranteeKind;
137
+ /** The grantee user/role uuid (in the grantee tenant). */
138
+ readonly granteeUuid: string;
139
+ /** The object type the grant covers. */
140
+ readonly typeKey: string;
141
+ /** Whole-object-type, or a single record. */
142
+ readonly scope: GrantScope;
143
+ /** REQUIRED iff `scope='record'`; FORBIDDEN for `scope='object_type'`. */
144
+ readonly recordId?: string;
145
+ /** The hard action ceiling — non-empty, non-wildcard, enumerated action keys. */
146
+ readonly allowedActions: readonly string[];
147
+ /** Mandatory FUTURE expiry (UTC epoch ms). No perpetual/absent/past grants. */
148
+ readonly expiresAtMs: number;
149
+ /**
150
+ * ⛔ The AUTHENTICATED principal id who is offering — supplied from the request principal,
151
+ * never a user-controlled body field. Proven to be an OWNING-tenant admin before the write.
152
+ */
153
+ readonly offeredBy: string;
154
+ }
155
+ /** Input to {@link CrossTenantGrantWriter.accept}. */
156
+ export interface AcceptGrantInput {
157
+ /** The grant to accept (its uuid). */
158
+ readonly grantUuid: string;
159
+ /**
160
+ * ⛔ The AUTHENTICATED principal accepting — supplied from the request principal. Proven to
161
+ * be a MEMBER of the grant's GRANTEE tenant before the write.
162
+ */
163
+ readonly acceptedBy: string;
164
+ }
165
+ /** Input to {@link CrossTenantGrantWriter.revoke}. */
166
+ export interface RevokeGrantInput {
167
+ /** The grant to revoke (its uuid). */
168
+ readonly grantUuid: string;
169
+ /**
170
+ * ⛔ The AUTHENTICATED principal revoking — supplied from the request principal. Proven to be
171
+ * EITHER an admin of the OWNING tenant OR a member of the GRANTEE tenant before the write.
172
+ */
173
+ readonly revokedBy: string;
174
+ }
175
+ /** Thrown when the who-may-grant bound rejects an offer/accept/revoke (fail-closed). */
176
+ export declare class GrantNotPermittedError extends Error {
177
+ constructor(message: string);
178
+ }
179
+ /** Thrown when the grant input is malformed (bad ceiling / expiry / scope). */
180
+ export declare class InvalidGrantError extends Error {
181
+ constructor(message: string);
182
+ }
183
+ /** Thrown when the grant store genuinely cannot answer/write (transient → 503-retryable). */
184
+ export declare class GrantStoreUnavailableError extends Error {
185
+ constructor(message: string);
186
+ }
187
+ export declare class CrossTenantGrantWriter {
188
+ private readonly db;
189
+ private readonly authorize;
190
+ private readonly audit;
191
+ private readonly schema;
192
+ private readonly now;
193
+ constructor(deps: {
194
+ db: GrantQueryable;
195
+ /**
196
+ * Answers "may `principalId` perform `action` in `tenantId`?" over the consumer's OWN
197
+ * authorization model (typically a thin closure that builds the principal and calls the
198
+ * app's `decide()`). Replaces the former `authority: GrantAuthorityResolver`.
199
+ */
200
+ authorize: GrantAuthorizeFn;
201
+ /** Optional — absent ⇒ lifecycle events are un-audited (never blocks the write). */
202
+ audit?: GrantAuditSink;
203
+ schema?: string;
204
+ /** Test seam: a deterministic clock (defaults to Date.now). */
205
+ nowMs?: () => number;
206
+ });
207
+ /**
208
+ * OFFER a grant — the OWNING tenant consents to let a grantee tenant's principal in. Enforces
209
+ * the offer bound (owning-tenant admin, non-empty/non-wildcard ceiling, future expiry, scope
210
+ * ↔ recordId). Creates a fresh row in 'offered' status. Emits `grant.offered`.
211
+ *
212
+ * @returns the new grant's uuid.
213
+ * @throws {@link GrantNotPermittedError} if the offerer is not an owning-tenant admin.
214
+ * @throws {@link InvalidGrantError} on a bad ceiling / expiry / scope.
215
+ * @throws {@link GrantStoreUnavailableError} if the store cannot be reached (transient).
216
+ */
217
+ offer(input: OfferGrantInput): Promise<string>;
218
+ /**
219
+ * ACCEPT a grant — the GRANTEE tenant activates it. Only a MEMBER of the grant's grantee
220
+ * tenant may accept, and only a grant currently 'offered'. Moves it to 'accepted' (the point
221
+ * at which it becomes ACTIVE for `findActiveGrant`). Re-accepting an 'accepted' grant is a
222
+ * benign no-op; accepting a 'revoked' grant is rejected. Emits `grant.accepted`.
223
+ *
224
+ * @throws {@link GrantNotPermittedError} if the accepter is not a grantee-tenant member, the
225
+ * grant does not exist, or it is not in 'offered' status.
226
+ * @throws {@link GrantStoreUnavailableError} if the store cannot be reached (transient).
227
+ */
228
+ accept(input: AcceptGrantInput): Promise<void>;
229
+ /**
230
+ * REVOKE a grant — EITHER an admin of the owning tenant OR a member of the grantee tenant may
231
+ * revoke (either side, ADR-0009). Moves it to 'revoked' + stamps `ctg_revoked_at` (it becomes
232
+ * immediately inactive — `findActiveGrant` reads live). Re-revoking a 'revoked' grant is a
233
+ * benign no-op. Emits `grant.revoked`.
234
+ *
235
+ * @throws {@link GrantNotPermittedError} if the revoker is neither an owning-tenant admin nor
236
+ * a grantee-tenant member, or the grant does not exist.
237
+ * @throws {@link GrantStoreUnavailableError} if the store cannot be reached (transient).
238
+ */
239
+ revoke(input: RevokeGrantInput): Promise<void>;
240
+ /**
241
+ * Enforce the offer INPUT bounds (ceiling non-empty + non-wildcard, future expiry, scope ↔
242
+ * recordId). Throws {@link InvalidGrantError} on any failure (fail-closed). The is-admin
243
+ * authority check is done separately (it needs I/O).
244
+ */
245
+ private assertValidOffer;
246
+ /**
247
+ * Run an authority resolver call, translating a store outage into
248
+ * {@link GrantStoreUnavailableError} (retryable) — a resolver that THROWS is an outage, not a
249
+ * "not authorized". A resolver returning `false` is a legitimate deny (handled by the caller).
250
+ */
251
+ private resolveAuthority;
252
+ /** Fetch a grant's lifecycle row (tenants + status) by uuid — never trusts caller-supplied tenants. */
253
+ private fetchGrant;
254
+ /** Emit an offer event (fields already assembled). Best-effort. */
255
+ private emit;
256
+ /** Emit an accept/revoke event, deriving structured attributes from the fetched row. */
257
+ private emitFromRow;
258
+ }
259
+ //# sourceMappingURL=cross-tenant-grant-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-tenant-grant-writer.d.ts","sourceRoot":"","sources":["../../src/authz/cross-tenant-grant-writer.ts"],"names":[],"mappings":"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"}