nid-webauthn-emulator 0.2.5 → 0.2.7

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.
@@ -241,4 +241,71 @@ const credentials_memory_repository_1 = require("../../src/repository/credential
241
241
  }).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
242
242
  });
243
243
  });
244
+ (0, globals_1.describe)("Authenticator Credential Management additional error coverage", () => {
245
+ (0, globals_1.test)("dispatch default branch throws invalid command", () => {
246
+ const authenticator = new src_1.AuthenticatorEmulator();
247
+ // Build a CTAP request with an unknown subCommand to hit default branch
248
+ const data = encode_utils_1.default.encodeCbor({ "1": -1 });
249
+ (0, globals_1.expect)(() => authenticator.command({ command: ctap_model_1.CTAP_COMMAND.authenticatorCredentialManagement, data })).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_COMMAND));
250
+ });
251
+ (0, globals_1.test)("enumerateCredentialsBegin without repository throws not allowed", () => {
252
+ const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
253
+ const req = (0, ctap_model_1.packCredentialManagementRequest)({
254
+ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
255
+ subCommandParams: { rpId: "example.com" },
256
+ });
257
+ (0, globals_1.expect)(() => authenticator.command(req)).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
258
+ });
259
+ (0, globals_1.test)("updateUserInformation without repository throws not allowed", () => {
260
+ const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
261
+ const req = (0, ctap_model_1.packCredentialManagementRequest)({
262
+ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
263
+ subCommandParams: { rpId: "example.com", user: { id: new Uint8Array([1]), name: "n", displayName: "d" } },
264
+ });
265
+ (0, globals_1.expect)(() => authenticator.command(req)).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
266
+ });
267
+ (0, globals_1.test)("deleteCredential without repository throws not allowed", () => {
268
+ const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
269
+ const req = (0, ctap_model_1.packCredentialManagementRequest)({ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential });
270
+ (0, globals_1.expect)(() => authenticator.command(req)).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
271
+ });
272
+ (0, globals_1.test)("deleteCredential without credentialId throws invalid parameter", () => {
273
+ const authenticator = new src_1.AuthenticatorEmulator();
274
+ const req = (0, ctap_model_1.packCredentialManagementRequest)({ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential });
275
+ (0, globals_1.expect)(() => authenticator.command(req)).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER));
276
+ });
277
+ (0, globals_1.test)("deleteCredential for non-existent id throws no credentials", () => {
278
+ const authenticator = new src_1.AuthenticatorEmulator();
279
+ const credentialId = encode_utils_1.default.strToUint8Array("non-existent-id");
280
+ const req = (0, ctap_model_1.packCredentialManagementRequest)({
281
+ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential,
282
+ subCommandParams: { credentialId },
283
+ });
284
+ (0, globals_1.expect)(() => authenticator.command(req)).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS));
285
+ });
286
+ (0, globals_1.test)("private enumerateCredentialsBegin guard throws not allowed when repository missing", () => {
287
+ const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
288
+ const target = authenticator.authenticatorEnumerateCredentialsBegin.bind(authenticator);
289
+ const call = () => target({
290
+ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
291
+ subCommandParams: { rpId: "x" },
292
+ });
293
+ (0, globals_1.expect)(call).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
294
+ });
295
+ (0, globals_1.test)("private updateUserInformation guard throws not allowed when repository missing", () => {
296
+ const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
297
+ const target = authenticator.authenticatorUpdateUserInformation.bind(authenticator);
298
+ const call = () => target({
299
+ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
300
+ subCommandParams: { rpId: "x", user: { id: new Uint8Array([1]), name: "n", displayName: "d" } },
301
+ });
302
+ (0, globals_1.expect)(call).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
303
+ });
304
+ (0, globals_1.test)("private deleteCredential guard throws not allowed when repository missing", () => {
305
+ const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
306
+ const target = authenticator.authenticatorDeleteCredential.bind(authenticator);
307
+ const call = () => target({ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential });
308
+ (0, globals_1.expect)(call).toThrow(new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
309
+ });
310
+ });
244
311
  });
@@ -91,4 +91,88 @@ const ctap_model_1 = require("../../src/authenticator/ctap-model");
91
91
  };
92
92
  (0, globals_1.expect)(() => (0, ctap_model_1.unpackRequest)(request)).toThrow("CTAP error: CTAP2_ERR_INVALID_CBOR (18)");
93
93
  });
94
+ (0, globals_1.test)("unpackRequest default branch returns undefined request for unhandled command", () => {
95
+ const req = { command: ctap_model_1.CTAP_COMMAND.authenticatorClientPIN }; // not explicitly handled in switch
96
+ const unpacked = (0, ctap_model_1.unpackRequest)(req);
97
+ (0, globals_1.expect)(unpacked.command).toBe(ctap_model_1.CTAP_COMMAND.authenticatorClientPIN);
98
+ (0, globals_1.expect)(unpacked.request).toBeUndefined();
99
+ });
100
+ (0, globals_1.test)("credential management request with undefined optional params round-trips", () => {
101
+ const packed = (0, ctap_model_1.packCredentialManagementRequest)({
102
+ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
103
+ // subCommandParams and pinUvAuth* omitted intentionally
104
+ });
105
+ const unpacked = (0, ctap_model_1.unpackCredentialManagementRequest)(packed);
106
+ (0, globals_1.expect)(unpacked.subCommand).toBe(ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin);
107
+ (0, globals_1.expect)(unpacked.subCommandParams).toBeUndefined();
108
+ (0, globals_1.expect)(unpacked.pinUvAuthParam).toBeUndefined();
109
+ (0, globals_1.expect)(unpacked.pinUvAuthProtocol).toBeUndefined();
110
+ });
111
+ (0, globals_1.test)("credential management response optional binary fields handled when absent/present", () => {
112
+ // Absent case
113
+ const packedAbsent = (0, ctap_model_1.packCredentialManagementResponse)({});
114
+ const absent = (0, ctap_model_1.unpackCredentialManagementResponse)(packedAbsent);
115
+ (0, globals_1.expect)(absent.credentialID).toBeUndefined();
116
+ (0, globals_1.expect)(absent.publicKey).toBeUndefined();
117
+ (0, globals_1.expect)(absent.rpIDHash).toBeUndefined();
118
+ (0, globals_1.expect)(absent.largeBlobKey).toBeUndefined();
119
+ // Present case
120
+ const bin = new Uint8Array([1, 2, 3]);
121
+ const packedPresent = (0, ctap_model_1.packCredentialManagementResponse)({
122
+ credentialID: bin,
123
+ publicKey: bin,
124
+ rpIDHash: bin,
125
+ largeBlobKey: bin,
126
+ });
127
+ const present = (0, ctap_model_1.unpackCredentialManagementResponse)(packedPresent);
128
+ (0, globals_1.expect)(present.credentialID).toBeDefined();
129
+ (0, globals_1.expect)(present.publicKey).toBeDefined();
130
+ (0, globals_1.expect)(present.rpIDHash).toBeDefined();
131
+ (0, globals_1.expect)(present.largeBlobKey).toBeDefined();
132
+ if (present.credentialID && present.publicKey && present.rpIDHash && present.largeBlobKey) {
133
+ (0, globals_1.expect)(Array.from(present.credentialID)).toEqual([1, 2, 3]);
134
+ (0, globals_1.expect)(Array.from(present.publicKey)).toEqual([1, 2, 3]);
135
+ (0, globals_1.expect)(Array.from(present.rpIDHash)).toEqual([1, 2, 3]);
136
+ (0, globals_1.expect)(Array.from(present.largeBlobKey)).toEqual([1, 2, 3]);
137
+ }
138
+ });
139
+ (0, globals_1.test)("unpackCredentialManagementRequest throws on invalid CBOR", () => {
140
+ const bad = {
141
+ command: ctap_model_1.CTAP_COMMAND.authenticatorCredentialManagement,
142
+ data: new Uint8Array([1, 2, 3]),
143
+ };
144
+ (0, globals_1.expect)(() => (0, ctap_model_1.unpackCredentialManagementRequest)(bad)).toThrow(/CTAP2_ERR_INVALID_CBOR/);
145
+ });
146
+ (0, globals_1.test)("unpackCredentialManagementResponse throws on invalid CBOR", () => {
147
+ const bad = {
148
+ status: ctap_model_1.CTAP_STATUS_CODE.CTAP2_OK,
149
+ data: new Uint8Array([1, 2, 3]),
150
+ };
151
+ (0, globals_1.expect)(() => (0, ctap_model_1.unpackCredentialManagementResponse)(bad)).toThrow(/CTAP2_ERR_INVALID_CBOR/);
152
+ });
153
+ (0, globals_1.test)("unpackRequest CM includes pinUvAuthParam when present", () => {
154
+ const pin = new Uint8Array([7, 7]);
155
+ const packed = (0, ctap_model_1.packCredentialManagementRequest)({
156
+ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
157
+ subCommandParams: { rpId: "example.com" },
158
+ pinUvAuthParam: pin,
159
+ });
160
+ const unpacked = (0, ctap_model_1.unpackRequest)(packed);
161
+ const req = unpacked.request;
162
+ const isMgmt = (x) => typeof x === "object" && x !== null && "subCommand" in x;
163
+ (0, globals_1.expect)(isMgmt(req)).toBe(true);
164
+ if (isMgmt(req)) {
165
+ (0, globals_1.expect)(Array.from(req.pinUvAuthParam ?? new Uint8Array())).toEqual([7, 7]);
166
+ }
167
+ });
168
+ (0, globals_1.test)("unpackCredentialManagementRequest includes pinUvAuthParam when present", () => {
169
+ const pin = new Uint8Array([9, 9]);
170
+ const packed = (0, ctap_model_1.packCredentialManagementRequest)({
171
+ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
172
+ subCommandParams: { rpId: "example.com" },
173
+ pinUvAuthParam: pin,
174
+ });
175
+ const req = (0, ctap_model_1.unpackCredentialManagementRequest)(packed);
176
+ (0, globals_1.expect)(Array.from(req.pinUvAuthParam ?? new Uint8Array())).toEqual([9, 9]);
177
+ });
94
178
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const globals_1 = require("@jest/globals");
7
+ const encode_utils_1 = __importDefault(require("../../src/libs/encode-utils"));
8
+ (0, globals_1.describe)("EncodeUtils encodeCbor/decodeCbor edge cases", () => {
9
+ (0, globals_1.test)("handles ArrayBuffer values within objects and arrays", () => {
10
+ const ab = new Uint8Array([1, 2, 3]).buffer; // ArrayBuffer branch
11
+ const ua = new Uint8Array([4, 5]);
12
+ const obj = {
13
+ v1: ab,
14
+ v2: ua,
15
+ arr: [ab, ua],
16
+ };
17
+ const encoded = encode_utils_1.default.encodeCbor(obj);
18
+ const decoded = encode_utils_1.default.decodeCbor(encoded);
19
+ // ArrayBuffer is decoded back to Uint8Array
20
+ (0, globals_1.expect)(decoded.v1).toBeInstanceOf(Uint8Array);
21
+ (0, globals_1.expect)(Array.from(decoded.v1)).toEqual([1, 2, 3]);
22
+ // Existing Uint8Array round-trips
23
+ (0, globals_1.expect)(Array.from(decoded.v2)).toEqual([4, 5]);
24
+ // Mixed array elements preserved as Uint8Array
25
+ (0, globals_1.expect)(Array.isArray(decoded.arr)).toBe(true);
26
+ const [a0, a1] = decoded.arr;
27
+ (0, globals_1.expect)(Array.from(a0)).toEqual([1, 2, 3]);
28
+ (0, globals_1.expect)(Array.from(a1)).toEqual([4, 5]);
29
+ });
30
+ (0, globals_1.test)("handles Buffer values without conversion in encoder and to Uint8Array on decode", () => {
31
+ const buf = Buffer.from([9, 8]);
32
+ const obj = { b: buf };
33
+ const encoded = encode_utils_1.default.encodeCbor(obj);
34
+ const decoded = encode_utils_1.default.decodeCbor(encoded);
35
+ (0, globals_1.expect)(decoded.b).toBeInstanceOf(Uint8Array);
36
+ (0, globals_1.expect)(Array.from(decoded.b)).toEqual([9, 8]);
37
+ });
38
+ });
@@ -61,15 +61,21 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
61
61
  const emulator = new src_1.default();
62
62
  const testServer = new webauthn_test_server_1.WebAuthnTestServer();
63
63
  const options = await testServer.getRegistrationOptions(user);
64
- await (0, globals_1.expect)(async () => {
65
- emulator.createJSON(illegalOrigin, options);
66
- }).rejects.toThrow(src_1.InvalidRpIdError);
64
+ const call = () => emulator.createJSON(illegalOrigin, options);
65
+ (0, globals_1.expect)(call).toThrow(DOMException);
66
+ try {
67
+ call();
68
+ throw new Error("Expected DOMException");
69
+ }
70
+ catch (e) {
71
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
72
+ (0, globals_1.expect)(e.name).toBe("SecurityError");
73
+ }
67
74
  });
68
75
  (0, globals_1.test)("Create Passkeys with no public key credential parameters _ failed to response", async () => {
69
76
  const emulator = new src_1.default();
70
- await (0, globals_1.expect)(async () => {
71
- emulator.create(webauthn_test_server_1.TEST_RP_ORIGIN, {});
72
- }).rejects.toThrow(src_1.NoPublicKeyError);
77
+ const call = () => emulator.create(webauthn_test_server_1.TEST_RP_ORIGIN, {});
78
+ (0, globals_1.expect)(call).toThrow(TypeError);
73
79
  });
74
80
  (0, globals_1.test)("Found in the exclude List _ CTAP Error", async () => {
75
81
  const emulator = new src_1.default();
@@ -78,18 +84,32 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
78
84
  const credential = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
79
85
  await testServer.getRegistrationVerification(user, credential);
80
86
  const options2 = await testServer.getRegistrationOptions(user);
81
- await (0, globals_1.expect)(async () => {
82
- emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options2);
83
- }).rejects.toThrow(new src_1.AuthenticationEmulatorError(src_1.CTAP_STATUS_CODE.CTAP2_ERR_CREDENTIAL_EXCLUDED));
87
+ const call = () => emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options2);
88
+ (0, globals_1.expect)(call).toThrow(DOMException);
89
+ try {
90
+ call();
91
+ throw new Error("Expected DOMException");
92
+ }
93
+ catch (e) {
94
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
95
+ (0, globals_1.expect)(e.name).toBe("NotAllowedError");
96
+ }
84
97
  });
85
98
  (0, globals_1.test)("Invalid algorithm _ CTAP Error", async () => {
86
99
  const emulator = new src_1.default();
87
100
  const testServer = new webauthn_test_server_1.WebAuthnTestServer();
88
101
  const options = await testServer.getRegistrationOptions(user);
89
102
  const illegalOptions = { ...options, pubKeyCredParams: [{ type: "public-key", alg: 99 }] };
90
- await (0, globals_1.expect)(async () => {
91
- emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, illegalOptions);
92
- }).rejects.toThrow(new src_1.AuthenticationEmulatorError(src_1.CTAP_STATUS_CODE.CTAP2_ERR_UNSUPPORTED_ALGORITHM));
103
+ const call = () => emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, illegalOptions);
104
+ (0, globals_1.expect)(call).toThrow(DOMException);
105
+ try {
106
+ call();
107
+ throw new Error("Expected DOMException");
108
+ }
109
+ catch (e) {
110
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
111
+ (0, globals_1.expect)(e.name).toBe("NotSupportedError");
112
+ }
93
113
  });
94
114
  (0, globals_1.test)("User registration operation failed _ CTAP Error", async () => {
95
115
  const authenticator = new src_1.AuthenticatorEmulator({
@@ -98,9 +118,16 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
98
118
  const emulator = new src_1.default(authenticator);
99
119
  const testServer = new webauthn_test_server_1.WebAuthnTestServer();
100
120
  const options = await testServer.getRegistrationOptions(user);
101
- await (0, globals_1.expect)(async () => {
102
- emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
103
- }).rejects.toThrow(new src_1.AuthenticationEmulatorError(src_1.CTAP_STATUS_CODE.CTAP2_ERR_OPERATION_DENIED));
121
+ const call = () => emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
122
+ (0, globals_1.expect)(call).toThrow(DOMException);
123
+ try {
124
+ call();
125
+ throw new Error("Expected DOMException");
126
+ }
127
+ catch (e) {
128
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
129
+ (0, globals_1.expect)(e.name).toBe("NotAllowedError");
130
+ }
104
131
  });
105
132
  (0, globals_1.test)("Create Passkeys without RP ID _ OK", async () => {
106
133
  const emulator = new src_1.default();
@@ -168,25 +195,38 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
168
195
  const [emulator, testServer] = await createCredential();
169
196
  const illegalOrigin = `${webauthn_test_server_1.TEST_RP_ORIGIN}_illegal`;
170
197
  const options = await testServer.getAuthenticationOptions();
171
- await (0, globals_1.expect)(async () => {
172
- emulator.getJSON(illegalOrigin, options);
173
- }).rejects.toThrow(src_1.InvalidRpIdError);
198
+ const call = () => emulator.getJSON(illegalOrigin, options);
199
+ (0, globals_1.expect)(call).toThrow(DOMException);
200
+ try {
201
+ call();
202
+ throw new Error("Expected DOMException");
203
+ }
204
+ catch (e) {
205
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
206
+ (0, globals_1.expect)(e.name).toBe("SecurityError");
207
+ }
174
208
  });
175
209
  (0, globals_1.test)("Request Passkeys with no public key credential parameters _ failed to response", async () => {
176
210
  const emulator = new src_1.default();
177
- await (0, globals_1.expect)(async () => {
178
- emulator.get(webauthn_test_server_1.TEST_RP_ORIGIN, {});
179
- }).rejects.toThrow(src_1.NoPublicKeyError);
211
+ const call = () => emulator.get(webauthn_test_server_1.TEST_RP_ORIGIN, {});
212
+ (0, globals_1.expect)(call).toThrow(TypeError);
180
213
  });
181
214
  (0, globals_1.test)("Not found in allow list _ CTAP Error", async () => {
182
215
  const [emulator, testServer] = await createCredential();
183
216
  const options = await testServer.getAuthenticationOptions();
184
- await (0, globals_1.expect)(async () => {
185
- emulator.getJSON(webauthn_test_server_1.TEST_RP_ORIGIN, {
186
- ...options,
187
- allowCredentials: [{ id: "AAAAAA", type: "public-key" }],
188
- });
189
- }).rejects.toThrow(new src_1.AuthenticationEmulatorError(src_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS));
217
+ const call = () => emulator.getJSON(webauthn_test_server_1.TEST_RP_ORIGIN, {
218
+ ...options,
219
+ allowCredentials: [{ id: "AAAAAA", type: "public-key" }],
220
+ });
221
+ (0, globals_1.expect)(call).toThrow(DOMException);
222
+ try {
223
+ call();
224
+ throw new Error("Expected DOMException");
225
+ }
226
+ catch (e) {
227
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
228
+ (0, globals_1.expect)(e.name).toBe("NotAllowedError");
229
+ }
190
230
  });
191
231
  (0, globals_1.test)("User authentication operation failed _ CTAP Error", async () => {
192
232
  const authenticator = new src_1.AuthenticatorEmulator({
@@ -194,9 +234,16 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
194
234
  });
195
235
  const [emulator, testServer] = await createCredential(authenticator);
196
236
  const options = await testServer.getAuthenticationOptions();
197
- await (0, globals_1.expect)(async () => {
198
- emulator.getJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
199
- }).rejects.toThrow(new src_1.AuthenticationEmulatorError(src_1.CTAP_STATUS_CODE.CTAP2_ERR_OPERATION_DENIED));
237
+ const call = () => emulator.getJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
238
+ (0, globals_1.expect)(call).toThrow(DOMException);
239
+ try {
240
+ call();
241
+ throw new Error("Expected DOMException");
242
+ }
243
+ catch (e) {
244
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
245
+ (0, globals_1.expect)(e.name).toBe("NotAllowedError");
246
+ }
200
247
  });
201
248
  (0, globals_1.test)("Only one credential is allowed and RP ID is undefined _ credential is undefined and OK", async () => {
202
249
  const [emulator, testServer] = await createCredential();
@@ -351,9 +398,16 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
351
398
  // Create authentication options
352
399
  const authOptions = await testServer.getAuthenticationOptions();
353
400
  // Standard RP ID validation only
354
- await (0, globals_1.expect)(async () => {
355
- emulator.getJSON(testServer.origin, authOptions);
356
- }).rejects.toThrow(src_1.InvalidRpIdError);
401
+ const invalidCall = () => emulator.getJSON(testServer.origin, authOptions);
402
+ (0, globals_1.expect)(invalidCall).toThrow(DOMException);
403
+ try {
404
+ invalidCall();
405
+ throw new Error("Expected DOMException");
406
+ }
407
+ catch (e) {
408
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
409
+ (0, globals_1.expect)(e.name).toBe("SecurityError");
410
+ }
357
411
  // Related origins validation
358
412
  const authResponse = emulator.getJSON(testServer.origin, authOptions, relatedOrigins);
359
413
  (0, globals_1.expect)(authResponse).toBeDefined();
@@ -419,3 +473,253 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
419
473
  }).toThrow(); // Should throw CTAP2_ERR_NO_CREDENTIALS
420
474
  });
421
475
  });
476
+ (0, globals_1.describe)("WebAuthnEmulator getClientExtensionResults.rk coverage", () => {
477
+ (0, globals_1.test)("rk=true when discoverable credential (user present)", async () => {
478
+ const emulator = new src_1.default(new src_1.AuthenticatorEmulator());
479
+ const testServer = new webauthn_test_server_1.WebAuthnTestServer();
480
+ const user = { username: "ext-user", id: "ext-user" };
481
+ const regOptions = await testServer.getRegistrationOptions(user);
482
+ const regResp = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, regOptions);
483
+ await testServer.getRegistrationVerification(user, regResp);
484
+ const reqOptions = await testServer.getAuthenticationOptions();
485
+ const parsed = (0, src_1.parseRequestOptionsFromJSON)(reqOptions);
486
+ const cred = emulator.get(webauthn_test_server_1.TEST_RP_ORIGIN, { publicKey: parsed });
487
+ const ext = cred.getClientExtensionResults();
488
+ (0, globals_1.expect)("credProps" in ext && ext.credProps).toBeTruthy();
489
+ if ("credProps" in ext && ext.credProps) {
490
+ (0, globals_1.expect)(ext.credProps.rk).toBe(true);
491
+ }
492
+ });
493
+ (0, globals_1.test)("rk=false with stateless + single allowCredential (no user)", async () => {
494
+ const emulator = new src_1.default(new src_1.AuthenticatorEmulator({ stateless: true }));
495
+ const testServer = new webauthn_test_server_1.WebAuthnTestServer();
496
+ const user = { username: "ext-user2", id: "ext-user2" };
497
+ const regOptions = await testServer.getRegistrationOptions(user);
498
+ const regResp = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, regOptions);
499
+ await testServer.getRegistrationVerification(user, regResp);
500
+ const reqOptions = await testServer.getAuthenticationOptions();
501
+ const singleAllow = {
502
+ ...reqOptions,
503
+ allowCredentials: [{ type: "public-key", id: regResp.id }],
504
+ };
505
+ const parsed = (0, src_1.parseRequestOptionsFromJSON)(singleAllow);
506
+ const cred = emulator.get(webauthn_test_server_1.TEST_RP_ORIGIN, { publicKey: parsed });
507
+ const ext = cred.getClientExtensionResults();
508
+ (0, globals_1.expect)("credProps" in ext && ext.credProps).toBeTruthy();
509
+ if ("credProps" in ext && ext.credProps) {
510
+ (0, globals_1.expect)(ext.credProps.rk).toBe(false);
511
+ }
512
+ });
513
+ (0, globals_1.test)("signalAllAcceptedCredentials skips credentials when userId mismatches", async () => {
514
+ // repository-backed authenticator with isolated repository
515
+ const emulator = new src_1.default(new src_1.AuthenticatorEmulator({ credentialsRepository: new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository() }));
516
+ const testServer = new webauthn_test_server_1.WebAuthnTestServer();
517
+ const user = { username: "ext-user3", id: "ext-user3" };
518
+ const regOptions = await testServer.getRegistrationOptions(user);
519
+ const regResp = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, regOptions);
520
+ const repository = emulator.authenticator.params.credentialsRepository;
521
+ await testServer.getRegistrationVerification(user, regResp);
522
+ // credentials exist
523
+ const before = repository.loadCredentials();
524
+ (0, globals_1.expect)(before.length).toBe(1);
525
+ const existingId = before.map((c) => encode_utils_1.default.encodeBase64Url(c.publicKeyCredentialSource.id))[0];
526
+ const actualUserId = encode_utils_1.default.encodeBase64Url(before[0].user.id);
527
+ // ensure mismatch by altering actualUserId
528
+ const wrongUserId = `${actualUserId}-mismatch`;
529
+ emulator.signalAllAcceptedCredentials({
530
+ rpId: webauthn_test_server_1.TEST_RP_ORIGIN.replace("https://", ""),
531
+ userId: wrongUserId,
532
+ allAcceptedCredentialIds: [], // even though empty, mismatch should skip deletion
533
+ });
534
+ const after = repository.loadCredentials();
535
+ const remainingIds = after.map((c) => encode_utils_1.default.encodeBase64Url(c.publicKeyCredentialSource.id));
536
+ (0, globals_1.expect)(remainingIds).toContain(existingId); // original credential remains
537
+ (0, globals_1.expect)(after.length).toBe(1); // unchanged
538
+ });
539
+ (0, globals_1.test)("signalAllAcceptedCredentials handles undefined totalCredentials (defaults to 0)", async () => {
540
+ class BeginNoTotalAuthenticator extends src_1.AuthenticatorEmulator {
541
+ command(request) {
542
+ if (request.command === src_1.CTAP_COMMAND.authenticatorCredentialManagement) {
543
+ return (0, src_1.packCredentialManagementResponse)({});
544
+ }
545
+ return super.command(request);
546
+ }
547
+ }
548
+ const emulator = new src_1.default(new BeginNoTotalAuthenticator());
549
+ (0, globals_1.expect)(() => emulator.signalAllAcceptedCredentials({
550
+ rpId: webauthn_test_server_1.TEST_RP_ORIGIN.replace("https://", ""),
551
+ userId: "any-user",
552
+ allAcceptedCredentialIds: [],
553
+ })).not.toThrow();
554
+ });
555
+ });
556
+ (0, globals_1.describe)("WebAuthnEmulator DOMException mapping coverage", () => {
557
+ (0, globals_1.test)("signalCurrentUserDetails with invalid parameter → DataError", () => {
558
+ const emulator = new src_1.default(new src_1.AuthenticatorEmulator());
559
+ const userId = encode_utils_1.default.encodeBase64Url(encode_utils_1.default.strToUint8Array("uid"));
560
+ const call = () => emulator.signalCurrentUserDetails({
561
+ // Force rpId to be undefined to trigger CTAP1_ERR_INVALID_PARAMETER inside authenticator
562
+ rpId: undefined,
563
+ userId,
564
+ name: "n",
565
+ displayName: "d",
566
+ });
567
+ (0, globals_1.expect)(call).toThrow(DOMException);
568
+ try {
569
+ call();
570
+ throw new Error("Expected DOMException");
571
+ }
572
+ catch (e) {
573
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
574
+ (0, globals_1.expect)(e.name).toBe("DataError");
575
+ }
576
+ });
577
+ (0, globals_1.test)("getAuthenticatorInfo with invalid CBOR → DataError", () => {
578
+ class BadCborAuthenticator extends src_1.AuthenticatorEmulator {
579
+ command() {
580
+ return { status: src_1.CTAP_STATUS_CODE.CTAP2_OK, data: new Uint8Array([0xff]) };
581
+ }
582
+ }
583
+ const emulator = new src_1.default(new BadCborAuthenticator());
584
+ const call = () => emulator.getAuthenticatorInfo();
585
+ (0, globals_1.expect)(call).toThrow(DOMException);
586
+ try {
587
+ call();
588
+ throw new Error("Expected DOMException");
589
+ }
590
+ catch (e) {
591
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
592
+ (0, globals_1.expect)(e.name).toBe("DataError");
593
+ }
594
+ });
595
+ (0, globals_1.test)("getAuthenticatorInfo with invalid command → UnknownError", () => {
596
+ class InvalidCommandAuthenticator extends src_1.AuthenticatorEmulator {
597
+ command() {
598
+ throw new src_1.AuthenticationEmulatorError(src_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_COMMAND);
599
+ }
600
+ }
601
+ const emulator = new src_1.default(new InvalidCommandAuthenticator());
602
+ const call = () => emulator.getAuthenticatorInfo();
603
+ (0, globals_1.expect)(call).toThrow(DOMException);
604
+ try {
605
+ call();
606
+ throw new Error("Expected DOMException");
607
+ }
608
+ catch (e) {
609
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
610
+ (0, globals_1.expect)(e.name).toBe("UnknownError");
611
+ }
612
+ });
613
+ });
614
+ (0, globals_1.describe)("WebAuthnEmulator credential management error mapping", () => {
615
+ (0, globals_1.test)("signalUnknownCredential on stateless authenticator → NotAllowedError", () => {
616
+ const emulator = new src_1.default(new src_1.AuthenticatorEmulator({ stateless: true }));
617
+ const call = () => emulator.signalUnknownCredential({
618
+ rpId: "example.com",
619
+ credentialId: encode_utils_1.default.encodeBase64Url(encode_utils_1.default.strToUint8Array("nope")),
620
+ });
621
+ (0, globals_1.expect)(call).toThrow(DOMException);
622
+ try {
623
+ call();
624
+ throw new Error("Expected DOMException");
625
+ }
626
+ catch (e) {
627
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
628
+ (0, globals_1.expect)(e.name).toBe("NotAllowedError");
629
+ }
630
+ });
631
+ (0, globals_1.test)("signalUnknownCredential for non-existent credential → NotAllowedError", async () => {
632
+ const repository = new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository();
633
+ const authenticator = new src_1.AuthenticatorEmulator({ credentialsRepository: repository });
634
+ const emulator = new src_1.default(authenticator);
635
+ const call = () => emulator.signalUnknownCredential({
636
+ rpId: "example.com",
637
+ credentialId: encode_utils_1.default.encodeBase64Url(encode_utils_1.default.strToUint8Array("missing")),
638
+ });
639
+ (0, globals_1.expect)(call).toThrow(DOMException);
640
+ try {
641
+ call();
642
+ throw new Error("Expected DOMException");
643
+ }
644
+ catch (e) {
645
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
646
+ (0, globals_1.expect)(e.name).toBe("NotAllowedError");
647
+ }
648
+ });
649
+ });
650
+ (0, globals_1.describe)("WebAuthnEmulator invalid CBOR mapping on create/get", () => {
651
+ (0, globals_1.test)("create path invalid CBOR → DataError", () => {
652
+ class BadCreateCborAuthenticator extends src_1.AuthenticatorEmulator {
653
+ command(request) {
654
+ if (request.command === src_1.CTAP_COMMAND.authenticatorMakeCredential) {
655
+ return { status: src_1.CTAP_STATUS_CODE.CTAP2_OK, data: new Uint8Array([0xff]) };
656
+ }
657
+ return super.command(request);
658
+ }
659
+ }
660
+ const emulator = new src_1.default(new BadCreateCborAuthenticator());
661
+ const user = { username: "bad-cbor", id: "bad-cbor" };
662
+ const server = new webauthn_test_server_1.WebAuthnTestServer();
663
+ return server.getRegistrationOptions(user).then((opts) => {
664
+ const call = () => emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, opts);
665
+ (0, globals_1.expect)(call).toThrow(DOMException);
666
+ try {
667
+ call();
668
+ throw new Error("Expected DOMException");
669
+ }
670
+ catch (e) {
671
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
672
+ (0, globals_1.expect)(e.name).toBe("DataError");
673
+ }
674
+ });
675
+ });
676
+ (0, globals_1.test)("get path invalid CBOR → DataError", () => {
677
+ class BadGetCborAuthenticator extends src_1.AuthenticatorEmulator {
678
+ command(request) {
679
+ if (request.command === src_1.CTAP_COMMAND.authenticatorGetAssertion) {
680
+ return { status: src_1.CTAP_STATUS_CODE.CTAP2_OK, data: new Uint8Array([0xff]) };
681
+ }
682
+ return super.command(request);
683
+ }
684
+ }
685
+ const emulator = new src_1.default(new BadGetCborAuthenticator());
686
+ const server = new webauthn_test_server_1.WebAuthnTestServer();
687
+ const user = { username: "bad-cbor2", id: "bad-cbor2" };
688
+ return server.getRegistrationOptions(user).then((reg) => {
689
+ const regResp = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, reg);
690
+ return server.getRegistrationVerification(user, regResp).then(async () => {
691
+ const req = await server.getAuthenticationOptions();
692
+ const call = () => emulator.getJSON(webauthn_test_server_1.TEST_RP_ORIGIN, req);
693
+ (0, globals_1.expect)(call).toThrow(DOMException);
694
+ try {
695
+ call();
696
+ throw new Error("Expected DOMException");
697
+ }
698
+ catch (e) {
699
+ (0, globals_1.expect)(e).toBeInstanceOf(DOMException);
700
+ (0, globals_1.expect)(e.name).toBe("DataError");
701
+ }
702
+ });
703
+ });
704
+ });
705
+ });
706
+ (0, globals_1.describe)("handleAuthenticatorCall other error path", () => {
707
+ (0, globals_1.test)("getAuthenticatorInfo rethrows non-CTAP errors as-is", () => {
708
+ class ThrowingAuthenticator extends src_1.AuthenticatorEmulator {
709
+ command() {
710
+ throw new Error("Non-CTAP failure");
711
+ }
712
+ }
713
+ const emulator = new src_1.default(new ThrowingAuthenticator());
714
+ try {
715
+ emulator.getAuthenticatorInfo();
716
+ throw new Error("Expected Error to be thrown");
717
+ }
718
+ catch (e) {
719
+ // Should not be mapped to DOMException; should be the original error
720
+ (0, globals_1.expect)(e).not.toBeInstanceOf(DOMException);
721
+ (0, globals_1.expect)(e).toBeInstanceOf(Error);
722
+ (0, globals_1.expect)(e.message).toBe("Non-CTAP failure");
723
+ }
724
+ });
725
+ });
@@ -19,7 +19,7 @@ export type PasskeyCredential = {
19
19
  readonly user: PublicKeyCredentialUserEntity | undefined;
20
20
  };
21
21
  export type AuthenticatorParameters = {
22
- readonly aaguid: Uint8Array;
22
+ readonly aaguid: Uint8Array<ArrayBuffer>;
23
23
  readonly transports: AuthenticatorTransport[];
24
24
  readonly algorithmIdentifiers: readonly (keyof typeof COSEAlgorithmIdentifier)[];
25
25
  readonly signCounterIncrement: number;
@@ -353,7 +353,7 @@ function getAssertion(rpIdHash, clientDataHash, newSignCounter, credential, inte
353
353
  type: "pkcs8",
354
354
  key: credential.privateKey,
355
355
  });
356
- const signature = (0, node_crypto_1.sign)(null, new Uint8Array(payload), privateKey);
356
+ const signature = encode_utils_1.default.bufferSourceToUint8Array(new Uint8Array((0, node_crypto_1.sign)(null, new Uint8Array(payload), privateKey)));
357
357
  return { authData: (0, webauthn_model_1.packAuthenticatorData)(authenticatorData), signature };
358
358
  }
359
359
  function makeCredential(aaguid, rpId, alg, transports, interactionResponse, user, statelessKey) {
@@ -9,7 +9,7 @@ export type AuthenticatorInteractionOptions = {
9
9
  };
10
10
  /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential */
11
11
  export interface AuthenticatorMakeCredentialRequest {
12
- clientDataHash: Uint8Array;
12
+ clientDataHash: Uint8Array<ArrayBuffer>;
13
13
  rp: PublicKeyCredentialRpEntity & {
14
14
  id: string;
15
15
  };
@@ -18,30 +18,30 @@ export interface AuthenticatorMakeCredentialRequest {
18
18
  excludeList?: PublicKeyCredentialDescriptor[];
19
19
  extensions?: object;
20
20
  options?: Partial<AuthenticatorOptions>;
21
- pinAuth?: Uint8Array;
21
+ pinAuth?: Uint8Array<ArrayBuffer>;
22
22
  pinProtocol?: number;
23
23
  }
24
24
  /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorMakeCredential */
25
25
  export interface AuthenticatorMakeCredentialResponse {
26
26
  fmt: string;
27
- authData: Uint8Array;
27
+ authData: Uint8Array<ArrayBuffer>;
28
28
  attStmt: object;
29
29
  }
30
30
  /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion */
31
31
  export interface AuthenticatorGetAssertionRequest {
32
32
  rpId: string;
33
- clientDataHash: Uint8Array;
33
+ clientDataHash: Uint8Array<ArrayBuffer>;
34
34
  allowList?: PublicKeyCredentialDescriptor[];
35
35
  extensions?: object;
36
36
  options?: Partial<AuthenticatorOptions>;
37
- pinAuth?: Uint8Array;
37
+ pinAuth?: Uint8Array<ArrayBuffer>;
38
38
  pinProtocol?: number;
39
39
  }
40
40
  /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion */
41
41
  export interface AuthenticatorGetAssertionResponse {
42
42
  credential?: PublicKeyCredentialDescriptor;
43
- authData: Uint8Array;
44
- signature: Uint8Array;
43
+ authData: Uint8Array<ArrayBuffer>;
44
+ signature: Uint8Array<ArrayBuffer>;
45
45
  user?: PublicKeyCredentialUserEntity;
46
46
  numberOfCredentials: number;
47
47
  }
@@ -49,7 +49,7 @@ export interface AuthenticatorGetAssertionResponse {
49
49
  export type AuthenticatorGetInfoResponse = {
50
50
  versions: string[];
51
51
  extensions?: string[];
52
- aaguid: Uint8Array;
52
+ aaguid: Uint8Array<ArrayBuffer>;
53
53
  options?: Partial<AuthenticatorOptions> & {
54
54
  plat?: boolean;
55
55
  clientPin?: boolean;
@@ -91,35 +91,35 @@ export declare enum CREDENTIAL_MANAGEMENT_SUBCOMMAND {
91
91
  export interface AuthenticatorCredentialManagementRequest {
92
92
  subCommand: CREDENTIAL_MANAGEMENT_SUBCOMMAND;
93
93
  subCommandParams?: {
94
- credentialId?: Uint8Array;
94
+ credentialId?: Uint8Array<ArrayBuffer>;
95
95
  rpId?: string;
96
96
  user?: PublicKeyCredentialUserEntity;
97
97
  };
98
98
  pinUvAuthProtocol?: number;
99
- pinUvAuthParam?: Uint8Array;
99
+ pinUvAuthParam?: Uint8Array<ArrayBuffer>;
100
100
  }
101
101
  export interface AuthenticatorCredentialManagementResponse {
102
102
  existingResidentCredentialsCount?: number;
103
103
  maxPossibleRemainingResidentCredentialsCount?: number;
104
104
  rp?: PublicKeyCredentialRpEntity;
105
- rpIDHash?: Uint8Array;
105
+ rpIDHash?: Uint8Array<ArrayBuffer>;
106
106
  totalRPs?: number;
107
107
  user?: PublicKeyCredentialUserEntity;
108
- credentialID?: Uint8Array;
109
- publicKey?: Uint8Array;
108
+ credentialID?: Uint8Array<ArrayBuffer>;
109
+ publicKey?: Uint8Array<ArrayBuffer>;
110
110
  totalCredentials?: number;
111
111
  credProtect?: number;
112
- largeBlobKey?: Uint8Array;
112
+ largeBlobKey?: Uint8Array<ArrayBuffer>;
113
113
  }
114
114
  /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#responses */
115
115
  export interface CTAPAuthenticatorResponse {
116
116
  status: CTAP_STATUS_CODE;
117
- data?: Uint8Array;
117
+ data?: Uint8Array<ArrayBuffer>;
118
118
  }
119
119
  /** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#commands */
120
120
  export interface CTAPAuthenticatorRequest {
121
121
  command: CTAP_COMMAND;
122
- data?: Uint8Array;
122
+ data?: Uint8Array<ArrayBuffer>;
123
123
  }
124
124
  export declare class AuthenticationEmulatorError extends Error {
125
125
  status: CTAP_STATUS_CODE;
@@ -1,9 +1,9 @@
1
1
  declare function encodeBase64Url(data: BufferSource): string;
2
- declare function decodeBase64Url(base64Url: string): Uint8Array;
3
- declare function bufferSourceToUint8Array(data: BufferSource): Uint8Array;
4
- declare function strToUint8Array(data: string): Uint8Array;
5
- declare function encodeCbor(data: object): Uint8Array;
6
- declare function decodeCbor<T>(data: Uint8Array): T;
2
+ declare function decodeBase64Url(base64Url: string): Uint8Array<ArrayBuffer>;
3
+ declare function bufferSourceToUint8Array(data: BufferSource): Uint8Array<ArrayBuffer>;
4
+ declare function strToUint8Array(data: string): Uint8Array<ArrayBuffer>;
5
+ declare function encodeCbor(data: object): Uint8Array<ArrayBuffer>;
6
+ declare function decodeCbor<T>(data: Uint8Array<ArrayBuffer>): T;
7
7
  declare const EncodeUtils: {
8
8
  strToUint8Array: typeof strToUint8Array;
9
9
  bufferSourceToUint8Array: typeof bufferSourceToUint8Array;
@@ -31,7 +31,13 @@ function strToUint8Array(data) {
31
31
  }
32
32
  function encodeCbor(data) {
33
33
  function encoder(value) {
34
- if (value instanceof Uint8Array || value instanceof ArrayBuffer) {
34
+ if (value instanceof Buffer) {
35
+ return value;
36
+ }
37
+ if (value instanceof Uint8Array) {
38
+ return Buffer.from(value);
39
+ }
40
+ if (value instanceof ArrayBuffer) {
35
41
  return Buffer.from(value);
36
42
  }
37
43
  if (Array.isArray(value)) {
@@ -4,13 +4,13 @@ export declare abstract class CoseKey {
4
4
  kty: number;
5
5
  alg: number;
6
6
  constructor(kty: number, alg: number);
7
- abstract toBytes(): Uint8Array;
7
+ abstract toBytes(): Uint8Array<ArrayBuffer>;
8
8
  abstract toJwk(): JsonWebKey;
9
9
  toKeyObject(): KeyObject;
10
10
  static fromKeyObject(keyObject: KeyObject): CoseKey;
11
- toDer(): Uint8Array;
12
- static fromDer(der: Uint8Array): CoseKey;
11
+ toDer(): Uint8Array<ArrayBuffer>;
12
+ static fromDer(der: Uint8Array<ArrayBuffer>): CoseKey;
13
13
  equals(other: CoseKey): boolean;
14
- static fromBytes(bytes: Uint8Array): CoseKey;
14
+ static fromBytes(bytes: Uint8Array<ArrayBuffer>): CoseKey;
15
15
  static fromJwk(jwk: JsonWebKey): CoseKey;
16
16
  }
@@ -21,7 +21,7 @@ class CoseKey {
21
21
  return CoseKey.fromJwk(keyObject.export({ format: "jwk" }));
22
22
  }
23
23
  toDer() {
24
- return this.toKeyObject().export({ format: "der", type: "spki" });
24
+ return new Uint8Array(this.toKeyObject().export({ format: "der", type: "spki" }));
25
25
  }
26
26
  static fromDer(der) {
27
27
  return CoseKey.fromKeyObject(node_crypto_1.default.createPublicKey({ format: "der", type: "spki", key: Buffer.from(der) }));
@@ -10,16 +10,14 @@ export type AuthenticatorInfo = {
10
10
  };
11
11
  export declare class WebAuthnEmulatorError extends Error {
12
12
  }
13
- export declare class NoPublicKeyError extends WebAuthnEmulatorError {
14
- }
15
- export declare class InvalidRpIdError extends WebAuthnEmulatorError {
16
- }
17
13
  /**
18
14
  * WebAuthn emulator
19
15
  */
20
16
  export declare class WebAuthnEmulator {
21
17
  authenticator: AuthenticatorEmulator;
22
18
  constructor(authenticator?: AuthenticatorEmulator);
19
+ private mapAuthenticatorErrorToDOMException;
20
+ private handleAuthenticatorCall;
23
21
  getJSON(origin: string, optionsJSON: PublicKeyCredentialRequestOptionsJSON, relatedOrigins?: string[]): AuthenticationResponseJSON;
24
22
  createJSON(origin: string, optionsJSON: PublicKeyCredentialCreationOptionsJSON, relatedOrigins?: string[]): RegistrationResponseJSON;
25
23
  getAuthenticatorInfo(): AuthenticatorInfo;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.WebAuthnEmulator = exports.InvalidRpIdError = exports.NoPublicKeyError = exports.WebAuthnEmulatorError = void 0;
6
+ exports.WebAuthnEmulator = exports.WebAuthnEmulatorError = void 0;
7
7
  const node_crypto_1 = require("node:crypto");
8
8
  const authenticator_emulator_1 = require("../authenticator/authenticator-emulator");
9
9
  const ctap_model_1 = require("../authenticator/ctap-model");
@@ -13,12 +13,6 @@ const webauthn_model_json_1 = require("./webauthn-model-json");
13
13
  class WebAuthnEmulatorError extends Error {
14
14
  }
15
15
  exports.WebAuthnEmulatorError = WebAuthnEmulatorError;
16
- class NoPublicKeyError extends WebAuthnEmulatorError {
17
- }
18
- exports.NoPublicKeyError = NoPublicKeyError;
19
- class InvalidRpIdError extends WebAuthnEmulatorError {
20
- }
21
- exports.InvalidRpIdError = InvalidRpIdError;
22
16
  /**
23
17
  * WebAuthn emulator
24
18
  */
@@ -27,6 +21,41 @@ class WebAuthnEmulator {
27
21
  constructor(authenticator = new authenticator_emulator_1.AuthenticatorEmulator()) {
28
22
  this.authenticator = authenticator;
29
23
  }
24
+ mapAuthenticatorErrorToDOMException(error) {
25
+ let name;
26
+ switch (error.status) {
27
+ case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_CREDENTIAL_EXCLUDED:
28
+ name = "NotAllowedError";
29
+ break;
30
+ case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_UNSUPPORTED_ALGORITHM:
31
+ name = "NotSupportedError";
32
+ break;
33
+ case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_OPERATION_DENIED:
34
+ case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED:
35
+ case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS:
36
+ name = "NotAllowedError";
37
+ break;
38
+ case ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER:
39
+ case ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_INVALID_CBOR:
40
+ name = "DataError";
41
+ break;
42
+ default:
43
+ name = "UnknownError";
44
+ break;
45
+ }
46
+ return new DOMException(error.message, name);
47
+ }
48
+ handleAuthenticatorCall(fn) {
49
+ try {
50
+ return fn();
51
+ }
52
+ catch (error) {
53
+ if (error instanceof ctap_model_1.AuthenticationEmulatorError) {
54
+ throw this.mapAuthenticatorErrorToDOMException(error);
55
+ }
56
+ throw error;
57
+ }
58
+ }
30
59
  getJSON(origin, optionsJSON, relatedOrigins = []) {
31
60
  const options = (0, webauthn_model_json_1.parseRequestOptionsFromJSON)(optionsJSON);
32
61
  const response = this.get(origin, { publicKey: options }, relatedOrigins);
@@ -38,7 +67,7 @@ class WebAuthnEmulator {
38
67
  return response.toJSON();
39
68
  }
40
69
  getAuthenticatorInfo() {
41
- const authenticatorInfo = (0, ctap_model_1.unpackGetInfoResponse)(this.authenticator.command({ command: ctap_model_1.CTAP_COMMAND.authenticatorGetInfo }));
70
+ const authenticatorInfo = this.handleAuthenticatorCall(() => (0, ctap_model_1.unpackGetInfoResponse)(this.authenticator.command({ command: ctap_model_1.CTAP_COMMAND.authenticatorGetInfo })));
42
71
  return {
43
72
  version: authenticatorInfo.versions.join(", "),
44
73
  aaguid: encode_utils_1.default.encodeBase64Url(authenticatorInfo.aaguid),
@@ -50,13 +79,13 @@ class WebAuthnEmulator {
50
79
  }
51
80
  signalUnknownCredential(options) {
52
81
  const credentialId = (0, webauthn_model_json_1.decodeBase64Url)(options.credentialId);
53
- this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
82
+ this.handleAuthenticatorCall(() => this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
54
83
  subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential,
55
84
  subCommandParams: {
56
85
  credentialId: encode_utils_1.default.bufferSourceToUint8Array(credentialId),
57
86
  rpId: options.rpId,
58
87
  },
59
- }));
88
+ })));
60
89
  }
61
90
  /**
62
91
  * Signal all accepted credentials for a user
@@ -66,12 +95,12 @@ class WebAuthnEmulator {
66
95
  // Create a set of accepted credential IDs for quick lookup
67
96
  const acceptedCredentialIds = new Set(options.allAcceptedCredentialIds);
68
97
  // Start enumerating credentials for the specified RP
69
- const beginResponse = (0, ctap_model_1.unpackCredentialManagementResponse)(this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
98
+ const beginResponse = this.handleAuthenticatorCall(() => (0, ctap_model_1.unpackCredentialManagementResponse)(this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
70
99
  subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
71
100
  subCommandParams: {
72
101
  rpId: options.rpId,
73
102
  },
74
- })));
103
+ }))));
75
104
  // Get the total number of credentials
76
105
  const totalCredentials = beginResponse.totalCredentials ?? 0;
77
106
  // Get all credentials for the RP and delete those not in the accepted list
@@ -84,9 +113,9 @@ class WebAuthnEmulator {
84
113
  // Process each credential
85
114
  for (let i = 0; i < totalCredentials; i++) {
86
115
  // Get next credential and unpack the response
87
- const credResponse = (0, ctap_model_1.unpackCredentialManagementResponse)(this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
116
+ const credResponse = this.handleAuthenticatorCall(() => (0, ctap_model_1.unpackCredentialManagementResponse)(this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
88
117
  subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsGetNextCredential,
89
- })));
118
+ }))));
90
119
  // Skip if this is not for the user we're looking for
91
120
  if (credResponse.user && encode_utils_1.default.encodeBase64Url(credResponse.user.id) !== userId) {
92
121
  continue;
@@ -96,13 +125,13 @@ class WebAuthnEmulator {
96
125
  const credentialId = encode_utils_1.default.encodeBase64Url(credResponse.credentialID);
97
126
  if (!acceptedCredentialIds.has(credentialId)) {
98
127
  // Delete the credential directly
99
- this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
128
+ this.handleAuthenticatorCall(() => this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
100
129
  subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential,
101
130
  subCommandParams: {
102
131
  credentialId: credResponse.credentialID,
103
132
  rpId: rpId,
104
133
  },
105
- }));
134
+ })));
106
135
  }
107
136
  }
108
137
  }
@@ -120,21 +149,21 @@ class WebAuthnEmulator {
120
149
  displayName: options.displayName,
121
150
  };
122
151
  // Update user information using the credential management API
123
- this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
152
+ this.handleAuthenticatorCall(() => this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
124
153
  subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
125
154
  subCommandParams: {
126
155
  rpId: options.rpId,
127
156
  user: user,
128
157
  },
129
- }));
158
+ })));
130
159
  }
131
160
  /** @see https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get */
132
161
  get(origin, options, relatedOrigins = []) {
133
162
  if (!options.publicKey)
134
- throw new NoPublicKeyError("PublicKeyCredentialCreationOptions are required");
163
+ throw new TypeError("PublicKeyCredentialRequestOptions are required");
135
164
  const rpId = new webauthn_model_1.RpId(options.publicKey.rpId ?? new URL(origin).hostname);
136
165
  if (!rpId.validate(origin, relatedOrigins))
137
- throw new InvalidRpIdError(`Invalid rpId: RP_ID=${rpId.value}, ORIGIN=${origin}`);
166
+ throw new DOMException(`Invalid rpId: RP_ID=${rpId.value}, ORIGIN=${origin}`, "SecurityError");
138
167
  const clientData = {
139
168
  type: "webauthn.get",
140
169
  challenge: encode_utils_1.default.encodeBase64Url(options.publicKey.challenge),
@@ -143,24 +172,24 @@ class WebAuthnEmulator {
143
172
  };
144
173
  const authenticatorRequest = (0, ctap_model_1.packGetAssertionRequest)({
145
174
  rpId: rpId.value,
146
- clientDataHash: (0, node_crypto_1.createHash)("sha256").update(JSON.stringify(clientData)).digest(),
175
+ clientDataHash: encode_utils_1.default.bufferSourceToUint8Array(new Uint8Array((0, node_crypto_1.createHash)("sha256").update(JSON.stringify(clientData)).digest())),
147
176
  allowList: options.publicKey.allowCredentials,
148
177
  options: (0, webauthn_model_1.toFido2RequestOptions)(options.publicKey.userVerification),
149
178
  });
150
- const authenticatorResponse = (0, ctap_model_1.unpackGetAssertionResponse)(this.authenticator.command(authenticatorRequest));
179
+ const authenticatorResponse = this.handleAuthenticatorCall(() => (0, ctap_model_1.unpackGetAssertionResponse)(this.authenticator.command(authenticatorRequest)));
151
180
  const responseId = authenticatorResponse.credential?.id ?? options.publicKey.allowCredentials?.[0]?.id;
152
181
  const authData = (0, webauthn_model_1.unpackAuthenticatorData)(authenticatorResponse.authData);
153
182
  const rawId = encode_utils_1.default.bufferSourceToUint8Array(responseId);
154
183
  const publicKeyCredential = {
155
184
  id: encode_utils_1.default.encodeBase64Url(rawId),
156
185
  type: "public-key",
157
- rawId,
186
+ rawId: rawId.buffer,
158
187
  response: {
159
- clientDataJSON: encode_utils_1.default.strToUint8Array(JSON.stringify(clientData)),
160
- authenticatorData: (0, webauthn_model_1.packAuthenticatorData)(authData),
161
- signature: authenticatorResponse.signature,
188
+ clientDataJSON: encode_utils_1.default.strToUint8Array(JSON.stringify(clientData)).buffer,
189
+ authenticatorData: (0, webauthn_model_1.packAuthenticatorData)(authData).buffer,
190
+ signature: authenticatorResponse.signature.buffer,
162
191
  userHandle: authenticatorResponse.user
163
- ? encode_utils_1.default.bufferSourceToUint8Array(authenticatorResponse.user.id)
192
+ ? encode_utils_1.default.bufferSourceToUint8Array(authenticatorResponse.user.id).buffer
164
193
  : null,
165
194
  },
166
195
  authenticatorAttachment: null,
@@ -172,10 +201,10 @@ class WebAuthnEmulator {
172
201
  /** @see https://developer.mozilla.org/ja/docs/Web/API/CredentialsContainer/create */
173
202
  create(origin, options, relatedOrigins = []) {
174
203
  if (!options.publicKey)
175
- throw new NoPublicKeyError("PublicKeyCredentialCreationOptions are required");
204
+ throw new TypeError("PublicKeyCredentialCreationOptions are required");
176
205
  const rpId = new webauthn_model_1.RpId(options.publicKey.rp.id ?? new URL(origin).hostname);
177
206
  if (!rpId.validate(origin, relatedOrigins))
178
- throw new InvalidRpIdError(`Invalid rpId: RP_ID=${rpId.value}, ORIGIN=${origin}`);
207
+ throw new DOMException(`Invalid rpId: RP_ID=${rpId.value}, ORIGIN=${origin}`, "SecurityError");
179
208
  const clientData = {
180
209
  challenge: encode_utils_1.default.encodeBase64Url(options.publicKey.challenge),
181
210
  origin,
@@ -184,14 +213,14 @@ class WebAuthnEmulator {
184
213
  };
185
214
  const clientDataJSON = JSON.stringify(clientData);
186
215
  const authenticatorRequest = (0, ctap_model_1.packMakeCredentialRequest)({
187
- clientDataHash: encode_utils_1.default.bufferSourceToUint8Array((0, node_crypto_1.createHash)("sha256").update(clientDataJSON).digest()),
216
+ clientDataHash: encode_utils_1.default.bufferSourceToUint8Array(new Uint8Array((0, node_crypto_1.createHash)("sha256").update(clientDataJSON).digest())),
188
217
  rp: { name: options.publicKey.rp.name, id: rpId.value },
189
218
  user: options.publicKey.user,
190
219
  pubKeyCredParams: options.publicKey.pubKeyCredParams,
191
220
  excludeList: options.publicKey.excludeCredentials,
192
221
  options: (0, webauthn_model_1.toFido2CreateOptions)(options.publicKey.authenticatorSelection),
193
222
  });
194
- const authenticatorResponse = (0, ctap_model_1.unpackMakeCredentialResponse)(this.authenticator.command(authenticatorRequest));
223
+ const authenticatorResponse = this.handleAuthenticatorCall(() => (0, ctap_model_1.unpackMakeCredentialResponse)(this.authenticator.command(authenticatorRequest)));
195
224
  const authData = (0, webauthn_model_1.unpackAuthenticatorData)(authenticatorResponse.authData);
196
225
  const attestedCredentialData = authData.attestedCredentialData;
197
226
  const attestationObject = {
@@ -200,17 +229,17 @@ class WebAuthnEmulator {
200
229
  authData,
201
230
  };
202
231
  const response = {
203
- attestationObject: (0, webauthn_model_1.packAttestationObject)(attestationObject),
204
- clientDataJSON: encode_utils_1.default.strToUint8Array(clientDataJSON),
205
- getAuthenticatorData: () => (0, webauthn_model_1.packAuthenticatorData)(authData),
206
- getPublicKey: () => attestedCredentialData.credentialPublicKey.toDer(),
232
+ attestationObject: (0, webauthn_model_1.packAttestationObject)(attestationObject).buffer,
233
+ clientDataJSON: encode_utils_1.default.strToUint8Array(clientDataJSON).buffer,
234
+ getAuthenticatorData: () => (0, webauthn_model_1.packAuthenticatorData)(authData).buffer,
235
+ getPublicKey: () => attestedCredentialData.credentialPublicKey.toDer().buffer,
207
236
  getPublicKeyAlgorithm: () => attestedCredentialData.credentialPublicKey.alg,
208
237
  getTransports: () => this.authenticator.params.transports,
209
238
  };
210
239
  const publicKeyCredential = {
211
240
  id: encode_utils_1.default.encodeBase64Url(attestedCredentialData.credentialId),
212
241
  type: "public-key",
213
- rawId: attestedCredentialData.credentialId,
242
+ rawId: attestedCredentialData.credentialId.buffer,
214
243
  response,
215
244
  authenticatorAttachment: null,
216
245
  getClientExtensionResults: () => ({ credProps: { rk: true } }),
@@ -3,27 +3,27 @@ import { CoseKey } from "./cose-key";
3
3
  export declare class RpId {
4
4
  readonly value: string;
5
5
  constructor(value: string);
6
- get hash(): Uint8Array;
6
+ get hash(): Uint8Array<ArrayBuffer>;
7
7
  /** @see https://www.w3.org/TR/webauthn-3/#sctn-validating-origin */
8
8
  validate(origin: string, relatedOrigins?: string[]): boolean;
9
9
  }
10
10
  /** @see https://www.w3.org/TR/webauthn-3/#public-key-credential-source */
11
11
  export type PublicKeyCredentialSource = {
12
12
  type: "public-key";
13
- id: Uint8Array;
14
- privateKey: Uint8Array;
13
+ id: Uint8Array<ArrayBuffer>;
14
+ privateKey: Uint8Array<ArrayBuffer>;
15
15
  rpId: RpId;
16
- userHandle?: Uint8Array;
16
+ userHandle?: Uint8Array<ArrayBuffer>;
17
17
  };
18
18
  /** @see https://www.w3.org/TR/webauthn-3/#sctn-attested-credential-data */
19
19
  export type AttestedCredentialData = {
20
- aaguid: Uint8Array;
21
- credentialId: Uint8Array;
20
+ aaguid: Uint8Array<ArrayBuffer>;
21
+ credentialId: Uint8Array<ArrayBuffer>;
22
22
  credentialPublicKey: CoseKey;
23
23
  };
24
24
  /** @see https://www.w3.org/TR/webauthn-3/#sctn-authenticator-data */
25
25
  export type AuthenticatorData = {
26
- rpIdHash: Uint8Array;
26
+ rpIdHash: Uint8Array<ArrayBuffer>;
27
27
  flags: {
28
28
  userPresent?: boolean;
29
29
  userVerified?: boolean;
@@ -49,10 +49,10 @@ export type CollectedClientData = {
49
49
  origin: string;
50
50
  crossOrigin: boolean;
51
51
  };
52
- export declare function packAttestationObject(attestationObject: AttestationObject): Uint8Array;
53
- export declare function packAuthenticatorData(authData: AuthenticatorData): Uint8Array;
54
- export declare function unpackAttestationObject(attestationObject: Uint8Array): AttestationObject;
55
- export declare function unpackAuthenticatorData(authData: Uint8Array): AuthenticatorData;
52
+ export declare function packAttestationObject(attestationObject: AttestationObject): Uint8Array<ArrayBuffer>;
53
+ export declare function packAuthenticatorData(authData: AuthenticatorData): Uint8Array<ArrayBuffer>;
54
+ export declare function unpackAttestationObject(attestationObject: Uint8Array<ArrayBuffer>): AttestationObject;
55
+ export declare function unpackAuthenticatorData(authData: Uint8Array<ArrayBuffer>): AuthenticatorData;
56
56
  export type PublicKeyCredentialSourceJSON = {
57
57
  type: "public-key";
58
58
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nid-webauthn-emulator",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "keywords": [
5
5
  "test",
6
6
  "e2e",
@@ -15,19 +15,19 @@
15
15
  "main": "dist/src/index.js",
16
16
  "types": "dist/src/index.d.ts",
17
17
  "devDependencies": {
18
- "@biomejs/biome": "^2.1.2",
19
- "@jest/globals": "^30.0.4",
20
- "@simplewebauthn/server": "^13.1.2",
18
+ "@biomejs/biome": "^2.2.4",
19
+ "@jest/globals": "^30.2.0",
20
+ "@simplewebauthn/server": "^13.2.1",
21
21
  "@tsconfig/recommended": "^1.0.10",
22
- "@types/node": "^24.2.0",
23
- "jest": "^30.0.5",
24
- "ts-jest": "^29.4.1",
22
+ "@types/node": "^24.5.2",
23
+ "jest": "^30.2.0",
24
+ "ts-jest": "^29.4.4",
25
25
  "ts-node": "^10.9.2",
26
- "typescript": "^5.8.3"
26
+ "typescript": "^5.9.2"
27
27
  },
28
28
  "dependencies": {
29
- "cbor": "^10.0.10",
30
- "tldts": "^7.0.10"
29
+ "cbor": "^10.0.11",
30
+ "tldts": "^7.0.16"
31
31
  },
32
32
  "scripts": {
33
33
  "check": "biome check .",