@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.
Files changed (74) hide show
  1. package/README.md +57 -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/WardenProvider.d.ts.map +1 -1
  15. package/dist/WardenProvider.js +10 -0
  16. package/dist/WardenProvider.js.map +1 -1
  17. package/dist/config.d.ts +9 -0
  18. package/dist/config.d.ts.map +1 -1
  19. package/dist/config.js.map +1 -1
  20. package/dist/index.d.ts +15 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +8 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/mfa/BackupCodesProvider.d.ts +39 -0
  25. package/dist/mfa/BackupCodesProvider.d.ts.map +1 -0
  26. package/dist/mfa/BackupCodesProvider.js +87 -0
  27. package/dist/mfa/BackupCodesProvider.js.map +1 -0
  28. package/dist/mfa/MfaManager.d.ts +107 -0
  29. package/dist/mfa/MfaManager.d.ts.map +1 -0
  30. package/dist/mfa/MfaManager.js +219 -0
  31. package/dist/mfa/MfaManager.js.map +1 -0
  32. package/dist/mfa/OtpProvider.d.ts +67 -0
  33. package/dist/mfa/OtpProvider.d.ts.map +1 -0
  34. package/dist/mfa/OtpProvider.js +104 -0
  35. package/dist/mfa/OtpProvider.js.map +1 -0
  36. package/dist/mfa/TotpProvider.d.ts +52 -0
  37. package/dist/mfa/TotpProvider.d.ts.map +1 -0
  38. package/dist/mfa/TotpProvider.js +103 -0
  39. package/dist/mfa/TotpProvider.js.map +1 -0
  40. package/dist/mfa/WebauthnProvider.d.ts +175 -0
  41. package/dist/mfa/WebauthnProvider.d.ts.map +1 -0
  42. package/dist/mfa/WebauthnProvider.js +239 -0
  43. package/dist/mfa/WebauthnProvider.js.map +1 -0
  44. package/dist/mfa/base32.d.ts +13 -0
  45. package/dist/mfa/base32.d.ts.map +1 -0
  46. package/dist/mfa/base32.js +52 -0
  47. package/dist/mfa/base32.js.map +1 -0
  48. package/dist/mfa/webauthn-codec.d.ts +58 -0
  49. package/dist/mfa/webauthn-codec.d.ts.map +1 -0
  50. package/dist/mfa/webauthn-codec.js +221 -0
  51. package/dist/mfa/webauthn-codec.js.map +1 -0
  52. package/dist/middleware.d.ts.map +1 -1
  53. package/dist/middleware.js +13 -1
  54. package/dist/middleware.js.map +1 -1
  55. package/index.darwin-arm64.node +0 -0
  56. package/index.darwin-x64.node +0 -0
  57. package/index.linux-arm64-gnu.node +0 -0
  58. package/index.linux-x64-gnu.node +0 -0
  59. package/index.win32-x64-msvc.node +0 -0
  60. package/package.json +1 -1
  61. package/src/Guard.ts +21 -0
  62. package/src/RedisBlacklistDriver.ts +60 -0
  63. package/src/ResilientBlacklistDriver.ts +84 -0
  64. package/src/WardenProvider.ts +11 -0
  65. package/src/config.ts +7 -0
  66. package/src/index.ts +58 -0
  67. package/src/mfa/BackupCodesProvider.ts +125 -0
  68. package/src/mfa/MfaManager.ts +307 -0
  69. package/src/mfa/OtpProvider.ts +177 -0
  70. package/src/mfa/TotpProvider.ts +140 -0
  71. package/src/mfa/WebauthnProvider.ts +416 -0
  72. package/src/mfa/base32.ts +54 -0
  73. package/src/mfa/webauthn-codec.ts +264 -0
  74. package/src/middleware.ts +14 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * WebAuthn wire-format codec — the parsing and crypto primitives behind
3
+ * `WebauthnProvider`, implemented from scratch on `node:crypto` so warden
4
+ * carries no third-party dependency.
5
+ *
6
+ * Scope: the subset of CBOR / COSE / authenticator-data that FIDO2 ceremonies
7
+ * actually use, with public-key algorithms Node can import via JWK:
8
+ * - ES256 / ES384 / ES512 (ECDSA, COSE alg -7 / -35 / -36)
9
+ * - RS256 / RS384 / RS512 (RSASSA-PKCS1-v1_5, alg -257 / -258 / -259)
10
+ * - EdDSA / Ed25519 (alg -8)
11
+ *
12
+ * Attestation *statements* are intentionally not verified (see WebauthnProvider).
13
+ */
14
+ import { type KeyObject } from "node:crypto";
15
+ export type CborValue = number | string | Buffer | CborValue[] | CborMap | boolean | null;
16
+ export type CborMap = Map<number | string, CborValue>;
17
+ export declare function base64urlToBuffer(s: string): Buffer;
18
+ export declare function bufferToBase64url(b: Buffer | Uint8Array): string;
19
+ export declare function sha256(data: Buffer): Buffer;
20
+ interface Decoded {
21
+ value: CborValue;
22
+ /** Byte offset immediately after the decoded item. */
23
+ next: number;
24
+ }
25
+ /**
26
+ * Decode a single CBOR item starting at `start`. Definite-length only —
27
+ * indefinite-length items and floats are rejected (WebAuthn uses neither).
28
+ */
29
+ export declare function decodeCbor(buf: Buffer, start?: number): Decoded;
30
+ export interface AuthenticatorData {
31
+ rpIdHash: Buffer;
32
+ flags: {
33
+ up: boolean;
34
+ uv: boolean;
35
+ at: boolean;
36
+ ed: boolean;
37
+ };
38
+ signCount: number;
39
+ credentialId?: Buffer;
40
+ cosePublicKey?: CborMap;
41
+ /** Raw COSE_Key bytes, ready to persist and re-decode at sign-in time. */
42
+ cosePublicKeyBytes?: Buffer;
43
+ }
44
+ /** Parse the raw authenticator-data structure (§6.1 of the WebAuthn spec). */
45
+ export declare function parseAuthenticatorData(authData: Buffer): AuthenticatorData;
46
+ /** Convert a COSE_Key map into a Node public key plus its COSE algorithm id. */
47
+ export declare function coseToKeyObject(cose: CborMap): {
48
+ key: KeyObject;
49
+ alg: number;
50
+ };
51
+ /**
52
+ * Verify a WebAuthn assertion signature over `data` (= authenticatorData ‖
53
+ * SHA-256(clientDataJSON)). ECDSA signatures are DER-encoded, matching what
54
+ * authenticators emit.
55
+ */
56
+ export declare function verifyWebauthnSignature(alg: number, key: KeyObject, data: Buffer, signature: Buffer): boolean;
57
+ export {};
58
+ //# sourceMappingURL=webauthn-codec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webauthn-codec.d.ts","sourceRoot":"","sources":["../../src/mfa/webauthn-codec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAKN,KAAK,SAAS,EACd,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,SAAS,GAClB,MAAM,GACN,MAAM,GACN,MAAM,GACN,SAAS,EAAE,GACX,OAAO,GACP,OAAO,GACP,IAAI,CAAC;AACR,MAAM,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,GAAG,MAAM,EAAE,SAAS,CAAC,CAAC;AAEtD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAEhE;AAED,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED,UAAU,OAAO;IAChB,KAAK,EAAE,SAAS,CAAC;IACjB,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAC;CACb;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,OAAO,CAwE1D;AAED,MAAM,WAAW,iBAAiB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAC;QAAC,EAAE,EAAE,OAAO,CAAA;KAAE,CAAC;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0EAA0E;IAC1E,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,8EAA8E;AAC9E,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CA+B1E;AAmBD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG;IAC/C,GAAG,EAAE,SAAS,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;CACZ,CAuCA;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACtC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GACf,OAAO,CAKT"}
@@ -0,0 +1,221 @@
1
+ /**
2
+ * WebAuthn wire-format codec — the parsing and crypto primitives behind
3
+ * `WebauthnProvider`, implemented from scratch on `node:crypto` so warden
4
+ * carries no third-party dependency.
5
+ *
6
+ * Scope: the subset of CBOR / COSE / authenticator-data that FIDO2 ceremonies
7
+ * actually use, with public-key algorithms Node can import via JWK:
8
+ * - ES256 / ES384 / ES512 (ECDSA, COSE alg -7 / -35 / -36)
9
+ * - RS256 / RS384 / RS512 (RSASSA-PKCS1-v1_5, alg -257 / -258 / -259)
10
+ * - EdDSA / Ed25519 (alg -8)
11
+ *
12
+ * Attestation *statements* are intentionally not verified (see WebauthnProvider).
13
+ */
14
+ import { createHash, createPublicKey, verify as cryptoVerify, } from "node:crypto";
15
+ export function base64urlToBuffer(s) {
16
+ return Buffer.from(s, "base64url");
17
+ }
18
+ export function bufferToBase64url(b) {
19
+ return Buffer.from(b).toString("base64url");
20
+ }
21
+ export function sha256(data) {
22
+ return createHash("sha256").update(data).digest();
23
+ }
24
+ /**
25
+ * Decode a single CBOR item starting at `start`. Definite-length only —
26
+ * indefinite-length items and floats are rejected (WebAuthn uses neither).
27
+ */
28
+ export function decodeCbor(buf, start = 0) {
29
+ const major = buf[start] >> 5;
30
+ const info = buf[start] & 0x1f;
31
+ let len;
32
+ let p = start + 1;
33
+ if (info < 24) {
34
+ len = info;
35
+ }
36
+ else if (info === 24) {
37
+ len = buf[p];
38
+ p += 1;
39
+ }
40
+ else if (info === 25) {
41
+ len = buf.readUInt16BE(p);
42
+ p += 2;
43
+ }
44
+ else if (info === 26) {
45
+ len = buf.readUInt32BE(p);
46
+ p += 4;
47
+ }
48
+ else if (info === 27) {
49
+ len = Number(buf.readBigUInt64BE(p));
50
+ p += 8;
51
+ }
52
+ else {
53
+ throw new Error("CBOR: indefinite or reserved length is not supported");
54
+ }
55
+ switch (major) {
56
+ case 0: // unsigned int
57
+ return { value: len, next: p };
58
+ case 1: // negative int
59
+ return { value: -1 - len, next: p };
60
+ case 2: // byte string
61
+ return { value: buf.subarray(p, p + len), next: p + len };
62
+ case 3: // text string
63
+ return { value: buf.toString("utf8", p, p + len), next: p + len };
64
+ case 4: {
65
+ // array
66
+ const arr = [];
67
+ let cur = p;
68
+ for (let i = 0; i < len; i++) {
69
+ const d = decodeCbor(buf, cur);
70
+ arr.push(d.value);
71
+ cur = d.next;
72
+ }
73
+ return { value: arr, next: cur };
74
+ }
75
+ case 5: {
76
+ // map
77
+ const map = new Map();
78
+ let cur = p;
79
+ for (let i = 0; i < len; i++) {
80
+ const k = decodeCbor(buf, cur);
81
+ cur = k.next;
82
+ const v = decodeCbor(buf, cur);
83
+ cur = v.next;
84
+ if (typeof k.value !== "number" && typeof k.value !== "string") {
85
+ throw new Error("CBOR: only integer/text map keys are supported");
86
+ }
87
+ map.set(k.value, v.value);
88
+ }
89
+ return { value: map, next: cur };
90
+ }
91
+ case 6: {
92
+ // tag — decode and surface the tagged content
93
+ const d = decodeCbor(buf, p);
94
+ return { value: d.value, next: d.next };
95
+ }
96
+ case 7:
97
+ if (info === 20)
98
+ return { value: false, next: p };
99
+ if (info === 21)
100
+ return { value: true, next: p };
101
+ if (info === 22)
102
+ return { value: null, next: p };
103
+ throw new Error("CBOR: simple/float values are not supported");
104
+ default:
105
+ throw new Error(`CBOR: unknown major type ${major}`);
106
+ }
107
+ }
108
+ /** Parse the raw authenticator-data structure (§6.1 of the WebAuthn spec). */
109
+ export function parseAuthenticatorData(authData) {
110
+ if (authData.length < 37) {
111
+ throw new Error("authenticatorData is too short");
112
+ }
113
+ const flagsByte = authData[32];
114
+ const flags = {
115
+ up: (flagsByte & 0x01) !== 0,
116
+ uv: (flagsByte & 0x04) !== 0,
117
+ at: (flagsByte & 0x40) !== 0,
118
+ ed: (flagsByte & 0x80) !== 0,
119
+ };
120
+ const result = {
121
+ rpIdHash: authData.subarray(0, 32),
122
+ flags,
123
+ signCount: authData.readUInt32BE(33),
124
+ };
125
+ if (flags.at) {
126
+ if (authData.length < 55) {
127
+ throw new Error("attested credential data is truncated");
128
+ }
129
+ const credIdLen = authData.readUInt16BE(53);
130
+ result.credentialId = authData.subarray(55, 55 + credIdLen);
131
+ const coseStart = 55 + credIdLen;
132
+ const decoded = decodeCbor(authData, coseStart);
133
+ if (!(decoded.value instanceof Map)) {
134
+ throw new Error("COSE public key is not a CBOR map");
135
+ }
136
+ result.cosePublicKey = decoded.value;
137
+ result.cosePublicKeyBytes = authData.subarray(coseStart, decoded.next);
138
+ }
139
+ return result;
140
+ }
141
+ // COSE algorithm id → the hash Node should use ( `null` = pre-hashed / Ed25519 ).
142
+ const ALG_HASH = {
143
+ [-7]: "sha256",
144
+ [-35]: "sha384",
145
+ [-36]: "sha512",
146
+ [-257]: "sha256",
147
+ [-258]: "sha384",
148
+ [-259]: "sha512",
149
+ [-8]: null,
150
+ };
151
+ const EC_CURVES = {
152
+ 1: "P-256",
153
+ 2: "P-384",
154
+ 3: "P-521",
155
+ };
156
+ /** Convert a COSE_Key map into a Node public key plus its COSE algorithm id. */
157
+ export function coseToKeyObject(cose) {
158
+ const kty = expectNumber(cose.get(1), "COSE kty");
159
+ const alg = expectNumber(cose.get(3), "COSE alg");
160
+ let jwk;
161
+ if (kty === 2) {
162
+ // EC2
163
+ const curve = EC_CURVES[expectNumber(cose.get(-1), "EC curve")];
164
+ if (!curve) {
165
+ throw new Error("unsupported COSE EC curve");
166
+ }
167
+ jwk = {
168
+ kty: "EC",
169
+ crv: curve,
170
+ x: bufferToBase64url(expectBytes(cose.get(-2), "EC x")),
171
+ y: bufferToBase64url(expectBytes(cose.get(-3), "EC y")),
172
+ };
173
+ }
174
+ else if (kty === 3) {
175
+ // RSA
176
+ jwk = {
177
+ kty: "RSA",
178
+ n: bufferToBase64url(expectBytes(cose.get(-1), "RSA n")),
179
+ e: bufferToBase64url(expectBytes(cose.get(-2), "RSA e")),
180
+ };
181
+ }
182
+ else if (kty === 1) {
183
+ // OKP / Ed25519
184
+ if (expectNumber(cose.get(-1), "OKP curve") !== 6) {
185
+ throw new Error("unsupported COSE OKP curve (only Ed25519)");
186
+ }
187
+ jwk = {
188
+ kty: "OKP",
189
+ crv: "Ed25519",
190
+ x: bufferToBase64url(expectBytes(cose.get(-2), "OKP x")),
191
+ };
192
+ }
193
+ else {
194
+ throw new Error(`unsupported COSE key type ${kty}`);
195
+ }
196
+ return { key: createPublicKey({ key: jwk, format: "jwk" }), alg };
197
+ }
198
+ /**
199
+ * Verify a WebAuthn assertion signature over `data` (= authenticatorData ‖
200
+ * SHA-256(clientDataJSON)). ECDSA signatures are DER-encoded, matching what
201
+ * authenticators emit.
202
+ */
203
+ export function verifyWebauthnSignature(alg, key, data, signature) {
204
+ if (!(alg in ALG_HASH)) {
205
+ throw new Error(`unsupported COSE algorithm ${alg}`);
206
+ }
207
+ return cryptoVerify(ALG_HASH[alg], data, key, signature);
208
+ }
209
+ function expectNumber(v, what) {
210
+ if (typeof v !== "number") {
211
+ throw new Error(`${what}: expected an integer`);
212
+ }
213
+ return v;
214
+ }
215
+ function expectBytes(v, what) {
216
+ if (!Buffer.isBuffer(v)) {
217
+ throw new Error(`${what}: expected a byte string`);
218
+ }
219
+ return v;
220
+ }
221
+ //# sourceMappingURL=webauthn-codec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webauthn-codec.js","sourceRoot":"","sources":["../../src/mfa/webauthn-codec.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAEN,KAAK,UAAU,EAGf,MAAM,kBAAkB,CAAC;AAM1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAcpE,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE;QACR,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,CAAC;IACF,QAAQ,EAAE;QACT,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/B,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;KAC9B,CAAC;IACF,SAAS,EAAE;QACV,OAAO,EAAE,CACR,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,KAC/D,OAAO,CAAC;KACb,CAAC;IACF,4DAA4D;IAC5D,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,mDAAmD;IACnD,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,+CAA+C;IAC/C,KAAK,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;KACzB,CAAC;CACF;AAED,KAAK,UAAU,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE7C;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,iBAoG1E"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAEN,KAAK,UAAU,EAGf,MAAM,kBAAkB,CAAC;AAO1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAcpE,MAAM,WAAW,aAAa;IAC7B,OAAO,EAAE;QACR,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAChC,CAAC;IACF,QAAQ,EAAE;QACT,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/B,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;KAC9B,CAAC;IACF,SAAS,EAAE;QACV,OAAO,EAAE,CACR,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK,OAAO,CAAC,KAC/D,OAAO,CAAC;KACb,CAAC;IACF,4DAA4D;IAC5D,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,mDAAmD;IACnD,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,+CAA+C;IAC/C,KAAK,CAAC,EAAE;QACP,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;KACzB,CAAC;CACF;AAED,KAAK,UAAU,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAE7C;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,aAAa,EAAE,IAAI,EAAE,UAAU,iBAiH1E"}
@@ -16,7 +16,7 @@
16
16
  * }
17
17
  */
18
18
  import { AuthManager, sanitizePayload, } from "./AuthManager.js";
19
- import { getGuardMetadata, getPermissionMetadata, getRoleMetadata, } from "./Guard.js";
19
+ import { getGuardMetadata, getPermissionMetadata, getRequireMfaMetadata, getRoleMetadata, } from "./Guard.js";
20
20
  function hasVerifyWithContext(strategy) {
21
21
  return (typeof strategy.verifyWithContext === "function");
22
22
  }
@@ -107,6 +107,18 @@ export async function wardenMiddleware(ctx, next) {
107
107
  ctx.response.json({ error: { code: "FORBIDDEN", message: denied } });
108
108
  return;
109
109
  }
110
+ // @RequireMfa gate: the user must carry a truthy `mfa` claim, set by the
111
+ // app's step-up flow once MfaManager.verify() succeeds.
112
+ if (getRequireMfaMetadata(controller, action) && user.mfa !== true) {
113
+ ctx.response.status(403);
114
+ ctx.response.json({
115
+ error: {
116
+ code: "MFA_REQUIRED",
117
+ message: "Multi-factor authentication is required for this action.",
118
+ },
119
+ });
120
+ return;
121
+ }
110
122
  }
111
123
  // Auth successful — attach to context and continue.
112
124
  ctx.auth = result;
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@c9up/warden",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Warden — Authentication for the Ream framework",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/Guard.ts CHANGED
@@ -13,6 +13,7 @@ import "reflect-metadata";
13
13
  const GUARD_KEY = Symbol.for("warden:guard");
14
14
  const PERMISSION_KEY = Symbol.for("warden:permission");
15
15
  const ROLE_KEY = Symbol.for("warden:role");
16
+ const MFA_KEY = Symbol.for("warden:mfa");
16
17
 
17
18
  /**
18
19
  * @Guard('jwt') — require authentication via the named strategy.
@@ -66,3 +67,23 @@ export function getRoleMetadata(
66
67
  ): string[] {
67
68
  return Reflect.getOwnMetadata(ROLE_KEY, target, propertyKey) ?? [];
68
69
  }
70
+
71
+ /**
72
+ * @RequireMfa() — require the authenticated user to have completed multi-factor
73
+ * authentication. The auth middleware rejects with 403 `MFA_REQUIRED` when the
74
+ * user payload lacks a truthy `mfa` claim (set by your MFA step-up flow once
75
+ * `MfaManager.verify()` succeeds).
76
+ */
77
+ export function RequireMfa(): MethodDecorator {
78
+ return (target, propertyKey) => {
79
+ Reflect.defineMetadata(MFA_KEY, true, target, propertyKey);
80
+ };
81
+ }
82
+
83
+ /** Whether the route requires completed MFA. */
84
+ export function getRequireMfaMetadata(
85
+ target: object,
86
+ propertyKey: string | symbol,
87
+ ): boolean {
88
+ return Reflect.getOwnMetadata(MFA_KEY, target, propertyKey) ?? false;
89
+ }
@@ -0,0 +1,60 @@
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
+
11
+ import type { BlacklistDriver } from "./TokenBlacklist.js";
12
+
13
+ /** The minimal ioredis-shaped surface this driver needs. */
14
+ export interface RedisLikeClient {
15
+ set(
16
+ key: string,
17
+ value: string,
18
+ mode: "EX",
19
+ ttlSeconds: number,
20
+ ): Promise<unknown>;
21
+ exists(key: string): Promise<number>;
22
+ }
23
+
24
+ export interface RedisBlacklistConfig {
25
+ /** Key prefix for blacklist entries. Default `warden:blacklist:`. */
26
+ prefix?: string;
27
+ }
28
+
29
+ export class RedisBlacklistDriver implements BlacklistDriver {
30
+ readonly #client: RedisLikeClient;
31
+ readonly #prefix: string;
32
+
33
+ constructor(client: RedisLikeClient, config: RedisBlacklistConfig = {}) {
34
+ this.#client = client;
35
+ this.#prefix = config.prefix ?? "warden:blacklist:";
36
+ }
37
+
38
+ /**
39
+ * Blacklist a JTI until its natural expiry. Tokens already past `expiresAt`
40
+ * are skipped — there is nothing left to revoke.
41
+ */
42
+ async add(jti: string, expiresAt: number): Promise<void> {
43
+ const ttlSeconds = Math.ceil((expiresAt - Date.now()) / 1000);
44
+ if (ttlSeconds <= 0) {
45
+ return;
46
+ }
47
+ await this.#client.set(this.#key(jti), "1", "EX", ttlSeconds);
48
+ }
49
+
50
+ async has(jti: string): Promise<boolean> {
51
+ return (await this.#client.exists(this.#key(jti))) > 0;
52
+ }
53
+
54
+ /** No-op: Redis/KeyDB evicts expired keys on its own. */
55
+ async cleanup(): Promise<void> {}
56
+
57
+ #key(jti: string): string {
58
+ return `${this.#prefix}${jti}`;
59
+ }
60
+ }
@@ -0,0 +1,84 @@
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
+
20
+ import {
21
+ type BlacklistDriver,
22
+ MemoryBlacklistDriver,
23
+ } from "./TokenBlacklist.js";
24
+
25
+ export interface BlacklistDegradeEvent {
26
+ op: "add" | "has" | "cleanup";
27
+ error: unknown;
28
+ }
29
+
30
+ export interface ResilientBlacklistConfig {
31
+ /** Invoked whenever the primary driver fails and the fallback is used. */
32
+ onDegrade?: (event: BlacklistDegradeEvent) => void;
33
+ }
34
+
35
+ function defaultOnDegrade({ op, error }: BlacklistDegradeEvent): void {
36
+ console.warn(
37
+ `[warden] blacklist primary failed on ${op}; using in-memory fallback:`,
38
+ error,
39
+ );
40
+ }
41
+
42
+ export class ResilientBlacklistDriver implements BlacklistDriver {
43
+ readonly #primary: BlacklistDriver;
44
+ readonly #fallback: BlacklistDriver;
45
+ readonly #onDegrade: (event: BlacklistDegradeEvent) => void;
46
+
47
+ constructor(
48
+ primary: BlacklistDriver,
49
+ fallback: BlacklistDriver = new MemoryBlacklistDriver(),
50
+ config: ResilientBlacklistConfig = {},
51
+ ) {
52
+ this.#primary = primary;
53
+ this.#fallback = fallback;
54
+ this.#onDegrade = config.onDegrade ?? defaultOnDegrade;
55
+ }
56
+
57
+ async add(jti: string, expiresAt: number): Promise<void> {
58
+ try {
59
+ await this.#primary.add(jti, expiresAt);
60
+ } catch (error) {
61
+ this.#onDegrade({ op: "add", error });
62
+ await this.#fallback.add(jti, expiresAt);
63
+ }
64
+ }
65
+
66
+ async has(jti: string): Promise<boolean> {
67
+ try {
68
+ return await this.#primary.has(jti);
69
+ } catch (error) {
70
+ this.#onDegrade({ op: "has", error });
71
+ // Fail-open: honour in-outage revocations on this instance, allow rest.
72
+ return this.#fallback.has(jti);
73
+ }
74
+ }
75
+
76
+ async cleanup(): Promise<void> {
77
+ try {
78
+ await this.#primary.cleanup();
79
+ } catch (error) {
80
+ this.#onDegrade({ op: "cleanup", error });
81
+ }
82
+ await this.#fallback.cleanup();
83
+ }
84
+ }
@@ -1,6 +1,7 @@
1
1
  import { AuthManager } from "./AuthManager.js";
2
2
  import type { WardenConfig } from "./config.js";
3
3
  import { WardenError } from "./errors.js";
4
+ import { MfaManager } from "./mfa/MfaManager.js";
4
5
  import { MemoryRightsStore } from "./rights/MemoryRightsStore.js";
5
6
  import { RightsResolver } from "./rights/RightsResolver.js";
6
7
  import { setAuth } from "./services/main.js";
@@ -76,6 +77,16 @@ export default class WardenProvider {
76
77
  this.app.container.singleton("auth", () =>
77
78
  this.app.container.resolve(AuthManager),
78
79
  );
80
+
81
+ // MFA (optional): the app builds an MfaManager with its persistent stores
82
+ // + providers and passes it via config.mfa. Registered by class and under
83
+ // the "mfa" string alias (same convention as "auth") so controllers and
84
+ // agnostic consumers can resolve it.
85
+ const mfa = config.mfa?.manager;
86
+ if (mfa) {
87
+ this.app.container.singleton(MfaManager, () => mfa);
88
+ this.app.container.singleton("mfa", () => mfa);
89
+ }
79
90
  }
80
91
 
81
92
  async boot() {
package/src/config.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import type { UserPayload } from "./AuthManager.js";
19
+ import type { MfaManager } from "./mfa/MfaManager.js";
19
20
  import type { RightsStore } from "./rights/types.js";
20
21
 
21
22
  export interface JwtConfig {
@@ -40,6 +41,12 @@ export interface WardenConfig {
40
41
  * in-memory shipped).
41
42
  */
42
43
  rights?: { store?: RightsStore };
44
+ /**
45
+ * Multi-factor authentication. Supply a configured `MfaManager` (built with
46
+ * your persistent stores + providers) to register it in the container as
47
+ * `MfaManager` / `"mfa"`. Required to use `@RequireMfa` step-up flows.
48
+ */
49
+ mfa?: { manager: MfaManager };
43
50
  }
44
51
 
45
52
  /** Typed config helper — identity function for editor inference. */