@astram/guard 0.11.3 → 0.12.1
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 +2 -0
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +19 -2
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +35 -1
- package/dist/auth/email-address.d.ts +4 -2
- package/dist/auth/email-address.d.ts.map +1 -1
- package/dist/auth/email-address.js +11 -2
- package/dist/auth/tokens.d.ts +6 -0
- package/dist/auth/tokens.d.ts.map +1 -1
- package/dist/auth/tokens.js +7 -1
- package/dist/config/app-config.d.ts +13 -0
- package/dist/config/app-config.d.ts.map +1 -1
- package/dist/config/app-config.js +14 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/request-auth/session-token-binder.d.ts +31 -8
- package/dist/request-auth/session-token-binder.d.ts.map +1 -1
- package/dist/request-auth/session-token-binder.js +74 -43
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +3 -1
- package/dist/session/redis-recent-rotation-store.d.ts +19 -0
- package/dist/session/redis-recent-rotation-store.d.ts.map +1 -0
- package/dist/session/redis-recent-rotation-store.js +115 -0
- package/dist/session/replay-outcome.d.ts +12 -0
- package/dist/session/replay-outcome.d.ts.map +1 -0
- package/dist/session/replay-outcome.js +42 -0
- package/package.json +3 -3
|
@@ -472,6 +472,8 @@ export declare class AuthController {
|
|
|
472
472
|
* this seam today (flagged core-API gap).
|
|
473
473
|
*/
|
|
474
474
|
refresh(body: RefreshRequest): Promise<SessionSuccess>;
|
|
475
|
+
/** KI #104: audit a tolerated repeat once its re-bind is decided (best-effort, never blocks). */
|
|
476
|
+
private recordReplay;
|
|
475
477
|
/**
|
|
476
478
|
* The refresh-time projection for one node: coarse grants + live roles (ADR-0035) and the
|
|
477
479
|
* subtree with its version stamp (Phase 1C: the version is read FIRST). Each part is best-effort
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAeH,OAAO,KAAK,EACV,aAAa,EAIb,cAAc,EAGd,gBAAgB,EAEjB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -69,6 +69,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
69
69
|
exports.AuthController = void 0;
|
|
70
70
|
const common_1 = require("@nestjs/common");
|
|
71
71
|
const node_crypto_1 = require("node:crypto");
|
|
72
|
+
const replay_outcome_js_1 = require("../session/replay-outcome.js");
|
|
72
73
|
const common_2 = require("@nestjs/common");
|
|
73
74
|
const classification_decorator_js_1 = require("../enforcement/classification.decorator.js");
|
|
74
75
|
const tokens_js_1 = require("./tokens.js");
|
|
@@ -1451,8 +1452,12 @@ let AuthController = class AuthController {
|
|
|
1451
1452
|
if (typeof probe?.isAvailable === 'function' && !(await probe.isAvailable())) {
|
|
1452
1453
|
retryableUnavailable();
|
|
1453
1454
|
}
|
|
1455
|
+
// KI #104: a tolerated repeat is audited after the re-bind, with its real outcome.
|
|
1456
|
+
let replay;
|
|
1454
1457
|
try {
|
|
1455
|
-
const
|
|
1458
|
+
const rotated = await (0, replay_outcome_js_1.captureReplayDuring)(() => this.sessionAuthority.rotate(refreshToken));
|
|
1459
|
+
const est = rotated.result;
|
|
1460
|
+
replay = rotated.replay;
|
|
1456
1461
|
// Carry the principal claims forward to the NEW access token (the durable
|
|
1457
1462
|
// session row lacks roles/kind — the session-claims entry has them).
|
|
1458
1463
|
if (this.tokenBinder !== undefined) {
|
|
@@ -1477,7 +1482,9 @@ let AuthController = class AuthController {
|
|
|
1477
1482
|
? await peek.sessionTenant(est.session.sessionId)
|
|
1478
1483
|
: null) ?? est.session.tenantId;
|
|
1479
1484
|
const fresh = await this.projectForNode(est.session.principalId, activeTenant);
|
|
1480
|
-
const rebound = await this.tokenBinder.rebindOnRotate(est.tokens.accessToken, est.session.sessionId, ttlMs, fresh.facts, fresh.subtree, fresh.subtreeVersion, fresh.roles, activeTenant
|
|
1485
|
+
const rebound = await this.tokenBinder.rebindOnRotate(est.tokens.accessToken, est.session.sessionId, ttlMs, fresh.facts, fresh.subtree, fresh.subtreeVersion, fresh.roles, activeTenant,
|
|
1486
|
+
// KI #110: a repeat never moves the pointer; its original takes the full bind.
|
|
1487
|
+
{ repeat: replay !== undefined });
|
|
1481
1488
|
// The claims were gone (Redis loss) or a logout won the race (`SET XX` refused):
|
|
1482
1489
|
// SILENT RECOVERY (invariant #12, ADR-0013/0035) rebuilds them from durable state; a
|
|
1483
1490
|
// rebuild that cannot be proven safe is a generic 401 — never an unbound token.
|
|
@@ -1490,15 +1497,25 @@ let AuthController = class AuthController {
|
|
|
1490
1497
|
genericUnauthorized();
|
|
1491
1498
|
}
|
|
1492
1499
|
}
|
|
1500
|
+
this.recordReplay(replay, 'tolerated');
|
|
1493
1501
|
return toSessionSuccess(est);
|
|
1494
1502
|
}
|
|
1495
1503
|
catch {
|
|
1496
1504
|
// The engine throws only its generic SessionRejectedError (reuse, invalid,
|
|
1497
1505
|
// expiry, or a swallowed store outage — all indistinguishable here). Map to a
|
|
1498
1506
|
// generic 401; never leak which, never echo the token.
|
|
1507
|
+
this.recordReplay(replay, 'refused');
|
|
1499
1508
|
genericUnauthorized();
|
|
1500
1509
|
}
|
|
1501
1510
|
}
|
|
1511
|
+
/** KI #104: audit a tolerated repeat once its re-bind is decided (best-effort, never blocks). */
|
|
1512
|
+
recordReplay(replay, outcome) {
|
|
1513
|
+
if (replay === undefined)
|
|
1514
|
+
return;
|
|
1515
|
+
void this.auditSink?.record((0, replay_outcome_js_1.replayAuditEvent)(replay, outcome, Date.now())).catch(() => {
|
|
1516
|
+
/* best-effort: an audit failure never changes the response */
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1502
1519
|
/**
|
|
1503
1520
|
* The refresh-time projection for one node: coarse grants + live roles (ADR-0035) and the
|
|
1504
1521
|
* subtree with its version stamp (Phase 1C: the version is read FIRST). Each part is best-effort
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.module.d.ts","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AAEH,OAAO,EAAkB,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAanF,OAAO,KAAK,EAGV,kBAAkB,EAOlB,QAAQ,EACR,cAAc,
|
|
1
|
+
{"version":3,"file":"auth.module.d.ts","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+DG;AAEH,OAAO,EAAkB,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAC;AAanF,OAAO,KAAK,EAGV,kBAAkB,EAOlB,QAAQ,EACR,cAAc,EAIf,MAAM,cAAc,CAAC;AAoGtB,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAChF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAGvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAGpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAavD;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,EAAE,QACqE,CAAC;AAE7F;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,EAC3C,eAAe,EAAE,cAAc,GAC9B,cAAc,CAEhB;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,cAAc,CAAC,CAGvE;AAyhBD;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACjD;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C;;;;;;OAMG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAC7C,QAAQ,CAAC,eAAe,CAAC,EAAE,eAAe,CAAC;IAC3C;;;;;;;;OAQG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;IAChD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAC/C,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,cAAc,CAAC;IACvC;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC;;oGAEgG;IAChG,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,uGAAuG;IACvG,QAAQ,CAAC,wBAAwB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtD;;;;;;;OAOG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IACvD;;;;;;;;OAQG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IACrD;;;;;;;OAOG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IAC/C;;;;;;OAMG;IACH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CAC1C;AAwID;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CAcrF;AAiGD;;;;;;GAMG;AACH,qBACa,UAAU;IACrB;;;;;OAKG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,iBAAsB,GAAG,aAAa;CA2K/D"}
|
package/dist/auth/auth.module.js
CHANGED
|
@@ -130,6 +130,7 @@ const mariadb_or_totp_verifier_js_1 = require("../authentication/mariadb-or-totp
|
|
|
130
130
|
const permission_facts_adapter_js_1 = require("../identity/permission-facts.adapter.js");
|
|
131
131
|
const auth_facts_controller_js_1 = require("../identity/auth-facts.controller.js");
|
|
132
132
|
const index_js_1 = require("../session/index.js");
|
|
133
|
+
const replay_outcome_js_1 = require("../session/replay-outcome.js");
|
|
133
134
|
const persistence_module_js_1 = require("./persistence.module.js");
|
|
134
135
|
const auth_controller_js_1 = require("./auth.controller.js");
|
|
135
136
|
const account_controller_js_1 = require("./account.controller.js");
|
|
@@ -479,11 +480,22 @@ const clockProvider = {
|
|
|
479
480
|
useFactory: () => new index_js_1.SystemClock(),
|
|
480
481
|
};
|
|
481
482
|
// --- Composed core engines --------------------------------------------------
|
|
483
|
+
/**
|
|
484
|
+
* KI #51 / #54 (ADR-0036): the sealed recent-rotation store over the shared Redis client, keyed and
|
|
485
|
+
* sealed with the refresh-token HMAC secret. `null` when `REFRESH_REPLAY_WINDOW_MS=0` (window off).
|
|
486
|
+
*/
|
|
487
|
+
const recentRotationStoreProvider = {
|
|
488
|
+
provide: tokens_js_1.SHIELD_RECENT_ROTATION_STORE,
|
|
489
|
+
useFactory: (redis, config) => config.refreshReplayWindowMs === 0
|
|
490
|
+
? null
|
|
491
|
+
: new index_js_1.RedisRecentRotationStore(redis, (0, app_config_js_1.assertRefreshHmacKey)(config.refreshHmacKey)),
|
|
492
|
+
inject: [tokens_js_1.SHIELD_REDIS, config_module_js_1.APP_CONFIG],
|
|
493
|
+
};
|
|
482
494
|
/** The composed SessionAuthority (core) — establish / rotate / revoke. */
|
|
483
495
|
const sessionAuthorityProvider = {
|
|
484
496
|
provide: tokens_js_1.SHIELD_SESSION_AUTHORITY,
|
|
485
497
|
// async: loads the core factory lazily (CJS→ESM, see loadCore()); Nest awaits it.
|
|
486
|
-
useFactory: async (recordStore, hotStore, tokenGenerator, refreshHasher, clock, lifetimes) => {
|
|
498
|
+
useFactory: async (recordStore, hotStore, tokenGenerator, refreshHasher, clock, lifetimes, config, recentRotations, auditSink) => {
|
|
487
499
|
const { createSessionAuthority } = await loadCore();
|
|
488
500
|
return createSessionAuthority({
|
|
489
501
|
recordStore,
|
|
@@ -492,6 +504,24 @@ const sessionAuthorityProvider = {
|
|
|
492
504
|
refreshHasher,
|
|
493
505
|
clock,
|
|
494
506
|
lifetimes,
|
|
507
|
+
// KI #51 / #54 (ADR-0036): a repeated refresh inside the window gets the same successor.
|
|
508
|
+
// Every such answer is audited (ids only; never a token). Validated by core at construction.
|
|
509
|
+
...(config.refreshReplayWindowMs !== undefined
|
|
510
|
+
? { replayWindowMs: config.refreshReplayWindowMs }
|
|
511
|
+
: {}),
|
|
512
|
+
...(recentRotations !== null
|
|
513
|
+
? {
|
|
514
|
+
recentRotations,
|
|
515
|
+
onReplayTolerated: (event) => {
|
|
516
|
+
// KI #104: inside the refresh route the event is recorded after the re-bind.
|
|
517
|
+
if ((0, replay_outcome_js_1.noteReplayTolerated)(event))
|
|
518
|
+
return;
|
|
519
|
+
void auditSink?.record((0, replay_outcome_js_1.replayAuditEvent)(event, 'tolerated', Date.now())).catch(() => {
|
|
520
|
+
/* best-effort: an audit failure never changes the response */
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
}
|
|
524
|
+
: {}),
|
|
495
525
|
});
|
|
496
526
|
},
|
|
497
527
|
inject: [
|
|
@@ -501,6 +531,9 @@ const sessionAuthorityProvider = {
|
|
|
501
531
|
index_js_1.SHIELD_REFRESH_HASHER,
|
|
502
532
|
index_js_1.SHIELD_CLOCK,
|
|
503
533
|
index_js_1.SHIELD_SESSION_LIFETIMES,
|
|
534
|
+
config_module_js_1.APP_CONFIG,
|
|
535
|
+
tokens_js_1.SHIELD_RECENT_ROTATION_STORE,
|
|
536
|
+
tokens_js_1.SHIELD_AUDIT_SINK,
|
|
504
537
|
],
|
|
505
538
|
};
|
|
506
539
|
/**
|
|
@@ -931,6 +964,7 @@ let AuthModule = AuthModule_1 = class AuthModule {
|
|
|
931
964
|
tokenGeneratorProvider,
|
|
932
965
|
refreshHasherProvider,
|
|
933
966
|
clockProvider,
|
|
967
|
+
recentRotationStoreProvider,
|
|
934
968
|
sessionAuthorityProvider,
|
|
935
969
|
// The login TOTP verifier (OR across devices + Redis replay guard, M-B), shared by
|
|
936
970
|
// the authenticator and the MFA step-up.
|
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
export declare function canonicalEmail(raw: string): string;
|
|
18
18
|
/**
|
|
19
19
|
* A deliberately minimal email shape check (non-empty, a single `@`, a `.` in the domain, no
|
|
20
|
-
* whitespace). A full RFC-5322 validator adds no security value here, only
|
|
21
|
-
* authoritative check is the confirm link reaching the inbox.
|
|
20
|
+
* whitespace, at most 255 characters). A full RFC-5322 validator adds no security value here, only
|
|
21
|
+
* false rejections; the authoritative check is the confirm link reaching the inbox. The length cap
|
|
22
|
+
* keeps every path (create, administrator change, self-service change) from reaching the store with
|
|
23
|
+
* a value it would refuse as errno 1406.
|
|
22
24
|
*/
|
|
23
25
|
export declare function isEmailShaped(value: string): boolean;
|
|
24
26
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"email-address.d.ts","sourceRoot":"","sources":["../../src/auth/email-address.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;
|
|
1
|
+
{"version":3,"file":"email-address.d.ts","sourceRoot":"","sources":["../../src/auth/email-address.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAQD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOpD;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI/C"}
|
|
@@ -22,14 +22,23 @@ exports.maskEmail = maskEmail;
|
|
|
22
22
|
function canonicalEmail(raw) {
|
|
23
23
|
return raw.trim().normalize('NFKC').trim().toLowerCase();
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The longest address any store takes: `users.u_email` and `email_change_token.ect_new_email` are
|
|
27
|
+
* VARCHAR(255), and MariaDB counts characters (code points), not UTF-16 units (KI #107).
|
|
28
|
+
*/
|
|
29
|
+
const MAX_EMAIL_CHARS = 255;
|
|
25
30
|
/**
|
|
26
31
|
* A deliberately minimal email shape check (non-empty, a single `@`, a `.` in the domain, no
|
|
27
|
-
* whitespace). A full RFC-5322 validator adds no security value here, only
|
|
28
|
-
* authoritative check is the confirm link reaching the inbox.
|
|
32
|
+
* whitespace, at most 255 characters). A full RFC-5322 validator adds no security value here, only
|
|
33
|
+
* false rejections; the authoritative check is the confirm link reaching the inbox. The length cap
|
|
34
|
+
* keeps every path (create, administrator change, self-service change) from reaching the store with
|
|
35
|
+
* a value it would refuse as errno 1406.
|
|
29
36
|
*/
|
|
30
37
|
function isEmailShaped(value) {
|
|
31
38
|
if (/\s/.test(value))
|
|
32
39
|
return false;
|
|
40
|
+
if ([...value].length > MAX_EMAIL_CHARS)
|
|
41
|
+
return false;
|
|
33
42
|
const at = value.indexOf('@');
|
|
34
43
|
if (at <= 0 || at !== value.lastIndexOf('@') || at === value.length - 1)
|
|
35
44
|
return false;
|
package/dist/auth/tokens.d.ts
CHANGED
|
@@ -68,6 +68,12 @@ export declare const SHIELD_AUTHENTICATOR: unique symbol;
|
|
|
68
68
|
* and `/auth/mfa` (establish after factor).
|
|
69
69
|
*/
|
|
70
70
|
export declare const SHIELD_SESSION_AUTHORITY: unique symbol;
|
|
71
|
+
/**
|
|
72
|
+
* KI #51 / #54 (ADR-0036): the recent-rotation store core's session authority uses to answer a
|
|
73
|
+
* repeated refresh with the same successor (`RedisRecentRotationStore`), or `null` when
|
|
74
|
+
* `REFRESH_REPLAY_WINDOW_MS=0` turns the window off.
|
|
75
|
+
*/
|
|
76
|
+
export declare const SHIELD_RECENT_ROTATION_STORE: unique symbol;
|
|
71
77
|
/**
|
|
72
78
|
* The `CredentialWriter` (guard port, `../auth/ports/credential-writer.ts`) — the
|
|
73
79
|
* WRITE side of the credential store (set password hash, read the stored hash by
|
|
@@ -1 +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;;;;;;;GAOG;AACH,eAAO,MAAM,uBAAuB,eAAoC,CAAC;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,eAAiC,CAAC;AAEnE;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,eAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/auth/tokens.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/tokens.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* ADR-0003: no framework/DB/crypto imports here — just Symbols (+ one re-export).
|
|
24
24
|
*/
|
|
25
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
-
exports.SHIELD_SESSION_RESTRICTIONS = exports.SHIELD_PERMISSION_VERSION_CACHE = exports.SHIELD_PERMISSION_FACTS = exports.SHIELD_RESET_LIMITER = exports.SHIELD_FORGOT_HINT_LIMITER = exports.SHIELD_FORGOT_LIMITER = exports.SHIELD_LOGIN_TOTP_VERIFIER = exports.SHIELD_MFA_STEP_UP = exports.SHIELD_MFA_LIMITER = exports.SHIELD_RECOVERY_LIMITER = exports.SHIELD_LOGIN_IP_LIMITER = exports.SHIELD_LOGIN_LIMITER = exports.SHIELD_MEMBERSHIP_CHECKER = exports.SHIELD_SCOPE_SUBTREE_RESOLVER = exports.SHIELD_SESSION_FACTS_PROJECTOR = exports.SHIELD_ROOT_IDS = exports.SHIELD_TOTP_VERIFIER_OVERRIDE = exports.SHIELD_TOTP_VERIFIER = exports.SHIELD_RESET_LINK_BASE_URL = exports.SHIELD_SESSION_SWEEPER = exports.SHIELD_MFA_DEVICE_STORE = exports.SHIELD_AUDIT_SINK = exports.SHIELD_PROFILE_WRITER = exports.SHIELD_RECOVERY_CODE_STORE = exports.SHIELD_EMAIL_SENDER = exports.SHIELD_CONFIRM_EMAIL_LIMITER = exports.SHIELD_EMAIL_CHANGE_LIMITER = exports.SHIELD_EMAIL_CHANGE_LINK_BASE_URL = exports.SHIELD_EMAIL_ADDRESS_CHANGER = exports.SHIELD_EMAIL_CHANGE_TOKEN_STORE = 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.SHIELD_DB_POOL = exports.SHIELD_PASSWORD_HASHER = void 0;
|
|
26
|
+
exports.SHIELD_SESSION_RESTRICTIONS = exports.SHIELD_PERMISSION_VERSION_CACHE = exports.SHIELD_PERMISSION_FACTS = exports.SHIELD_RESET_LIMITER = exports.SHIELD_FORGOT_HINT_LIMITER = exports.SHIELD_FORGOT_LIMITER = exports.SHIELD_LOGIN_TOTP_VERIFIER = exports.SHIELD_MFA_STEP_UP = exports.SHIELD_MFA_LIMITER = exports.SHIELD_RECOVERY_LIMITER = exports.SHIELD_LOGIN_IP_LIMITER = exports.SHIELD_LOGIN_LIMITER = exports.SHIELD_MEMBERSHIP_CHECKER = exports.SHIELD_SCOPE_SUBTREE_RESOLVER = exports.SHIELD_SESSION_FACTS_PROJECTOR = exports.SHIELD_ROOT_IDS = exports.SHIELD_TOTP_VERIFIER_OVERRIDE = exports.SHIELD_TOTP_VERIFIER = exports.SHIELD_RESET_LINK_BASE_URL = exports.SHIELD_SESSION_SWEEPER = exports.SHIELD_MFA_DEVICE_STORE = exports.SHIELD_AUDIT_SINK = exports.SHIELD_PROFILE_WRITER = exports.SHIELD_RECOVERY_CODE_STORE = exports.SHIELD_EMAIL_SENDER = exports.SHIELD_CONFIRM_EMAIL_LIMITER = exports.SHIELD_EMAIL_CHANGE_LIMITER = exports.SHIELD_EMAIL_CHANGE_LINK_BASE_URL = exports.SHIELD_EMAIL_ADDRESS_CHANGER = exports.SHIELD_EMAIL_CHANGE_TOKEN_STORE = exports.SHIELD_RESET_TOKEN_STORE = exports.SHIELD_CREDENTIAL_WRITER = exports.SHIELD_RECENT_ROTATION_STORE = exports.SHIELD_SESSION_AUTHORITY = exports.SHIELD_AUTHENTICATOR = exports.SHIELD_PROFILE_RESOLVER = exports.SHIELD_CREDENTIAL_RESOLVER = exports.SHIELD_REDIS = exports.SHIELD_DB_POOL = exports.SHIELD_PASSWORD_HASHER = void 0;
|
|
27
27
|
var argon2id_password_hasher_js_1 = require("../authentication/argon2id-password-hasher.js");
|
|
28
28
|
Object.defineProperty(exports, "SHIELD_PASSWORD_HASHER", { enumerable: true, get: function () { return argon2id_password_hasher_js_1.SHIELD_PASSWORD_HASHER; } });
|
|
29
29
|
/**
|
|
@@ -72,6 +72,12 @@ exports.SHIELD_AUTHENTICATOR = Symbol('SHIELD_AUTHENTICATOR');
|
|
|
72
72
|
* and `/auth/mfa` (establish after factor).
|
|
73
73
|
*/
|
|
74
74
|
exports.SHIELD_SESSION_AUTHORITY = Symbol('SHIELD_SESSION_AUTHORITY');
|
|
75
|
+
/**
|
|
76
|
+
* KI #51 / #54 (ADR-0036): the recent-rotation store core's session authority uses to answer a
|
|
77
|
+
* repeated refresh with the same successor (`RedisRecentRotationStore`), or `null` when
|
|
78
|
+
* `REFRESH_REPLAY_WINDOW_MS=0` turns the window off.
|
|
79
|
+
*/
|
|
80
|
+
exports.SHIELD_RECENT_ROTATION_STORE = Symbol('SHIELD_RECENT_ROTATION_STORE');
|
|
75
81
|
// ---------------------------------------------------------------------------
|
|
76
82
|
// Account & credential surface seams (forgot/reset, change-password, MFA
|
|
77
83
|
// enrollment, profile edit). All OPTIONAL, consumer-plugged through
|
|
@@ -67,6 +67,13 @@ export interface AppConfig {
|
|
|
67
67
|
* (50 failures / 15 min). Read from `LOGIN_IP_MAX_FAILURES` / `LOGIN_IP_WINDOW_MS`.
|
|
68
68
|
*/
|
|
69
69
|
readonly loginIpLimit?: LoginIpLimitConfig;
|
|
70
|
+
/**
|
|
71
|
+
* KI #51 / #54 (ADR-0036): the refresh replay window in ms (`REFRESH_REPLAY_WINDOW_MS`). A
|
|
72
|
+
* repeated refresh of one token inside it gets the same successor while that successor is unused.
|
|
73
|
+
* `undefined` ⇒ core's default (20 000); `0` disables; core rejects anything outside 0–120 000 at
|
|
74
|
+
* composition (boot fails loud).
|
|
75
|
+
*/
|
|
76
|
+
readonly refreshReplayWindowMs?: number;
|
|
70
77
|
}
|
|
71
78
|
/** The per-client-IP login failure limit (failures within `windowMs` that lock the IP). */
|
|
72
79
|
export interface LoginIpLimitConfig {
|
|
@@ -224,6 +231,12 @@ export declare function loadSessionLifetimes(env?: NodeJS.ProcessEnv): SessionLi
|
|
|
224
231
|
* windowMs ← LOGIN_IP_WINDOW_MS (default 900000 = 15 min)
|
|
225
232
|
*/
|
|
226
233
|
export declare function loadLoginIpLimit(env?: NodeJS.ProcessEnv): LoginIpLimitConfig;
|
|
234
|
+
/**
|
|
235
|
+
* Read `REFRESH_REPLAY_WINDOW_MS` (KI #51). Blank/absent ⇒ `undefined` (core default 20 s). Any
|
|
236
|
+
* other value is passed through as a number (`NaN` for garbage) so core's construction-time check
|
|
237
|
+
* fails the auth composition loudly instead of silently picking a window. Total; never throws.
|
|
238
|
+
*/
|
|
239
|
+
export declare function loadRefreshReplayWindowMs(env?: NodeJS.ProcessEnv): number | undefined;
|
|
227
240
|
/**
|
|
228
241
|
* Read the refresh-token HMAC key from env (raw; `''` when absent/blank). Total —
|
|
229
242
|
* does NOT throw, so {@link loadAppConfig} stays boot-safe for a non-auth scaffold.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-config.d.ts","sourceRoot":"","sources":["../../src/config/app-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAGpD,MAAM,WAAW,SAAS;IACxB,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,QAAQ,CAAC,WAAW,EAAE,aAAa,GAAG,MAAM,GAAG,YAAY,CAAC;IAC5D,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAGhC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,wBAAwB,CAAC;IAC7C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"app-config.d.ts","sourceRoot":"","sources":["../../src/config/app-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ exports.loadDbConfig = loadDbConfig;
|
|
|
7
7
|
exports.loadRedisConfig = loadRedisConfig;
|
|
8
8
|
exports.loadSessionLifetimes = loadSessionLifetimes;
|
|
9
9
|
exports.loadLoginIpLimit = loadLoginIpLimit;
|
|
10
|
+
exports.loadRefreshReplayWindowMs = loadRefreshReplayWindowMs;
|
|
10
11
|
exports.loadRefreshHmacKey = loadRefreshHmacKey;
|
|
11
12
|
exports.assertRefreshHmacKey = assertRefreshHmacKey;
|
|
12
13
|
exports.loadAppConfig = loadAppConfig;
|
|
@@ -201,6 +202,17 @@ function loadLoginIpLimit(env = process.env) {
|
|
|
201
202
|
windowMs: parsePositiveInt(env.LOGIN_IP_WINDOW_MS, exports.DEFAULT_LOGIN_IP_LIMIT.windowMs),
|
|
202
203
|
};
|
|
203
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Read `REFRESH_REPLAY_WINDOW_MS` (KI #51). Blank/absent ⇒ `undefined` (core default 20 s). Any
|
|
207
|
+
* other value is passed through as a number (`NaN` for garbage) so core's construction-time check
|
|
208
|
+
* fails the auth composition loudly instead of silently picking a window. Total; never throws.
|
|
209
|
+
*/
|
|
210
|
+
function loadRefreshReplayWindowMs(env = process.env) {
|
|
211
|
+
const raw = env.REFRESH_REPLAY_WINDOW_MS;
|
|
212
|
+
if (raw === undefined || raw.trim() === '')
|
|
213
|
+
return undefined;
|
|
214
|
+
return Number(raw.trim());
|
|
215
|
+
}
|
|
204
216
|
/**
|
|
205
217
|
* Read the refresh-token HMAC key from env (raw; `''` when absent/blank). Total —
|
|
206
218
|
* does NOT throw, so {@link loadAppConfig} stays boot-safe for a non-auth scaffold.
|
|
@@ -245,6 +257,7 @@ function assertRefreshHmacKey(key) {
|
|
|
245
257
|
* auth-composition time (see its doc).
|
|
246
258
|
*/
|
|
247
259
|
function loadAppConfig(env = process.env) {
|
|
260
|
+
const refreshReplayWindowMs = loadRefreshReplayWindowMs(env);
|
|
248
261
|
return {
|
|
249
262
|
port: parsePort(env.PORT, 3000),
|
|
250
263
|
environment: parseEnvironment(env.NODE_ENV),
|
|
@@ -254,6 +267,7 @@ function loadAppConfig(env = process.env) {
|
|
|
254
267
|
refreshHmacKey: loadRefreshHmacKey(env),
|
|
255
268
|
lifetimes: loadSessionLifetimes(env),
|
|
256
269
|
loginIpLimit: loadLoginIpLimit(env),
|
|
270
|
+
...(refreshReplayWindowMs !== undefined ? { refreshReplayWindowMs } : {}),
|
|
257
271
|
};
|
|
258
272
|
}
|
|
259
273
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export { stubDecisionAuthority, type Effect, type CoreDecisionAuthority, type Co
|
|
|
35
35
|
export { Argon2idPasswordHasher, argon2idHasherProvider, SHIELD_PASSWORD_HASHER, type Argon2idParams, } from './authentication/argon2id-password-hasher.js';
|
|
36
36
|
export { LocalKekCrypto } from './encryption/local-kek-crypto.js';
|
|
37
37
|
export { EncryptedFileSecretsProvider } from './secrets/encrypted-file-secrets.js';
|
|
38
|
-
export { MariaDbSessionRecordStore, SHIELD_SESSION_RECORD_STORE, RedisHotSessionStore, SHIELD_HOT_SESSION_STORE, CryptoTokenGenerator, Sha256RefreshTokenHasher, SystemClock, cryptoTokenGeneratorProvider, refreshHasherProvider, systemClockProvider, SHIELD_TOKEN_GENERATOR, SHIELD_REFRESH_HASHER, SHIELD_CLOCK, HotRevokingSessionRecordStore, type SessionHotRevoker, type LineageSessionLookup, SHIELD_SESSION_LIFETIMES, ROTATE_GRACE_MS, accessBindingTtlMs, } from './session/index.js';
|
|
38
|
+
export { MariaDbSessionRecordStore, SHIELD_SESSION_RECORD_STORE, RedisHotSessionStore, SHIELD_HOT_SESSION_STORE, RedisRecentRotationStore, type RedisRecentRotationStoreOptions, CryptoTokenGenerator, Sha256RefreshTokenHasher, SystemClock, cryptoTokenGeneratorProvider, refreshHasherProvider, systemClockProvider, SHIELD_TOKEN_GENERATOR, SHIELD_REFRESH_HASHER, SHIELD_CLOCK, HotRevokingSessionRecordStore, type SessionHotRevoker, type LineageSessionLookup, SHIELD_SESSION_LIFETIMES, ROTATE_GRACE_MS, accessBindingTtlMs, } from './session/index.js';
|
|
39
39
|
export { MariaDbRecordPermissionRuleAdapter, SHIELD_RECORD_RULE_PORT, type RuleQueryable, } from './authz/mariadb-record-rule-adapter.js';
|
|
40
40
|
export { MariaDbPolicyStore, SHIELD_POLICY_STORE, type RuleQueryable as PolicyStoreQueryable, } from './authz/mariadb-policy-store.js';
|
|
41
41
|
export { MariaDbListScopeAdapter, SHIELD_LIST_SCOPE_PORT, type ListScopeQueryable, } from './authz/mariadb-list-scope-adapter.js';
|
|
@@ -55,7 +55,7 @@ export { MariaDbSessionRestrictionStore, type SessionRestrictionStore, type Sess
|
|
|
55
55
|
export { buildListScopeFn, type ListScopeFn } from './authz/wire-record-authorization.js';
|
|
56
56
|
export { BootSequence, assertPoolBudget } from './boot/boot-sequence.js';
|
|
57
57
|
export { APP_CONFIG } from './config/config.module.js';
|
|
58
|
-
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 { 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, loadRefreshReplayWindowMs, DEFAULT_LOGIN_IP_LIMIT, isDevOrTestNodeEnv, type LoginIpLimitConfig, } from './config/app-config.js';
|
|
59
59
|
export { SHIELD_REDIS, SHIELD_CREDENTIAL_RESOLVER, SHIELD_PROFILE_RESOLVER, SHIELD_AUTHENTICATOR, SHIELD_SESSION_AUTHORITY, SHIELD_CREDENTIAL_WRITER, SHIELD_RESET_TOKEN_STORE, SHIELD_EMAIL_CHANGE_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';
|
|
60
60
|
export { MariaDbCredentialResolver } from './authentication/mariadb-credential-resolver.js';
|
|
61
61
|
export type { SessionView, SessionTokensView, SessionSuccess, LoginRequest, MfaChallengeResponse, LoginResponse, MfaRequest, MfaResponse, RefreshRequest, RefreshResponse, LogoutRequest, LogoutAck, } from './auth/dto.js';
|
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
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
* `@nestjs/core`, `reflect-metadata`, `rxjs` so the host owns one copy of each.
|
|
17
17
|
*/
|
|
18
18
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.
|
|
20
|
-
exports.
|
|
21
|
-
exports.
|
|
22
|
-
exports.ScopeUnknownParentError = void 0;
|
|
19
|
+
exports.SHIELD_TOKEN_GENERATOR = exports.systemClockProvider = exports.refreshHasherProvider = exports.cryptoTokenGeneratorProvider = exports.SystemClock = exports.Sha256RefreshTokenHasher = exports.CryptoTokenGenerator = exports.RedisRecentRotationStore = 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.uuidArrayField = exports.optionalUuidField = exports.uuidField = exports.UuidPipe = exports.normalizeClassification = exports.DEFAULT_CLASSIFIED_ACTION = exports.CLASSIFICATION_METADATA = exports.Classified = exports.Public = exports.SHIELD_RECORD_AUTHORIZATION = exports.SHIELD_DECISION_AUTHORITY = exports.ShieldModule = exports.actionsFor = exports.IDENTITY_ACTIONS = exports.MODULE_KEY = exports.buildIdentityModuleRegistry = exports.guardMigrationsDir = void 0;
|
|
20
|
+
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 = exports.accessBindingTtlMs = exports.ROTATE_GRACE_MS = exports.SHIELD_SESSION_LIFETIMES = exports.HotRevokingSessionRecordStore = exports.SHIELD_CLOCK = exports.SHIELD_REFRESH_HASHER = void 0;
|
|
21
|
+
exports.buildClosureRows = exports.ConfirmEmailController = 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.ProfileNameTakenError = exports.MariaDbEmailAddressChanger = exports.maskEmail = exports.isEmailShaped = exports.canonicalEmail = 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_EMAIL_CHANGE_TOKEN_STORE = 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 = exports.DEFAULT_LOGIN_IP_LIMIT = exports.loadRefreshReplayWindowMs = exports.loadLoginIpLimit = exports.DEFAULT_REDIS_URL = exports.DEFAULT_DB_CONFIG = exports.assertRefreshHmacKey = exports.loadRefreshHmacKey = void 0;
|
|
22
|
+
exports.ScopeUnknownParentError = exports.ScopeDepthError = exports.ScopeCycleError = void 0;
|
|
23
23
|
// Framework authz migrations (shield_authz DDL — permission / role_permission /
|
|
24
24
|
// authz_permission_version). Absolute path a consumer's `@astram/migrate` setup merges in.
|
|
25
25
|
var migrations_dir_js_1 = require("./migrations-dir.js");
|
|
@@ -139,6 +139,8 @@ Object.defineProperty(exports, "MariaDbSessionRecordStore", { enumerable: true,
|
|
|
139
139
|
Object.defineProperty(exports, "SHIELD_SESSION_RECORD_STORE", { enumerable: true, get: function () { return index_js_1.SHIELD_SESSION_RECORD_STORE; } });
|
|
140
140
|
Object.defineProperty(exports, "RedisHotSessionStore", { enumerable: true, get: function () { return index_js_1.RedisHotSessionStore; } });
|
|
141
141
|
Object.defineProperty(exports, "SHIELD_HOT_SESSION_STORE", { enumerable: true, get: function () { return index_js_1.SHIELD_HOT_SESSION_STORE; } });
|
|
142
|
+
// KI #51 / #54 (ADR-0036): the sealed recent-rotation window store.
|
|
143
|
+
Object.defineProperty(exports, "RedisRecentRotationStore", { enumerable: true, get: function () { return index_js_1.RedisRecentRotationStore; } });
|
|
142
144
|
Object.defineProperty(exports, "CryptoTokenGenerator", { enumerable: true, get: function () { return index_js_1.CryptoTokenGenerator; } });
|
|
143
145
|
Object.defineProperty(exports, "Sha256RefreshTokenHasher", { enumerable: true, get: function () { return index_js_1.Sha256RefreshTokenHasher; } });
|
|
144
146
|
Object.defineProperty(exports, "SystemClock", { enumerable: true, get: function () { return index_js_1.SystemClock; } });
|
|
@@ -310,6 +312,7 @@ Object.defineProperty(exports, "DEFAULT_REDIS_URL", { enumerable: true, get: fun
|
|
|
310
312
|
// Security hardening #5/#6 (additive): the per-IP login failure limit (D3) and the
|
|
311
313
|
// raw-NODE_ENV dev/test predicate that gates the dev email logger.
|
|
312
314
|
Object.defineProperty(exports, "loadLoginIpLimit", { enumerable: true, get: function () { return app_config_js_1.loadLoginIpLimit; } });
|
|
315
|
+
Object.defineProperty(exports, "loadRefreshReplayWindowMs", { enumerable: true, get: function () { return app_config_js_1.loadRefreshReplayWindowMs; } });
|
|
313
316
|
Object.defineProperty(exports, "DEFAULT_LOGIN_IP_LIMIT", { enumerable: true, get: function () { return app_config_js_1.DEFAULT_LOGIN_IP_LIMIT; } });
|
|
314
317
|
Object.defineProperty(exports, "isDevOrTestNodeEnv", { enumerable: true, get: function () { return app_config_js_1.isDevOrTestNodeEnv; } });
|
|
315
318
|
// ---------------------------------------------------------------------------
|
|
@@ -118,6 +118,25 @@ export interface SessionTokenBinderOptions {
|
|
|
118
118
|
*/
|
|
119
119
|
readonly rotateGraceMs?: number;
|
|
120
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* The rotate re-bind as ONE script (KI #105, KI #110). KEYS: 1 the new token's binding, 2 the session
|
|
123
|
+
* claims, 3 the current-token pointer, 4 the previous token's binding (= KEYS[1] when there is none),
|
|
124
|
+
* 5 the pending marker (the token a repeat bound for the grace only). ARGV: 1 payload, 2 access TTL,
|
|
125
|
+
* 3 session TTL, 4 grace-only TTL (min(access TTL, grace)), 5 the pointer value read before the
|
|
126
|
+
* rebind ('' = none), 6 the new token, 7 grace, 8 '1' when core answered a tolerated repeat.
|
|
127
|
+
*
|
|
128
|
+
* 0 the claims are gone (a logout won): nothing is written, the caller refuses.
|
|
129
|
+
* 2 nothing changes. A repeat whose token is bound (its original, or an earlier repeat, bound it);
|
|
130
|
+
* or an original whose token is bound but not pending (the session rotated on past it, and the
|
|
131
|
+
* binding keeps the grace it has).
|
|
132
|
+
* 3 bound for the grace TTL only; the claims, the pointer and the current token are left alone. A
|
|
133
|
+
* repeat whose token is unbound (its original has not re-bound yet: the token is marked pending
|
|
134
|
+
* so the original can still take the full bind), or an original whose pointer moved since it
|
|
135
|
+
* was read (the session rotated on).
|
|
136
|
+
* 1 the normal rotate (an original; its token unbound, or pending): bind for the access TTL,
|
|
137
|
+
* refresh the claims, move the pointer, grace-expire the previous token, clear the marker.
|
|
138
|
+
*/
|
|
139
|
+
export declare const REBIND_ON_ROTATE_SCRIPT = "if redis.call('EXISTS', KEYS[2]) == 0 then return 0 end\nlocal bound = redis.call('EXISTS', KEYS[1]) == 1\nif ARGV[8] == '1' then\n if bound then return 2 end\n redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[4])\n redis.call('SET', KEYS[5], ARGV[6], 'PX', ARGV[4])\n return 3\nend\nif bound and redis.call('GET', KEYS[5]) ~= ARGV[6] then return 2 end\nlocal current = redis.call('GET', KEYS[3])\nif current == false then current = '' end\nif current ~= ARGV[5] then\n if bound then return 2 end\n redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[4])\n return 3\nend\nredis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])\nredis.call('SET', KEYS[2], ARGV[1], 'PX', ARGV[3], 'XX')\nredis.call('SET', KEYS[3], ARGV[6], 'PX', ARGV[3])\nif ARGV[5] ~= '' and KEYS[4] ~= KEYS[1] then\n local remaining = redis.call('PTTL', KEYS[4])\n if remaining == -1 or remaining > tonumber(ARGV[7]) then\n redis.call('PEXPIRE', KEYS[4], ARGV[7])\n end\nend\nredis.call('DEL', KEYS[5])\nreturn 1";
|
|
121
140
|
export declare class SessionTokenBinder {
|
|
122
141
|
private readonly redis;
|
|
123
142
|
private readonly options;
|
|
@@ -134,12 +153,6 @@ export declare class SessionTokenBinder {
|
|
|
134
153
|
* token so a rotation can grace-expire it and a revoke can drop it.
|
|
135
154
|
*/
|
|
136
155
|
bind(accessToken: string, claims: BoundPrincipalClaims, ttlMs: number): Promise<void>;
|
|
137
|
-
/**
|
|
138
|
-
* Grace-expire the session's previous access token after a rotation (plan D7): it stays
|
|
139
|
-
* usable for at most `rotateGraceMs` (never longer than it already had), then expires.
|
|
140
|
-
* Best-effort — a failure leaves the old binding to its own (≤ access TTL) expiry.
|
|
141
|
-
*/
|
|
142
|
-
private graceExpire;
|
|
143
156
|
/**
|
|
144
157
|
* On refresh/rotate the controller has the NEW access token + the session, but not
|
|
145
158
|
* the full Principal (roles/kind/root live only in the binding, not the
|
|
@@ -149,7 +162,13 @@ export declare class SessionTokenBinder {
|
|
|
149
162
|
* then refuse the refresh (the auth controller answers 401: an unbound token is never
|
|
150
163
|
* handed out). A Redis error on the writes propagates (the caller fails closed too).
|
|
151
164
|
* The new binding's TTL is `ttlMs` (≤ the access TTL); the session's previous access
|
|
152
|
-
* token is grace-expired (≤ 30 s by default, plan D7).
|
|
165
|
+
* token is grace-expired (≤ 30 s by default, plan D7). All writes are one script
|
|
166
|
+
* ({@link REBIND_ON_ROTATE_SCRIPT}, KI #105, KI #110). `opts.repeat` (core answered the rotation
|
|
167
|
+
* from the replay window) never moves the pointer: a bound token changes nothing, an unbound one is
|
|
168
|
+
* bound for the grace only and marked pending. An original rotation takes the full bind when its
|
|
169
|
+
* token is unbound or pending and the pointer has not moved since it was read; otherwise its token
|
|
170
|
+
* is left as it is (or bound for the grace only), so no rotation can take the session back from a
|
|
171
|
+
* newer token.
|
|
153
172
|
*
|
|
154
173
|
* `freshFacts` (ADR-0011 eager projection at REFRESH):
|
|
155
174
|
* - `undefined` (omitted, the default) → carry whatever `cachedFacts` the session
|
|
@@ -181,7 +200,11 @@ export declare class SessionTokenBinder {
|
|
|
181
200
|
* switch raced the refresh) → nothing fresh is applied and the cached facts are stripped (the
|
|
182
201
|
* next request reloads for the right node). Absent → applied as before.
|
|
183
202
|
*/
|
|
184
|
-
freshForTenantId?: string
|
|
203
|
+
freshForTenantId?: string,
|
|
204
|
+
/** `repeat`: core answered this rotation from the replay window (KI #110). Absent → an original. */
|
|
205
|
+
opts?: {
|
|
206
|
+
readonly repeat?: boolean;
|
|
207
|
+
}): Promise<boolean>;
|
|
185
208
|
/**
|
|
186
209
|
* SILENT RECOVERY (ADR-0013 / ADR-0035, GAPS #5): write a session binding REBUILT from durable
|
|
187
210
|
* state after the session-claims entry was lost (Redis restart/flush). The caller (refresh) has
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-token-binder.d.ts","sourceRoot":"","sources":["../../src/request-auth/session-token-binder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AAKjC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,oFAAoF;AACpF,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,YAAY,CAAC;IACpC;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjD;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CACvC;AA2CD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAQ5D;AAED,4FAA4F;AAC5F,wBAAgB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,GAAG,SAAS,CAIzF;
|
|
1
|
+
{"version":3,"file":"session-token-binder.d.ts","sourceRoot":"","sources":["../../src/request-auth/session-token-binder.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* and the stored value carries no secret (claims only — ids, not credentials).
|
|
25
25
|
*/
|
|
26
26
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
-
exports.SessionTokenBinder = void 0;
|
|
27
|
+
exports.SessionTokenBinder = exports.REBIND_ON_ROTATE_SCRIPT = void 0;
|
|
28
28
|
exports.factsSlot = factsSlot;
|
|
29
29
|
exports.liveRoleIds = liveRoleIds;
|
|
30
30
|
/** A valid stamp: a non-negative safe integer. */
|
|
@@ -148,8 +148,62 @@ function sessClaimsKey(sessionId) {
|
|
|
148
148
|
function sessAtKey(sessionId) {
|
|
149
149
|
return `${SESS_AT_PREFIX}${sessionId}`;
|
|
150
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Redis key prefix for the session-id → the token a tolerated repeat bound for the grace only
|
|
153
|
+
* (KI #110): the one token the original rotation may still upgrade to the full bind.
|
|
154
|
+
*/
|
|
155
|
+
const SESS_GRACE_PREFIX = 'shield:sessgrace:';
|
|
156
|
+
function sessGraceKey(sessionId) {
|
|
157
|
+
return `${SESS_GRACE_PREFIX}${sessionId}`;
|
|
158
|
+
}
|
|
151
159
|
/** Default grace for the previous access token after a rotation (plan decision D7). */
|
|
152
160
|
const DEFAULT_ROTATE_GRACE_MS = 30_000;
|
|
161
|
+
/**
|
|
162
|
+
* The rotate re-bind as ONE script (KI #105, KI #110). KEYS: 1 the new token's binding, 2 the session
|
|
163
|
+
* claims, 3 the current-token pointer, 4 the previous token's binding (= KEYS[1] when there is none),
|
|
164
|
+
* 5 the pending marker (the token a repeat bound for the grace only). ARGV: 1 payload, 2 access TTL,
|
|
165
|
+
* 3 session TTL, 4 grace-only TTL (min(access TTL, grace)), 5 the pointer value read before the
|
|
166
|
+
* rebind ('' = none), 6 the new token, 7 grace, 8 '1' when core answered a tolerated repeat.
|
|
167
|
+
*
|
|
168
|
+
* 0 the claims are gone (a logout won): nothing is written, the caller refuses.
|
|
169
|
+
* 2 nothing changes. A repeat whose token is bound (its original, or an earlier repeat, bound it);
|
|
170
|
+
* or an original whose token is bound but not pending (the session rotated on past it, and the
|
|
171
|
+
* binding keeps the grace it has).
|
|
172
|
+
* 3 bound for the grace TTL only; the claims, the pointer and the current token are left alone. A
|
|
173
|
+
* repeat whose token is unbound (its original has not re-bound yet: the token is marked pending
|
|
174
|
+
* so the original can still take the full bind), or an original whose pointer moved since it
|
|
175
|
+
* was read (the session rotated on).
|
|
176
|
+
* 1 the normal rotate (an original; its token unbound, or pending): bind for the access TTL,
|
|
177
|
+
* refresh the claims, move the pointer, grace-expire the previous token, clear the marker.
|
|
178
|
+
*/
|
|
179
|
+
exports.REBIND_ON_ROTATE_SCRIPT = `if redis.call('EXISTS', KEYS[2]) == 0 then return 0 end
|
|
180
|
+
local bound = redis.call('EXISTS', KEYS[1]) == 1
|
|
181
|
+
if ARGV[8] == '1' then
|
|
182
|
+
if bound then return 2 end
|
|
183
|
+
redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[4])
|
|
184
|
+
redis.call('SET', KEYS[5], ARGV[6], 'PX', ARGV[4])
|
|
185
|
+
return 3
|
|
186
|
+
end
|
|
187
|
+
if bound and redis.call('GET', KEYS[5]) ~= ARGV[6] then return 2 end
|
|
188
|
+
local current = redis.call('GET', KEYS[3])
|
|
189
|
+
if current == false then current = '' end
|
|
190
|
+
if current ~= ARGV[5] then
|
|
191
|
+
if bound then return 2 end
|
|
192
|
+
redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[4])
|
|
193
|
+
return 3
|
|
194
|
+
end
|
|
195
|
+
redis.call('SET', KEYS[1], ARGV[1], 'PX', ARGV[2])
|
|
196
|
+
redis.call('SET', KEYS[2], ARGV[1], 'PX', ARGV[3], 'XX')
|
|
197
|
+
redis.call('SET', KEYS[3], ARGV[6], 'PX', ARGV[3])
|
|
198
|
+
if ARGV[5] ~= '' and KEYS[4] ~= KEYS[1] then
|
|
199
|
+
local remaining = redis.call('PTTL', KEYS[4])
|
|
200
|
+
if remaining == -1 or remaining > tonumber(ARGV[7]) then
|
|
201
|
+
redis.call('PEXPIRE', KEYS[4], ARGV[7])
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
redis.call('DEL', KEYS[5])
|
|
205
|
+
return 1`;
|
|
206
|
+
const REBIND_REFUSED = 0;
|
|
153
207
|
/** How long refresh waits for Redis to answer PING before answering 503 (ADR-0035, F1). */
|
|
154
208
|
const DEFAULT_PING_TIMEOUT_MS = 1_000;
|
|
155
209
|
class SessionTokenBinder {
|
|
@@ -184,25 +238,6 @@ class SessionTokenBinder {
|
|
|
184
238
|
await this.redis.set(sessClaimsKey(claims.sessionId), payload, 'PX', sessTtl);
|
|
185
239
|
await this.redis.set(sessAtKey(claims.sessionId), accessToken, 'PX', sessTtl);
|
|
186
240
|
}
|
|
187
|
-
/**
|
|
188
|
-
* Grace-expire the session's previous access token after a rotation (plan D7): it stays
|
|
189
|
-
* usable for at most `rotateGraceMs` (never longer than it already had), then expires.
|
|
190
|
-
* Best-effort — a failure leaves the old binding to its own (≤ access TTL) expiry.
|
|
191
|
-
*/
|
|
192
|
-
async graceExpire(previousToken, newToken) {
|
|
193
|
-
if (previousToken === null || previousToken === '' || previousToken === newToken)
|
|
194
|
-
return;
|
|
195
|
-
const grace = this.options.rotateGraceMs ?? DEFAULT_ROTATE_GRACE_MS;
|
|
196
|
-
try {
|
|
197
|
-
const remaining = await this.redis.pttl(atKey(previousToken));
|
|
198
|
-
if (remaining === -1 || remaining > grace) {
|
|
199
|
-
await this.redis.pexpire(atKey(previousToken), grace);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
catch {
|
|
203
|
-
// best-effort; the old binding still expires at its own access TTL.
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
241
|
/**
|
|
207
242
|
* On refresh/rotate the controller has the NEW access token + the session, but not
|
|
208
243
|
* the full Principal (roles/kind/root live only in the binding, not the
|
|
@@ -212,7 +247,13 @@ class SessionTokenBinder {
|
|
|
212
247
|
* then refuse the refresh (the auth controller answers 401: an unbound token is never
|
|
213
248
|
* handed out). A Redis error on the writes propagates (the caller fails closed too).
|
|
214
249
|
* The new binding's TTL is `ttlMs` (≤ the access TTL); the session's previous access
|
|
215
|
-
* token is grace-expired (≤ 30 s by default, plan D7).
|
|
250
|
+
* token is grace-expired (≤ 30 s by default, plan D7). All writes are one script
|
|
251
|
+
* ({@link REBIND_ON_ROTATE_SCRIPT}, KI #105, KI #110). `opts.repeat` (core answered the rotation
|
|
252
|
+
* from the replay window) never moves the pointer: a bound token changes nothing, an unbound one is
|
|
253
|
+
* bound for the grace only and marked pending. An original rotation takes the full bind when its
|
|
254
|
+
* token is unbound or pending and the pointer has not moved since it was read; otherwise its token
|
|
255
|
+
* is left as it is (or bound for the grace only), so no rotation can take the session back from a
|
|
256
|
+
* newer token.
|
|
216
257
|
*
|
|
217
258
|
* `freshFacts` (ADR-0011 eager projection at REFRESH):
|
|
218
259
|
* - `undefined` (omitted, the default) → carry whatever `cachedFacts` the session
|
|
@@ -244,9 +285,15 @@ class SessionTokenBinder {
|
|
|
244
285
|
* switch raced the refresh) → nothing fresh is applied and the cached facts are stripped (the
|
|
245
286
|
* next request reloads for the right node). Absent → applied as before.
|
|
246
287
|
*/
|
|
247
|
-
freshForTenantId
|
|
288
|
+
freshForTenantId,
|
|
289
|
+
/** `repeat`: core answered this rotation from the replay window (KI #110). Absent → an original. */
|
|
290
|
+
opts) {
|
|
291
|
+
// The session's current access token, read FIRST: the script moves the pointer only if it still
|
|
292
|
+
// holds this value (KI #105).
|
|
293
|
+
let previousToken;
|
|
248
294
|
let raw;
|
|
249
295
|
try {
|
|
296
|
+
previousToken = await this.redis.get(sessAtKey(sessionId));
|
|
250
297
|
raw = await this.redis.get(sessClaimsKey(sessionId));
|
|
251
298
|
}
|
|
252
299
|
catch {
|
|
@@ -304,28 +351,12 @@ class SessionTokenBinder {
|
|
|
304
351
|
payload = raw;
|
|
305
352
|
}
|
|
306
353
|
}
|
|
307
|
-
// The session's previous access token (best-effort read — a miss only skips the grace).
|
|
308
|
-
let previousToken = null;
|
|
309
|
-
try {
|
|
310
|
-
previousToken = await this.redis.get(sessAtKey(sessionId));
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
previousToken = null;
|
|
314
|
-
}
|
|
315
354
|
const sessTtl = this.sessionTtl(ttlMs);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const kept = await this.redis.set(sessClaimsKey(sessionId), payload, 'PX', sessTtl, 'XX');
|
|
322
|
-
if (kept !== 'OK') {
|
|
323
|
-
await this.unbind(newAccessToken);
|
|
324
|
-
return false;
|
|
325
|
-
}
|
|
326
|
-
await this.redis.set(sessAtKey(sessionId), newAccessToken, 'PX', sessTtl);
|
|
327
|
-
await this.graceExpire(previousToken, newAccessToken);
|
|
328
|
-
return true;
|
|
355
|
+
const grace = this.options.rotateGraceMs ?? DEFAULT_ROTATE_GRACE_MS;
|
|
356
|
+
const expected = previousToken ?? '';
|
|
357
|
+
// KI #105: one script, so nothing can land between the checks and the writes.
|
|
358
|
+
const outcome = await this.redis.eval(exports.REBIND_ON_ROTATE_SCRIPT, 5, atKey(newAccessToken), sessClaimsKey(sessionId), sessAtKey(sessionId), expected === '' ? atKey(newAccessToken) : atKey(expected), sessGraceKey(sessionId), payload, String(ttlMs), String(sessTtl), String(Math.max(1, Math.min(ttlMs, grace))), expected, newAccessToken, String(grace), opts?.repeat === true ? '1' : '0');
|
|
359
|
+
return Number(outcome) !== REBIND_REFUSED;
|
|
329
360
|
}
|
|
330
361
|
/**
|
|
331
362
|
* SILENT RECOVERY (ADR-0013 / ADR-0035, GAPS #5): write a session binding REBUILT from durable
|
package/dist/session/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export { MariaDbSessionRecordStore, SHIELD_SESSION_RECORD_STORE, } from './mariadb-session-record-store.js';
|
|
15
15
|
export { RedisHotSessionStore, SHIELD_HOT_SESSION_STORE } from './redis-hot-session-store.js';
|
|
16
|
+
export { RedisRecentRotationStore, type RedisRecentRotationStoreOptions, } from './redis-recent-rotation-store.js';
|
|
16
17
|
export { HotRevokingSessionRecordStore, type SessionHotRevoker, type LineageSessionLookup, } from './hot-revoking-session-record-store.js';
|
|
17
18
|
export { SHIELD_SESSION_LIFETIMES, ROTATE_GRACE_MS, DEFAULT_ACCESS_TTL_MS, accessBindingTtlMs, } from './session-lifetimes.js';
|
|
18
19
|
export { CryptoTokenGenerator, Sha256RefreshTokenHasher, SystemClock, cryptoTokenGeneratorProvider, refreshHasherProvider, systemClockProvider, SHIELD_TOKEN_GENERATOR, SHIELD_REFRESH_HASHER, SHIELD_CLOCK, } from './crypto-adapters.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAE9F,OAAO,EACL,6BAA6B,EAC7B,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,GAC1B,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,WAAW,EACX,4BAA4B,EAC5B,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,YAAY,GACb,MAAM,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/session/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,yBAAyB,EACzB,2BAA2B,GAC5B,MAAM,mCAAmC,CAAC;AAE3C,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAE9F,OAAO,EACL,wBAAwB,EACxB,KAAK,+BAA+B,GACrC,MAAM,kCAAkC,CAAC;AAE1C,OAAO,EACL,6BAA6B,EAC7B,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,GAC1B,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EACL,wBAAwB,EACxB,eAAe,EACf,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,oBAAoB,EACpB,wBAAwB,EACxB,WAAW,EACX,4BAA4B,EAC5B,qBAAqB,EACrB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,YAAY,GACb,MAAM,sBAAsB,CAAC"}
|
package/dist/session/index.js
CHANGED
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
* A consumer wires these + `createSessionAuthority(...)` from `@astram/core`.
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.SHIELD_CLOCK = exports.SHIELD_REFRESH_HASHER = exports.SHIELD_TOKEN_GENERATOR = exports.systemClockProvider = exports.refreshHasherProvider = exports.cryptoTokenGeneratorProvider = exports.SystemClock = exports.Sha256RefreshTokenHasher = exports.CryptoTokenGenerator = exports.accessBindingTtlMs = exports.DEFAULT_ACCESS_TTL_MS = exports.ROTATE_GRACE_MS = exports.SHIELD_SESSION_LIFETIMES = exports.HotRevokingSessionRecordStore = exports.SHIELD_HOT_SESSION_STORE = exports.RedisHotSessionStore = exports.SHIELD_SESSION_RECORD_STORE = exports.MariaDbSessionRecordStore = void 0;
|
|
16
|
+
exports.SHIELD_CLOCK = exports.SHIELD_REFRESH_HASHER = exports.SHIELD_TOKEN_GENERATOR = exports.systemClockProvider = exports.refreshHasherProvider = exports.cryptoTokenGeneratorProvider = exports.SystemClock = exports.Sha256RefreshTokenHasher = exports.CryptoTokenGenerator = exports.accessBindingTtlMs = exports.DEFAULT_ACCESS_TTL_MS = exports.ROTATE_GRACE_MS = exports.SHIELD_SESSION_LIFETIMES = exports.HotRevokingSessionRecordStore = exports.RedisRecentRotationStore = exports.SHIELD_HOT_SESSION_STORE = exports.RedisHotSessionStore = exports.SHIELD_SESSION_RECORD_STORE = exports.MariaDbSessionRecordStore = void 0;
|
|
17
17
|
var mariadb_session_record_store_js_1 = require("./mariadb-session-record-store.js");
|
|
18
18
|
Object.defineProperty(exports, "MariaDbSessionRecordStore", { enumerable: true, get: function () { return mariadb_session_record_store_js_1.MariaDbSessionRecordStore; } });
|
|
19
19
|
Object.defineProperty(exports, "SHIELD_SESSION_RECORD_STORE", { enumerable: true, get: function () { return mariadb_session_record_store_js_1.SHIELD_SESSION_RECORD_STORE; } });
|
|
20
20
|
var redis_hot_session_store_js_1 = require("./redis-hot-session-store.js");
|
|
21
21
|
Object.defineProperty(exports, "RedisHotSessionStore", { enumerable: true, get: function () { return redis_hot_session_store_js_1.RedisHotSessionStore; } });
|
|
22
22
|
Object.defineProperty(exports, "SHIELD_HOT_SESSION_STORE", { enumerable: true, get: function () { return redis_hot_session_store_js_1.SHIELD_HOT_SESSION_STORE; } });
|
|
23
|
+
var redis_recent_rotation_store_js_1 = require("./redis-recent-rotation-store.js");
|
|
24
|
+
Object.defineProperty(exports, "RedisRecentRotationStore", { enumerable: true, get: function () { return redis_recent_rotation_store_js_1.RedisRecentRotationStore; } });
|
|
23
25
|
var hot_revoking_session_record_store_js_1 = require("./hot-revoking-session-record-store.js");
|
|
24
26
|
Object.defineProperty(exports, "HotRevokingSessionRecordStore", { enumerable: true, get: function () { return hot_revoking_session_record_store_js_1.HotRevokingSessionRecordStore; } });
|
|
25
27
|
var session_lifetimes_js_1 = require("./session-lifetimes.js");
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type Redis from 'ioredis';
|
|
2
|
+
import type { RecentRotation, RecentRotationStore } from '@astram/core';
|
|
3
|
+
export interface RedisRecentRotationStoreOptions {
|
|
4
|
+
/** How often `recall` re-reads while waiting (default 25 ms). */
|
|
5
|
+
readonly pollMs?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class RedisRecentRotationStore implements RecentRotationStore {
|
|
8
|
+
private readonly redis;
|
|
9
|
+
private readonly secret;
|
|
10
|
+
private readonly keyNameKey;
|
|
11
|
+
private readonly pollMs;
|
|
12
|
+
constructor(redis: Redis, serverSecret: string, options?: RedisRecentRotationStoreOptions);
|
|
13
|
+
remember(previousRefreshToken: string, rotation: RecentRotation, ttlMs: number): Promise<void>;
|
|
14
|
+
recall(previousRefreshToken: string, waitMs: number): Promise<RecentRotation | null>;
|
|
15
|
+
private open;
|
|
16
|
+
private keyFor;
|
|
17
|
+
private sealKey;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=redis-recent-rotation-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-recent-rotation-store.d.ts","sourceRoot":"","sources":["../../src/session/redis-recent-rotation-store.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AASxE,MAAM,WAAW,+BAA+B;IAC9C,iEAAiE;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAiBD,qBAAa,wBAAyB,YAAW,mBAAmB;IAMhE,OAAO,CAAC,QAAQ,CAAC,KAAK;IALxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAGb,KAAK,EAAE,KAAK,EAC7B,YAAY,EAAE,MAAM,EACpB,OAAO,GAAE,+BAAoC;IAYzC,QAAQ,CACZ,oBAAoB,EAAE,MAAM,EAC5B,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC;IAqBV,MAAM,CAAC,oBAAoB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAW1F,OAAO,CAAC,IAAI;IAkBZ,OAAO,CAAC,MAAM;IAOd,OAAO,CAAC,OAAO;CAKhB"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisRecentRotationStore = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* `RedisRecentRotationStore` — the guard adapter for core's OPTIONAL {@link RecentRotationStore}
|
|
6
|
+
* port (KI #51 / KI #54, ADR-0036): remembers a rotation's successor pair for a few seconds so a
|
|
7
|
+
* repeated refresh of the same token gets the SAME pair instead of killing the lineage.
|
|
8
|
+
*
|
|
9
|
+
* The pair is made of RAW tokens, so it is never stored in the clear:
|
|
10
|
+
* - key `shield:rotated:<HMAC-SHA-256(K_name, old token)>`, K_name = HKDF(server secret, info
|
|
11
|
+
* 'astram/recent-rotation/key-name') — no token in it, and never equal to a refresh-token
|
|
12
|
+
* hash (those are HMAC under the raw secret);
|
|
13
|
+
* - value AES-256-GCM over the JSON pair, under a key derived (HKDF-SHA-256) from the OLD raw
|
|
14
|
+
* refresh token with the server secret as salt; the Redis key is the AAD (a value moved under
|
|
15
|
+
* another key does not open). Only a caller presenting the old token can open it; Redis alone
|
|
16
|
+
* (or the DB, which holds only HMAC hashes) yields nothing usable.
|
|
17
|
+
* - `SET … PX <window> NX` (first write wins).
|
|
18
|
+
*
|
|
19
|
+
* `recall` polls for up to `waitMs` (a concurrent winner may still be writing). A malformed,
|
|
20
|
+
* tampered or foreign value is `null`; a Redis error propagates, and the engine treats it as no
|
|
21
|
+
* entry, so the replay revokes the lineage (fail closed). The engine re-proves every answer against
|
|
22
|
+
* the durable store, so this adapter can never create a session on its own.
|
|
23
|
+
*/
|
|
24
|
+
const node_crypto_1 = require("node:crypto");
|
|
25
|
+
const KEY_PREFIX = 'shield:rotated:';
|
|
26
|
+
const HKDF_INFO = 'astram/recent-rotation/v1';
|
|
27
|
+
const KEY_NAME_INFO = 'astram/recent-rotation/key-name';
|
|
28
|
+
const IV_BYTES = 12;
|
|
29
|
+
const TAG_BYTES = 16;
|
|
30
|
+
const DEFAULT_POLL_MS = 25;
|
|
31
|
+
function sleep(ms) {
|
|
32
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
33
|
+
}
|
|
34
|
+
function isRotation(value) {
|
|
35
|
+
if (value === null || typeof value !== 'object')
|
|
36
|
+
return false;
|
|
37
|
+
const v = value;
|
|
38
|
+
return (typeof v.accessToken === 'string' &&
|
|
39
|
+
typeof v.refreshToken === 'string' &&
|
|
40
|
+
typeof v.accessExpiresAtMs === 'number' &&
|
|
41
|
+
typeof v.rotatedAtMs === 'number');
|
|
42
|
+
}
|
|
43
|
+
class RedisRecentRotationStore {
|
|
44
|
+
redis;
|
|
45
|
+
secret;
|
|
46
|
+
keyNameKey;
|
|
47
|
+
pollMs;
|
|
48
|
+
constructor(redis, serverSecret, options = {}) {
|
|
49
|
+
this.redis = redis;
|
|
50
|
+
if (typeof serverSecret !== 'string' || serverSecret.length === 0) {
|
|
51
|
+
throw new Error('RedisRecentRotationStore: a non-empty server secret is required');
|
|
52
|
+
}
|
|
53
|
+
this.secret = Buffer.from(serverSecret, 'utf8');
|
|
54
|
+
this.keyNameKey = Buffer.from((0, node_crypto_1.hkdfSync)('sha256', this.secret, Buffer.alloc(0), KEY_NAME_INFO, 32));
|
|
55
|
+
this.pollMs = Math.max(1, Math.floor(options.pollMs ?? DEFAULT_POLL_MS));
|
|
56
|
+
}
|
|
57
|
+
async remember(previousRefreshToken, rotation, ttlMs) {
|
|
58
|
+
const key = this.keyFor(previousRefreshToken);
|
|
59
|
+
const iv = (0, node_crypto_1.randomBytes)(IV_BYTES);
|
|
60
|
+
const cipher = (0, node_crypto_1.createCipheriv)('aes-256-gcm', this.sealKey(previousRefreshToken), iv);
|
|
61
|
+
cipher.setAAD(Buffer.from(key, 'utf8'));
|
|
62
|
+
const body = Buffer.concat([
|
|
63
|
+
cipher.update(JSON.stringify({
|
|
64
|
+
accessToken: rotation.accessToken,
|
|
65
|
+
refreshToken: rotation.refreshToken,
|
|
66
|
+
accessExpiresAtMs: rotation.accessExpiresAtMs,
|
|
67
|
+
rotatedAtMs: rotation.rotatedAtMs,
|
|
68
|
+
}), 'utf8'),
|
|
69
|
+
cipher.final(),
|
|
70
|
+
]);
|
|
71
|
+
const sealed = Buffer.concat([iv, cipher.getAuthTag(), body]).toString('base64');
|
|
72
|
+
await this.redis.set(key, sealed, 'PX', Math.max(1, Math.floor(ttlMs)), 'NX');
|
|
73
|
+
}
|
|
74
|
+
async recall(previousRefreshToken, waitMs) {
|
|
75
|
+
const key = this.keyFor(previousRefreshToken);
|
|
76
|
+
const deadline = Date.now() + Math.max(0, waitMs);
|
|
77
|
+
for (;;) {
|
|
78
|
+
const sealed = await this.redis.get(key);
|
|
79
|
+
if (sealed !== null)
|
|
80
|
+
return this.open(previousRefreshToken, key, sealed);
|
|
81
|
+
if (Date.now() >= deadline)
|
|
82
|
+
return null;
|
|
83
|
+
await sleep(Math.min(this.pollMs, Math.max(1, deadline - Date.now())));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
open(previousRefreshToken, key, sealed) {
|
|
87
|
+
try {
|
|
88
|
+
const raw = Buffer.from(sealed, 'base64');
|
|
89
|
+
if (raw.length <= IV_BYTES + TAG_BYTES)
|
|
90
|
+
return null;
|
|
91
|
+
const iv = raw.subarray(0, IV_BYTES);
|
|
92
|
+
const tag = raw.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);
|
|
93
|
+
const body = raw.subarray(IV_BYTES + TAG_BYTES);
|
|
94
|
+
const decipher = (0, node_crypto_1.createDecipheriv)('aes-256-gcm', this.sealKey(previousRefreshToken), iv);
|
|
95
|
+
decipher.setAAD(Buffer.from(key, 'utf8'));
|
|
96
|
+
decipher.setAuthTag(tag);
|
|
97
|
+
const json = Buffer.concat([decipher.update(body), decipher.final()]).toString('utf8');
|
|
98
|
+
const parsed = JSON.parse(json);
|
|
99
|
+
return isRotation(parsed) ? parsed : null;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return null; // wrong token, wrong secret, tampered or malformed
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
keyFor(previousRefreshToken) {
|
|
106
|
+
const digest = (0, node_crypto_1.createHmac)('sha256', this.keyNameKey)
|
|
107
|
+
.update(previousRefreshToken, 'utf8')
|
|
108
|
+
.digest('hex');
|
|
109
|
+
return `${KEY_PREFIX}${digest}`;
|
|
110
|
+
}
|
|
111
|
+
sealKey(previousRefreshToken) {
|
|
112
|
+
return Buffer.from((0, node_crypto_1.hkdfSync)('sha256', Buffer.from(previousRefreshToken, 'utf8'), this.secret, HKDF_INFO, 32));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.RedisRecentRotationStore = RedisRecentRotationStore;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ReplayToleratedEvent } from '@astram/core';
|
|
2
|
+
import type { AuthAuditEvent } from '../auth/ports/audit-sink.js';
|
|
3
|
+
/** Runs `fn` and returns its result with the tolerated-repeat event core reported during it, if any. */
|
|
4
|
+
export declare function captureReplayDuring<T>(fn: () => Promise<T>): Promise<{
|
|
5
|
+
result: T;
|
|
6
|
+
replay: ReplayToleratedEvent | undefined;
|
|
7
|
+
}>;
|
|
8
|
+
/** Notes the event for the enclosing {@link captureReplayDuring}; false when there is none. */
|
|
9
|
+
export declare function noteReplayTolerated(event: ReplayToleratedEvent): boolean;
|
|
10
|
+
/** The audit event for a tolerated repeat: answered (`tolerated`) or refused at the re-bind. */
|
|
11
|
+
export declare function replayAuditEvent(event: ReplayToleratedEvent, outcome: 'tolerated' | 'refused', occurredAtMs: number): AuthAuditEvent;
|
|
12
|
+
//# sourceMappingURL=replay-outcome.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-outcome.d.ts","sourceRoot":"","sources":["../../src/session/replay-outcome.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAQlE,wGAAwG;AACxG,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC;IAAE,MAAM,EAAE,CAAC,CAAC;IAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,CAAA;CAAE,CAAC,CAIlE;AAED,+FAA+F;AAC/F,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAKxE;AAED,gGAAgG;AAChG,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,YAAY,EAAE,MAAM,GACnB,cAAc,CAYhB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.captureReplayDuring = captureReplayDuring;
|
|
4
|
+
exports.noteReplayTolerated = noteReplayTolerated;
|
|
5
|
+
exports.replayAuditEvent = replayAuditEvent;
|
|
6
|
+
/**
|
|
7
|
+
* KI #104: core reports a tolerated refresh repeat (`onReplayTolerated`) from inside `rotate`, before
|
|
8
|
+
* the controller has re-bound the access token. The refresh route runs `rotate` inside
|
|
9
|
+
* {@link captureReplayDuring}; the callback then only notes the event, and the route records the
|
|
10
|
+
* audit AFTER the re-bind with the real outcome (`session.refresh_replay_tolerated`, or
|
|
11
|
+
* `session.refresh_replay_refused` when the re-bind refuses). A rotate outside that scope (another
|
|
12
|
+
* caller) is recorded at once, as before.
|
|
13
|
+
*/
|
|
14
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
15
|
+
const capture = new node_async_hooks_1.AsyncLocalStorage();
|
|
16
|
+
/** Runs `fn` and returns its result with the tolerated-repeat event core reported during it, if any. */
|
|
17
|
+
async function captureReplayDuring(fn) {
|
|
18
|
+
const slot = {};
|
|
19
|
+
const result = await capture.run(slot, fn);
|
|
20
|
+
return { result, replay: slot.event };
|
|
21
|
+
}
|
|
22
|
+
/** Notes the event for the enclosing {@link captureReplayDuring}; false when there is none. */
|
|
23
|
+
function noteReplayTolerated(event) {
|
|
24
|
+
const slot = capture.getStore();
|
|
25
|
+
if (slot === undefined)
|
|
26
|
+
return false;
|
|
27
|
+
slot.event = event;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
/** The audit event for a tolerated repeat: answered (`tolerated`) or refused at the re-bind. */
|
|
31
|
+
function replayAuditEvent(event, outcome, occurredAtMs) {
|
|
32
|
+
return {
|
|
33
|
+
type: 'authentication',
|
|
34
|
+
occurredAtMs,
|
|
35
|
+
actorId: event.principalId,
|
|
36
|
+
tenantId: event.tenantId,
|
|
37
|
+
action: outcome === 'tolerated'
|
|
38
|
+
? 'session.refresh_replay_tolerated'
|
|
39
|
+
: 'session.refresh_replay_refused',
|
|
40
|
+
attributes: { sessionId: event.sessionId },
|
|
41
|
+
};
|
|
42
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astram/guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
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",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"argon2": "^0.41.1",
|
|
36
36
|
"ioredis": "^5.4.1",
|
|
37
37
|
"mariadb": "^3.5.4",
|
|
38
|
-
"@astram/
|
|
39
|
-
"@astram/
|
|
38
|
+
"@astram/core": "1.5.0",
|
|
39
|
+
"@astram/engine": "0.5.3"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
42
|
"@nestjs/common": "^11",
|