@c9up/warden 0.1.4 → 0.1.6

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.
Files changed (88) hide show
  1. package/README.md +58 -0
  2. package/dist/Guard.d.ts +9 -0
  3. package/dist/Guard.d.ts.map +1 -1
  4. package/dist/Guard.js +16 -0
  5. package/dist/Guard.js.map +1 -1
  6. package/dist/RedisBlacklistDriver.d.ts +32 -0
  7. package/dist/RedisBlacklistDriver.d.ts.map +1 -0
  8. package/dist/RedisBlacklistDriver.js +37 -0
  9. package/dist/RedisBlacklistDriver.js.map +1 -0
  10. package/dist/ResilientBlacklistDriver.d.ts +35 -0
  11. package/dist/ResilientBlacklistDriver.d.ts.map +1 -0
  12. package/dist/ResilientBlacklistDriver.js +61 -0
  13. package/dist/ResilientBlacklistDriver.js.map +1 -0
  14. package/dist/TokenBlacklist.d.ts +6 -2
  15. package/dist/TokenBlacklist.d.ts.map +1 -1
  16. package/dist/TokenBlacklist.js +2 -2
  17. package/dist/TokenBlacklist.js.map +1 -1
  18. package/dist/WardenProvider.d.ts.map +1 -1
  19. package/dist/WardenProvider.js +47 -6
  20. package/dist/WardenProvider.js.map +1 -1
  21. package/dist/config.d.ts +65 -1
  22. package/dist/config.d.ts.map +1 -1
  23. package/dist/config.js.map +1 -1
  24. package/dist/index.d.ts +15 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +8 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/mfa/BackupCodesProvider.d.ts +39 -0
  29. package/dist/mfa/BackupCodesProvider.d.ts.map +1 -0
  30. package/dist/mfa/BackupCodesProvider.js +87 -0
  31. package/dist/mfa/BackupCodesProvider.js.map +1 -0
  32. package/dist/mfa/MfaManager.d.ts +107 -0
  33. package/dist/mfa/MfaManager.d.ts.map +1 -0
  34. package/dist/mfa/MfaManager.js +219 -0
  35. package/dist/mfa/MfaManager.js.map +1 -0
  36. package/dist/mfa/OtpProvider.d.ts +67 -0
  37. package/dist/mfa/OtpProvider.d.ts.map +1 -0
  38. package/dist/mfa/OtpProvider.js +104 -0
  39. package/dist/mfa/OtpProvider.js.map +1 -0
  40. package/dist/mfa/TotpProvider.d.ts +52 -0
  41. package/dist/mfa/TotpProvider.d.ts.map +1 -0
  42. package/dist/mfa/TotpProvider.js +103 -0
  43. package/dist/mfa/TotpProvider.js.map +1 -0
  44. package/dist/mfa/WebauthnProvider.d.ts +175 -0
  45. package/dist/mfa/WebauthnProvider.d.ts.map +1 -0
  46. package/dist/mfa/WebauthnProvider.js +239 -0
  47. package/dist/mfa/WebauthnProvider.js.map +1 -0
  48. package/dist/mfa/base32.d.ts +13 -0
  49. package/dist/mfa/base32.d.ts.map +1 -0
  50. package/dist/mfa/base32.js +52 -0
  51. package/dist/mfa/base32.js.map +1 -0
  52. package/dist/mfa/webauthn-codec.d.ts +58 -0
  53. package/dist/mfa/webauthn-codec.d.ts.map +1 -0
  54. package/dist/mfa/webauthn-codec.js +221 -0
  55. package/dist/mfa/webauthn-codec.js.map +1 -0
  56. package/dist/middleware.d.ts +31 -0
  57. package/dist/middleware.d.ts.map +1 -1
  58. package/dist/middleware.js +77 -4
  59. package/dist/middleware.js.map +1 -1
  60. package/dist/standalone.d.ts.map +1 -1
  61. package/dist/standalone.js +12 -0
  62. package/dist/standalone.js.map +1 -1
  63. package/dist/strategies/ApiKeyStrategy.d.ts.map +1 -1
  64. package/dist/strategies/ApiKeyStrategy.js +14 -1
  65. package/dist/strategies/ApiKeyStrategy.js.map +1 -1
  66. package/index.darwin-arm64.node +0 -0
  67. package/index.darwin-x64.node +0 -0
  68. package/index.linux-arm64-gnu.node +0 -0
  69. package/index.linux-x64-gnu.node +0 -0
  70. package/index.win32-x64-msvc.node +0 -0
  71. package/package.json +2 -2
  72. package/src/Guard.ts +21 -0
  73. package/src/RedisBlacklistDriver.ts +60 -0
  74. package/src/ResilientBlacklistDriver.ts +84 -0
  75. package/src/TokenBlacklist.ts +7 -3
  76. package/src/WardenProvider.ts +53 -7
  77. package/src/config.ts +60 -1
  78. package/src/index.ts +58 -0
  79. package/src/mfa/BackupCodesProvider.ts +125 -0
  80. package/src/mfa/MfaManager.ts +307 -0
  81. package/src/mfa/OtpProvider.ts +177 -0
  82. package/src/mfa/TotpProvider.ts +140 -0
  83. package/src/mfa/WebauthnProvider.ts +416 -0
  84. package/src/mfa/base32.ts +54 -0
  85. package/src/mfa/webauthn-codec.ts +264 -0
  86. package/src/middleware.ts +111 -2
  87. package/src/standalone.ts +14 -1
  88. package/src/strategies/ApiKeyStrategy.ts +14 -1
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;AAKpB,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;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"}
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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"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * MfaManager — orchestrates the stateful "enable 2FA" lifecycle for a user:
3
+ * TOTP enrollment (pending → confirmed) and single-use backup codes, over a
4
+ * pluggable factor store (in-memory default).
5
+ *
6
+ * OTP (email/SMS) and WebAuthn are NOT folded in here: OTP is ephemeral
7
+ * (challenge/response, no enrollment) and WebAuthn already persists its own
8
+ * passkeys in its credential store. Use `OtpProvider` / `WebauthnProvider`
9
+ * directly for those, alongside this manager.
10
+ */
11
+ import type { BackupCodesProvider } from "./BackupCodesProvider.js";
12
+ import type { TotpProvider } from "./TotpProvider.js";
13
+ export type MfaFactorKind = "totp" | "backup_codes";
14
+ export interface MfaFactor {
15
+ id: string;
16
+ userId: string;
17
+ kind: MfaFactorKind;
18
+ label?: string;
19
+ /** TOTP shared secret (base32), present for `totp` factors. */
20
+ secret?: string;
21
+ /** Salted hashes of the remaining backup codes, for `backup_codes` factors. */
22
+ backupHashes?: string[];
23
+ /** Epoch ms when the factor became usable; absent while pending. */
24
+ confirmedAt?: number;
25
+ createdAt: number;
26
+ }
27
+ /** Public, secret-free view of an enrolled factor. */
28
+ export interface MfaFactorSummary {
29
+ id: string;
30
+ kind: MfaFactorKind;
31
+ label?: string;
32
+ confirmed: boolean;
33
+ }
34
+ export interface MfaFactorStore {
35
+ save(factor: MfaFactor): Promise<void>;
36
+ findById(id: string): Promise<MfaFactor | null>;
37
+ findByUser(userId: string): Promise<MfaFactor[]>;
38
+ delete(id: string): Promise<void>;
39
+ }
40
+ export declare class MemoryMfaFactorStore implements MfaFactorStore {
41
+ #private;
42
+ save(factor: MfaFactor): Promise<void>;
43
+ findById(id: string): Promise<MfaFactor | null>;
44
+ findByUser(userId: string): Promise<MfaFactor[]>;
45
+ delete(id: string): Promise<void>;
46
+ }
47
+ export interface MfaRateLimitConfig {
48
+ /** Failed verifications allowed per user before lockout. Default `5`. */
49
+ maxAttempts?: number;
50
+ /** Lockout / counter window in seconds. Default `900` (15 min). */
51
+ windowSeconds?: number;
52
+ }
53
+ export interface MfaManagerConfig {
54
+ /** App/brand name shown in the authenticator (the TOTP issuer). */
55
+ issuer: string;
56
+ totp?: TotpProvider;
57
+ backupCodes?: BackupCodesProvider;
58
+ store?: MfaFactorStore;
59
+ /** Per-user brute-force protection on `verify()`. Defaults to 5 / 15 min. */
60
+ rateLimit?: MfaRateLimitConfig;
61
+ }
62
+ export declare class MfaManager {
63
+ #private;
64
+ constructor(config: MfaManagerConfig);
65
+ /**
66
+ * Begin TOTP enrollment: persist a pending factor and return the secret +
67
+ * `otpauth://` URI to show as a QR code. The factor is unusable until
68
+ * `confirmTotp()` succeeds.
69
+ */
70
+ enrollTotp(user: {
71
+ id: string;
72
+ name: string;
73
+ }, label?: string): Promise<{
74
+ factorId: string;
75
+ secret: string;
76
+ uri: string;
77
+ }>;
78
+ /** Confirm a pending TOTP factor by verifying a first code. */
79
+ confirmTotp(factorId: string, code: string): Promise<boolean>;
80
+ /** Verify a TOTP code against the user's confirmed authenticator factors. */
81
+ verifyTotp(userId: string, code: string): Promise<boolean>;
82
+ /**
83
+ * Generate (or regenerate) the user's backup codes. Returns the plaintext
84
+ * codes to display ONCE; only hashes are stored. Any prior backup-code
85
+ * factor is replaced.
86
+ */
87
+ createBackupCodes(userId: string, label?: string): Promise<string[]>;
88
+ /** Verify and consume one backup code for the user. */
89
+ verifyBackupCode(userId: string, code: string): Promise<boolean>;
90
+ /**
91
+ * Verify a code entered in a single "2FA code" field: tries the user's TOTP
92
+ * factors first, then falls back to consuming a backup code.
93
+ */
94
+ verify(userId: string, code: string): Promise<boolean>;
95
+ /**
96
+ * Whether the user is currently locked out of `verify()` after too many
97
+ * failed attempts. Use it to surface a "try again later" message.
98
+ */
99
+ isLocked(userId: string): boolean;
100
+ /** List the user's factors without exposing any secret material. */
101
+ listFactors(userId: string): Promise<MfaFactorSummary[]>;
102
+ /** Whether the user has at least one confirmed factor. */
103
+ isEnabled(userId: string): Promise<boolean>;
104
+ /** Remove a factor (e.g. the user disables their authenticator). */
105
+ disableFactor(factorId: string): Promise<void>;
106
+ }
107
+ //# sourceMappingURL=MfaManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MfaManager.d.ts","sourceRoot":"","sources":["../../src/mfa/MfaManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,219 @@
1
+ /**
2
+ * MfaManager — orchestrates the stateful "enable 2FA" lifecycle for a user:
3
+ * TOTP enrollment (pending → confirmed) and single-use backup codes, over a
4
+ * pluggable factor store (in-memory default).
5
+ *
6
+ * OTP (email/SMS) and WebAuthn are NOT folded in here: OTP is ephemeral
7
+ * (challenge/response, no enrollment) and WebAuthn already persists its own
8
+ * passkeys in its credential store. Use `OtpProvider` / `WebauthnProvider`
9
+ * directly for those, alongside this manager.
10
+ */
11
+ import { randomBytes } from "node:crypto";
12
+ import { WardenError } from "../errors.js";
13
+ export class MemoryMfaFactorStore {
14
+ #store = new Map();
15
+ async save(factor) {
16
+ this.#store.set(factor.id, factor);
17
+ }
18
+ async findById(id) {
19
+ return this.#store.get(id) ?? null;
20
+ }
21
+ async findByUser(userId) {
22
+ return [...this.#store.values()].filter((f) => f.userId === userId);
23
+ }
24
+ async delete(id) {
25
+ this.#store.delete(id);
26
+ }
27
+ }
28
+ export class MfaManager {
29
+ #issuer;
30
+ #totp;
31
+ #backupCodes;
32
+ #store;
33
+ #maxAttempts;
34
+ #windowMs;
35
+ #attempts = new Map();
36
+ constructor(config) {
37
+ if (!config?.issuer) {
38
+ throw new WardenError("INVALID_CONFIG", "MfaManager requires an issuer");
39
+ }
40
+ this.#issuer = config.issuer;
41
+ this.#totp = config.totp;
42
+ this.#backupCodes = config.backupCodes;
43
+ this.#store = config.store ?? new MemoryMfaFactorStore();
44
+ this.#maxAttempts = config.rateLimit?.maxAttempts ?? 5;
45
+ this.#windowMs = (config.rateLimit?.windowSeconds ?? 900) * 1000;
46
+ }
47
+ // ── TOTP enrollment ──────────────────────────────────────────────
48
+ /**
49
+ * Begin TOTP enrollment: persist a pending factor and return the secret +
50
+ * `otpauth://` URI to show as a QR code. The factor is unusable until
51
+ * `confirmTotp()` succeeds.
52
+ */
53
+ async enrollTotp(user, label) {
54
+ const totp = this.#requireTotp();
55
+ const { secret, uri } = totp.enroll(user.name, this.#issuer);
56
+ const factor = {
57
+ id: newId(),
58
+ userId: user.id,
59
+ kind: "totp",
60
+ label,
61
+ secret,
62
+ createdAt: Date.now(),
63
+ };
64
+ await this.#store.save(factor);
65
+ return { factorId: factor.id, secret, uri };
66
+ }
67
+ /** Confirm a pending TOTP factor by verifying a first code. */
68
+ async confirmTotp(factorId, code) {
69
+ const totp = this.#requireTotp();
70
+ const factor = await this.#store.findById(factorId);
71
+ if (!factor || factor.kind !== "totp" || !factor.secret) {
72
+ return false;
73
+ }
74
+ if (!totp.verify(factor.secret, code)) {
75
+ return false;
76
+ }
77
+ await this.#store.save({ ...factor, confirmedAt: Date.now() });
78
+ return true;
79
+ }
80
+ /** Verify a TOTP code against the user's confirmed authenticator factors. */
81
+ async verifyTotp(userId, code) {
82
+ const totp = this.#requireTotp();
83
+ const factors = await this.#store.findByUser(userId);
84
+ return factors.some((f) => f.kind === "totp" &&
85
+ f.confirmedAt !== undefined &&
86
+ f.secret !== undefined &&
87
+ totp.verify(f.secret, code));
88
+ }
89
+ // ── Backup codes ─────────────────────────────────────────────────
90
+ /**
91
+ * Generate (or regenerate) the user's backup codes. Returns the plaintext
92
+ * codes to display ONCE; only hashes are stored. Any prior backup-code
93
+ * factor is replaced.
94
+ */
95
+ async createBackupCodes(userId, label) {
96
+ const provider = this.#requireBackupCodes();
97
+ const existing = await this.#store.findByUser(userId);
98
+ for (const f of existing) {
99
+ if (f.kind === "backup_codes") {
100
+ await this.#store.delete(f.id);
101
+ }
102
+ }
103
+ const { codes, hashes } = provider.generate();
104
+ await this.#store.save({
105
+ id: newId(),
106
+ userId,
107
+ kind: "backup_codes",
108
+ label,
109
+ backupHashes: hashes,
110
+ // Backup codes are usable as soon as they are shown.
111
+ confirmedAt: Date.now(),
112
+ createdAt: Date.now(),
113
+ });
114
+ return codes;
115
+ }
116
+ /** Verify and consume one backup code for the user. */
117
+ async verifyBackupCode(userId, code) {
118
+ const provider = this.#requireBackupCodes();
119
+ const factors = await this.#store.findByUser(userId);
120
+ for (const factor of factors) {
121
+ if (factor.kind !== "backup_codes" || !factor.backupHashes) {
122
+ continue;
123
+ }
124
+ const result = provider.verify(factor.backupHashes, code);
125
+ if (result.ok) {
126
+ await this.#store.save({ ...factor, backupHashes: result.remaining });
127
+ return true;
128
+ }
129
+ }
130
+ return false;
131
+ }
132
+ // ── Unified verify + status ──────────────────────────────────────
133
+ /**
134
+ * Verify a code entered in a single "2FA code" field: tries the user's TOTP
135
+ * factors first, then falls back to consuming a backup code.
136
+ */
137
+ async verify(userId, code) {
138
+ if (this.#isLocked(userId)) {
139
+ return false;
140
+ }
141
+ let ok = false;
142
+ if (this.#totp && (await this.verifyTotp(userId, code))) {
143
+ ok = true;
144
+ }
145
+ else if (this.#backupCodes &&
146
+ (await this.verifyBackupCode(userId, code))) {
147
+ ok = true;
148
+ }
149
+ if (ok) {
150
+ this.#attempts.delete(userId);
151
+ }
152
+ else {
153
+ this.#recordFailure(userId);
154
+ }
155
+ return ok;
156
+ }
157
+ /**
158
+ * Whether the user is currently locked out of `verify()` after too many
159
+ * failed attempts. Use it to surface a "try again later" message.
160
+ */
161
+ isLocked(userId) {
162
+ return this.#isLocked(userId);
163
+ }
164
+ /** List the user's factors without exposing any secret material. */
165
+ async listFactors(userId) {
166
+ const factors = await this.#store.findByUser(userId);
167
+ return factors.map((f) => ({
168
+ id: f.id,
169
+ kind: f.kind,
170
+ label: f.label,
171
+ confirmed: f.confirmedAt !== undefined,
172
+ }));
173
+ }
174
+ /** Whether the user has at least one confirmed factor. */
175
+ async isEnabled(userId) {
176
+ const factors = await this.#store.findByUser(userId);
177
+ return factors.some((f) => f.confirmedAt !== undefined);
178
+ }
179
+ /** Remove a factor (e.g. the user disables their authenticator). */
180
+ async disableFactor(factorId) {
181
+ await this.#store.delete(factorId);
182
+ }
183
+ #isLocked(userId) {
184
+ const entry = this.#attempts.get(userId);
185
+ if (!entry) {
186
+ return false;
187
+ }
188
+ if (entry.resetAt < Date.now()) {
189
+ this.#attempts.delete(userId);
190
+ return false;
191
+ }
192
+ return entry.count >= this.#maxAttempts;
193
+ }
194
+ #recordFailure(userId) {
195
+ const now = Date.now();
196
+ let entry = this.#attempts.get(userId);
197
+ if (!entry || entry.resetAt < now) {
198
+ entry = { count: 0, resetAt: now + this.#windowMs };
199
+ this.#attempts.set(userId, entry);
200
+ }
201
+ entry.count++;
202
+ }
203
+ #requireTotp() {
204
+ if (!this.#totp) {
205
+ throw new WardenError("INVALID_CONFIG", "MfaManager has no TotpProvider configured");
206
+ }
207
+ return this.#totp;
208
+ }
209
+ #requireBackupCodes() {
210
+ if (!this.#backupCodes) {
211
+ throw new WardenError("INVALID_CONFIG", "MfaManager has no BackupCodesProvider configured");
212
+ }
213
+ return this.#backupCodes;
214
+ }
215
+ }
216
+ function newId() {
217
+ return randomBytes(16).toString("hex");
218
+ }
219
+ //# sourceMappingURL=MfaManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MfaManager.js","sourceRoot":"","sources":["../../src/mfa/MfaManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * OtpProvider — delivered one-time passcodes (email / SMS). Unlike TOTP this is
3
+ * a challenge/response flow: `start()` mints a code, persists it (hashed, with
4
+ * an expiry and an attempt budget) and hands it to a delivery channel; the user
5
+ * later submits it to `verify()`.
6
+ *
7
+ * Delivery is pluggable — the app supplies an email or SMS channel. The store
8
+ * is pluggable too, with an in-memory default mirroring the rest of warden.
9
+ */
10
+ /** Sends the code to the user. Implemented by the consuming app (email/SMS). */
11
+ export interface OtpDeliveryChannel {
12
+ send(recipient: string, code: string): Promise<void>;
13
+ }
14
+ /** A persisted, pending OTP challenge. */
15
+ export interface OtpChallenge {
16
+ id: string;
17
+ recipient: string;
18
+ /** Salted hash of the code — never the plaintext. */
19
+ hash: string;
20
+ expiresAt: number;
21
+ attempts: number;
22
+ }
23
+ /** Storage for pending challenges. */
24
+ export interface OtpChallengeStore {
25
+ save(challenge: OtpChallenge): Promise<void>;
26
+ find(id: string): Promise<OtpChallenge | null>;
27
+ delete(id: string): Promise<void>;
28
+ }
29
+ export declare class MemoryOtpChallengeStore implements OtpChallengeStore {
30
+ #private;
31
+ save(c: OtpChallenge): Promise<void>;
32
+ find(id: string): Promise<OtpChallenge | null>;
33
+ delete(id: string): Promise<void>;
34
+ }
35
+ export interface OtpConfig {
36
+ channel: OtpDeliveryChannel;
37
+ /** Where pending challenges live. Default in-memory. */
38
+ store?: OtpChallengeStore;
39
+ /** Number of digits in the code. Default `6`. */
40
+ digits?: number;
41
+ /** How long a code stays valid, in seconds. Default `300` (5 min). */
42
+ ttlSeconds?: number;
43
+ /** Max verification attempts before the challenge is burned. Default `5`. */
44
+ maxAttempts?: number;
45
+ }
46
+ export interface OtpStartResult {
47
+ challengeId: string;
48
+ expiresAt: number;
49
+ }
50
+ export type OtpFailureReason = "not_found" | "expired" | "too_many_attempts" | "mismatch";
51
+ export interface OtpVerification {
52
+ ok: boolean;
53
+ reason?: OtpFailureReason;
54
+ }
55
+ export declare class OtpProvider {
56
+ #private;
57
+ readonly kind: "otp";
58
+ constructor(config: OtpConfig);
59
+ /** Mint a code, persist the challenge, and deliver it to `recipient`. */
60
+ start(recipient: string, nowMs?: number): Promise<OtpStartResult>;
61
+ /**
62
+ * Verify a submitted code. Wrong codes consume an attempt; the challenge is
63
+ * deleted on success, on expiry, or once the attempt budget is exhausted.
64
+ */
65
+ verify(challengeId: string, code: string, nowMs?: number): Promise<OtpVerification>;
66
+ }
67
+ //# sourceMappingURL=OtpProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OtpProvider.d.ts","sourceRoot":"","sources":["../../src/mfa/OtpProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,gFAAgF;AAChF,MAAM,WAAW,kBAAkB;IAClC,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrD;AAED,0CAA0C;AAC1C,MAAM,WAAW,YAAY;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED,sCAAsC;AACtC,MAAM,WAAW,iBAAiB;IACjC,IAAI,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC/C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClC;AAED,qBAAa,uBAAwB,YAAW,iBAAiB;;IAE1D,IAAI,CAAC,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAGpC,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAG9C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGvC;AAED,MAAM,WAAW,SAAS;IACzB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,wDAAwD;IACxD,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,gBAAgB,GACzB,WAAW,GACX,SAAS,GACT,mBAAmB,GACnB,UAAU,CAAC;AAEd,MAAM,WAAW,eAAe;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,gBAAgB,CAAC;CAC1B;AAED,qBAAa,WAAW;;IACvB,QAAQ,CAAC,IAAI,EAAG,KAAK,CAAU;gBAOnB,MAAM,EAAE,SAAS;IAc7B,yEAAyE;IACnE,KAAK,CACV,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAmB,GACxB,OAAO,CAAC,cAAc,CAAC;IAe1B;;;OAGG;IACG,MAAM,CACX,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,MAAmB,GACxB,OAAO,CAAC,eAAe,CAAC;CA0B3B"}