@c9up/warden 0.1.4 → 0.1.5
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/README.md +57 -0
- package/dist/Guard.d.ts +9 -0
- package/dist/Guard.d.ts.map +1 -1
- package/dist/Guard.js +16 -0
- package/dist/Guard.js.map +1 -1
- package/dist/RedisBlacklistDriver.d.ts +32 -0
- package/dist/RedisBlacklistDriver.d.ts.map +1 -0
- package/dist/RedisBlacklistDriver.js +37 -0
- package/dist/RedisBlacklistDriver.js.map +1 -0
- package/dist/ResilientBlacklistDriver.d.ts +35 -0
- package/dist/ResilientBlacklistDriver.d.ts.map +1 -0
- package/dist/ResilientBlacklistDriver.js +61 -0
- package/dist/ResilientBlacklistDriver.js.map +1 -0
- package/dist/WardenProvider.d.ts.map +1 -1
- package/dist/WardenProvider.js +10 -0
- package/dist/WardenProvider.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/mfa/BackupCodesProvider.d.ts +39 -0
- package/dist/mfa/BackupCodesProvider.d.ts.map +1 -0
- package/dist/mfa/BackupCodesProvider.js +87 -0
- package/dist/mfa/BackupCodesProvider.js.map +1 -0
- package/dist/mfa/MfaManager.d.ts +107 -0
- package/dist/mfa/MfaManager.d.ts.map +1 -0
- package/dist/mfa/MfaManager.js +219 -0
- package/dist/mfa/MfaManager.js.map +1 -0
- package/dist/mfa/OtpProvider.d.ts +67 -0
- package/dist/mfa/OtpProvider.d.ts.map +1 -0
- package/dist/mfa/OtpProvider.js +104 -0
- package/dist/mfa/OtpProvider.js.map +1 -0
- package/dist/mfa/TotpProvider.d.ts +52 -0
- package/dist/mfa/TotpProvider.d.ts.map +1 -0
- package/dist/mfa/TotpProvider.js +103 -0
- package/dist/mfa/TotpProvider.js.map +1 -0
- package/dist/mfa/WebauthnProvider.d.ts +175 -0
- package/dist/mfa/WebauthnProvider.d.ts.map +1 -0
- package/dist/mfa/WebauthnProvider.js +239 -0
- package/dist/mfa/WebauthnProvider.js.map +1 -0
- package/dist/mfa/base32.d.ts +13 -0
- package/dist/mfa/base32.d.ts.map +1 -0
- package/dist/mfa/base32.js +52 -0
- package/dist/mfa/base32.js.map +1 -0
- package/dist/mfa/webauthn-codec.d.ts +58 -0
- package/dist/mfa/webauthn-codec.d.ts.map +1 -0
- package/dist/mfa/webauthn-codec.js +221 -0
- package/dist/mfa/webauthn-codec.js.map +1 -0
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +13 -1
- package/dist/middleware.js.map +1 -1
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
- package/src/Guard.ts +21 -0
- package/src/RedisBlacklistDriver.ts +60 -0
- package/src/ResilientBlacklistDriver.ts +84 -0
- package/src/WardenProvider.ts +11 -0
- package/src/config.ts +7 -0
- package/src/index.ts +58 -0
- package/src/mfa/BackupCodesProvider.ts +125 -0
- package/src/mfa/MfaManager.ts +307 -0
- package/src/mfa/OtpProvider.ts +177 -0
- package/src/mfa/TotpProvider.ts +140 -0
- package/src/mfa/WebauthnProvider.ts +416 -0
- package/src/mfa/base32.ts +54 -0
- package/src/mfa/webauthn-codec.ts +264 -0
- package/src/middleware.ts +14 -0
package/README.md
CHANGED
|
@@ -29,6 +29,63 @@ class OrderController {
|
|
|
29
29
|
- RBAC: `hasRole`, `hasPermission`, `hasAllPermissions`
|
|
30
30
|
- Strategy exception safety (catch → AuthResult)
|
|
31
31
|
- Runtime strategy registration
|
|
32
|
+
- **Multi-factor authentication** — TOTP, backup codes, email/SMS OTP, WebAuthn/passkeys, all dependency-free
|
|
33
|
+
- JWT revocation blacklist (Memory / Redis-KeyDB / resilient fallback drivers)
|
|
34
|
+
|
|
35
|
+
## Multi-factor authentication
|
|
36
|
+
|
|
37
|
+
Four providers, all implemented on `node:crypto` with **zero third-party dependency**
|
|
38
|
+
(WebAuthn included). Every provider and the manager take pluggable stores, defaulting
|
|
39
|
+
to in-memory — supply persistent stores (Atlas, KeyDB…) in production.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import {
|
|
43
|
+
MfaManager,
|
|
44
|
+
TotpProvider,
|
|
45
|
+
BackupCodesProvider,
|
|
46
|
+
RequireMfa,
|
|
47
|
+
} from '@c9up/warden'
|
|
48
|
+
|
|
49
|
+
const mfa = new MfaManager({
|
|
50
|
+
issuer: 'Fluveo',
|
|
51
|
+
totp: new TotpProvider(),
|
|
52
|
+
backupCodes: new BackupCodesProvider(),
|
|
53
|
+
// store: new AtlasMfaFactorStore(db), // persistent factors in production
|
|
54
|
+
rateLimit: { maxAttempts: 5, windowSeconds: 900 }, // per-user brute-force lock
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// Enroll (returns the otpauth:// URI to render as a QR code)
|
|
58
|
+
const { factorId, uri } = await mfa.enrollTotp({ id: user.id, name: user.email })
|
|
59
|
+
await mfa.confirmTotp(factorId, codeFromAuthenticatorApp) // → true once confirmed
|
|
60
|
+
const recoveryCodes = await mfa.createBackupCodes(user.id) // show once
|
|
61
|
+
|
|
62
|
+
// At sign-in step-up: TOTP code OR a backup code (rate-limited)
|
|
63
|
+
if (await mfa.verify(user.id, submittedCode)) {
|
|
64
|
+
// issue a JWT carrying `mfa: true`
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Gate sensitive routes — the middleware returns `403 MFA_REQUIRED` unless the
|
|
69
|
+
authenticated user's payload carries a truthy `mfa` claim:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
class TransferController {
|
|
73
|
+
@Guard('jwt')
|
|
74
|
+
@RequireMfa()
|
|
75
|
+
async transfer() { /* only reachable after MFA step-up */ }
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Wire the manager into the container via config:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// config/auth.ts
|
|
83
|
+
import { defineConfig } from '@c9up/warden/config'
|
|
84
|
+
export default defineConfig({ jwt: { /* … */ }, mfa: { manager: mfa } })
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Email/SMS OTP (`OtpProvider`) and WebAuthn/passkeys (`WebauthnProvider`) are used
|
|
88
|
+
directly — OTP needs a delivery channel, WebAuthn persists passkeys in its own store.
|
|
32
89
|
|
|
33
90
|
## License
|
|
34
91
|
|
package/dist/Guard.d.ts
CHANGED
|
@@ -23,4 +23,13 @@ export declare function getGuardMetadata(target: object, propertyKey: string | s
|
|
|
23
23
|
export declare function getPermissionMetadata(target: object, propertyKey: string | symbol): string[];
|
|
24
24
|
/** Get role metadata. */
|
|
25
25
|
export declare function getRoleMetadata(target: object, propertyKey: string | symbol): string[];
|
|
26
|
+
/**
|
|
27
|
+
* @RequireMfa() — require the authenticated user to have completed multi-factor
|
|
28
|
+
* authentication. The auth middleware rejects with 403 `MFA_REQUIRED` when the
|
|
29
|
+
* user payload lacks a truthy `mfa` claim (set by your MFA step-up flow once
|
|
30
|
+
* `MfaManager.verify()` succeeds).
|
|
31
|
+
*/
|
|
32
|
+
export declare function RequireMfa(): MethodDecorator;
|
|
33
|
+
/** Whether the route requires completed MFA. */
|
|
34
|
+
export declare function getRequireMfaMetadata(target: object, propertyKey: string | symbol): boolean;
|
|
26
35
|
//# sourceMappingURL=Guard.d.ts.map
|
package/dist/Guard.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Guard.d.ts","sourceRoot":"","sources":["../src/Guard.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"Guard.d.ts","sourceRoot":"","sources":["../src/Guard.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,kBAAkB,CAAC;AAQ1B;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CAKvE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,WAAW,EAAE,MAAM,EAAE,GAAG,eAAe,CAIpE;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,CAIxD;AAED,0BAA0B;AAC1B,wBAAgB,gBAAgB,CAC/B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,MAAM,GAC1B,MAAM,EAAE,CAEV;AAED,+BAA+B;AAC/B,wBAAgB,qBAAqB,CACpC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,MAAM,GAC1B,MAAM,EAAE,CAEV;AAED,yBAAyB;AACzB,wBAAgB,eAAe,CAC9B,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,MAAM,GAC1B,MAAM,EAAE,CAEV;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,eAAe,CAI5C;AAED,gDAAgD;AAChD,wBAAgB,qBAAqB,CACpC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,GAAG,MAAM,GAC1B,OAAO,CAET"}
|
package/dist/Guard.js
CHANGED
|
@@ -11,6 +11,7 @@ import "reflect-metadata";
|
|
|
11
11
|
const GUARD_KEY = Symbol.for("warden:guard");
|
|
12
12
|
const PERMISSION_KEY = Symbol.for("warden:permission");
|
|
13
13
|
const ROLE_KEY = Symbol.for("warden:role");
|
|
14
|
+
const MFA_KEY = Symbol.for("warden:mfa");
|
|
14
15
|
/**
|
|
15
16
|
* @Guard('jwt') — require authentication via the named strategy.
|
|
16
17
|
* At least one strategy name is required.
|
|
@@ -49,4 +50,19 @@ export function getPermissionMetadata(target, propertyKey) {
|
|
|
49
50
|
export function getRoleMetadata(target, propertyKey) {
|
|
50
51
|
return Reflect.getOwnMetadata(ROLE_KEY, target, propertyKey) ?? [];
|
|
51
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* @RequireMfa() — require the authenticated user to have completed multi-factor
|
|
55
|
+
* authentication. The auth middleware rejects with 403 `MFA_REQUIRED` when the
|
|
56
|
+
* user payload lacks a truthy `mfa` claim (set by your MFA step-up flow once
|
|
57
|
+
* `MfaManager.verify()` succeeds).
|
|
58
|
+
*/
|
|
59
|
+
export function RequireMfa() {
|
|
60
|
+
return (target, propertyKey) => {
|
|
61
|
+
Reflect.defineMetadata(MFA_KEY, true, target, propertyKey);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Whether the route requires completed MFA. */
|
|
65
|
+
export function getRequireMfaMetadata(target, propertyKey) {
|
|
66
|
+
return Reflect.getOwnMetadata(MFA_KEY, target, propertyKey) ?? false;
|
|
67
|
+
}
|
|
52
68
|
//# sourceMappingURL=Guard.js.map
|
package/dist/Guard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Guard.js","sourceRoot":"","sources":["../src/Guard.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,gFAAgF;AAChF,0EAA0E;AAC1E,0EAA0E;AAC1E,OAAO,kBAAkB,CAAC;AAE1B,oFAAoF;AACpF,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAC7C,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AACvD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"Guard.js","sourceRoot":"","sources":["../src/Guard.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,gFAAgF;AAChF,0EAA0E;AAC1E,0EAA0E;AAC1E,OAAO,kBAAkB,CAAC;AAE1B,oFAAoF;AACpF,MAAM,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAC7C,MAAM,cAAc,GAAG,MAAM,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;AACvD,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;AAC3C,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;AAEzC;;;GAGG;AACH,MAAM,UAAU,KAAK,CAAC,KAAa,EAAE,GAAG,IAAc;IACrD,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,CAAC;IACpC,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE;QAC9B,OAAO,CAAC,cAAc,CAAC,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IACpE,CAAC,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,GAAG,WAAqB;IAClD,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE;QAC9B,OAAO,CAAC,cAAc,CAAC,cAAc,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAC1E,CAAC,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,IAAI,CAAC,GAAG,KAAe;IACtC,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE;QAC9B,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAC9D,CAAC,CAAC;AACH,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,gBAAgB,CAC/B,MAAc,EACd,WAA4B;IAE5B,OAAO,OAAO,CAAC,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC;AACrE,CAAC;AAED,+BAA+B;AAC/B,MAAM,UAAU,qBAAqB,CACpC,MAAc,EACd,WAA4B;IAE5B,OAAO,OAAO,CAAC,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC;AAC1E,CAAC;AAED,yBAAyB;AACzB,MAAM,UAAU,eAAe,CAC9B,MAAc,EACd,WAA4B;IAE5B,OAAO,OAAO,CAAC,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU;IACzB,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,EAAE;QAC9B,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAC5D,CAAC,CAAC;AACH,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,qBAAqB,CACpC,MAAc,EACd,WAA4B;IAE5B,OAAO,OAAO,CAAC,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,IAAI,KAAK,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedisBlacklistDriver — a persistent JWT blacklist backed by Redis / KeyDB,
|
|
3
|
+
* so logout revocation survives a process restart (unlike MemoryBlacklistDriver).
|
|
4
|
+
*
|
|
5
|
+
* Agnostic by construction: it depends on no Redis package. The caller injects
|
|
6
|
+
* any ioredis-compatible client (KeyDB, Redis, a mock…) that exposes `set` with
|
|
7
|
+
* an `"EX"` ttl and `exists`. Native key expiry does the housekeeping, so
|
|
8
|
+
* `cleanup()` is a no-op.
|
|
9
|
+
*/
|
|
10
|
+
import type { BlacklistDriver } from "./TokenBlacklist.js";
|
|
11
|
+
/** The minimal ioredis-shaped surface this driver needs. */
|
|
12
|
+
export interface RedisLikeClient {
|
|
13
|
+
set(key: string, value: string, mode: "EX", ttlSeconds: number): Promise<unknown>;
|
|
14
|
+
exists(key: string): Promise<number>;
|
|
15
|
+
}
|
|
16
|
+
export interface RedisBlacklistConfig {
|
|
17
|
+
/** Key prefix for blacklist entries. Default `warden:blacklist:`. */
|
|
18
|
+
prefix?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare class RedisBlacklistDriver implements BlacklistDriver {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(client: RedisLikeClient, config?: RedisBlacklistConfig);
|
|
23
|
+
/**
|
|
24
|
+
* Blacklist a JTI until its natural expiry. Tokens already past `expiresAt`
|
|
25
|
+
* are skipped — there is nothing left to revoke.
|
|
26
|
+
*/
|
|
27
|
+
add(jti: string, expiresAt: number): Promise<void>;
|
|
28
|
+
has(jti: string): Promise<boolean>;
|
|
29
|
+
/** No-op: Redis/KeyDB evicts expired keys on its own. */
|
|
30
|
+
cleanup(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=RedisBlacklistDriver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RedisBlacklistDriver.d.ts","sourceRoot":"","sources":["../src/RedisBlacklistDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,4DAA4D;AAC5D,MAAM,WAAW,eAAe;IAC/B,GAAG,CACF,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAAC;IACpB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,oBAAoB;IACpC,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,oBAAqB,YAAW,eAAe;;gBAI/C,MAAM,EAAE,eAAe,EAAE,MAAM,GAAE,oBAAyB;IAKtE;;;OAGG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIxC,yDAAyD;IACnD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAK9B"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RedisBlacklistDriver — a persistent JWT blacklist backed by Redis / KeyDB,
|
|
3
|
+
* so logout revocation survives a process restart (unlike MemoryBlacklistDriver).
|
|
4
|
+
*
|
|
5
|
+
* Agnostic by construction: it depends on no Redis package. The caller injects
|
|
6
|
+
* any ioredis-compatible client (KeyDB, Redis, a mock…) that exposes `set` with
|
|
7
|
+
* an `"EX"` ttl and `exists`. Native key expiry does the housekeeping, so
|
|
8
|
+
* `cleanup()` is a no-op.
|
|
9
|
+
*/
|
|
10
|
+
export class RedisBlacklistDriver {
|
|
11
|
+
#client;
|
|
12
|
+
#prefix;
|
|
13
|
+
constructor(client, config = {}) {
|
|
14
|
+
this.#client = client;
|
|
15
|
+
this.#prefix = config.prefix ?? "warden:blacklist:";
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Blacklist a JTI until its natural expiry. Tokens already past `expiresAt`
|
|
19
|
+
* are skipped — there is nothing left to revoke.
|
|
20
|
+
*/
|
|
21
|
+
async add(jti, expiresAt) {
|
|
22
|
+
const ttlSeconds = Math.ceil((expiresAt - Date.now()) / 1000);
|
|
23
|
+
if (ttlSeconds <= 0) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
await this.#client.set(this.#key(jti), "1", "EX", ttlSeconds);
|
|
27
|
+
}
|
|
28
|
+
async has(jti) {
|
|
29
|
+
return (await this.#client.exists(this.#key(jti))) > 0;
|
|
30
|
+
}
|
|
31
|
+
/** No-op: Redis/KeyDB evicts expired keys on its own. */
|
|
32
|
+
async cleanup() { }
|
|
33
|
+
#key(jti) {
|
|
34
|
+
return `${this.#prefix}${jti}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=RedisBlacklistDriver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RedisBlacklistDriver.js","sourceRoot":"","sources":["../src/RedisBlacklistDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAoBH,MAAM,OAAO,oBAAoB;IACvB,OAAO,CAAkB;IACzB,OAAO,CAAS;IAEzB,YAAY,MAAuB,EAAE,SAA+B,EAAE;QACrE,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,IAAI,mBAAmB,CAAC;IACrD,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,SAAiB;QACvC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;QAC9D,IAAI,UAAU,IAAI,CAAC,EAAE,CAAC;YACrB,OAAO;QACR,CAAC;QACD,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACpB,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxD,CAAC;IAED,yDAAyD;IACzD,KAAK,CAAC,OAAO,KAAmB,CAAC;IAEjC,IAAI,CAAC,GAAW;QACf,OAAO,GAAG,IAAI,CAAC,OAAO,GAAG,GAAG,EAAE,CAAC;IAChC,CAAC;CACD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResilientBlacklistDriver — wraps a primary blacklist (e.g. KeyDB-backed
|
|
3
|
+
* `RedisBlacklistDriver`) with an in-memory fallback so a cache outage never
|
|
4
|
+
* takes the whole app down.
|
|
5
|
+
*
|
|
6
|
+
* Policy (fail-open on reads):
|
|
7
|
+
* - `add()` — if the primary throws, the revocation is recorded in the
|
|
8
|
+
* fallback so logout stays effective on this instance.
|
|
9
|
+
* - `has()` — if the primary throws, consult the fallback: a token revoked
|
|
10
|
+
* during the outage is still caught here; anything else is
|
|
11
|
+
* allowed through (fail-open). Access tokens are short-lived, so
|
|
12
|
+
* the window a missed revocation survives is bounded by the token
|
|
13
|
+
* TTL — the blacklist is defense-in-depth above expiry, not the
|
|
14
|
+
* primary boundary.
|
|
15
|
+
*
|
|
16
|
+
* Every degradation invokes `onDegrade` (defaults to a stderr warning) so the
|
|
17
|
+
* outage is observable.
|
|
18
|
+
*/
|
|
19
|
+
import { type BlacklistDriver } from "./TokenBlacklist.js";
|
|
20
|
+
export interface BlacklistDegradeEvent {
|
|
21
|
+
op: "add" | "has" | "cleanup";
|
|
22
|
+
error: unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface ResilientBlacklistConfig {
|
|
25
|
+
/** Invoked whenever the primary driver fails and the fallback is used. */
|
|
26
|
+
onDegrade?: (event: BlacklistDegradeEvent) => void;
|
|
27
|
+
}
|
|
28
|
+
export declare class ResilientBlacklistDriver implements BlacklistDriver {
|
|
29
|
+
#private;
|
|
30
|
+
constructor(primary: BlacklistDriver, fallback?: BlacklistDriver, config?: ResilientBlacklistConfig);
|
|
31
|
+
add(jti: string, expiresAt: number): Promise<void>;
|
|
32
|
+
has(jti: string): Promise<boolean>;
|
|
33
|
+
cleanup(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=ResilientBlacklistDriver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResilientBlacklistDriver.d.ts","sourceRoot":"","sources":["../src/ResilientBlacklistDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EACN,KAAK,eAAe,EAEpB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,WAAW,qBAAqB;IACrC,EAAE,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAAC;IAC9B,KAAK,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACxC,0EAA0E;IAC1E,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;CACnD;AASD,qBAAa,wBAAyB,YAAW,eAAe;;gBAM9D,OAAO,EAAE,eAAe,EACxB,QAAQ,GAAE,eAA6C,EACvD,MAAM,GAAE,wBAA6B;IAOhC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASlD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUlC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ9B"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResilientBlacklistDriver — wraps a primary blacklist (e.g. KeyDB-backed
|
|
3
|
+
* `RedisBlacklistDriver`) with an in-memory fallback so a cache outage never
|
|
4
|
+
* takes the whole app down.
|
|
5
|
+
*
|
|
6
|
+
* Policy (fail-open on reads):
|
|
7
|
+
* - `add()` — if the primary throws, the revocation is recorded in the
|
|
8
|
+
* fallback so logout stays effective on this instance.
|
|
9
|
+
* - `has()` — if the primary throws, consult the fallback: a token revoked
|
|
10
|
+
* during the outage is still caught here; anything else is
|
|
11
|
+
* allowed through (fail-open). Access tokens are short-lived, so
|
|
12
|
+
* the window a missed revocation survives is bounded by the token
|
|
13
|
+
* TTL — the blacklist is defense-in-depth above expiry, not the
|
|
14
|
+
* primary boundary.
|
|
15
|
+
*
|
|
16
|
+
* Every degradation invokes `onDegrade` (defaults to a stderr warning) so the
|
|
17
|
+
* outage is observable.
|
|
18
|
+
*/
|
|
19
|
+
import { MemoryBlacklistDriver, } from "./TokenBlacklist.js";
|
|
20
|
+
function defaultOnDegrade({ op, error }) {
|
|
21
|
+
console.warn(`[warden] blacklist primary failed on ${op}; using in-memory fallback:`, error);
|
|
22
|
+
}
|
|
23
|
+
export class ResilientBlacklistDriver {
|
|
24
|
+
#primary;
|
|
25
|
+
#fallback;
|
|
26
|
+
#onDegrade;
|
|
27
|
+
constructor(primary, fallback = new MemoryBlacklistDriver(), config = {}) {
|
|
28
|
+
this.#primary = primary;
|
|
29
|
+
this.#fallback = fallback;
|
|
30
|
+
this.#onDegrade = config.onDegrade ?? defaultOnDegrade;
|
|
31
|
+
}
|
|
32
|
+
async add(jti, expiresAt) {
|
|
33
|
+
try {
|
|
34
|
+
await this.#primary.add(jti, expiresAt);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
this.#onDegrade({ op: "add", error });
|
|
38
|
+
await this.#fallback.add(jti, expiresAt);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async has(jti) {
|
|
42
|
+
try {
|
|
43
|
+
return await this.#primary.has(jti);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
this.#onDegrade({ op: "has", error });
|
|
47
|
+
// Fail-open: honour in-outage revocations on this instance, allow rest.
|
|
48
|
+
return this.#fallback.has(jti);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async cleanup() {
|
|
52
|
+
try {
|
|
53
|
+
await this.#primary.cleanup();
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
this.#onDegrade({ op: "cleanup", error });
|
|
57
|
+
}
|
|
58
|
+
await this.#fallback.cleanup();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=ResilientBlacklistDriver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ResilientBlacklistDriver.js","sourceRoot":"","sources":["../src/ResilientBlacklistDriver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAEN,qBAAqB,GACrB,MAAM,qBAAqB,CAAC;AAY7B,SAAS,gBAAgB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAyB;IAC7D,OAAO,CAAC,IAAI,CACX,wCAAwC,EAAE,6BAA6B,EACvE,KAAK,CACL,CAAC;AACH,CAAC;AAED,MAAM,OAAO,wBAAwB;IAC3B,QAAQ,CAAkB;IAC1B,SAAS,CAAkB;IAC3B,UAAU,CAAyC;IAE5D,YACC,OAAwB,EACxB,WAA4B,IAAI,qBAAqB,EAAE,EACvD,SAAmC,EAAE;QAErC,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;QACxB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,SAAS,IAAI,gBAAgB,CAAC;IACxD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,SAAiB;QACvC,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtC,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC1C,CAAC;IACF,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACpB,IAAI,CAAC;YACJ,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;YACtC,wEAAwE;YACxE,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;IACF,CAAC;IAED,KAAK,CAAC,OAAO;QACZ,IAAI,CAAC;YACJ,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,UAAU,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IAChC,CAAC;CACD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WardenProvider.d.ts","sourceRoot":"","sources":["../src/WardenProvider.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"WardenProvider.d.ts","sourceRoot":"","sources":["../src/WardenProvider.ts"],"names":[],"mappings":"AASA,UAAU,eAAe;IACxB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,GAAG,IAAI,CAAC;IACxD,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;CACjC;AAED,UAAU,iBAAiB;IAC1B,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,gBAAgB;IAChC,SAAS,EAAE,eAAe,CAAC;IAC3B,MAAM,EAAE,iBAAiB,CAAC;CAC1B;AAED,MAAM,CAAC,OAAO,OAAO,cAAc;IACtB,SAAS,CAAC,GAAG,EAAE,gBAAgB;gBAArB,GAAG,EAAE,gBAAgB;IAE3C,QAAQ;IAiEF,IAAI;CAGV"}
|
package/dist/WardenProvider.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AuthManager } from "./AuthManager.js";
|
|
2
2
|
import { WardenError } from "./errors.js";
|
|
3
|
+
import { MfaManager } from "./mfa/MfaManager.js";
|
|
3
4
|
import { MemoryRightsStore } from "./rights/MemoryRightsStore.js";
|
|
4
5
|
import { RightsResolver } from "./rights/RightsResolver.js";
|
|
5
6
|
import { setAuth } from "./services/main.js";
|
|
@@ -53,6 +54,15 @@ export default class WardenProvider {
|
|
|
53
54
|
// `container.resolve("auth")` threw and its admin auth gate
|
|
54
55
|
// silently fell back to open-mode.
|
|
55
56
|
this.app.container.singleton("auth", () => this.app.container.resolve(AuthManager));
|
|
57
|
+
// MFA (optional): the app builds an MfaManager with its persistent stores
|
|
58
|
+
// + providers and passes it via config.mfa. Registered by class and under
|
|
59
|
+
// the "mfa" string alias (same convention as "auth") so controllers and
|
|
60
|
+
// agnostic consumers can resolve it.
|
|
61
|
+
const mfa = config.mfa?.manager;
|
|
62
|
+
if (mfa) {
|
|
63
|
+
this.app.container.singleton(MfaManager, () => mfa);
|
|
64
|
+
this.app.container.singleton("mfa", () => mfa);
|
|
65
|
+
}
|
|
56
66
|
}
|
|
57
67
|
async boot() {
|
|
58
68
|
setAuth(this.app.container.resolve(AuthManager));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WardenProvider.js","sourceRoot":"","sources":["../src/WardenProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAgB1D,MAAM,CAAC,OAAO,OAAO,cAAc;IACZ;IAAtB,YAAsB,GAAqB;QAArB,QAAG,GAAH,GAAG,CAAkB;IAAG,CAAC;IAE/C,QAAQ;QACP,4EAA4E;QAC5E,sEAAsE;QACtE,8DAA8D;QAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAe,MAAM,CAAC,CAAC;QAEzD,qEAAqE;QACrE,oEAAoE;QACpE,mEAAmE;QACnE,kEAAkE;QAClE,qEAAqE;QACrE,mBAAmB;QACnB,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC;YAClB,MAAM,IAAI,WAAW,CACpB,uBAAuB,EACvB,8JAA8J,CAC9J,CAAC;QACH,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;QAErD,uEAAuE;QACvE,yEAAyE;QACzE,uEAAuE;QACvE,uEAAuE;QACvE,sEAAsE;QACtE,iEAAiE;QACjE,oCAAoC;QACpC,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,IAAI,IAAI,iBAAiB,EAAE,CAAC;QACpE,MAAM,cAAc,GAAG,IAAI,cAAc,CAAC,WAAW,CAAC,CAAC;QACvD,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,cAAc,EAAE,GAAG,EAAE,CAAC,cAAc,CAAC,CAAC;QACnE,IAAI,WAAW,YAAY,iBAAiB,EAAE,CAAC;YAC9C,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC;QACpE,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE,GAAG,EAAE;YAC9C,MAAM,UAAU,GAAgC,EAAE,GAAG,EAAE,CAAC;YACxD,OAAO,IAAI,WAAW,CAAC;gBACtB,eAAe,EAAE,MAAM,CAAC,eAAe,IAAI,KAAK;gBAChD,UAAU;gBACV,MAAM,EAAE,cAAc;aACtB,CAAC,CAAC;QACJ,CAAC,CAAC,CAAC;QACH,oEAAoE;QACpE,mEAAmE;QACnE,4DAA4D;QAC5D,8DAA8D;QAC9D,4DAA4D;QAC5D,mCAAmC;QACnC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,CACzC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,WAAW,CAAC,CACvC,CAAC;
|
|
1
|
+
{"version":3,"file":"WardenProvider.js","sourceRoot":"","sources":["../src/WardenProvider.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* })
|
|
16
16
|
*/
|
|
17
17
|
import type { UserPayload } from "./AuthManager.js";
|
|
18
|
+
import type { MfaManager } from "./mfa/MfaManager.js";
|
|
18
19
|
import type { RightsStore } from "./rights/types.js";
|
|
19
20
|
export interface JwtConfig {
|
|
20
21
|
secret: string;
|
|
@@ -36,6 +37,14 @@ export interface WardenConfig {
|
|
|
36
37
|
rights?: {
|
|
37
38
|
store?: RightsStore;
|
|
38
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* Multi-factor authentication. Supply a configured `MfaManager` (built with
|
|
42
|
+
* your persistent stores + providers) to register it in the container as
|
|
43
|
+
* `MfaManager` / `"mfa"`. Required to use `@RequireMfa` step-up flows.
|
|
44
|
+
*/
|
|
45
|
+
mfa?: {
|
|
46
|
+
manager: MfaManager;
|
|
47
|
+
};
|
|
39
48
|
}
|
|
40
49
|
/** Typed config helper — identity function for editor inference. */
|
|
41
50
|
export declare function defineConfig(config: WardenConfig): WardenConfig;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACtD,iBAAiB,EAAE,CAClB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,KACZ,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,YAAY;IAC5B,8CAA8C;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kCAAkC;IAClC,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB;;;;;OAKG;IACH,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACtD,iBAAiB,EAAE,CAClB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,KACZ,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,YAAY;IAC5B,8CAA8C;IAC9C,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kCAAkC;IAClC,GAAG,CAAC,EAAE,SAAS,CAAC;IAChB;;;;;OAKG;IACH,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,CAAC;IACjC;;;;OAIG;IACH,GAAG,CAAC,EAAE;QAAE,OAAO,EAAE,UAAU,CAAA;KAAE,CAAC;CAC9B;AAED,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAE/D"}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAoCH,oEAAoE;AACpE,MAAM,UAAU,YAAY,CAAC,MAAoB;IAChD,OAAO,MAAM,CAAC;AACf,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,9 +20,23 @@ export { GitHubDriver } from "./firstcontact/drivers/GitHubDriver.js";
|
|
|
20
20
|
export { GoogleDriver } from "./firstcontact/drivers/GoogleDriver.js";
|
|
21
21
|
export { FirstContactManager } from "./firstcontact/FirstContactManager.js";
|
|
22
22
|
export type { FirstContactDriver, OAuthConfig, OAuthToken, OAuthUser, } from "./firstcontact/types.js";
|
|
23
|
-
export { Guard, getGuardMetadata, getPermissionMetadata, getRoleMetadata, Permission, Role, } from "./Guard.js";
|
|
23
|
+
export { Guard, getGuardMetadata, getPermissionMetadata, getRequireMfaMetadata, getRoleMetadata, Permission, RequireMfa, Role, } from "./Guard.js";
|
|
24
|
+
export type { BackupCodesConfig, BackupCodesResult, BackupCodeVerification, } from "./mfa/BackupCodesProvider.js";
|
|
25
|
+
export { BackupCodesProvider } from "./mfa/BackupCodesProvider.js";
|
|
26
|
+
export type { MfaFactor, MfaFactorKind, MfaFactorStore, MfaFactorSummary, MfaManagerConfig, } from "./mfa/MfaManager.js";
|
|
27
|
+
export { MemoryMfaFactorStore, MfaManager } from "./mfa/MfaManager.js";
|
|
28
|
+
export type { OtpChallenge, OtpChallengeStore, OtpConfig, OtpDeliveryChannel, OtpFailureReason, OtpStartResult, OtpVerification, } from "./mfa/OtpProvider.js";
|
|
29
|
+
export { MemoryOtpChallengeStore, OtpProvider } from "./mfa/OtpProvider.js";
|
|
30
|
+
export type { TotpAlgorithm, TotpConfig, TotpEnrollment, } from "./mfa/TotpProvider.js";
|
|
31
|
+
export { TotpProvider } from "./mfa/TotpProvider.js";
|
|
32
|
+
export type { AuthenticationOptionsJSON, AuthenticationResponseJSON, RegistrationOptionsJSON, RegistrationResponseJSON, StoredPasskey, WebauthnChallengeStore, WebauthnConfig, WebauthnCredentialStore, WebauthnUser, } from "./mfa/WebauthnProvider.js";
|
|
33
|
+
export { MemoryWebauthnChallengeStore, MemoryWebauthnCredentialStore, WebauthnProvider, } from "./mfa/WebauthnProvider.js";
|
|
34
|
+
export type { RedisBlacklistConfig, RedisLikeClient, } from "./RedisBlacklistDriver.js";
|
|
35
|
+
export { RedisBlacklistDriver } from "./RedisBlacklistDriver.js";
|
|
24
36
|
export type { RefreshTokenDriver, StoredRefreshToken, } from "./RefreshTokenStore.js";
|
|
25
37
|
export { generateRefreshToken, MemoryRefreshTokenDriver, } from "./RefreshTokenStore.js";
|
|
38
|
+
export type { BlacklistDegradeEvent, ResilientBlacklistConfig, } from "./ResilientBlacklistDriver.js";
|
|
39
|
+
export { ResilientBlacklistDriver } from "./ResilientBlacklistDriver.js";
|
|
26
40
|
export { MemoryRightsStore } from "./rights/MemoryRightsStore.js";
|
|
27
41
|
export { RightsResolver } from "./rights/RightsResolver.js";
|
|
28
42
|
export type { EffectivePermissions, RightsStore, Scope, } from "./rights/types.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACX,UAAU,EACV,UAAU,EACV,YAAY,EACZ,WAAW,GACX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EACN,MAAM,EACN,UAAU,EACV,iBAAiB,GACjB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,YAAY,EACX,OAAO,EACP,cAAc,EACd,kBAAkB,EAClB,cAAc,GACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAC5E,YAAY,EACX,kBAAkB,EAClB,WAAW,EACX,UAAU,EACV,SAAS,GACT,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACN,KAAK,EACL,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,IAAI,GACJ,MAAM,YAAY,CAAC;AACpB,YAAY,EACX,kBAAkB,EAClB,kBAAkB,GAClB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACN,oBAAoB,EACpB,wBAAwB,GACxB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,YAAY,EACX,oBAAoB,EACpB,WAAW,EACX,KAAK,GACL,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,YAAY,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC7E,YAAY,EACX,YAAY,EACZ,qBAAqB,GACrB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -15,8 +15,15 @@ export { WardenError } from "./errors.js";
|
|
|
15
15
|
export { GitHubDriver } from "./firstcontact/drivers/GitHubDriver.js";
|
|
16
16
|
export { GoogleDriver } from "./firstcontact/drivers/GoogleDriver.js";
|
|
17
17
|
export { FirstContactManager } from "./firstcontact/FirstContactManager.js";
|
|
18
|
-
export { Guard, getGuardMetadata, getPermissionMetadata, getRoleMetadata, Permission, Role, } from "./Guard.js";
|
|
18
|
+
export { Guard, getGuardMetadata, getPermissionMetadata, getRequireMfaMetadata, getRoleMetadata, Permission, RequireMfa, Role, } from "./Guard.js";
|
|
19
|
+
export { BackupCodesProvider } from "./mfa/BackupCodesProvider.js";
|
|
20
|
+
export { MemoryMfaFactorStore, MfaManager } from "./mfa/MfaManager.js";
|
|
21
|
+
export { MemoryOtpChallengeStore, OtpProvider } from "./mfa/OtpProvider.js";
|
|
22
|
+
export { TotpProvider } from "./mfa/TotpProvider.js";
|
|
23
|
+
export { MemoryWebauthnChallengeStore, MemoryWebauthnCredentialStore, WebauthnProvider, } from "./mfa/WebauthnProvider.js";
|
|
24
|
+
export { RedisBlacklistDriver } from "./RedisBlacklistDriver.js";
|
|
19
25
|
export { generateRefreshToken, MemoryRefreshTokenDriver, } from "./RefreshTokenStore.js";
|
|
26
|
+
export { ResilientBlacklistDriver } from "./ResilientBlacklistDriver.js";
|
|
20
27
|
export { MemoryRightsStore } from "./rights/MemoryRightsStore.js";
|
|
21
28
|
export { RightsResolver } from "./rights/RightsResolver.js";
|
|
22
29
|
export { scopeKey } from "./rights/types.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EACN,MAAM,EACN,UAAU,EACV,iBAAiB,GACjB,MAAM,yBAAyB,CAAC;AAQjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAO5E,OAAO,EACN,KAAK,EACL,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,IAAI,GACJ,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EACN,MAAM,EACN,UAAU,EACV,iBAAiB,GACjB,MAAM,yBAAyB,CAAC;AAQjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,uCAAuC,CAAC;AAO5E,OAAO,EACN,KAAK,EACL,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,UAAU,EACV,IAAI,GACJ,MAAM,YAAY,CAAC;AAMpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAQnE,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAUvE,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAM5E,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAYrD,OAAO,EACN,4BAA4B,EAC5B,6BAA6B,EAC7B,gBAAgB,GAChB,MAAM,2BAA2B,CAAC;AAKnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAKjE,OAAO,EACN,oBAAoB,EACpB,wBAAwB,GACxB,MAAM,wBAAwB,CAAC;AAKhC,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAM5D,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAK7E,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BackupCodesProvider — single-use recovery codes for when a user loses their
|
|
3
|
+
* authenticator. Codes are shown to the user exactly once at generation time;
|
|
4
|
+
* only salted hashes are persisted. Verifying a code consumes it.
|
|
5
|
+
*
|
|
6
|
+
* Codes are high-entropy random tokens (default ~50 bits), so a single salted
|
|
7
|
+
* SHA-256 is the right primitive here — not an expensive password KDF.
|
|
8
|
+
*/
|
|
9
|
+
export interface BackupCodesConfig {
|
|
10
|
+
/** How many codes to generate. Default `10`. */
|
|
11
|
+
count?: number;
|
|
12
|
+
/** Characters per code (base32, excludes the separator). Default `10`. */
|
|
13
|
+
length?: number;
|
|
14
|
+
}
|
|
15
|
+
export interface BackupCodesResult {
|
|
16
|
+
/** Plaintext codes — display ONCE, never persist. */
|
|
17
|
+
codes: string[];
|
|
18
|
+
/** Salted hashes (`salt$digest`) to persist against the user. */
|
|
19
|
+
hashes: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface BackupCodeVerification {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
/** The hash list with the consumed code removed (persist this on success). */
|
|
24
|
+
remaining: string[];
|
|
25
|
+
}
|
|
26
|
+
export declare class BackupCodesProvider {
|
|
27
|
+
#private;
|
|
28
|
+
readonly kind: "backup_codes";
|
|
29
|
+
constructor(config?: BackupCodesConfig);
|
|
30
|
+
/** Generate a fresh batch of codes plus the hashes to persist. */
|
|
31
|
+
generate(): BackupCodesResult;
|
|
32
|
+
/**
|
|
33
|
+
* Verify a user-supplied code against the stored hashes. On a match the
|
|
34
|
+
* code is consumed: `remaining` excludes it and must be persisted. Input is
|
|
35
|
+
* normalized (separators stripped, upper-cased) before comparison.
|
|
36
|
+
*/
|
|
37
|
+
verify(hashes: string[], code: string): BackupCodeVerification;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=BackupCodesProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackupCodesProvider.d.ts","sourceRoot":"","sources":["../../src/mfa/BackupCodesProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,WAAW,iBAAiB;IACjC,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IACjC,qDAAqD;IACrD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,iEAAiE;IACjE,MAAM,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACtC,EAAE,EAAE,OAAO,CAAC;IACZ,8EAA8E;IAC9E,SAAS,EAAE,MAAM,EAAE,CAAC;CACpB;AAID,qBAAa,mBAAmB;;IAC/B,QAAQ,CAAC,IAAI,EAAG,cAAc,CAAU;gBAG5B,MAAM,GAAE,iBAAsB;IAgB1C,kEAAkE;IAClE,QAAQ,IAAI,iBAAiB;IAgB7B;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,sBAAsB;CAiB9D"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BackupCodesProvider — single-use recovery codes for when a user loses their
|
|
3
|
+
* authenticator. Codes are shown to the user exactly once at generation time;
|
|
4
|
+
* only salted hashes are persisted. Verifying a code consumes it.
|
|
5
|
+
*
|
|
6
|
+
* Codes are high-entropy random tokens (default ~50 bits), so a single salted
|
|
7
|
+
* SHA-256 is the right primitive here — not an expensive password KDF.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
10
|
+
import { WardenError } from "../errors.js";
|
|
11
|
+
import { base32Encode } from "./base32.js";
|
|
12
|
+
const DEFAULTS = { count: 10, length: 10 };
|
|
13
|
+
export class BackupCodesProvider {
|
|
14
|
+
kind = "backup_codes";
|
|
15
|
+
#cfg;
|
|
16
|
+
constructor(config = {}) {
|
|
17
|
+
this.#cfg = { ...DEFAULTS, ...config };
|
|
18
|
+
if (this.#cfg.count < 1) {
|
|
19
|
+
throw new WardenError("INVALID_CONFIG", `backup code count must be >= 1, got ${this.#cfg.count}`);
|
|
20
|
+
}
|
|
21
|
+
if (this.#cfg.length < 8) {
|
|
22
|
+
throw new WardenError("INVALID_CONFIG", `backup code length must be >= 8 for adequate entropy, got ${this.#cfg.length}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** Generate a fresh batch of codes plus the hashes to persist. */
|
|
26
|
+
generate() {
|
|
27
|
+
const codes = [];
|
|
28
|
+
const hashes = [];
|
|
29
|
+
for (let i = 0; i < this.#cfg.count; i++) {
|
|
30
|
+
const raw = base32Encode(randomBytes(this.#cfg.length)).slice(0, this.#cfg.length);
|
|
31
|
+
// Split in the middle for readability: "ABCDE-FGHIJ".
|
|
32
|
+
const half = Math.floor(this.#cfg.length / 2);
|
|
33
|
+
codes.push(`${raw.slice(0, half)}-${raw.slice(half)}`);
|
|
34
|
+
hashes.push(saltedHash(raw));
|
|
35
|
+
}
|
|
36
|
+
return { codes, hashes };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Verify a user-supplied code against the stored hashes. On a match the
|
|
40
|
+
* code is consumed: `remaining` excludes it and must be persisted. Input is
|
|
41
|
+
* normalized (separators stripped, upper-cased) before comparison.
|
|
42
|
+
*/
|
|
43
|
+
verify(hashes, code) {
|
|
44
|
+
const candidate = normalize(code);
|
|
45
|
+
let matchedIndex = -1;
|
|
46
|
+
// Scan every entry (no early exit) so timing doesn't leak position.
|
|
47
|
+
for (let i = 0; i < hashes.length; i++) {
|
|
48
|
+
if (matchesStored(hashes[i], candidate)) {
|
|
49
|
+
matchedIndex = i;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (matchedIndex === -1) {
|
|
53
|
+
return { ok: false, remaining: hashes };
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
ok: true,
|
|
57
|
+
remaining: hashes.filter((_, i) => i !== matchedIndex),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function normalize(code) {
|
|
62
|
+
return code.replace(/[\s-]/g, "").toUpperCase();
|
|
63
|
+
}
|
|
64
|
+
/** Hash a normalized code under a fresh random salt → `salt$digest` (hex). */
|
|
65
|
+
function saltedHash(normalized) {
|
|
66
|
+
const salt = randomBytes(8);
|
|
67
|
+
const digest = digestWith(salt, normalized);
|
|
68
|
+
return `${salt.toString("hex")}$${digest.toString("hex")}`;
|
|
69
|
+
}
|
|
70
|
+
/** Re-hash the candidate under the stored entry's salt and compare. */
|
|
71
|
+
function matchesStored(stored, candidate) {
|
|
72
|
+
const sep = stored.indexOf("$");
|
|
73
|
+
if (sep === -1) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const salt = Buffer.from(stored.slice(0, sep), "hex");
|
|
77
|
+
const expected = Buffer.from(stored.slice(sep + 1), "hex");
|
|
78
|
+
const actual = digestWith(salt, candidate);
|
|
79
|
+
if (actual.length !== expected.length) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
return timingSafeEqual(actual, expected);
|
|
83
|
+
}
|
|
84
|
+
function digestWith(salt, normalized) {
|
|
85
|
+
return createHash("sha256").update(salt).update(normalized).digest();
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=BackupCodesProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BackupCodesProvider.js","sourceRoot":"","sources":["../../src/mfa/BackupCodesProvider.ts"],"names":[],"mappings":"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"}
|