@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,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 4 MFA-enrollment — the **pure TOTP-enrollment helpers**: generate a fresh
|
|
3
|
+
* base32 TOTP secret, compose the `otpauth://` provisioning URI an authenticator app
|
|
4
|
+
* scans, and mint one-time recovery codes.
|
|
5
|
+
*
|
|
6
|
+
* ── WHY THIS LIVES IN GUARD (not core) ──────────────────────────────────────
|
|
7
|
+
* Core owns the TOTP *verifier* (`createTotpFactorVerifier`, RFC 6238) but is
|
|
8
|
+
* ZERO-dep + node-type-free (ADR-0003), so it cannot mint secrets (needs
|
|
9
|
+
* `node:crypto.randomBytes`) nor build a URI. Enrollment is the *provisioning* side
|
|
10
|
+
* — it produces the secret the verifier later checks against. Guard owns node
|
|
11
|
+
* builtins, so the generation lives here. NO core change: the secret this emits is a
|
|
12
|
+
* plain base32 string, exactly what `createTotpFactorVerifier` decodes.
|
|
13
|
+
*
|
|
14
|
+
* ── SECURITY ─────────────────────────────────────────────────────────────────
|
|
15
|
+
* The secret + recovery codes are credential material: never logged, returned to the
|
|
16
|
+
* user exactly ONCE at enrollment, and (recovery codes) stored only Argon2-hashed.
|
|
17
|
+
* These functions are PURE (no I/O, no storage) — the caller decides what to persist.
|
|
18
|
+
*
|
|
19
|
+
* ADR-0003: guard owns `node:crypto`; this file has NO framework/HTTP/DB coupling.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Generate a fresh, cryptographically-random base32 TOTP secret (160 bits → 32
|
|
23
|
+
* chars). This is what the user's authenticator app is provisioned with and what the
|
|
24
|
+
* core verifier later checks presented codes against.
|
|
25
|
+
*/
|
|
26
|
+
export declare function generateTotpSecret(): string;
|
|
27
|
+
/**
|
|
28
|
+
* Build the `otpauth://totp/...` provisioning URI an authenticator app scans (or the
|
|
29
|
+
* user enters manually). `label` identifies the account (shown in the app), `issuer`
|
|
30
|
+
* names the service. All three URI components are percent-encoded so a label/issuer
|
|
31
|
+
* with spaces or `:`/`?`/`&` cannot break the URI. Fixed to the defaults the core
|
|
32
|
+
* verifier uses (SHA-1, 6 digits, 30s period).
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildOtpauthUri(secret: string, label: string, issuer: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Mint `count` unique one-time recovery codes (each `xxxxx-xxxxx`, 10 lowercase hex
|
|
37
|
+
* chars grouped for readability). Returned RAW to the caller ONCE at enrollment; the
|
|
38
|
+
* store persists them Argon2-hashed. Uniqueness is enforced (a hex collision at this
|
|
39
|
+
* entropy is astronomically unlikely, but the loop guarantees `count` distinct codes).
|
|
40
|
+
*/
|
|
41
|
+
export declare function generateRecoveryCodes(count?: number): string[];
|
|
42
|
+
//# sourceMappingURL=totp-enrollment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"totp-enrollment.d.ts","sourceRoot":"","sources":["../../src/auth/totp-enrollment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA4CH;;;;GAIG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAOrF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,GAAE,MAAoC,GAAG,MAAM,EAAE,CAO3F"}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"totp-enrollment.js","sourceRoot":"","sources":["../../src/auth/totp-enrollment.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;AAiDH,gDAEC;AASD,0CAOC;AAQD,sDAOC;AAhFD,6CAA0C;AAE1C,sFAAsF;AACtF,MAAM,eAAe,GAAG,kCAAkC,CAAC;AAE3D;;;;GAIG;AACH,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB,kFAAkF;AAClF,MAAM,2BAA2B,GAAG,EAAE,CAAC;AAEvC,yFAAyF;AACzF,MAAM,mBAAmB,GAAG,CAAC,CAAC;AAE9B;;;;GAIG;AACH,SAAS,YAAY,CAAC,KAAiB;IACrC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC;QAC5B,IAAI,IAAI,CAAC,CAAC;QACV,OAAO,IAAI,IAAI,CAAC,EAAE,CAAC;YACjB,IAAI,IAAI,CAAC,CAAC;YACV,GAAG,IAAI,eAAe,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;QACb,6EAA6E;QAC7E,GAAG,IAAI,eAAe,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB;IAChC,OAAO,YAAY,CAAC,IAAA,yBAAW,EAAC,YAAY,CAAC,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,eAAe,CAAC,MAAc,EAAE,KAAa,EAAE,MAAc;IAC3E,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC3C,OAAO,CACL,kBAAkB,SAAS,IAAI,QAAQ,EAAE;QACzC,WAAW,MAAM,WAAW,SAAS,qBAAqB,CAC3D,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,qBAAqB,CAAC,QAAgB,2BAA2B;IAC/E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,OAAO,KAAK,CAAC,IAAI,GAAG,KAAK,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,IAAA,yBAAW,EAAC,mBAAmB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe;QAC7E,KAAK,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;AACpB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"argon2id-password-hasher.js","sourceRoot":"","sources":["../../src/authentication/argon2id-password-hasher.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EH,wDAQC;AAhFD,+CAAiC;AAmBjC,MAAM,cAAc,GAA6B;IAC/C,UAAU,EAAE,MAAM;IAClB,QAAQ,EAAE,CAAC;IACX,WAAW,EAAE,CAAC;CACf,CAAC;AAEF,0EAA0E;AAC7D,QAAA,sBAAsB,GAAG,MAAM,CAAC,wBAAwB,CAAC,CAAC;AAEvE;;;;GAIG;AACH,MAAa,sBAAsB;IAChB,OAAO,CAAuC;IAE/D,YAAY,SAAyB,EAAE;QACrC,IAAI,CAAC,OAAO,GAAG;YACb,IAAI,EAAE,MAAM,CAAC,QAAQ;YACrB,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,cAAc,CAAC,UAAU;YAC1D,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,cAAc,CAAC,QAAQ;YACpD,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,cAAc,CAAC,WAAW;SAC9D,CAAC;IACJ,CAAC;IAED,8EAA8E;IAC9E,KAAK,CAAC,IAAI,CAAC,SAAiB;QAC1B,6EAA6E;QAC7E,OAAO,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,MAAM,CAAC,IAAY,EAAE,SAAiB;QAC1C,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,yEAAyE;YACzE,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF;AA/BD,wDA+BC;AAED;;;;;GAKG;AACH,SAAgB,sBAAsB,CAAC,SAAyB,EAAE;IAIhE,OAAO;QACL,OAAO,EAAE,8BAAsB;QAC/B,QAAQ,EAAE,IAAI,sBAAsB,CAAC,MAAM,CAAC;KAC7C,CAAC;AACJ,CAAC"}
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `MariaDbCredentialResolver` — the concrete
|
|
3
|
-
* port) backed by the
|
|
4
|
-
* `
|
|
5
|
-
*
|
|
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
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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
|
-
*
|
|
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 =
|
|
47
|
-
* 4. NEVER log the hash
|
|
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
|
-
*
|
|
51
|
-
*
|
|
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
|
|
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
|
|
92
|
-
*
|
|
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
|
|
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
|
|
4
|
-
* port) backed by the
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
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
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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
|
-
*
|
|
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 =
|
|
48
|
-
* 4. NEVER log the hash
|
|
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
|
-
*
|
|
52
|
-
*
|
|
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
|
|
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
|
|
127
|
-
*
|
|
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
|
|
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
|
-
|
|
143
|
-
|
|
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 @@
|
|
|
1
|
+
{"version":3,"file":"mariadb-credential-resolver.js","sourceRoot":"","sources":["../../src/authentication/mariadb-credential-resolver.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;;;AASH,kFAAkF;AAClF,+CAA+D;AAAtD,uHAAA,0BAA0B,OAAA;AAkBnC;;;;;;;;;;;GAWG;AACH,SAAS,YAAY,CAAC,GAAY,EAAE,IAAY;IAC9C,IAAI,KAAK,GAAY,GAAG,CAAC;IACzB,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,2EAA2E;YAC3E,mDAAmD;YACnD,MAAM,IAAI,KAAK,CACb,oEAAoE,IAAI,IAAI;gBAC1E,2EAA2E,CAC9E,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,EAAE,CAAC;QACxE,MAAM,IAAI,KAAK,CACb,qDAAqD,IAAI,oBAAoB;YAC3E,qDAAqD,CACxD,CAAC;IACJ,CAAC;IACD,OAAO,KAA0B,CAAC;AACpC,CAAC;AAED;;;;;;;GAOG;AACH,MAAa,yBAAyB;IACP;IAA7B,YAA6B,IAAU;QAAV,SAAI,GAAJ,IAAI,CAAM;IAAG,CAAC;IAE3C;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,KAAK,CAAC,OAAO,CAAC,YAAoB;QAChC,8EAA8E;QAC9E,8EAA8E;QAC9E,8CAA8C;QAC9C,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CACjC;;;gBAGU,EACV,CAAC,YAAY,CAAC,CACf,CAAoB,CAAC;QAEtB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,+DAA+D;QAC/D,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,IAAI,CAAC;QACd,CAAC;QAED,6EAA6E;QAC7E,+EAA+E;QAC/E,MAAM,eAAe,GAAqB,EAAE,CAAC;QAE7C,OAAO;YACL,YAAY,EAAE,GAAG,CAAC,gBAAgB;YAClC,MAAM,EAAE;gBACN,EAAE,EAAE,GAAG,CAAC,eAAe;gBACvB,QAAQ,EAAE,GAAG,CAAC,YAAY;gBAC1B,OAAO,EAAE,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,YAAY,CAAC;aAClD;YACD,eAAe;SAChB,CAAC;IACJ,CAAC;CACF;AAxDD,8DAwDC"}
|