@astram/guard 0.1.0 → 0.2.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/auth.controller.d.ts +102 -0
- package/dist/auth/auth.controller.d.ts.map +1 -0
- package/dist/auth/auth.controller.js +313 -0
- package/dist/auth/auth.module.d.ts +104 -0
- package/dist/auth/auth.module.d.ts.map +1 -0
- package/dist/auth/auth.module.js +318 -0
- package/dist/auth/connections.d.ts +61 -0
- package/dist/auth/connections.d.ts.map +1 -0
- package/dist/auth/connections.js +116 -0
- package/dist/auth/dto.d.ts +144 -0
- package/dist/auth/dto.d.ts.map +1 -0
- package/dist/auth/dto.js +29 -0
- package/dist/auth/pending-mfa-store.d.ts +72 -0
- package/dist/auth/pending-mfa-store.d.ts.map +1 -0
- package/dist/auth/pending-mfa-store.js +106 -0
- package/dist/auth/persistence.module.d.ts +42 -0
- package/dist/auth/persistence.module.d.ts.map +1 -0
- package/dist/auth/persistence.module.js +94 -0
- package/dist/auth/tokens.d.ts +62 -0
- package/dist/auth/tokens.d.ts.map +1 -0
- package/dist/auth/tokens.js +65 -0
- package/dist/authentication/mariadb-credential-resolver.d.ts +96 -0
- package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -0
- package/dist/authentication/mariadb-credential-resolver.js +156 -0
- package/dist/config/app-config.d.ts +134 -1
- package/dist/config/app-config.d.ts.map +1 -1
- package/dist/config/app-config.js +144 -2
- package/dist/index.d.ts +9 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +46 -1
- package/dist/module/shield.module.d.ts +51 -0
- package/dist/module/shield.module.d.ts.map +1 -1
- package/dist/module/shield.module.js +65 -4
- package/package.json +9 -9
package/dist/auth/dto.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 0b Auth-surface **endpoint DTOs / view types** — the request/response
|
|
4
|
+
* SHAPES the four `@Public()` `/auth/*` endpoints speak (spec §2). ⛔ FROZEN by
|
|
5
|
+
* this prelude; **Team C builds controllers to these**. Types only — no NestJS /
|
|
6
|
+
* class-validator decorators here (the prelude froze the wire shape; Team C adds
|
|
7
|
+
* the validation decorators on its own DTO classes if it wants runtime
|
|
8
|
+
* validation, keeping this file framework-agnostic and importable anywhere).
|
|
9
|
+
*
|
|
10
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
* SECURITY RULES these shapes ENCODE (spec §2, ACs 2/3/6; enforce in controllers):
|
|
12
|
+
*
|
|
13
|
+
* • ANTI-ENUMERATION — `/auth/login` and `/auth/mfa` failure responses are a
|
|
14
|
+
* SINGLE generic 401 for {unknown user, wrong password, wrong/expired factor}.
|
|
15
|
+
* The response body MUST NOT distinguish which failed (the engine's decoy-verify
|
|
16
|
+
* already equalizes timing). NEVER add a `reason`/`field`/`code` that leaks
|
|
17
|
+
* "no such user" vs "bad password".
|
|
18
|
+
* • NO SECRET LEAK — NO response/log ever carries a password, a password hash, a
|
|
19
|
+
* TOTP secret, or a RAW refresh/access token OTHER than the two token fields a
|
|
20
|
+
* successful lifecycle event legitimately returns ({@link SessionTokensView}).
|
|
21
|
+
* The {@link SessionView} below deliberately OMITS every secret — it is the
|
|
22
|
+
* ONLY session shape a response body may embed.
|
|
23
|
+
* • FAIL-CLOSED — a store/transient failure on refresh is the ADR-0016 retryable
|
|
24
|
+
* signal (503-style), NOT a terminal deny/logout (see {@link RefreshResponse}).
|
|
25
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
26
|
+
*
|
|
27
|
+
* ADR-0003: framework-agnostic — no framework / HTTP / DB imports.
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 0b Auth-surface — the **pending-MFA challenge store** backing
|
|
3
|
+
* `/auth/login` (mfa branch) → `/auth/mfa`.
|
|
4
|
+
*
|
|
5
|
+
* ── WHY THIS EXISTS ──────────────────────────────────────────────────────────
|
|
6
|
+
* The core `Authenticator` has NO separate "verify one more factor" entry point:
|
|
7
|
+
* `authenticate(hint, credentials[])` is the ONLY seam (see
|
|
8
|
+
* `../../core/authentication/authenticator.ts`). To COMPLETE an MFA login the
|
|
9
|
+
* controller must RE-CALL `authenticate(hint, [password, totp])` with BOTH the
|
|
10
|
+
* original password AND the freshly-presented TOTP code. So between the login that
|
|
11
|
+
* returned `mfa_required` and the `/auth/mfa` that finishes it, the server must
|
|
12
|
+
* hold the ORIGINAL `identityHint` + `password` (the "pending authentication").
|
|
13
|
+
*
|
|
14
|
+
* ── THE NON-FORGEABILITY DESIGN (the security decision) ──────────────────────
|
|
15
|
+
* The `challengeToken` handed to the client is a **cryptographically-random opaque
|
|
16
|
+
* handle** (`node:crypto.randomBytes`), NOT a signed/encrypted blob carrying the
|
|
17
|
+
* credentials. The pending `{ identityHint, password }` state lives ONLY
|
|
18
|
+
* server-side, keyed by that random token. Consequences:
|
|
19
|
+
* • FORGERY-PROOF BY CONSTRUCTION — a hand-crafted token maps to no entry, so it
|
|
20
|
+
* is rejected. There is no signature to strip, no payload to tamper with, and
|
|
21
|
+
* the client is never given the credentials (even encrypted) to hold.
|
|
22
|
+
* • SHORT-LIVED — each entry has an expiry (default 5 min). An expired handle is
|
|
23
|
+
* treated as unknown (same generic 401 as a forged one — anti-enumeration).
|
|
24
|
+
* • SINGLE-USE — `consume()` DELETES the entry as it reads it, so a
|
|
25
|
+
* challengeToken cannot be replayed to brute-force TOTP codes indefinitely on
|
|
26
|
+
* one handle. (A caller that fails still burns the handle → must re-login. This
|
|
27
|
+
* is the built-in anti-brute-force cooldown for THIS layer; the resolver/engine
|
|
28
|
+
* add no separate one.)
|
|
29
|
+
* • NEVER LOGGED — the stored password + the token are never logged.
|
|
30
|
+
*
|
|
31
|
+
* TRADE-OFF (documented): server-side state is process-local (an in-memory `Map`).
|
|
32
|
+
* For the single-instance framework scope of Phase 0b this is correct and the
|
|
33
|
+
* safest option (the password never reaches the client). A multi-instance
|
|
34
|
+
* deployment would swap this `Map` for a shared TTL store (Redis) behind the SAME
|
|
35
|
+
* interface — the controller depends only on `store/consume`, not on the backing.
|
|
36
|
+
*
|
|
37
|
+
* ADR-0003 note: this file DOES use `node:crypto` (guard owns node builtins; core
|
|
38
|
+
* does not). It holds NO framework/HTTP coupling — a plain injectable class.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* A minimal server-side, single-use, TTL'd store of pending MFA challenges.
|
|
42
|
+
* Injected into the auth controller. Deliberately depends on nothing framework-y.
|
|
43
|
+
*/
|
|
44
|
+
export declare class PendingMfaStore {
|
|
45
|
+
private readonly ttlMs;
|
|
46
|
+
/** Clock (UTC epoch ms) — injectable for deterministic tests. */
|
|
47
|
+
private readonly nowMs;
|
|
48
|
+
private readonly pending;
|
|
49
|
+
constructor(ttlMs?: number,
|
|
50
|
+
/** Clock (UTC epoch ms) — injectable for deterministic tests. */
|
|
51
|
+
nowMs?: () => number);
|
|
52
|
+
/**
|
|
53
|
+
* Store a pending authentication and return the opaque, unguessable
|
|
54
|
+
* `challengeToken` the client re-presents to `/auth/mfa`. The token carries NO
|
|
55
|
+
* information about the user — it is a random handle only.
|
|
56
|
+
*/
|
|
57
|
+
store(identityHint: string, password: string): string;
|
|
58
|
+
/**
|
|
59
|
+
* Look up + SINGLE-USE consume a pending challenge. Returns the held credentials
|
|
60
|
+
* for a live, known token; `null` for an unknown, forged, or expired token (the
|
|
61
|
+
* controller maps `null` to the SAME generic 401 as a wrong code — no leak of
|
|
62
|
+
* WHY it failed). The entry is DELETED whether or not it was live, so a handle
|
|
63
|
+
* is never replayable.
|
|
64
|
+
*/
|
|
65
|
+
consume(token: string): {
|
|
66
|
+
identityHint: string;
|
|
67
|
+
password: string;
|
|
68
|
+
} | null;
|
|
69
|
+
/** Drop expired entries so the map cannot grow unbounded from abandoned logins. */
|
|
70
|
+
private sweep;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=pending-mfa-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pending-mfa-store.d.ts","sourceRoot":"","sources":["../../src/auth/pending-mfa-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAmBH;;;GAGG;AACH,qBAAa,eAAe;IAIxB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,KAAK;IALxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;gBAGxC,KAAK,GAAE,MAAuB;IAC/C,iEAAiE;IAChD,KAAK,GAAE,MAAM,MAAiB;IAGjD;;;;OAIG;IACH,KAAK,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAWrD;;;;;;OAMG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAWzE,mFAAmF;IACnF,OAAO,CAAC,KAAK;CAMd"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 0b Auth-surface — the **pending-MFA challenge store** backing
|
|
4
|
+
* `/auth/login` (mfa branch) → `/auth/mfa`.
|
|
5
|
+
*
|
|
6
|
+
* ── WHY THIS EXISTS ──────────────────────────────────────────────────────────
|
|
7
|
+
* The core `Authenticator` has NO separate "verify one more factor" entry point:
|
|
8
|
+
* `authenticate(hint, credentials[])` is the ONLY seam (see
|
|
9
|
+
* `../../core/authentication/authenticator.ts`). To COMPLETE an MFA login the
|
|
10
|
+
* controller must RE-CALL `authenticate(hint, [password, totp])` with BOTH the
|
|
11
|
+
* original password AND the freshly-presented TOTP code. So between the login that
|
|
12
|
+
* returned `mfa_required` and the `/auth/mfa` that finishes it, the server must
|
|
13
|
+
* hold the ORIGINAL `identityHint` + `password` (the "pending authentication").
|
|
14
|
+
*
|
|
15
|
+
* ── THE NON-FORGEABILITY DESIGN (the security decision) ──────────────────────
|
|
16
|
+
* The `challengeToken` handed to the client is a **cryptographically-random opaque
|
|
17
|
+
* handle** (`node:crypto.randomBytes`), NOT a signed/encrypted blob carrying the
|
|
18
|
+
* credentials. The pending `{ identityHint, password }` state lives ONLY
|
|
19
|
+
* server-side, keyed by that random token. Consequences:
|
|
20
|
+
* • FORGERY-PROOF BY CONSTRUCTION — a hand-crafted token maps to no entry, so it
|
|
21
|
+
* is rejected. There is no signature to strip, no payload to tamper with, and
|
|
22
|
+
* the client is never given the credentials (even encrypted) to hold.
|
|
23
|
+
* • SHORT-LIVED — each entry has an expiry (default 5 min). An expired handle is
|
|
24
|
+
* treated as unknown (same generic 401 as a forged one — anti-enumeration).
|
|
25
|
+
* • SINGLE-USE — `consume()` DELETES the entry as it reads it, so a
|
|
26
|
+
* challengeToken cannot be replayed to brute-force TOTP codes indefinitely on
|
|
27
|
+
* one handle. (A caller that fails still burns the handle → must re-login. This
|
|
28
|
+
* is the built-in anti-brute-force cooldown for THIS layer; the resolver/engine
|
|
29
|
+
* add no separate one.)
|
|
30
|
+
* • NEVER LOGGED — the stored password + the token are never logged.
|
|
31
|
+
*
|
|
32
|
+
* TRADE-OFF (documented): server-side state is process-local (an in-memory `Map`).
|
|
33
|
+
* For the single-instance framework scope of Phase 0b this is correct and the
|
|
34
|
+
* safest option (the password never reaches the client). A multi-instance
|
|
35
|
+
* deployment would swap this `Map` for a shared TTL store (Redis) behind the SAME
|
|
36
|
+
* interface — the controller depends only on `store/consume`, not on the backing.
|
|
37
|
+
*
|
|
38
|
+
* ADR-0003 note: this file DOES use `node:crypto` (guard owns node builtins; core
|
|
39
|
+
* does not). It holds NO framework/HTTP coupling — a plain injectable class.
|
|
40
|
+
*/
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.PendingMfaStore = void 0;
|
|
43
|
+
const node_crypto_1 = require("node:crypto");
|
|
44
|
+
/** Default challenge lifetime — short, per spec §2 ("a short TTL"). */
|
|
45
|
+
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
46
|
+
/** How many random bytes back a challenge handle (256-bit, url-safe hex → 64 chars). */
|
|
47
|
+
const TOKEN_BYTES = 32;
|
|
48
|
+
/**
|
|
49
|
+
* A minimal server-side, single-use, TTL'd store of pending MFA challenges.
|
|
50
|
+
* Injected into the auth controller. Deliberately depends on nothing framework-y.
|
|
51
|
+
*/
|
|
52
|
+
class PendingMfaStore {
|
|
53
|
+
ttlMs;
|
|
54
|
+
nowMs;
|
|
55
|
+
pending = new Map();
|
|
56
|
+
constructor(ttlMs = DEFAULT_TTL_MS,
|
|
57
|
+
/** Clock (UTC epoch ms) — injectable for deterministic tests. */
|
|
58
|
+
nowMs = Date.now) {
|
|
59
|
+
this.ttlMs = ttlMs;
|
|
60
|
+
this.nowMs = nowMs;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Store a pending authentication and return the opaque, unguessable
|
|
64
|
+
* `challengeToken` the client re-presents to `/auth/mfa`. The token carries NO
|
|
65
|
+
* information about the user — it is a random handle only.
|
|
66
|
+
*/
|
|
67
|
+
store(identityHint, password) {
|
|
68
|
+
this.sweep();
|
|
69
|
+
const token = (0, node_crypto_1.randomBytes)(TOKEN_BYTES).toString('hex');
|
|
70
|
+
this.pending.set(token, {
|
|
71
|
+
identityHint,
|
|
72
|
+
password,
|
|
73
|
+
expiresAtMs: this.nowMs() + this.ttlMs,
|
|
74
|
+
});
|
|
75
|
+
return token;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Look up + SINGLE-USE consume a pending challenge. Returns the held credentials
|
|
79
|
+
* for a live, known token; `null` for an unknown, forged, or expired token (the
|
|
80
|
+
* controller maps `null` to the SAME generic 401 as a wrong code — no leak of
|
|
81
|
+
* WHY it failed). The entry is DELETED whether or not it was live, so a handle
|
|
82
|
+
* is never replayable.
|
|
83
|
+
*/
|
|
84
|
+
consume(token) {
|
|
85
|
+
if (typeof token !== 'string' || token.length === 0)
|
|
86
|
+
return null;
|
|
87
|
+
const entry = this.pending.get(token);
|
|
88
|
+
// Single-use: remove on read regardless of validity (a spent/expired handle
|
|
89
|
+
// can never be retried → forces a fresh login, the anti-brute-force cooldown).
|
|
90
|
+
this.pending.delete(token);
|
|
91
|
+
if (entry === undefined)
|
|
92
|
+
return null;
|
|
93
|
+
if (this.nowMs() >= entry.expiresAtMs)
|
|
94
|
+
return null;
|
|
95
|
+
return { identityHint: entry.identityHint, password: entry.password };
|
|
96
|
+
}
|
|
97
|
+
/** Drop expired entries so the map cannot grow unbounded from abandoned logins. */
|
|
98
|
+
sweep() {
|
|
99
|
+
const now = this.nowMs();
|
|
100
|
+
for (const [token, entry] of this.pending) {
|
|
101
|
+
if (now >= entry.expiresAtMs)
|
|
102
|
+
this.pending.delete(token);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.PendingMfaStore = PendingMfaStore;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PersistenceModule` — the ONE place live infra connections are created (spec
|
|
3
|
+
* §4). Provides the live `mariadb` {@link Pool} ({@link SHIELD_DB_POOL}) and the
|
|
4
|
+
* live `ioredis` client ({@link SHIELD_REDIS}) from {@link AppConfig}, with
|
|
5
|
+
* graceful shutdown. Everything auth persists (sessions, credentials, hot state)
|
|
6
|
+
* flows through these two connections.
|
|
7
|
+
*
|
|
8
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
* Team B (connection + composition) OWNS this file. The two factories create the
|
|
10
|
+
* real driver objects; the {@link ConnectionLifecycle} provider (a class Nest
|
|
11
|
+
* invokes `onModuleDestroy` on) closes them on shutdown — a value provider is not
|
|
12
|
+
* itself lifecycle-aware, so the teardown lives in that holder (see
|
|
13
|
+
* `connections.ts`). Both connections are exported so AuthModule can inject the
|
|
14
|
+
* pool (credential resolver + session record store) and the redis (hot store).
|
|
15
|
+
*
|
|
16
|
+
* SHIELD_DB_POOL — {@link createDbPool}: `mariadb.createPool` for the
|
|
17
|
+
* `shield_core` account from `config.db`, `connectionLimit` from the ADR-0018
|
|
18
|
+
* budget (`config.pool.perAccountMax.shield_core`). NOT a duplicate pool knob.
|
|
19
|
+
* SHIELD_REDIS — {@link createRedisClient}: `new Redis(config.redis.url)`
|
|
20
|
+
* (ioredis). The hot tier (ADR-0013).
|
|
21
|
+
*
|
|
22
|
+
* Neither factory reads the HMAC key (that is the session/hasher composition in
|
|
23
|
+
* AuthModule). A blank `db.password` is allowed — it fails LOUD at connect time,
|
|
24
|
+
* which is correct for a runtime credential. Nothing here logs a credential.
|
|
25
|
+
*
|
|
26
|
+
* ⛔ OPT-IN: like AuthModule, this module opens LIVE connections, so it must only
|
|
27
|
+
* be imported when a deployment actually wants auth (AuthModule imports it). An
|
|
28
|
+
* enforcement-only app that never imports AuthModule never opens a pool/redis and
|
|
29
|
+
* keeps the no-`.env` boot. See auth.module.ts for the opt-in mechanism.
|
|
30
|
+
*
|
|
31
|
+
* ADR-0003: the real `mariadb`/`ioredis` live in guard (Core stays zero-dep).
|
|
32
|
+
*/
|
|
33
|
+
import { type DynamicModule } from '@nestjs/common';
|
|
34
|
+
/**
|
|
35
|
+
* The persistence module. `forRoot()` returns the two connection providers (both
|
|
36
|
+
* exported so AuthModule can inject them) plus the shutdown holder. Opt-in: only
|
|
37
|
+
* imported when auth is wired.
|
|
38
|
+
*/
|
|
39
|
+
export declare class PersistenceModule {
|
|
40
|
+
static forRoot(): DynamicModule;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=persistence.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.module.d.ts","sourceRoot":"","sources":["../../src/auth/persistence.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAU,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AA0C3E;;;;GAIG;AACH,qBACa,iBAAiB;IAC5B,MAAM,CAAC,OAAO,IAAI,aAAa;CAOhC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* `PersistenceModule` — the ONE place live infra connections are created (spec
|
|
4
|
+
* §4). Provides the live `mariadb` {@link Pool} ({@link SHIELD_DB_POOL}) and the
|
|
5
|
+
* live `ioredis` client ({@link SHIELD_REDIS}) from {@link AppConfig}, with
|
|
6
|
+
* graceful shutdown. Everything auth persists (sessions, credentials, hot state)
|
|
7
|
+
* flows through these two connections.
|
|
8
|
+
*
|
|
9
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
* Team B (connection + composition) OWNS this file. The two factories create the
|
|
11
|
+
* real driver objects; the {@link ConnectionLifecycle} provider (a class Nest
|
|
12
|
+
* invokes `onModuleDestroy` on) closes them on shutdown — a value provider is not
|
|
13
|
+
* itself lifecycle-aware, so the teardown lives in that holder (see
|
|
14
|
+
* `connections.ts`). Both connections are exported so AuthModule can inject the
|
|
15
|
+
* pool (credential resolver + session record store) and the redis (hot store).
|
|
16
|
+
*
|
|
17
|
+
* SHIELD_DB_POOL — {@link createDbPool}: `mariadb.createPool` for the
|
|
18
|
+
* `shield_core` account from `config.db`, `connectionLimit` from the ADR-0018
|
|
19
|
+
* budget (`config.pool.perAccountMax.shield_core`). NOT a duplicate pool knob.
|
|
20
|
+
* SHIELD_REDIS — {@link createRedisClient}: `new Redis(config.redis.url)`
|
|
21
|
+
* (ioredis). The hot tier (ADR-0013).
|
|
22
|
+
*
|
|
23
|
+
* Neither factory reads the HMAC key (that is the session/hasher composition in
|
|
24
|
+
* AuthModule). A blank `db.password` is allowed — it fails LOUD at connect time,
|
|
25
|
+
* which is correct for a runtime credential. Nothing here logs a credential.
|
|
26
|
+
*
|
|
27
|
+
* ⛔ OPT-IN: like AuthModule, this module opens LIVE connections, so it must only
|
|
28
|
+
* be imported when a deployment actually wants auth (AuthModule imports it). An
|
|
29
|
+
* enforcement-only app that never imports AuthModule never opens a pool/redis and
|
|
30
|
+
* keeps the no-`.env` boot. See auth.module.ts for the opt-in mechanism.
|
|
31
|
+
*
|
|
32
|
+
* ADR-0003: the real `mariadb`/`ioredis` live in guard (Core stays zero-dep).
|
|
33
|
+
*/
|
|
34
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
35
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
36
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
37
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
38
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
39
|
+
};
|
|
40
|
+
var PersistenceModule_1;
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.PersistenceModule = void 0;
|
|
43
|
+
const common_1 = require("@nestjs/common");
|
|
44
|
+
const config_module_js_1 = require("../config/config.module.js");
|
|
45
|
+
const tokens_js_1 = require("./tokens.js");
|
|
46
|
+
const connections_js_1 = require("./connections.js");
|
|
47
|
+
/**
|
|
48
|
+
* The live MariaDB pool provider. Creates the ONE `shield_core` pool from config
|
|
49
|
+
* via {@link createDbPool}. `inject: [APP_CONFIG]` so the factory sees the resolved
|
|
50
|
+
* config. Teardown is handled by {@link ConnectionLifecycle}.
|
|
51
|
+
*/
|
|
52
|
+
const dbPoolProvider = {
|
|
53
|
+
provide: tokens_js_1.SHIELD_DB_POOL,
|
|
54
|
+
useFactory: (config) => (0, connections_js_1.createDbPool)(config),
|
|
55
|
+
inject: [config_module_js_1.APP_CONFIG],
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The live Redis client provider. Creates the ONE hot-tier client from
|
|
59
|
+
* `config.redis.url` via {@link createRedisClient}. Teardown via
|
|
60
|
+
* {@link ConnectionLifecycle}.
|
|
61
|
+
*/
|
|
62
|
+
const redisProvider = {
|
|
63
|
+
provide: tokens_js_1.SHIELD_REDIS,
|
|
64
|
+
useFactory: (config) => (0, connections_js_1.createRedisClient)(config),
|
|
65
|
+
inject: [config_module_js_1.APP_CONFIG],
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* The lifecycle holder — injects the two connections and closes them on shutdown
|
|
69
|
+
* (`onModuleDestroy`). A value-provider connection gets no lifecycle callback, so
|
|
70
|
+
* this class is where graceful teardown happens.
|
|
71
|
+
*/
|
|
72
|
+
const lifecycleProvider = {
|
|
73
|
+
provide: connections_js_1.ConnectionLifecycle,
|
|
74
|
+
useFactory: (pool, redis) => new connections_js_1.ConnectionLifecycle(pool, redis),
|
|
75
|
+
inject: [tokens_js_1.SHIELD_DB_POOL, tokens_js_1.SHIELD_REDIS],
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* The persistence module. `forRoot()` returns the two connection providers (both
|
|
79
|
+
* exported so AuthModule can inject them) plus the shutdown holder. Opt-in: only
|
|
80
|
+
* imported when auth is wired.
|
|
81
|
+
*/
|
|
82
|
+
let PersistenceModule = PersistenceModule_1 = class PersistenceModule {
|
|
83
|
+
static forRoot() {
|
|
84
|
+
return {
|
|
85
|
+
module: PersistenceModule_1,
|
|
86
|
+
providers: [dbPoolProvider, redisProvider, lifecycleProvider],
|
|
87
|
+
exports: [tokens_js_1.SHIELD_DB_POOL, tokens_js_1.SHIELD_REDIS],
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
exports.PersistenceModule = PersistenceModule;
|
|
92
|
+
exports.PersistenceModule = PersistenceModule = PersistenceModule_1 = __decorate([
|
|
93
|
+
(0, common_1.Module)({})
|
|
94
|
+
], PersistenceModule);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 0b Auth-surface **DI provider tokens** — the shared injection seams the
|
|
3
|
+
* three parallel teams bind/consume so they never collide (ADR-0023: composition
|
|
4
|
+
* is the framework's job; these are the framework's internal wiring points).
|
|
5
|
+
*
|
|
6
|
+
* ⛔ FROZEN by this prelude. Team B (composition) PROVIDES these; Team A
|
|
7
|
+
* (credential store) provides the resolver behind {@link SHIELD_CREDENTIAL_RESOLVER};
|
|
8
|
+
* Team C (`/auth` controllers) CONSUMES {@link SHIELD_AUTHENTICATOR} +
|
|
9
|
+
* {@link SHIELD_SESSION_AUTHORITY}. Do not rename these Symbols — a rename is a
|
|
10
|
+
* breaking change to every team's wiring.
|
|
11
|
+
*
|
|
12
|
+
* NOTE — some session/hasher adapters already ship their OWN tokens in
|
|
13
|
+
* `@astram/guard` (`SHIELD_PASSWORD_HASHER`, `SHIELD_SESSION_RECORD_STORE`,
|
|
14
|
+
* `SHIELD_HOT_SESSION_STORE`, `SHIELD_TOKEN_GENERATOR`, `SHIELD_REFRESH_HASHER`,
|
|
15
|
+
* `SHIELD_CLOCK`). Those stay the canonical tokens for those adapters. This file
|
|
16
|
+
* adds ONLY the NEW seams Phase 0b introduces: the live connections
|
|
17
|
+
* ({@link SHIELD_DB_POOL} / {@link SHIELD_REDIS}), the credential resolver, and the
|
|
18
|
+
* two COMPOSED engines ({@link SHIELD_AUTHENTICATOR} / {@link SHIELD_SESSION_AUTHORITY}).
|
|
19
|
+
* {@link SHIELD_PASSWORD_HASHER} is re-exported from the argon2 adapter for
|
|
20
|
+
* convenience so a team can import every auth-relevant token from one module.
|
|
21
|
+
*
|
|
22
|
+
* ADR-0003: no framework/DB/crypto imports here — just Symbols (+ one re-export).
|
|
23
|
+
*/
|
|
24
|
+
export { SHIELD_PASSWORD_HASHER } from '../authentication/argon2id-password-hasher.js';
|
|
25
|
+
/**
|
|
26
|
+
* The ONE live `mariadb` `Pool` (the `shield_core` account — DML on
|
|
27
|
+
* `shield_identity`, ADR-0013/0014). **Provided by Team B** (the persistence
|
|
28
|
+
* factory — the single place a live DB connection is created). **Consumed by**
|
|
29
|
+
* the {@link SHIELD_CREDENTIAL_RESOLVER} factory and the
|
|
30
|
+
* {@link SHIELD_SESSION_RECORD_STORE} factory. Disposed on app shutdown.
|
|
31
|
+
*/
|
|
32
|
+
export declare const SHIELD_DB_POOL: unique symbol;
|
|
33
|
+
/**
|
|
34
|
+
* The ONE live `ioredis` client (the hot session tier, ADR-0013). **Provided by
|
|
35
|
+
* Team B** (the persistence factory). **Consumed by** the
|
|
36
|
+
* {@link SHIELD_HOT_SESSION_STORE} factory. Disposed on app shutdown.
|
|
37
|
+
*/
|
|
38
|
+
export declare const SHIELD_REDIS: unique symbol;
|
|
39
|
+
/**
|
|
40
|
+
* The `CredentialResolver` (core port) that resolves an identity hint →
|
|
41
|
+
* `ResolvedCredential | null`. **Provided by Team A** (the
|
|
42
|
+
* `MariaDbCredentialResolver`, injected with {@link SHIELD_DB_POOL}). **Consumed
|
|
43
|
+
* by** the {@link SHIELD_AUTHENTICATOR} factory (Team B) as
|
|
44
|
+
* `createAuthenticator({ resolver })`.
|
|
45
|
+
*/
|
|
46
|
+
export declare const SHIELD_CREDENTIAL_RESOLVER: unique symbol;
|
|
47
|
+
/**
|
|
48
|
+
* The composed `Authenticator` (core engine) — `createAuthenticator({ hasher,
|
|
49
|
+
* resolver, identityBroker, factorVerifiers:[totp] })`. **Provided by Team B**
|
|
50
|
+
* (the AuthModule factory). **Consumed by** Team C's `/auth/login` +
|
|
51
|
+
* `/auth/mfa` controllers.
|
|
52
|
+
*/
|
|
53
|
+
export declare const SHIELD_AUTHENTICATOR: unique symbol;
|
|
54
|
+
/**
|
|
55
|
+
* The composed `SessionAuthority` (core engine) — `createSessionAuthority({
|
|
56
|
+
* recordStore, hotStore, tokenGenerator, refreshHasher, clock, lifetimes })`.
|
|
57
|
+
* **Provided by Team B** (the AuthModule factory). **Consumed by** Team C's
|
|
58
|
+
* `/auth/login` (establish), `/auth/refresh` (rotate), `/auth/logout` (revoke),
|
|
59
|
+
* and `/auth/mfa` (establish after factor).
|
|
60
|
+
*/
|
|
61
|
+
export declare const SHIELD_SESSION_AUTHORITY: unique symbol;
|
|
62
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/auth/tokens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,+CAA+C,CAAC;AAEvF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,eAA2B,CAAC;AAEvD;;;;GAIG;AACH,eAAO,MAAM,YAAY,eAAyB,CAAC;AAEnD;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,eAAuC,CAAC;AAE/E;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,eAAiC,CAAC;AAEnE;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,eAAqC,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Phase 0b Auth-surface **DI provider tokens** — the shared injection seams the
|
|
4
|
+
* three parallel teams bind/consume so they never collide (ADR-0023: composition
|
|
5
|
+
* is the framework's job; these are the framework's internal wiring points).
|
|
6
|
+
*
|
|
7
|
+
* ⛔ FROZEN by this prelude. Team B (composition) PROVIDES these; Team A
|
|
8
|
+
* (credential store) provides the resolver behind {@link SHIELD_CREDENTIAL_RESOLVER};
|
|
9
|
+
* Team C (`/auth` controllers) CONSUMES {@link SHIELD_AUTHENTICATOR} +
|
|
10
|
+
* {@link SHIELD_SESSION_AUTHORITY}. Do not rename these Symbols — a rename is a
|
|
11
|
+
* breaking change to every team's wiring.
|
|
12
|
+
*
|
|
13
|
+
* NOTE — some session/hasher adapters already ship their OWN tokens in
|
|
14
|
+
* `@astram/guard` (`SHIELD_PASSWORD_HASHER`, `SHIELD_SESSION_RECORD_STORE`,
|
|
15
|
+
* `SHIELD_HOT_SESSION_STORE`, `SHIELD_TOKEN_GENERATOR`, `SHIELD_REFRESH_HASHER`,
|
|
16
|
+
* `SHIELD_CLOCK`). Those stay the canonical tokens for those adapters. This file
|
|
17
|
+
* adds ONLY the NEW seams Phase 0b introduces: the live connections
|
|
18
|
+
* ({@link SHIELD_DB_POOL} / {@link SHIELD_REDIS}), the credential resolver, and the
|
|
19
|
+
* two COMPOSED engines ({@link SHIELD_AUTHENTICATOR} / {@link SHIELD_SESSION_AUTHORITY}).
|
|
20
|
+
* {@link SHIELD_PASSWORD_HASHER} is re-exported from the argon2 adapter for
|
|
21
|
+
* convenience so a team can import every auth-relevant token from one module.
|
|
22
|
+
*
|
|
23
|
+
* ADR-0003: no framework/DB/crypto imports here — just Symbols (+ one re-export).
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.SHIELD_SESSION_AUTHORITY = exports.SHIELD_AUTHENTICATOR = exports.SHIELD_CREDENTIAL_RESOLVER = exports.SHIELD_REDIS = exports.SHIELD_DB_POOL = exports.SHIELD_PASSWORD_HASHER = void 0;
|
|
27
|
+
var argon2id_password_hasher_js_1 = require("../authentication/argon2id-password-hasher.js");
|
|
28
|
+
Object.defineProperty(exports, "SHIELD_PASSWORD_HASHER", { enumerable: true, get: function () { return argon2id_password_hasher_js_1.SHIELD_PASSWORD_HASHER; } });
|
|
29
|
+
/**
|
|
30
|
+
* The ONE live `mariadb` `Pool` (the `shield_core` account — DML on
|
|
31
|
+
* `shield_identity`, ADR-0013/0014). **Provided by Team B** (the persistence
|
|
32
|
+
* factory — the single place a live DB connection is created). **Consumed by**
|
|
33
|
+
* the {@link SHIELD_CREDENTIAL_RESOLVER} factory and the
|
|
34
|
+
* {@link SHIELD_SESSION_RECORD_STORE} factory. Disposed on app shutdown.
|
|
35
|
+
*/
|
|
36
|
+
exports.SHIELD_DB_POOL = Symbol('SHIELD_DB_POOL');
|
|
37
|
+
/**
|
|
38
|
+
* The ONE live `ioredis` client (the hot session tier, ADR-0013). **Provided by
|
|
39
|
+
* Team B** (the persistence factory). **Consumed by** the
|
|
40
|
+
* {@link SHIELD_HOT_SESSION_STORE} factory. Disposed on app shutdown.
|
|
41
|
+
*/
|
|
42
|
+
exports.SHIELD_REDIS = Symbol('SHIELD_REDIS');
|
|
43
|
+
/**
|
|
44
|
+
* The `CredentialResolver` (core port) that resolves an identity hint →
|
|
45
|
+
* `ResolvedCredential | null`. **Provided by Team A** (the
|
|
46
|
+
* `MariaDbCredentialResolver`, injected with {@link SHIELD_DB_POOL}). **Consumed
|
|
47
|
+
* by** the {@link SHIELD_AUTHENTICATOR} factory (Team B) as
|
|
48
|
+
* `createAuthenticator({ resolver })`.
|
|
49
|
+
*/
|
|
50
|
+
exports.SHIELD_CREDENTIAL_RESOLVER = Symbol('SHIELD_CREDENTIAL_RESOLVER');
|
|
51
|
+
/**
|
|
52
|
+
* The composed `Authenticator` (core engine) — `createAuthenticator({ hasher,
|
|
53
|
+
* resolver, identityBroker, factorVerifiers:[totp] })`. **Provided by Team B**
|
|
54
|
+
* (the AuthModule factory). **Consumed by** Team C's `/auth/login` +
|
|
55
|
+
* `/auth/mfa` controllers.
|
|
56
|
+
*/
|
|
57
|
+
exports.SHIELD_AUTHENTICATOR = Symbol('SHIELD_AUTHENTICATOR');
|
|
58
|
+
/**
|
|
59
|
+
* The composed `SessionAuthority` (core engine) — `createSessionAuthority({
|
|
60
|
+
* recordStore, hotStore, tokenGenerator, refreshHasher, clock, lifetimes })`.
|
|
61
|
+
* **Provided by Team B** (the AuthModule factory). **Consumed by** Team C's
|
|
62
|
+
* `/auth/login` (establish), `/auth/refresh` (rotate), `/auth/logout` (revoke),
|
|
63
|
+
* and `/auth/mfa` (establish after factor).
|
|
64
|
+
*/
|
|
65
|
+
exports.SHIELD_SESSION_AUTHORITY = Symbol('SHIELD_SESSION_AUTHORITY');
|
|
@@ -0,0 +1,96 @@
|
|
|
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.
|
|
6
|
+
*
|
|
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.
|
|
12
|
+
*
|
|
13
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
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):
|
|
18
|
+
*
|
|
19
|
+
* identity_credential
|
|
20
|
+
* ┌────────────────────────┬──────────────┬────────────────────────────────────┐
|
|
21
|
+
* │ column │ type │ notes │
|
|
22
|
+
* ├────────────────────────┼──────────────┼────────────────────────────────────┤
|
|
23
|
+
* │ ic_identity_hint │ VARCHAR(255) │ UNIQUE — the login key (user/email)│
|
|
24
|
+
* │ ic_password_hash │ VARCHAR(255) │ Argon2id encoded hash (verify in) │
|
|
25
|
+
* │ ic_principal_id │ VARCHAR(255) │ → ResolvedCredential.claims.id │
|
|
26
|
+
* │ ic_tenant_id │ VARCHAR(255) │ → claims.tenantId │
|
|
27
|
+
* │ ic_roles │ JSON / TEXT │ role-id array → claims.roleIds │
|
|
28
|
+
* │ ic_totp_secret │ VARCHAR(255) │ NULLABLE — base32 TOTP secret; │
|
|
29
|
+
* │ │ │ present ⇒ requiredFactors:[totp] │
|
|
30
|
+
* │ ic_created_at_ms │ BIGINT │ UTC epoch ms (platform sets UTC) │
|
|
31
|
+
* │ ic_updated_at_ms │ BIGINT │ UTC epoch ms │
|
|
32
|
+
* └────────────────────────┴──────────────┴────────────────────────────────────┘
|
|
33
|
+
* PRIMARY KEY / UNIQUE on ic_identity_hint (single-row resolve by hint).
|
|
34
|
+
*
|
|
35
|
+
* 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
|
|
38
|
+
* WHERE ic_identity_hint = ? LIMIT 1` (parameterized — NEVER string-built).
|
|
39
|
+
* 2. Return `null` when NO row matches — the ONLY "not found" signal
|
|
40
|
+
* (anti-enumeration; MUST NOT throw for an unknown hint; the engine burns a
|
|
41
|
+
* decoy verify on `null` so timing is uniform).
|
|
42
|
+
* 3. Map a found row → `ResolvedCredential`:
|
|
43
|
+
* passwordHash = ic_password_hash
|
|
44
|
+
* claims = { id: ic_principal_id, tenantId: ic_tenant_id,
|
|
45
|
+
* 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.
|
|
48
|
+
* 5. A store outage (connection/query error) MAY throw — the engine treats a
|
|
49
|
+
* 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").
|
|
52
|
+
*
|
|
53
|
+
* (`claims.kind` is optional — the Internal adapter defaults an absent kind; add
|
|
54
|
+
* 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
|
|
56
|
+
* allowlist, never a claim, per core/identity.)
|
|
57
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
58
|
+
*
|
|
59
|
+
* ADR-0003: the concrete DB lives in guard (Core stays zero-dep). ADR-0013/0014:
|
|
60
|
+
* reads `shield_identity` via the hot-path `shield_core` account.
|
|
61
|
+
*/
|
|
62
|
+
import type { Pool } from 'mariadb';
|
|
63
|
+
import type { CredentialResolver, ResolvedCredential } from '@astram/core';
|
|
64
|
+
/** DI token for the resolver — re-exported from the shared auth tokens module. */
|
|
65
|
+
export { SHIELD_CREDENTIAL_RESOLVER } from '../auth/tokens.js';
|
|
66
|
+
/**
|
|
67
|
+
* The concrete MariaDB-backed {@link CredentialResolver}. Constructed with the
|
|
68
|
+
* live `shield_core` {@link Pool} (via {@link SHIELD_CREDENTIAL_RESOLVER}'s
|
|
69
|
+
* factory, which injects {@link SHIELD_DB_POOL}). Stateless per call apart from
|
|
70
|
+
* the injected pool.
|
|
71
|
+
*
|
|
72
|
+
* ⛔ SKELETON: `resolve()` throws until Team A implements the SELECT above.
|
|
73
|
+
*/
|
|
74
|
+
export declare class MariaDbCredentialResolver implements CredentialResolver {
|
|
75
|
+
private readonly pool;
|
|
76
|
+
constructor(pool: Pool);
|
|
77
|
+
/**
|
|
78
|
+
* Resolve `identityHint` → {@link ResolvedCredential}, or `null` when no such
|
|
79
|
+
* principal exists.
|
|
80
|
+
*
|
|
81
|
+
* - Parameterized SELECT (`WHERE ic_identity_hint = ? LIMIT 1`) — never
|
|
82
|
+
* string-built, so the hint can never inject SQL.
|
|
83
|
+
* - **No row ⇒ `null`** — the ONLY "not found" signal (anti-enumeration; the
|
|
84
|
+
* engine burns a decoy password verify on `null` so an unknown user is
|
|
85
|
+
* indistinguishable from a wrong password by content or timing). We do NOT
|
|
86
|
+
* throw for an unknown hint.
|
|
87
|
+
* - A **query/connection ERROR propagates** (rejects) — an outage is NOT an
|
|
88
|
+
* unknown user; the engine treats a resolver throw as fail-closed
|
|
89
|
+
* `authentication_unavailable`. We never convert an error into `null` (that
|
|
90
|
+
* 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.
|
|
93
|
+
*/
|
|
94
|
+
resolve(identityHint: string): Promise<ResolvedCredential | null>;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=mariadb-credential-resolver.d.ts.map
|
|
@@ -0,0 +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"}
|