@astram/guard 0.8.0 → 0.9.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 +25 -5
- package/dist/auth/account.controller.d.ts.map +1 -1
- package/dist/auth/account.controller.js +89 -23
- package/dist/authentication/mariadb-api-key-store.d.ts +2 -1
- package/dist/authentication/mariadb-api-key-store.d.ts.map +1 -1
- package/dist/authentication/mariadb-api-key-store.js +13 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/internal.d.ts +12 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +16 -0
- package/package.json +13 -2
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
import type { FactorVerifier, PasswordHasher } from '@astram/core';
|
|
46
46
|
import type { SessionTokenBinder } from '../request-auth/session-token-binder.js';
|
|
47
47
|
import type { SessionRestrictionStore } from '../session/session-restriction-store.js';
|
|
48
|
+
import type { AttemptLimiter } from './rate-limit/attempt-limiter.js';
|
|
48
49
|
import type { CredentialWriter } from './ports/credential-writer.js';
|
|
49
50
|
import type { MfaDevice, MfaDeviceStore } from './ports/mfa-device-store.js';
|
|
50
51
|
import type { CurrentUserProfile, ProfileResolver } from './profile-resolver.js';
|
|
@@ -62,7 +63,10 @@ export declare class AccountController {
|
|
|
62
63
|
private readonly profileWriter?;
|
|
63
64
|
private readonly mfaDevices?;
|
|
64
65
|
private readonly sessionRestrictions?;
|
|
65
|
-
|
|
66
|
+
private readonly loginTotpVerifier?;
|
|
67
|
+
private readonly mfaLimiter?;
|
|
68
|
+
private readonly loginLimiter?;
|
|
69
|
+
constructor(binder?: SessionTokenBinder | undefined, hasher?: PasswordHasher | undefined, writer?: (CredentialWriter | null) | undefined, sweeper?: (SessionSweeper | null) | undefined, recoveryCodes?: (RecoveryCodeStore | null) | undefined, totpVerifier?: (FactorVerifier | null) | undefined, profileResolver?: (ProfileResolver | null) | undefined, profileWriter?: (ProfileWriter | null) | undefined, mfaDevices?: (MfaDeviceStore | null) | undefined, sessionRestrictions?: (SessionRestrictionStore | null) | undefined, loginTotpVerifier?: (FactorVerifier | null) | undefined, mfaLimiter?: (AttemptLimiter | null) | undefined, loginLimiter?: (AttemptLimiter | null) | undefined);
|
|
66
70
|
/**
|
|
67
71
|
* Resolve a human-readable otpauth account LABEL for the caller (email, else display
|
|
68
72
|
* name) via the profile resolver. Best-effort: no resolver / null profile / a thrown
|
|
@@ -111,8 +115,9 @@ export declare class AccountController {
|
|
|
111
115
|
* • no/invalid token → 401 (generic)
|
|
112
116
|
* • newPassword shorter than the minimum → 422 `{ error: 'weak_password' }`
|
|
113
117
|
* • writer/hasher seam absent → 501 `{ error: 'not_available' }`
|
|
114
|
-
* • wrong current password → 401 (generic, no write)
|
|
115
|
-
* •
|
|
118
|
+
* • wrong current password → 401 (generic, no write; counted)
|
|
119
|
+
* • account login budget exhausted → 401 (generic, not verified, no write)
|
|
120
|
+
* • success → 204 (no body; counter reset)
|
|
116
121
|
*/
|
|
117
122
|
changePassword(authorization: string | undefined, body: {
|
|
118
123
|
currentPassword?: string;
|
|
@@ -156,10 +161,25 @@ export declare class AccountController {
|
|
|
156
161
|
private replaceRecoveryCodes;
|
|
157
162
|
/**
|
|
158
163
|
* Verify a presented `code` against ANY of the caller's CONFIRMED device secrets
|
|
159
|
-
* (TOTP)
|
|
160
|
-
*
|
|
164
|
+
* (TOTP) through the replay-guarded login verifier: the secrets are bundled
|
|
165
|
+
* newline-joined (the `OrTotpVerifier` contract) and an accepted code is claimed in
|
|
166
|
+
* Redis, so it cannot be reused on this or any other MFA path inside the window. A
|
|
167
|
+
* Redis error on the claim → 503 (fail closed; never an unguarded accept, never a
|
|
168
|
+
* fall-through to the recovery-code check). Guarded verifier not wired → `false`.
|
|
169
|
+
* NEVER logs the code.
|
|
161
170
|
*/
|
|
162
171
|
private verifyAgainstAnyDevice;
|
|
172
|
+
/**
|
|
173
|
+
* The shared second-factor proof for the code-gated account routes (Phase 8 #1),
|
|
174
|
+
* mirroring `/auth/mfa`: the `mfa:pid:<principalId>` budget is checked BEFORE any TOTP
|
|
175
|
+
* or recovery-code verification (so a locked caller never triggers the Argon2
|
|
176
|
+
* recovery scan) → a TOTP from any confirmed device, else a recovery code (consumed) →
|
|
177
|
+
* a failed proof records a failure (400), a good one resets the counter. Locked → 429.
|
|
178
|
+
* The limiter fails open on a Redis blip (its documented posture); the TOTP replay
|
|
179
|
+
* claim fails closed (503), and the session itself is Redis-bound, so a Redis outage
|
|
180
|
+
* cannot open this path.
|
|
181
|
+
*/
|
|
182
|
+
private requireMfaProof;
|
|
163
183
|
/**
|
|
164
184
|
* `GET /account/mfa/devices` — list the caller's CONFIRMED authenticator devices.
|
|
165
185
|
* `mfaEnabled` reflects whether any confirmed device exists. Pending (unconfirmed)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account.controller.d.ts","sourceRoot":"","sources":["../../src/auth/account.controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAiBH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAa,MAAM,cAAc,CAAC;AAI9E,OAAO,KAAK,EAEV,kBAAkB,EACnB,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"account.controller.d.ts","sourceRoot":"","sources":["../../src/auth/account.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -62,6 +62,7 @@ const classification_decorator_js_1 = require("../enforcement/classification.dec
|
|
|
62
62
|
const request_principal_js_1 = require("../request-auth/request-principal.js");
|
|
63
63
|
const tokens_js_1 = require("../request-auth/tokens.js");
|
|
64
64
|
const tokens_js_2 = require("./tokens.js");
|
|
65
|
+
const canonical_hint_js_1 = require("./rate-limit/canonical-hint.js");
|
|
65
66
|
const totp_enrollment_js_1 = require("./totp-enrollment.js");
|
|
66
67
|
/** Max length of a device name (matches the `md_name VARCHAR(120)` column). */
|
|
67
68
|
const MAX_DEVICE_NAME_LEN = 120;
|
|
@@ -103,6 +104,18 @@ function notAvailable() {
|
|
|
103
104
|
function badRequest() {
|
|
104
105
|
throw new common_1.HttpException({ error: 'invalid_request' }, common_1.HttpStatus.BAD_REQUEST);
|
|
105
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The 429 for a caller whose proof budget is exhausted (Phase 8 #1). These routes are
|
|
109
|
+
* self-scoped and already authenticated, so the throttle signal reveals nothing about
|
|
110
|
+
* another account; a 401 here would read as "session expired" to the client's refresh logic.
|
|
111
|
+
*/
|
|
112
|
+
function tooManyRequests() {
|
|
113
|
+
throw new common_1.HttpException({ error: 'too_many_requests' }, common_1.HttpStatus.TOO_MANY_REQUESTS);
|
|
114
|
+
}
|
|
115
|
+
/** The retryable 503 when the replay guard (Redis) cannot answer: fail closed, no mutation. */
|
|
116
|
+
function retryableUnavailable() {
|
|
117
|
+
throw new common_1.HttpException({ error: 'authentication_unavailable' }, common_1.HttpStatus.SERVICE_UNAVAILABLE);
|
|
118
|
+
}
|
|
106
119
|
/**
|
|
107
120
|
* The 422 for a semantically invalid profile edit — an empty change (`no_changes`)
|
|
108
121
|
* or a malformed email (`invalid_email`). Distinct from {@link badRequest}: this is
|
|
@@ -123,7 +136,10 @@ let AccountController = class AccountController {
|
|
|
123
136
|
profileWriter;
|
|
124
137
|
mfaDevices;
|
|
125
138
|
sessionRestrictions;
|
|
126
|
-
|
|
139
|
+
loginTotpVerifier;
|
|
140
|
+
mfaLimiter;
|
|
141
|
+
loginLimiter;
|
|
142
|
+
constructor(binder, hasher, writer, sweeper, recoveryCodes, totpVerifier, profileResolver, profileWriter, mfaDevices, sessionRestrictions, loginTotpVerifier, mfaLimiter, loginLimiter) {
|
|
127
143
|
this.binder = binder;
|
|
128
144
|
this.hasher = hasher;
|
|
129
145
|
this.writer = writer;
|
|
@@ -134,6 +150,9 @@ let AccountController = class AccountController {
|
|
|
134
150
|
this.profileWriter = profileWriter;
|
|
135
151
|
this.mfaDevices = mfaDevices;
|
|
136
152
|
this.sessionRestrictions = sessionRestrictions;
|
|
153
|
+
this.loginTotpVerifier = loginTotpVerifier;
|
|
154
|
+
this.mfaLimiter = mfaLimiter;
|
|
155
|
+
this.loginLimiter = loginLimiter;
|
|
137
156
|
}
|
|
138
157
|
/**
|
|
139
158
|
* Resolve a human-readable otpauth account LABEL for the caller (email, else display
|
|
@@ -220,8 +239,9 @@ let AccountController = class AccountController {
|
|
|
220
239
|
* • no/invalid token → 401 (generic)
|
|
221
240
|
* • newPassword shorter than the minimum → 422 `{ error: 'weak_password' }`
|
|
222
241
|
* • writer/hasher seam absent → 501 `{ error: 'not_available' }`
|
|
223
|
-
* • wrong current password → 401 (generic, no write)
|
|
224
|
-
* •
|
|
242
|
+
* • wrong current password → 401 (generic, no write; counted)
|
|
243
|
+
* • account login budget exhausted → 401 (generic, not verified, no write)
|
|
244
|
+
* • success → 204 (no body; counter reset)
|
|
225
245
|
*/
|
|
226
246
|
async changePassword(authorization, body) {
|
|
227
247
|
const claims = await this.requireClaims(authorization);
|
|
@@ -240,14 +260,25 @@ let AccountController = class AccountController {
|
|
|
240
260
|
}
|
|
241
261
|
const writer = this.writer;
|
|
242
262
|
const hasher = this.hasher;
|
|
263
|
+
// Phase 8 #8: the current-password check spends the SAME per-account budget as
|
|
264
|
+
// password login (`login:pid:<principalId>`, shared counter): checked BEFORE the
|
|
265
|
+
// verify (a locked account never reaches the hasher), a wrong current password records
|
|
266
|
+
// a failure, and a success resets it — exactly as a completed login does. Locked → the
|
|
267
|
+
// same generic 401 as a wrong password (no oracle).
|
|
268
|
+
const loginKey = (0, canonical_hint_js_1.loginHintKey)('', claims.id);
|
|
269
|
+
if (this.loginLimiter != null && !(await this.loginLimiter.check(loginKey))) {
|
|
270
|
+
unauthorized();
|
|
271
|
+
}
|
|
243
272
|
// Verify the current password: read the stored hash BY PRINCIPAL ID via the writer
|
|
244
273
|
// seam's `getPasswordHash` (change-password holds the principal id from the token,
|
|
245
274
|
// whereas CredentialResolver keys by identityHint). A missing hash or a mismatch is
|
|
246
275
|
// the SAME generic 401 (no oracle), and no write happens.
|
|
247
276
|
const stored = await writer.getPasswordHash(claims.id);
|
|
248
277
|
if (stored === null || !(await hasher.verify(stored, currentPassword))) {
|
|
278
|
+
await this.loginLimiter?.recordFailure(loginKey);
|
|
249
279
|
unauthorized();
|
|
250
280
|
}
|
|
281
|
+
await this.loginLimiter?.reset(loginKey);
|
|
251
282
|
const newHash = await hasher.hash(newPassword);
|
|
252
283
|
await writer.setPassword(claims.id, newHash);
|
|
253
284
|
// Revoke the caller's OTHER sessions (keep the current one live). Best-effort:
|
|
@@ -339,16 +370,52 @@ let AccountController = class AccountController {
|
|
|
339
370
|
}
|
|
340
371
|
/**
|
|
341
372
|
* Verify a presented `code` against ANY of the caller's CONFIRMED device secrets
|
|
342
|
-
* (TOTP)
|
|
343
|
-
*
|
|
373
|
+
* (TOTP) through the replay-guarded login verifier: the secrets are bundled
|
|
374
|
+
* newline-joined (the `OrTotpVerifier` contract) and an accepted code is claimed in
|
|
375
|
+
* Redis, so it cannot be reused on this or any other MFA path inside the window. A
|
|
376
|
+
* Redis error on the claim → 503 (fail closed; never an unguarded accept, never a
|
|
377
|
+
* fall-through to the recovery-code check). Guarded verifier not wired → `false`.
|
|
378
|
+
* NEVER logs the code.
|
|
344
379
|
*/
|
|
345
380
|
async verifyAgainstAnyDevice(store, principalId, code) {
|
|
381
|
+
if (this.loginTotpVerifier === undefined || this.loginTotpVerifier === null)
|
|
382
|
+
return false;
|
|
346
383
|
const secrets = await store.listConfirmedSecrets(principalId);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
384
|
+
if (secrets.length === 0)
|
|
385
|
+
return false;
|
|
386
|
+
try {
|
|
387
|
+
const verdict = await this.loginTotpVerifier.verify({ factor: 'totp', payload: { code } }, secrets.join('\n'));
|
|
388
|
+
return verdict.satisfied;
|
|
350
389
|
}
|
|
351
|
-
|
|
390
|
+
catch {
|
|
391
|
+
retryableUnavailable();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* The shared second-factor proof for the code-gated account routes (Phase 8 #1),
|
|
396
|
+
* mirroring `/auth/mfa`: the `mfa:pid:<principalId>` budget is checked BEFORE any TOTP
|
|
397
|
+
* or recovery-code verification (so a locked caller never triggers the Argon2
|
|
398
|
+
* recovery scan) → a TOTP from any confirmed device, else a recovery code (consumed) →
|
|
399
|
+
* a failed proof records a failure (400), a good one resets the counter. Locked → 429.
|
|
400
|
+
* The limiter fails open on a Redis blip (its documented posture); the TOTP replay
|
|
401
|
+
* claim fails closed (503), and the session itself is Redis-bound, so a Redis outage
|
|
402
|
+
* cannot open this path.
|
|
403
|
+
*/
|
|
404
|
+
async requireMfaProof(store, principalId, code) {
|
|
405
|
+
const mfaKey = `mfa:pid:${principalId}`;
|
|
406
|
+
if (this.mfaLimiter != null && !(await this.mfaLimiter.check(mfaKey))) {
|
|
407
|
+
tooManyRequests();
|
|
408
|
+
}
|
|
409
|
+
const byTotp = await this.verifyAgainstAnyDevice(store, principalId, code);
|
|
410
|
+
const byRecovery = !byTotp &&
|
|
411
|
+
this.recoveryCodes !== undefined &&
|
|
412
|
+
this.recoveryCodes !== null &&
|
|
413
|
+
(await this.recoveryCodes.consume(principalId, code));
|
|
414
|
+
if (!byTotp && !byRecovery) {
|
|
415
|
+
await this.mfaLimiter?.recordFailure(mfaKey);
|
|
416
|
+
badRequest();
|
|
417
|
+
}
|
|
418
|
+
await this.mfaLimiter?.reset(mfaKey);
|
|
352
419
|
}
|
|
353
420
|
/**
|
|
354
421
|
* `GET /account/mfa/devices` — list the caller's CONFIRMED authenticator devices.
|
|
@@ -504,14 +571,9 @@ let AccountController = class AccountController {
|
|
|
504
571
|
const store = this.mfaDevices;
|
|
505
572
|
const code = typeof body?.code === 'string' ? body.code : '';
|
|
506
573
|
// Proof BEFORE the delete: a TOTP from any confirmed device (incl. the one being
|
|
507
|
-
// removed — legit for the last-device disable) OR a valid recovery code.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
this.recoveryCodes !== undefined &&
|
|
511
|
-
this.recoveryCodes !== null &&
|
|
512
|
-
(await this.recoveryCodes.consume(claims.id, code));
|
|
513
|
-
if (!byTotp && !byRecovery)
|
|
514
|
-
badRequest();
|
|
574
|
+
// removed — legit for the last-device disable) OR a valid recovery code. Rate-limited
|
|
575
|
+
// and replay-guarded like `/auth/mfa` (Phase 8 #1).
|
|
576
|
+
await this.requireMfaProof(store, claims.id, code);
|
|
515
577
|
const removed = await store.remove(claims.id, id);
|
|
516
578
|
if (!removed) {
|
|
517
579
|
throw new common_1.HttpException({ error: 'not_found' }, common_1.HttpStatus.NOT_FOUND);
|
|
@@ -551,11 +613,9 @@ let AccountController = class AccountController {
|
|
|
551
613
|
// MFA must be on — else there's nothing to protect / no code source → generic 400.
|
|
552
614
|
if ((await store.countConfirmed(claims.id)) === 0)
|
|
553
615
|
badRequest();
|
|
554
|
-
// Proof: a TOTP from any confirmed device OR an existing recovery code
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (!byTotp && !byRecovery)
|
|
558
|
-
badRequest();
|
|
616
|
+
// Proof: a TOTP from any confirmed device OR an existing recovery code (rate-limited
|
|
617
|
+
// and replay-guarded like `/auth/mfa`, Phase 8 #1).
|
|
618
|
+
await this.requireMfaProof(store, claims.id, code);
|
|
559
619
|
const recoveryCodes = await this.replaceRecoveryCodes(claims.id, recoveryStore, hasher);
|
|
560
620
|
return { recoveryCodes };
|
|
561
621
|
}
|
|
@@ -664,7 +724,13 @@ exports.AccountController = AccountController = __decorate([
|
|
|
664
724
|
__param(8, (0, common_1.Inject)(tokens_js_2.SHIELD_MFA_DEVICE_STORE)),
|
|
665
725
|
__param(9, (0, common_1.Optional)()),
|
|
666
726
|
__param(9, (0, common_1.Inject)(tokens_js_2.SHIELD_SESSION_RESTRICTIONS)),
|
|
667
|
-
|
|
727
|
+
__param(10, (0, common_1.Optional)()),
|
|
728
|
+
__param(10, (0, common_1.Inject)(tokens_js_2.SHIELD_LOGIN_TOTP_VERIFIER)),
|
|
729
|
+
__param(11, (0, common_1.Optional)()),
|
|
730
|
+
__param(11, (0, common_1.Inject)(tokens_js_2.SHIELD_MFA_LIMITER)),
|
|
731
|
+
__param(12, (0, common_1.Optional)()),
|
|
732
|
+
__param(12, (0, common_1.Inject)(tokens_js_2.SHIELD_LOGIN_LIMITER)),
|
|
733
|
+
__metadata("design:paramtypes", [Function, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object])
|
|
668
734
|
], AccountController);
|
|
669
735
|
/**
|
|
670
736
|
* A deliberately-minimal email shape check (non-empty, a single `@`, a `.` in the
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* an unknown account (anti-enumeration — the authenticator maps null → a uniform failure). Its
|
|
11
11
|
* scope is the account's LIVE `shield_app.service_account.sa_scope_subtree` (JOINed), never the
|
|
12
12
|
* copy stored on the key at issue time; a disabled account, a missing account row, or an account
|
|
13
|
-
* whose tenant disagrees with the key's also yields `null` (security hardening Phase 1C)
|
|
13
|
+
* whose tenant disagrees with the key's also yields `null` (security hardening Phase 1C), and so does
|
|
14
|
+
* an account whose tenant company no longer exists (Phase 8 #3). A genuine
|
|
14
15
|
* store OUTAGE (the `query` rejecting) PROPAGATES (the authenticator catches it → fail-closed), never
|
|
15
16
|
* swallowed to null. Every query is parameterized; the hash is never logged.
|
|
16
17
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mariadb-api-key-store.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-api-key-store.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"mariadb-api-key-store.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-api-key-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA+C9D,qBAAa,kBAAmB,YAAW,WAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAEjC,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IA0DrE;;;;OAIG;IACG,UAAU,CAAC,KAAK,EAAE;QACtB,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBjB,iGAAiG;IAC3F,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQxD,+FAA+F;IACzF,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAQ7D"}
|
|
@@ -47,11 +47,18 @@ class MariaDbApiKeyStore {
|
|
|
47
47
|
// key row's `sak_role_ids` is the issue-time copy; the account's CURRENT `sa_role_ids` (the same
|
|
48
48
|
// column gate 5's `readRoleIdsForTenant` reads) is what the principal carries, so role-keyed
|
|
49
49
|
// record rules, list scope and shares see a role change on the next request.
|
|
50
|
+
//
|
|
51
|
+
// LIVE TENANT (security hardening Phase 8 #3): `sa_tenant_id` has no FK to `companies`, so the
|
|
52
|
+
// account's tenant company is LEFT JOINed (PK lookup, same single query) and a key whose tenant
|
|
53
|
+
// no longer exists is denied — defence in depth behind the company delete, which disables the
|
|
54
|
+
// tenant's accounts and revokes their keys in its own transaction.
|
|
50
55
|
const rows = (await this.pool.query(`SELECT k.sak_account_id, k.sak_tenant_id, k.sak_role_ids,
|
|
51
56
|
k.sak_secret_hash, k.sak_version, k.sak_expires_at_ms, k.sak_revoked_at_ms,
|
|
52
|
-
sa.sa_scope_subtree, sa.sa_role_ids, sa.sa_status, sa.sa_tenant_id
|
|
57
|
+
sa.sa_scope_subtree, sa.sa_role_ids, sa.sa_status, sa.sa_tenant_id,
|
|
58
|
+
c.c_uuid AS tenant_company_uuid
|
|
53
59
|
FROM \`${AUTHZ}\`.\`service_account_key\` k
|
|
54
60
|
JOIN \`${APP}\`.\`service_account\` sa ON sa.sa_id = k.sak_account_id
|
|
61
|
+
LEFT JOIN \`${APP}\`.\`companies\` c ON c.c_uuid = sa.sa_tenant_id
|
|
55
62
|
WHERE k.sak_account_id = ?
|
|
56
63
|
ORDER BY k.sak_version DESC
|
|
57
64
|
LIMIT 1`, [accountId]));
|
|
@@ -65,6 +72,11 @@ class MariaDbApiKeyStore {
|
|
|
65
72
|
return null;
|
|
66
73
|
if (row.sa_tenant_id !== row.sak_tenant_id)
|
|
67
74
|
return null;
|
|
75
|
+
// ...or when the account's tenant company no longer exists (Phase 8 #3; fail closed on any
|
|
76
|
+
// non-string, so a row without the joined column denies too).
|
|
77
|
+
if (typeof row.tenant_company_uuid !== 'string' || row.tenant_company_uuid.length === 0) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
68
80
|
return {
|
|
69
81
|
accountId: row.sak_account_id,
|
|
70
82
|
tenantId: row.sak_tenant_id,
|
package/dist/index.d.ts
CHANGED
|
@@ -55,10 +55,9 @@ export { buildListScopeFn, type ListScopeFn } from './authz/wire-record-authoriz
|
|
|
55
55
|
export { BootSequence, assertPoolBudget } from './boot/boot-sequence.js';
|
|
56
56
|
export { APP_CONFIG } from './config/config.module.js';
|
|
57
57
|
export { loadAppConfig, loadPoolBudget, evaluatePoolBudget, DEFAULT_POOL_BUDGET, SCHEMA_SVC_POOL_HARD_CAP, type AppConfig, type PoolBudgetConfig, type PoolBudgetEvaluation, type RuntimeAccountPools, loadDbConfig, loadRedisConfig, loadSessionLifetimes, loadRefreshHmacKey, assertRefreshHmacKey, DEFAULT_DB_CONFIG, DEFAULT_REDIS_URL, type DbConfig, type RedisConfig, type SessionLifetimeOverrides, loadLoginIpLimit, DEFAULT_LOGIN_IP_LIMIT, isDevOrTestNodeEnv, type LoginIpLimitConfig, } from './config/app-config.js';
|
|
58
|
-
export {
|
|
58
|
+
export { SHIELD_REDIS, SHIELD_CREDENTIAL_RESOLVER, SHIELD_PROFILE_RESOLVER, SHIELD_AUTHENTICATOR, SHIELD_SESSION_AUTHORITY, SHIELD_CREDENTIAL_WRITER, SHIELD_RESET_TOKEN_STORE, SHIELD_EMAIL_SENDER, SHIELD_RECOVERY_CODE_STORE, SHIELD_PROFILE_WRITER, SHIELD_SESSION_SWEEPER, SHIELD_AUDIT_SINK, SHIELD_MFA_DEVICE_STORE, SHIELD_TOTP_VERIFIER_OVERRIDE, } from './auth/tokens.js';
|
|
59
59
|
export { MariaDbCredentialResolver } from './authentication/mariadb-credential-resolver.js';
|
|
60
60
|
export type { SessionView, SessionTokensView, SessionSuccess, LoginRequest, MfaChallengeResponse, LoginResponse, MfaRequest, MfaResponse, RefreshRequest, RefreshResponse, LogoutRequest, LogoutAck, } from './auth/dto.js';
|
|
61
|
-
export { PersistenceModule } from './auth/persistence.module.js';
|
|
62
61
|
export { AuthModule, type AuthModuleOptions } from './auth/auth.module.js';
|
|
63
62
|
export { makeDefaultTotpVerifier, resolveTotpVerifier, hmacSha1 } from './auth/auth.module.js';
|
|
64
63
|
export type { CredentialResolver, ResolvedCredential, RequiredFactor } from '@astram/core';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
19
|
exports.accessBindingTtlMs = exports.ROTATE_GRACE_MS = exports.SHIELD_SESSION_LIFETIMES = exports.HotRevokingSessionRecordStore = exports.SHIELD_CLOCK = exports.SHIELD_REFRESH_HASHER = exports.SHIELD_TOKEN_GENERATOR = exports.systemClockProvider = exports.refreshHasherProvider = exports.cryptoTokenGeneratorProvider = exports.SystemClock = exports.Sha256RefreshTokenHasher = exports.CryptoTokenGenerator = exports.SHIELD_HOT_SESSION_STORE = exports.RedisHotSessionStore = exports.SHIELD_SESSION_RECORD_STORE = exports.MariaDbSessionRecordStore = exports.EncryptedFileSecretsProvider = exports.LocalKekCrypto = exports.SHIELD_PASSWORD_HASHER = exports.argon2idHasherProvider = exports.Argon2idPasswordHasher = exports.stubDecisionAuthority = exports.GlobalEnforcementGuard = exports.NO_SESSION_FACTS = exports.SHIELD_SESSION_FACTS_ALS = exports.MariaDbApiKeyStore = exports.isApiKey = exports.extractBearerToken = exports.principalFromClaims = exports.resolveRequestAuth = exports.resolveRequestPrincipal = exports.SessionTokenBinder = exports.SHIELD_SERVICE_AUTHENTICATOR = exports.SHIELD_SESSION_TOKEN_BINDER = exports.CatalogGatabilityError = exports.CatalogReconciler = exports.OperationInventoryScanner = exports.normalizeClassification = exports.DEFAULT_CLASSIFIED_ACTION = exports.CLASSIFICATION_METADATA = exports.Classified = exports.Public = exports.SHIELD_DECISION_AUTHORITY = exports.ShieldModule = exports.actionsFor = exports.IDENTITY_ACTIONS = exports.MODULE_KEY = exports.buildIdentityModuleRegistry = exports.guardMigrationsDir = void 0;
|
|
20
20
|
exports.DEFAULT_LOGIN_IP_LIMIT = exports.loadLoginIpLimit = exports.DEFAULT_REDIS_URL = exports.DEFAULT_DB_CONFIG = exports.assertRefreshHmacKey = exports.loadRefreshHmacKey = exports.loadSessionLifetimes = exports.loadRedisConfig = exports.loadDbConfig = exports.SCHEMA_SVC_POOL_HARD_CAP = exports.DEFAULT_POOL_BUDGET = exports.evaluatePoolBudget = exports.loadPoolBudget = exports.loadAppConfig = exports.APP_CONFIG = exports.assertPoolBudget = exports.BootSequence = exports.buildListScopeFn = exports.MariaDbSessionRestrictionStore = exports.SHIELD_SESSION_RESTRICTIONS = exports.SHIELD_PERMISSION_VERSION_CACHE = exports.DEFAULT_PERMISSION_VERSION_TTL_MS = exports.PERMISSION_VERSION_KEY_PREFIX = exports.RedisPermissionVersionCache = exports.withRedisPermissionVersion = exports.buildRecordAuthorization = exports.SHIELD_OPERATION_AUTHORITY_PORT = exports.ManifestOperationAuthorityAdapter = exports.GrantStoreUnavailableError = exports.InvalidGrantError = exports.GrantNotPermittedError = exports.SHIELD_CROSS_TENANT_GRANT_WRITER = exports.CrossTenantGrantWriter = exports.SHIELD_CROSS_TENANT_GRANT_PORT = exports.MariaDbCrossTenantGrantAdapter = exports.makeShareDeleteCascade = exports.ShareStoreUnavailableError = exports.ShareNotPermittedError = exports.SHIELD_RECORD_SHARE_WRITER = exports.RecordShareWriter = exports.SHIELD_RECORD_SHARE_PORT = exports.MariaDbRecordShareAdapter = exports.UnknownScopeFieldError = exports.translateListScope = exports.SHIELD_LIST_SCOPE_PORT = exports.MariaDbListScopeAdapter = exports.SHIELD_POLICY_STORE = exports.MariaDbPolicyStore = exports.SHIELD_RECORD_RULE_PORT = exports.MariaDbRecordPermissionRuleAdapter = void 0;
|
|
21
|
-
exports.ScopeUnknownParentError = exports.ScopeDepthError = exports.ScopeCycleError = exports.buildClosureRows = exports.AccountController = exports.generateRecoveryCodes = exports.buildOtpauthUri = exports.generateTotpSecret = exports.SHIELD_LOGIN_TOTP_VERIFIER = exports.SHIELD_MFA_STEP_UP = exports.SHIELD_MFA_LIMITER = exports.challengeMatches = exports.credentialFingerprint = exports.CredentialMfaStepUp = exports.RedisPendingMfaStore = exports.PendingMfaStore = exports.AuthController = exports.SHIELD_SCOPE_SUBTREE_RESOLVER = exports.SHIELD_SESSION_FACTS_PROJECTOR = exports.hmacSha1 = exports.resolveTotpVerifier = exports.makeDefaultTotpVerifier = exports.AuthModule = exports.
|
|
21
|
+
exports.ScopeUnknownParentError = exports.ScopeDepthError = exports.ScopeCycleError = exports.buildClosureRows = exports.AccountController = exports.generateRecoveryCodes = exports.buildOtpauthUri = exports.generateTotpSecret = exports.SHIELD_LOGIN_TOTP_VERIFIER = exports.SHIELD_MFA_STEP_UP = exports.SHIELD_MFA_LIMITER = exports.challengeMatches = exports.credentialFingerprint = exports.CredentialMfaStepUp = exports.RedisPendingMfaStore = exports.PendingMfaStore = exports.AuthController = exports.SHIELD_SCOPE_SUBTREE_RESOLVER = exports.SHIELD_SESSION_FACTS_PROJECTOR = exports.hmacSha1 = exports.resolveTotpVerifier = exports.makeDefaultTotpVerifier = exports.AuthModule = exports.MariaDbCredentialResolver = exports.SHIELD_TOTP_VERIFIER_OVERRIDE = exports.SHIELD_MFA_DEVICE_STORE = exports.SHIELD_AUDIT_SINK = exports.SHIELD_SESSION_SWEEPER = exports.SHIELD_PROFILE_WRITER = exports.SHIELD_RECOVERY_CODE_STORE = exports.SHIELD_EMAIL_SENDER = exports.SHIELD_RESET_TOKEN_STORE = exports.SHIELD_CREDENTIAL_WRITER = exports.SHIELD_SESSION_AUTHORITY = exports.SHIELD_AUTHENTICATOR = exports.SHIELD_PROFILE_RESOLVER = exports.SHIELD_CREDENTIAL_RESOLVER = exports.SHIELD_REDIS = exports.isDevOrTestNodeEnv = void 0;
|
|
22
22
|
// Framework authz migrations (shield_authz DDL — permission / role_permission /
|
|
23
23
|
// authz_permission_version). Absolute path a consumer's `@astram/migrate` setup merges in.
|
|
24
24
|
var migrations_dir_js_1 = require("./migrations-dir.js");
|
|
@@ -310,9 +310,9 @@ Object.defineProperty(exports, "isDevOrTestNodeEnv", { enumerable: true, get: fu
|
|
|
310
310
|
// path is untouched; ShieldModule wiring is deferred to the integration owner.
|
|
311
311
|
// ---------------------------------------------------------------------------
|
|
312
312
|
// Shared DI provider tokens (the seams the teams bind/consume — see tokens.ts for
|
|
313
|
-
// who provides/consumes each).
|
|
313
|
+
// who provides/consumes each). The raw pool token `SHIELD_DB_POOL` is NOT here (Phase 8 #5):
|
|
314
|
+
// it is on `@astram/guard/internal`, with the `PersistenceModule` that provides it.
|
|
314
315
|
var tokens_js_4 = require("./auth/tokens.js");
|
|
315
|
-
Object.defineProperty(exports, "SHIELD_DB_POOL", { enumerable: true, get: function () { return tokens_js_4.SHIELD_DB_POOL; } });
|
|
316
316
|
Object.defineProperty(exports, "SHIELD_REDIS", { enumerable: true, get: function () { return tokens_js_4.SHIELD_REDIS; } });
|
|
317
317
|
Object.defineProperty(exports, "SHIELD_CREDENTIAL_RESOLVER", { enumerable: true, get: function () { return tokens_js_4.SHIELD_CREDENTIAL_RESOLVER; } });
|
|
318
318
|
Object.defineProperty(exports, "SHIELD_PROFILE_RESOLVER", { enumerable: true, get: function () { return tokens_js_4.SHIELD_PROFILE_RESOLVER; } });
|
|
@@ -339,8 +339,8 @@ Object.defineProperty(exports, "MariaDbCredentialResolver", { enumerable: true,
|
|
|
339
339
|
// The composition module skeletons (Team B fills the factory bodies; the
|
|
340
340
|
// integration owner imports AuthModule into ShieldModule — see auth.module.ts /
|
|
341
341
|
// DONE.md).
|
|
342
|
-
|
|
343
|
-
|
|
342
|
+
// `PersistenceModule` (it provides the raw `SHIELD_DB_POOL` pool) is NOT exported here (Phase 8
|
|
343
|
+
// #5): a feature module importing it could inject the pool. It is on `@astram/guard/internal`.
|
|
344
344
|
var auth_module_js_1 = require("./auth/auth.module.js");
|
|
345
345
|
Object.defineProperty(exports, "AuthModule", { enumerable: true, get: function () { return auth_module_js_1.AuthModule; } });
|
|
346
346
|
// Phase-2 verifier-seam helpers. `makeDefaultTotpVerifier()` builds the framework's
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @astram/guard/internal — framework-composition and test surface (Phase 8 #5).
|
|
3
|
+
*
|
|
4
|
+
* `PersistenceModule` provides the raw `shield_core` pool under `SHIELD_DB_POOL`; a module that
|
|
5
|
+
* imported it could inject that pool. Neither is on the package entry point (`@astram/guard`):
|
|
6
|
+
* `ShieldModule.forRoot({ auth: true })` composes them internally, and the pool provider is not
|
|
7
|
+
* exported app-wide. The scaffolded app's lint wall rejects this subpath outside the composition
|
|
8
|
+
* root.
|
|
9
|
+
*/
|
|
10
|
+
export { PersistenceModule } from './auth/persistence.module.js';
|
|
11
|
+
export { SHIELD_DB_POOL } from './auth/tokens.js';
|
|
12
|
+
//# sourceMappingURL=internal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SHIELD_DB_POOL = exports.PersistenceModule = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* @astram/guard/internal — framework-composition and test surface (Phase 8 #5).
|
|
6
|
+
*
|
|
7
|
+
* `PersistenceModule` provides the raw `shield_core` pool under `SHIELD_DB_POOL`; a module that
|
|
8
|
+
* imported it could inject that pool. Neither is on the package entry point (`@astram/guard`):
|
|
9
|
+
* `ShieldModule.forRoot({ auth: true })` composes them internally, and the pool provider is not
|
|
10
|
+
* exported app-wide. The scaffolded app's lint wall rejects this subpath outside the composition
|
|
11
|
+
* root.
|
|
12
|
+
*/
|
|
13
|
+
var persistence_module_js_1 = require("./auth/persistence.module.js");
|
|
14
|
+
Object.defineProperty(exports, "PersistenceModule", { enumerable: true, get: function () { return persistence_module_js_1.PersistenceModule; } });
|
|
15
|
+
var tokens_js_1 = require("./auth/tokens.js");
|
|
16
|
+
Object.defineProperty(exports, "SHIELD_DB_POOL", { enumerable: true, get: function () { return tokens_js_1.SHIELD_DB_POOL; } });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astram/guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Astram framework — the enforcement guard + real-world adapters (the 'hands'). One global deny-by-default gate every request passes through (ADR-0002), classification decorators, boot-pool assertion (ADR-0018), the forRoot() registration seam, and the DB/Redis/crypto adapters that connect the sealed core to a real backend. Depends on @astram/core (ADR-0003, ADR-0023).",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"default": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./internal": {
|
|
15
|
+
"types": "./dist/internal.d.ts",
|
|
16
|
+
"default": "./dist/internal.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"typesVersions": {
|
|
20
|
+
"*": {
|
|
21
|
+
"internal": [
|
|
22
|
+
"dist/internal.d.ts"
|
|
23
|
+
]
|
|
13
24
|
}
|
|
14
25
|
},
|
|
15
26
|
"files": [
|
|
@@ -25,7 +36,7 @@
|
|
|
25
36
|
"ioredis": "^5.4.1",
|
|
26
37
|
"mariadb": "^3.4.0",
|
|
27
38
|
"@astram/core": "1.4.0",
|
|
28
|
-
"@astram/engine": "0.
|
|
39
|
+
"@astram/engine": "0.5.0"
|
|
29
40
|
},
|
|
30
41
|
"peerDependencies": {
|
|
31
42
|
"@nestjs/common": "^11",
|