@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
@@ -0,0 +1,104 @@
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
+ import { createHash, randomBytes, randomInt, timingSafeEqual, } from "node:crypto";
11
+ import { WardenError } from "../errors.js";
12
+ export class MemoryOtpChallengeStore {
13
+ #store = new Map();
14
+ async save(c) {
15
+ this.#store.set(c.id, c);
16
+ }
17
+ async find(id) {
18
+ return this.#store.get(id) ?? null;
19
+ }
20
+ async delete(id) {
21
+ this.#store.delete(id);
22
+ }
23
+ }
24
+ export class OtpProvider {
25
+ kind = "otp";
26
+ #channel;
27
+ #store;
28
+ #digits;
29
+ #ttlMs;
30
+ #maxAttempts;
31
+ constructor(config) {
32
+ if (!config?.channel) {
33
+ throw new WardenError("INVALID_CONFIG", "OtpProvider requires a delivery channel (email/SMS)");
34
+ }
35
+ this.#channel = config.channel;
36
+ this.#store = config.store ?? new MemoryOtpChallengeStore();
37
+ this.#digits = config.digits ?? 6;
38
+ this.#ttlMs = (config.ttlSeconds ?? 300) * 1000;
39
+ this.#maxAttempts = config.maxAttempts ?? 5;
40
+ }
41
+ /** Mint a code, persist the challenge, and deliver it to `recipient`. */
42
+ async start(recipient, nowMs = Date.now()) {
43
+ const code = this.#randomCode();
44
+ const id = randomBytes(16).toString("hex");
45
+ const expiresAt = nowMs + this.#ttlMs;
46
+ await this.#store.save({
47
+ id,
48
+ recipient,
49
+ hash: saltedHash(code),
50
+ expiresAt,
51
+ attempts: 0,
52
+ });
53
+ await this.#channel.send(recipient, code);
54
+ return { challengeId: id, expiresAt };
55
+ }
56
+ /**
57
+ * Verify a submitted code. Wrong codes consume an attempt; the challenge is
58
+ * deleted on success, on expiry, or once the attempt budget is exhausted.
59
+ */
60
+ async verify(challengeId, code, nowMs = Date.now()) {
61
+ const challenge = await this.#store.find(challengeId);
62
+ if (!challenge) {
63
+ return { ok: false, reason: "not_found" };
64
+ }
65
+ if (nowMs > challenge.expiresAt) {
66
+ await this.#store.delete(challengeId);
67
+ return { ok: false, reason: "expired" };
68
+ }
69
+ if (matchesStored(challenge.hash, code.replace(/\s/g, ""))) {
70
+ await this.#store.delete(challengeId);
71
+ return { ok: true };
72
+ }
73
+ const attempts = challenge.attempts + 1;
74
+ if (attempts >= this.#maxAttempts) {
75
+ await this.#store.delete(challengeId);
76
+ return { ok: false, reason: "too_many_attempts" };
77
+ }
78
+ await this.#store.save({ ...challenge, attempts });
79
+ return { ok: false, reason: "mismatch" };
80
+ }
81
+ #randomCode() {
82
+ const max = 10 ** this.#digits;
83
+ return randomInt(0, max).toString().padStart(this.#digits, "0");
84
+ }
85
+ }
86
+ function saltedHash(code) {
87
+ const salt = randomBytes(8);
88
+ const digest = createHash("sha256").update(salt).update(code).digest();
89
+ return `${salt.toString("hex")}$${digest.toString("hex")}`;
90
+ }
91
+ function matchesStored(stored, candidate) {
92
+ const sep = stored.indexOf("$");
93
+ if (sep === -1) {
94
+ return false;
95
+ }
96
+ const salt = Buffer.from(stored.slice(0, sep), "hex");
97
+ const expected = Buffer.from(stored.slice(sep + 1), "hex");
98
+ const actual = createHash("sha256").update(salt).update(candidate).digest();
99
+ if (actual.length !== expected.length) {
100
+ return false;
101
+ }
102
+ return timingSafeEqual(actual, expected);
103
+ }
104
+ //# sourceMappingURL=OtpProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OtpProvider.js","sourceRoot":"","sources":["../../src/mfa/OtpProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * TotpProvider — RFC 6238 time-based one-time passwords (HOTP/RFC 4226 under
3
+ * the hood). Pure TypeScript on Node's `crypto.createHmac` — no native binding
4
+ * and no third-party dependency: a TOTP is a single HMAC, not CPU-bound work.
5
+ *
6
+ * Compatible with Google Authenticator / 1Password / Authy by default
7
+ * (SHA1, 6 digits, 30s period).
8
+ */
9
+ export type TotpAlgorithm = "SHA1" | "SHA256" | "SHA512";
10
+ export interface TotpConfig {
11
+ /** HMAC algorithm. Default `SHA1` (authenticator-app compatible). */
12
+ algorithm?: TotpAlgorithm;
13
+ /** Number of digits in the generated code. Default `6`. */
14
+ digits?: number;
15
+ /** Time step in seconds. Default `30`. */
16
+ period?: number;
17
+ /**
18
+ * How many time steps before/after `now` are accepted on `verify`, to
19
+ * tolerate clock skew. Default `1` (i.e. ±30s with the default period).
20
+ */
21
+ window?: number;
22
+ }
23
+ export interface TotpEnrollment {
24
+ /** Base32 secret to persist against the user. */
25
+ secret: string;
26
+ /** `otpauth://` URI to render as a QR code during enrollment. */
27
+ uri: string;
28
+ }
29
+ export declare class TotpProvider {
30
+ #private;
31
+ readonly kind: "totp";
32
+ constructor(config?: TotpConfig);
33
+ /**
34
+ * Generate a fresh secret and the `otpauth://` provisioning URI. Persist
35
+ * `secret`; render `uri` as a QR code for the user to scan.
36
+ *
37
+ * @param account Identifies the account in the authenticator (usually the
38
+ * user's email or username).
39
+ * @param issuer Your app/brand name, shown as the entry label.
40
+ */
41
+ enroll(account: string, issuer: string): TotpEnrollment;
42
+ /** Build the `otpauth://totp/...` provisioning URI for an existing secret. */
43
+ uri(secret: string, account: string, issuer: string): string;
44
+ /** Generate the code for a given secret at a given time (ms since epoch). */
45
+ generate(secret: string, atMs?: number): string;
46
+ /**
47
+ * Verify a user-supplied code against the secret, accepting codes from the
48
+ * surrounding `window` time steps to tolerate clock skew. Constant-time.
49
+ */
50
+ verify(secret: string, code: string, atMs?: number): boolean;
51
+ }
52
+ //# sourceMappingURL=TotpProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TotpProvider.d.ts","sourceRoot":"","sources":["../../src/mfa/TotpProvider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEzD,MAAM,WAAW,UAAU;IAC1B,qEAAqE;IACrE,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC9B,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;CACZ;AASD,qBAAa,YAAY;;IACxB,QAAQ,CAAC,IAAI,EAAG,MAAM,CAAU;gBAGpB,MAAM,GAAE,UAAe;IAUnC;;;;;;;OAOG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,cAAc;IAKvD,8EAA8E;IAC9E,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAY5D,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,MAAmB,GAAG,MAAM;IAK3D;;;OAGG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,MAAmB,GAAG,OAAO;CAoCxE"}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * TotpProvider — RFC 6238 time-based one-time passwords (HOTP/RFC 4226 under
3
+ * the hood). Pure TypeScript on Node's `crypto.createHmac` — no native binding
4
+ * and no third-party dependency: a TOTP is a single HMAC, not CPU-bound work.
5
+ *
6
+ * Compatible with Google Authenticator / 1Password / Authy by default
7
+ * (SHA1, 6 digits, 30s period).
8
+ */
9
+ import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
10
+ import { WardenError } from "../errors.js";
11
+ import { base32Decode, base32Encode } from "./base32.js";
12
+ const DEFAULTS = {
13
+ algorithm: "SHA1",
14
+ digits: 6,
15
+ period: 30,
16
+ window: 1,
17
+ };
18
+ export class TotpProvider {
19
+ kind = "totp";
20
+ #cfg;
21
+ constructor(config = {}) {
22
+ this.#cfg = { ...DEFAULTS, ...config };
23
+ if (this.#cfg.digits < 6 || this.#cfg.digits > 8) {
24
+ throw new WardenError("INVALID_CONFIG", `TOTP digits must be 6-8, got ${this.#cfg.digits}`);
25
+ }
26
+ }
27
+ /**
28
+ * Generate a fresh secret and the `otpauth://` provisioning URI. Persist
29
+ * `secret`; render `uri` as a QR code for the user to scan.
30
+ *
31
+ * @param account Identifies the account in the authenticator (usually the
32
+ * user's email or username).
33
+ * @param issuer Your app/brand name, shown as the entry label.
34
+ */
35
+ enroll(account, issuer) {
36
+ const secret = base32Encode(randomBytes(20));
37
+ return { secret, uri: this.uri(secret, account, issuer) };
38
+ }
39
+ /** Build the `otpauth://totp/...` provisioning URI for an existing secret. */
40
+ uri(secret, account, issuer) {
41
+ const label = encodeURIComponent(`${issuer}:${account}`);
42
+ const params = new URLSearchParams({
43
+ secret,
44
+ issuer,
45
+ algorithm: this.#cfg.algorithm,
46
+ digits: String(this.#cfg.digits),
47
+ period: String(this.#cfg.period),
48
+ });
49
+ return `otpauth://totp/${label}?${params.toString()}`;
50
+ }
51
+ /** Generate the code for a given secret at a given time (ms since epoch). */
52
+ generate(secret, atMs = Date.now()) {
53
+ const counter = Math.floor(atMs / 1000 / this.#cfg.period);
54
+ return this.#hotp(base32Decode(secret), counter);
55
+ }
56
+ /**
57
+ * Verify a user-supplied code against the secret, accepting codes from the
58
+ * surrounding `window` time steps to tolerate clock skew. Constant-time.
59
+ */
60
+ verify(secret, code, atMs = Date.now()) {
61
+ const normalized = code.replace(/\s/g, "");
62
+ if (normalized.length !== this.#cfg.digits) {
63
+ return false;
64
+ }
65
+ const key = base32Decode(secret);
66
+ const current = Math.floor(atMs / 1000 / this.#cfg.period);
67
+ for (let offset = -this.#cfg.window; offset <= this.#cfg.window; offset++) {
68
+ if (constantTimeEqual(this.#hotp(key, current + offset), normalized)) {
69
+ return true;
70
+ }
71
+ }
72
+ return false;
73
+ }
74
+ /** RFC 4226 HOTP: HMAC over the 8-byte counter, dynamically truncated. */
75
+ #hotp(key, counter) {
76
+ const buf = Buffer.alloc(8);
77
+ // 64-bit big-endian counter. Codes stay valid well past year 2^53,
78
+ // so splitting hi/lo at 2^32 is safe and avoids BigInt overhead.
79
+ buf.writeUInt32BE(Math.floor(counter / 2 ** 32), 0);
80
+ buf.writeUInt32BE(counter >>> 0, 4);
81
+ const hmac = createHmac(this.#cfg.algorithm.toLowerCase(), Buffer.from(key))
82
+ .update(buf)
83
+ .digest();
84
+ const off = hmac[hmac.length - 1] & 0xf;
85
+ const bin = ((hmac[off] & 0x7f) << 24) |
86
+ ((hmac[off + 1] & 0xff) << 16) |
87
+ ((hmac[off + 2] & 0xff) << 8) |
88
+ (hmac[off + 3] & 0xff);
89
+ return (bin % 10 ** this.#cfg.digits)
90
+ .toString()
91
+ .padStart(this.#cfg.digits, "0");
92
+ }
93
+ }
94
+ /** Length-safe constant-time string compare (avoids early-exit timing leaks). */
95
+ function constantTimeEqual(a, b) {
96
+ const ba = Buffer.from(a);
97
+ const bb = Buffer.from(b);
98
+ if (ba.length !== bb.length) {
99
+ return false;
100
+ }
101
+ return timingSafeEqual(ba, bb);
102
+ }
103
+ //# sourceMappingURL=TotpProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TotpProvider.js","sourceRoot":"","sources":["../../src/mfa/TotpProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,175 @@
1
+ /**
2
+ * WebauthnProvider — passkeys / FIDO2 via WebAuthn, implemented in-house on
3
+ * `node:crypto` (see `webauthn-codec.ts`). No third-party dependency: warden
4
+ * keeps its zero-runtime-dependency footprint.
5
+ *
6
+ * The browser side produces the standard `PublicKeyCredential` JSON; this
7
+ * provider builds the ceremony options and verifies the responses server-side.
8
+ *
9
+ * Attestation statements are NOT verified — this is a deliberate
10
+ * trust-on-registration posture (request `attestation: "none"` on the client),
11
+ * which is what the vast majority of relying parties use for 2FA / passkeys.
12
+ * Full attestation (packed/tpm/apple/MDS cert-chain validation) can be layered
13
+ * on later without changing this API.
14
+ *
15
+ * Both the per-ceremony challenge and the persisted passkeys go through
16
+ * pluggable stores (in-memory defaults), mirroring the rest of warden.
17
+ */
18
+ /** Browser → server payload from `navigator.credentials.create()`. */
19
+ export interface RegistrationResponseJSON {
20
+ id: string;
21
+ rawId: string;
22
+ type: "public-key";
23
+ response: {
24
+ clientDataJSON: string;
25
+ attestationObject: string;
26
+ transports?: string[];
27
+ };
28
+ }
29
+ /** Browser → server payload from `navigator.credentials.get()`. */
30
+ export interface AuthenticationResponseJSON {
31
+ id: string;
32
+ rawId: string;
33
+ type: "public-key";
34
+ response: {
35
+ clientDataJSON: string;
36
+ authenticatorData: string;
37
+ signature: string;
38
+ userHandle?: string;
39
+ };
40
+ }
41
+ export interface RegistrationOptionsJSON {
42
+ challenge: string;
43
+ rp: {
44
+ name: string;
45
+ id: string;
46
+ };
47
+ user: {
48
+ id: string;
49
+ name: string;
50
+ displayName: string;
51
+ };
52
+ pubKeyCredParams: Array<{
53
+ type: "public-key";
54
+ alg: number;
55
+ }>;
56
+ timeout: number;
57
+ attestation: "none";
58
+ excludeCredentials: Array<{
59
+ id: string;
60
+ type: "public-key";
61
+ transports?: string[];
62
+ }>;
63
+ authenticatorSelection: {
64
+ residentKey: string;
65
+ userVerification: string;
66
+ };
67
+ }
68
+ export interface AuthenticationOptionsJSON {
69
+ challenge: string;
70
+ timeout: number;
71
+ rpId: string;
72
+ userVerification: string;
73
+ allowCredentials?: Array<{
74
+ id: string;
75
+ type: "public-key";
76
+ transports?: string[];
77
+ }>;
78
+ }
79
+ /** Transient per-ceremony challenge storage. */
80
+ export interface WebauthnChallengeStore {
81
+ save(state: string, challenge: string): Promise<void>;
82
+ /** Return the challenge for `state` and remove it (single-use). */
83
+ take(state: string): Promise<string | null>;
84
+ }
85
+ /** A registered passkey, persisted between ceremonies. */
86
+ export interface StoredPasskey {
87
+ /** Base64URL credential id. */
88
+ id: string;
89
+ userId: string;
90
+ /** Base64URL of the raw COSE public-key bytes. */
91
+ publicKey: string;
92
+ /** COSE algorithm id (e.g. -7 for ES256). */
93
+ alg: number;
94
+ counter: number;
95
+ transports?: string[];
96
+ }
97
+ export interface WebauthnCredentialStore {
98
+ save(passkey: StoredPasskey): Promise<void>;
99
+ findById(id: string): Promise<StoredPasskey | null>;
100
+ findByUser(userId: string): Promise<StoredPasskey[]>;
101
+ updateCounter(id: string, counter: number): Promise<void>;
102
+ }
103
+ export declare class MemoryWebauthnChallengeStore implements WebauthnChallengeStore {
104
+ #private;
105
+ save(state: string, challenge: string): Promise<void>;
106
+ take(state: string): Promise<string | null>;
107
+ }
108
+ export declare class MemoryWebauthnCredentialStore implements WebauthnCredentialStore {
109
+ #private;
110
+ save(passkey: StoredPasskey): Promise<void>;
111
+ findById(id: string): Promise<StoredPasskey | null>;
112
+ findByUser(userId: string): Promise<StoredPasskey[]>;
113
+ updateCounter(id: string, counter: number): Promise<void>;
114
+ }
115
+ export interface WebauthnConfig {
116
+ /** Human-readable relying-party name (your app). */
117
+ rpName: string;
118
+ /** Relying-party ID — your registrable domain (e.g. `fluveo.ch`). */
119
+ rpID: string;
120
+ /** Expected origin(s) of the ceremony (e.g. `https://fluveo.ch`). */
121
+ origin: string | string[];
122
+ challengeStore?: WebauthnChallengeStore;
123
+ credentialStore?: WebauthnCredentialStore;
124
+ /** Ceremony timeout in ms. Default `60000`. */
125
+ timeout?: number;
126
+ /**
127
+ * COSE algorithms offered to the authenticator, in preference order.
128
+ * Default ES256, RS256, EdDSA.
129
+ */
130
+ supportedAlgorithms?: number[];
131
+ }
132
+ export interface WebauthnUser {
133
+ id: string;
134
+ name: string;
135
+ displayName?: string;
136
+ }
137
+ export declare class WebauthnProvider {
138
+ #private;
139
+ readonly kind: "webauthn";
140
+ constructor(config: WebauthnConfig);
141
+ /**
142
+ * Begin passkey registration. Returns the options to hand to the browser,
143
+ * plus an opaque `state` token the caller stashes in the session and passes
144
+ * back to `finishRegistration()`.
145
+ */
146
+ startRegistration(user: WebauthnUser): Promise<{
147
+ options: RegistrationOptionsJSON;
148
+ state: string;
149
+ }>;
150
+ /**
151
+ * Complete registration: validate client data + authenticator data against
152
+ * the stored challenge and persist the new passkey on success.
153
+ */
154
+ finishRegistration(state: string, userId: string, response: RegistrationResponseJSON): Promise<{
155
+ verified: boolean;
156
+ }>;
157
+ /**
158
+ * Begin authentication. Pass `userId` to restrict to that user's passkeys
159
+ * (2FA step); omit it for a usernameless / discoverable-credential sign-in.
160
+ */
161
+ startAuthentication(userId?: string): Promise<{
162
+ options: AuthenticationOptionsJSON;
163
+ state: string;
164
+ }>;
165
+ /**
166
+ * Complete authentication: look up the asserted credential, verify the
167
+ * signature against the stored challenge, and advance the replay counter.
168
+ * Returns the owning `userId` on success.
169
+ */
170
+ finishAuthentication(state: string, response: AuthenticationResponseJSON): Promise<{
171
+ verified: boolean;
172
+ userId?: string;
173
+ }>;
174
+ }
175
+ //# sourceMappingURL=WebauthnProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebauthnProvider.d.ts","sourceRoot":"","sources":["../../src/mfa/WebauthnProvider.ts"],"names":[],"mappings":"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"}