nid-webauthn-emulator 0.2.13 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +7 -1
- package/README.md +7 -1
- package/dist/spec/authenticator/authenticator-emulator.spec.js +66 -65
- package/dist/spec/authenticator/ctap-model.spec.js +58 -53
- package/dist/spec/integration/integration.spec.js +7 -4
- package/dist/spec/integration/passkeys-api-client.d.ts +0 -1
- package/dist/spec/integration/webauthn-io.d.ts +0 -1
- package/dist/spec/libs/browser-injection.spec.js +11 -7
- package/dist/spec/libs/cbor.spec.js +48 -35
- package/dist/spec/libs/cose-key.spec.js +46 -40
- package/dist/spec/repository/credential-repository.spec.js +13 -12
- package/dist/spec/test-utils/unit-test.spec.js +28 -27
- package/dist/spec/webauthn/cose-key.spec.js +18 -17
- package/dist/spec/webauthn/webauthn-emulator.spec.js +138 -137
- package/dist/spec/webauthn/webauthn-model-json.spec.js +24 -23
- package/dist/spec/webauthn/webauthn-model.spec.js +25 -17
- package/dist/spec/webauthn/webauthn-test-server.d.ts +0 -1
- package/dist/spec/webauthn/webauthn-test-server.js +2 -2
- package/dist/src/repository/credentials-repository.d.ts +0 -1
- package/dist/src/test-utils/unit-test.d.ts +7 -5
- package/dist/src/webauthn/webauthn-emulator.d.ts +4 -4
- package/dist/src/webauthn/webauthn-model-json.d.ts +2 -94
- package/dist/src/webauthn/webauthn-model-json.js +1 -1
- package/package.json +6 -8
package/README.ja.md
CHANGED
|
@@ -94,6 +94,12 @@ const authenticatorResponse = unpackMakeCredentialResponse(this.authenticator.co
|
|
|
94
94
|
|
|
95
95
|
WebAuthn の著名なデモサイトである [webauthn.io](https://webauthn.io/) での使用例を示します。[統合テスト](spec/integration/integration.spec.ts) に実際にうごくテストコード例があります。
|
|
96
96
|
|
|
97
|
+
webauthn.io の統合テストは外部サイトに依存するため、通常の `pnpm test` ではスキップされます。このテストだけを実行する場合は、下記のコマンドを利用してください。
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
pnpm test:webauthn-io
|
|
101
|
+
```
|
|
102
|
+
|
|
97
103
|
```TypeScript
|
|
98
104
|
// Origin および WebAuthn API エミュレータを初期化します
|
|
99
105
|
// ここでは、https://webauthn.io を Origin として利用します
|
|
@@ -203,7 +209,7 @@ await page.evaluate(BrowserInjection.HookWebAuthnApis);
|
|
|
203
209
|
|
|
204
210
|
## ユニットテストでの WebAuthn API フック
|
|
205
211
|
|
|
206
|
-
|
|
212
|
+
ユニットテストで WebAuthn API を差し替えたい場合は、`src/test-utils/unit-test.ts` の `createPasskeysEmulator` を使うと `PublicKeyCredential` / `navigator.credentials` を簡単にモックできます。
|
|
207
213
|
|
|
208
214
|
```TypeScript
|
|
209
215
|
import { createPasskeysEmulator } from "nid-webauthn-emulator";
|
package/README.md
CHANGED
|
@@ -95,6 +95,12 @@ const authenticatorResponse = unpackMakeCredentialResponse(this.authenticator.co
|
|
|
95
95
|
|
|
96
96
|
Here's an example of usage with [webauthn.io](https://webauthn.io/), a well-known WebAuthn demo site. You can find working test code examples in the [integration tests](spec/integration/integration.spec.ts).
|
|
97
97
|
|
|
98
|
+
The webauthn.io integration test is skipped by the default `pnpm test` command because it depends on an external site. To run only that test, use:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
pnpm test:webauthn-io
|
|
102
|
+
```
|
|
103
|
+
|
|
98
104
|
```TypeScript
|
|
99
105
|
// Initialize the Origin and WebAuthn API emulator
|
|
100
106
|
// Here we use https://webauthn.io as the Origin
|
|
@@ -212,7 +218,7 @@ This ensures that the `WebAuthnEmulator` methods defined earlier with `exposeFun
|
|
|
212
218
|
|
|
213
219
|
## Hooking WebAuthn APIs in Unit Tests
|
|
214
220
|
|
|
215
|
-
If you want to swap out WebAuthn APIs in
|
|
221
|
+
If you want to swap out WebAuthn APIs in unit tests, you can mock `PublicKeyCredential` / `navigator.credentials` using `createPasskeysEmulator` from `src/test-utils/unit-test.ts`.
|
|
216
222
|
|
|
217
223
|
```TypeScript
|
|
218
224
|
import { createPasskeysEmulator } from "nid-webauthn-emulator";
|
|
@@ -3,24 +3,25 @@ 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
|
-
const
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = require("node:test");
|
|
7
8
|
const src_1 = require("../../src");
|
|
8
9
|
const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
9
10
|
const encode_utils_1 = __importDefault(require("../../src/libs/encode-utils"));
|
|
10
11
|
const credentials_memory_repository_1 = require("../../src/repository/credentials-memory-repository");
|
|
11
|
-
(0,
|
|
12
|
+
(0, node_test_1.describe)("Authenticator Emulator Exceptional Test", () => {
|
|
12
13
|
// Success case has been tested in the webauthn-emulator.spec.ts
|
|
13
|
-
(0,
|
|
14
|
+
(0, node_test_1.test)("Unknown command _ CTAP Error", async () => {
|
|
14
15
|
const testRequest = {
|
|
15
16
|
command: ctap_model_1.CTAP_COMMAND.authenticatorReset,
|
|
16
17
|
};
|
|
17
18
|
const authenticator = new src_1.AuthenticatorEmulator();
|
|
18
|
-
|
|
19
|
+
strict_1.default.throws(() => {
|
|
19
20
|
authenticator.command(testRequest);
|
|
20
|
-
}
|
|
21
|
+
}, { message: "CTAP error: CTAP1_ERR_INVALID_COMMAND (1)" });
|
|
21
22
|
});
|
|
22
23
|
});
|
|
23
|
-
(0,
|
|
24
|
+
(0, node_test_1.describe)("Authenticator Credential Management Tests", () => {
|
|
24
25
|
let authenticator;
|
|
25
26
|
let repository;
|
|
26
27
|
// Test data
|
|
@@ -41,7 +42,7 @@ const credentials_memory_repository_1 = require("../../src/repository/credential
|
|
|
41
42
|
name: "user3",
|
|
42
43
|
displayName: "User Three",
|
|
43
44
|
};
|
|
44
|
-
(0,
|
|
45
|
+
(0, node_test_1.beforeEach)(() => {
|
|
45
46
|
// Create a new repository for each test
|
|
46
47
|
repository = new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository();
|
|
47
48
|
// Create authenticator with the repository
|
|
@@ -64,8 +65,8 @@ const credentials_memory_repository_1 = require("../../src/repository/credential
|
|
|
64
65
|
};
|
|
65
66
|
authenticator.authenticatorMakeCredential(makeCredentialRequest);
|
|
66
67
|
}
|
|
67
|
-
(0,
|
|
68
|
-
(0,
|
|
68
|
+
(0, node_test_1.describe)("enumerateCredentialsBegin", () => {
|
|
69
|
+
(0, node_test_1.test)("should return the total number of credentials for a specific RP", () => {
|
|
69
70
|
// Create request for enumerateCredentialsBegin
|
|
70
71
|
const request = {
|
|
71
72
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
@@ -76,19 +77,19 @@ const credentials_memory_repository_1 = require("../../src/repository/credential
|
|
|
76
77
|
// Execute the command
|
|
77
78
|
const response = authenticator.authenticatorCredentialManagement(request);
|
|
78
79
|
// Verify the response
|
|
79
|
-
|
|
80
|
+
strict_1.default.equal(response.totalCredentials, 2); // We created 2 credentials for rpId1
|
|
80
81
|
});
|
|
81
|
-
(0,
|
|
82
|
+
(0, node_test_1.test)("should throw an error if rpId is not provided", () => {
|
|
82
83
|
// Create request without rpId
|
|
83
84
|
const request = {
|
|
84
85
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
85
86
|
};
|
|
86
87
|
// Execute the command and expect an error
|
|
87
|
-
|
|
88
|
+
strict_1.default.throws(() => {
|
|
88
89
|
authenticator.authenticatorCredentialManagement(request);
|
|
89
|
-
}
|
|
90
|
+
}, new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER));
|
|
90
91
|
});
|
|
91
|
-
(0,
|
|
92
|
+
(0, node_test_1.test)("should return 0 credentials for an RP with no credentials", () => {
|
|
92
93
|
// Create request for a non-existent RP
|
|
93
94
|
const request = {
|
|
94
95
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
@@ -99,11 +100,11 @@ const credentials_memory_repository_1 = require("../../src/repository/credential
|
|
|
99
100
|
// Execute the command
|
|
100
101
|
const response = authenticator.authenticatorCredentialManagement(request);
|
|
101
102
|
// Verify the response
|
|
102
|
-
|
|
103
|
+
strict_1.default.equal(response.totalCredentials, 0);
|
|
103
104
|
});
|
|
104
105
|
});
|
|
105
|
-
(0,
|
|
106
|
-
(0,
|
|
106
|
+
(0, node_test_1.describe)("enumerateCredentialsGetNextCredential", () => {
|
|
107
|
+
(0, node_test_1.test)("should return credentials one by one", () => {
|
|
107
108
|
// First, call enumerateCredentialsBegin
|
|
108
109
|
const beginRequest = {
|
|
109
110
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
@@ -118,34 +119,34 @@ const credentials_memory_repository_1 = require("../../src/repository/credential
|
|
|
118
119
|
};
|
|
119
120
|
// Get first credential
|
|
120
121
|
const response1 = authenticator.authenticatorCredentialManagement(getNextRequest);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
strict_1.default.notEqual(response1.user, undefined);
|
|
123
|
+
strict_1.default.notEqual(response1.credentialID, undefined);
|
|
124
|
+
strict_1.default.notEqual(response1.publicKey, undefined);
|
|
124
125
|
// Get second credential
|
|
125
126
|
const response2 = authenticator.authenticatorCredentialManagement(getNextRequest);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
strict_1.default.notEqual(response2.user, undefined);
|
|
128
|
+
strict_1.default.notEqual(response2.credentialID, undefined);
|
|
129
|
+
strict_1.default.notEqual(response2.publicKey, undefined);
|
|
129
130
|
// Verify we got different credentials
|
|
130
131
|
if (response1.credentialID && response2.credentialID) {
|
|
131
|
-
|
|
132
|
+
strict_1.default.notEqual(encode_utils_1.default.encodeBase64Url(response1.credentialID), encode_utils_1.default.encodeBase64Url(response2.credentialID));
|
|
132
133
|
}
|
|
133
134
|
// Verify we can't get more credentials
|
|
134
|
-
|
|
135
|
+
strict_1.default.throws(() => {
|
|
135
136
|
authenticator.authenticatorCredentialManagement(getNextRequest);
|
|
136
|
-
}
|
|
137
|
+
}, new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS));
|
|
137
138
|
});
|
|
138
|
-
(0,
|
|
139
|
+
(0, node_test_1.test)("should throw an error if called without calling enumerateCredentialsBegin first", () => {
|
|
139
140
|
const getNextRequest = {
|
|
140
141
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsGetNextCredential,
|
|
141
142
|
};
|
|
142
|
-
|
|
143
|
+
strict_1.default.throws(() => {
|
|
143
144
|
authenticator.authenticatorCredentialManagement(getNextRequest);
|
|
144
|
-
}
|
|
145
|
+
}, new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS));
|
|
145
146
|
});
|
|
146
147
|
});
|
|
147
|
-
(0,
|
|
148
|
-
(0,
|
|
148
|
+
(0, node_test_1.describe)("updateUserInformation", () => {
|
|
149
|
+
(0, node_test_1.test)("should update user information for all credentials of a user", () => {
|
|
149
150
|
// Create updated user information
|
|
150
151
|
const updatedUser = {
|
|
151
152
|
id: user1.id, // Same ID
|
|
@@ -178,33 +179,33 @@ const credentials_memory_repository_1 = require("../../src/repository/credential
|
|
|
178
179
|
// Find the credential for user1
|
|
179
180
|
if (nextResponse.user &&
|
|
180
181
|
encode_utils_1.default.encodeBase64Url(nextResponse.user.id) === encode_utils_1.default.encodeBase64Url(user1.id)) {
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
strict_1.default.equal(nextResponse.user.name, updatedUser.name);
|
|
183
|
+
strict_1.default.equal(nextResponse.user.displayName, updatedUser.displayName);
|
|
183
184
|
}
|
|
184
185
|
});
|
|
185
|
-
(0,
|
|
186
|
+
(0, node_test_1.test)("should throw an error if rpId is not provided", () => {
|
|
186
187
|
const request = {
|
|
187
188
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
188
189
|
subCommandParams: {
|
|
189
190
|
user: user1,
|
|
190
191
|
},
|
|
191
192
|
};
|
|
192
|
-
|
|
193
|
+
strict_1.default.throws(() => {
|
|
193
194
|
authenticator.authenticatorCredentialManagement(request);
|
|
194
|
-
}
|
|
195
|
+
}, new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER));
|
|
195
196
|
});
|
|
196
|
-
(0,
|
|
197
|
+
(0, node_test_1.test)("should throw an error if user is not provided", () => {
|
|
197
198
|
const request = {
|
|
198
199
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
199
200
|
subCommandParams: {
|
|
200
201
|
rpId: rpId1,
|
|
201
202
|
},
|
|
202
203
|
};
|
|
203
|
-
|
|
204
|
+
strict_1.default.throws(() => {
|
|
204
205
|
authenticator.authenticatorCredentialManagement(request);
|
|
205
|
-
}
|
|
206
|
+
}, new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER));
|
|
206
207
|
});
|
|
207
|
-
(0,
|
|
208
|
+
(0, node_test_1.test)("should throw an error if no credentials exist for the user", () => {
|
|
208
209
|
const nonExistentUser = {
|
|
209
210
|
id: encode_utils_1.default.strToUint8Array("nonexistent-id"),
|
|
210
211
|
name: "nonexistent",
|
|
@@ -217,13 +218,13 @@ const credentials_memory_repository_1 = require("../../src/repository/credential
|
|
|
217
218
|
user: nonExistentUser,
|
|
218
219
|
},
|
|
219
220
|
};
|
|
220
|
-
|
|
221
|
+
strict_1.default.throws(() => {
|
|
221
222
|
authenticator.authenticatorCredentialManagement(request);
|
|
222
|
-
}
|
|
223
|
+
}, new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS));
|
|
223
224
|
});
|
|
224
225
|
});
|
|
225
|
-
(0,
|
|
226
|
-
(0,
|
|
226
|
+
(0, node_test_1.describe)("authenticatorCredentialManagement with stateless authenticator", () => {
|
|
227
|
+
(0, node_test_1.test)("should throw an error if authenticator is stateless", () => {
|
|
227
228
|
// Create a stateless authenticator
|
|
228
229
|
const statelessAuthenticator = new src_1.AuthenticatorEmulator({
|
|
229
230
|
stateless: true,
|
|
@@ -236,76 +237,76 @@ const credentials_memory_repository_1 = require("../../src/repository/credential
|
|
|
236
237
|
},
|
|
237
238
|
};
|
|
238
239
|
// Execute the command and expect an error
|
|
239
|
-
|
|
240
|
+
strict_1.default.throws(() => {
|
|
240
241
|
statelessAuthenticator.authenticatorCredentialManagement(request);
|
|
241
|
-
}
|
|
242
|
+
}, new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
242
243
|
});
|
|
243
244
|
});
|
|
244
|
-
(0,
|
|
245
|
-
(0,
|
|
245
|
+
(0, node_test_1.describe)("Authenticator Credential Management additional error coverage", () => {
|
|
246
|
+
(0, node_test_1.test)("dispatch default branch throws invalid command", () => {
|
|
246
247
|
const authenticator = new src_1.AuthenticatorEmulator();
|
|
247
248
|
// Build a CTAP request with an unknown subCommand to hit default branch
|
|
248
249
|
const data = encode_utils_1.default.encodeCbor({ "1": -1 });
|
|
249
|
-
|
|
250
|
+
strict_1.default.throws(() => authenticator.command({ command: ctap_model_1.CTAP_COMMAND.authenticatorCredentialManagement, data }), new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_COMMAND));
|
|
250
251
|
});
|
|
251
|
-
(0,
|
|
252
|
+
(0, node_test_1.test)("enumerateCredentialsBegin without repository throws not allowed", () => {
|
|
252
253
|
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
253
254
|
const req = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
254
255
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
255
256
|
subCommandParams: { rpId: "example.com" },
|
|
256
257
|
});
|
|
257
|
-
|
|
258
|
+
strict_1.default.throws(() => authenticator.command(req), new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
258
259
|
});
|
|
259
|
-
(0,
|
|
260
|
+
(0, node_test_1.test)("updateUserInformation without repository throws not allowed", () => {
|
|
260
261
|
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
261
262
|
const req = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
262
263
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
263
264
|
subCommandParams: { rpId: "example.com", user: { id: new Uint8Array([1]), name: "n", displayName: "d" } },
|
|
264
265
|
});
|
|
265
|
-
|
|
266
|
+
strict_1.default.throws(() => authenticator.command(req), new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
266
267
|
});
|
|
267
|
-
(0,
|
|
268
|
+
(0, node_test_1.test)("deleteCredential without repository throws not allowed", () => {
|
|
268
269
|
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
269
270
|
const req = (0, ctap_model_1.packCredentialManagementRequest)({ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential });
|
|
270
|
-
|
|
271
|
+
strict_1.default.throws(() => authenticator.command(req), new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
271
272
|
});
|
|
272
|
-
(0,
|
|
273
|
+
(0, node_test_1.test)("deleteCredential without credentialId throws invalid parameter", () => {
|
|
273
274
|
const authenticator = new src_1.AuthenticatorEmulator();
|
|
274
275
|
const req = (0, ctap_model_1.packCredentialManagementRequest)({ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential });
|
|
275
|
-
|
|
276
|
+
strict_1.default.throws(() => authenticator.command(req), new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER));
|
|
276
277
|
});
|
|
277
|
-
(0,
|
|
278
|
+
(0, node_test_1.test)("deleteCredential for non-existent id throws no credentials", () => {
|
|
278
279
|
const authenticator = new src_1.AuthenticatorEmulator();
|
|
279
280
|
const credentialId = encode_utils_1.default.strToUint8Array("non-existent-id");
|
|
280
281
|
const req = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
281
282
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential,
|
|
282
283
|
subCommandParams: { credentialId },
|
|
283
284
|
});
|
|
284
|
-
|
|
285
|
+
strict_1.default.throws(() => authenticator.command(req), new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS));
|
|
285
286
|
});
|
|
286
|
-
(0,
|
|
287
|
+
(0, node_test_1.test)("private enumerateCredentialsBegin guard throws not allowed when repository missing", () => {
|
|
287
288
|
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
288
289
|
const target = authenticator.authenticatorEnumerateCredentialsBegin.bind(authenticator);
|
|
289
290
|
const call = () => target({
|
|
290
291
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
291
292
|
subCommandParams: { rpId: "x" },
|
|
292
293
|
});
|
|
293
|
-
(
|
|
294
|
+
strict_1.default.throws(call, new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
294
295
|
});
|
|
295
|
-
(0,
|
|
296
|
+
(0, node_test_1.test)("private updateUserInformation guard throws not allowed when repository missing", () => {
|
|
296
297
|
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
297
298
|
const target = authenticator.authenticatorUpdateUserInformation.bind(authenticator);
|
|
298
299
|
const call = () => target({
|
|
299
300
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
300
301
|
subCommandParams: { rpId: "x", user: { id: new Uint8Array([1]), name: "n", displayName: "d" } },
|
|
301
302
|
});
|
|
302
|
-
(
|
|
303
|
+
strict_1.default.throws(call, new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
303
304
|
});
|
|
304
|
-
(0,
|
|
305
|
+
(0, node_test_1.test)("private deleteCredential guard throws not allowed when repository missing", () => {
|
|
305
306
|
const authenticator = new src_1.AuthenticatorEmulator({ stateless: true });
|
|
306
307
|
const target = authenticator.authenticatorDeleteCredential.bind(authenticator);
|
|
307
308
|
const call = () => target({ subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential });
|
|
308
|
-
(
|
|
309
|
+
strict_1.default.throws(call, new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
309
310
|
});
|
|
310
311
|
});
|
|
311
312
|
});
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = require("node:test");
|
|
4
8
|
const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
5
|
-
(0,
|
|
6
|
-
(0,
|
|
9
|
+
(0, node_test_1.describe)("CTAP Model Test", () => {
|
|
10
|
+
(0, node_test_1.test)("MakeCredentialRequest CTAP Object pack and unpack", async () => {
|
|
7
11
|
const testRequest = {
|
|
8
12
|
clientDataHash: new Uint8Array([99, 104, 97, 108, 108, 101, 110, 103, 101]),
|
|
9
13
|
rp: { name: "webauthn.io", id: "webauthn.io" },
|
|
@@ -18,10 +22,10 @@ const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
|
18
22
|
const packed = (0, ctap_model_1.packMakeCredentialRequest)(testRequest);
|
|
19
23
|
const unpacked = (0, ctap_model_1.unpackRequest)(packed);
|
|
20
24
|
const rePacked = (0, ctap_model_1.packMakeCredentialRequest)(unpacked.request);
|
|
21
|
-
(
|
|
22
|
-
|
|
25
|
+
strict_1.default.deepEqual(packed, rePacked);
|
|
26
|
+
strict_1.default.deepEqual(unpacked.request, testRequest);
|
|
23
27
|
});
|
|
24
|
-
(0,
|
|
28
|
+
(0, node_test_1.test)("GetAssertionRequest CTAP Object pack and unpack", async () => {
|
|
25
29
|
const testRequest = {
|
|
26
30
|
rpId: "webauthn.io",
|
|
27
31
|
clientDataHash: new Uint8Array([99, 104, 97, 108, 108, 101, 110, 103, 101]),
|
|
@@ -34,10 +38,10 @@ const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
|
34
38
|
const packed = (0, ctap_model_1.packGetAssertionRequest)(testRequest);
|
|
35
39
|
const unpacked = (0, ctap_model_1.unpackRequest)(packed);
|
|
36
40
|
const rePacked = (0, ctap_model_1.packGetAssertionRequest)(unpacked.request);
|
|
37
|
-
(
|
|
38
|
-
|
|
41
|
+
strict_1.default.deepEqual(packed, rePacked);
|
|
42
|
+
strict_1.default.deepEqual(unpacked.request, testRequest);
|
|
39
43
|
});
|
|
40
|
-
(0,
|
|
44
|
+
(0, node_test_1.test)("MakeCredentialResponse CTAP Object pack and unpack", async () => {
|
|
41
45
|
const testResponse = {
|
|
42
46
|
fmt: "none",
|
|
43
47
|
attStmt: {},
|
|
@@ -46,11 +50,12 @@ const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
|
46
50
|
const packed = (0, ctap_model_1.packMakeCredentialResponse)(testResponse);
|
|
47
51
|
const unpacked = (0, ctap_model_1.unpackMakeCredentialResponse)(packed);
|
|
48
52
|
const rePacked = (0, ctap_model_1.packMakeCredentialResponse)(unpacked);
|
|
49
|
-
(
|
|
50
|
-
(
|
|
53
|
+
strict_1.default.deepEqual(packed, rePacked);
|
|
54
|
+
strict_1.default.deepEqual(unpacked, testResponse);
|
|
51
55
|
});
|
|
52
|
-
(0,
|
|
56
|
+
(0, node_test_1.test)("GetAssertionResponse CTAP Object pack and unpack", async () => {
|
|
53
57
|
const testResponse = {
|
|
58
|
+
credential: undefined,
|
|
54
59
|
authData: new Uint8Array([97, 117, 116, 104, 68, 97, 116, 97]),
|
|
55
60
|
signature: new Uint8Array([115, 105, 103]),
|
|
56
61
|
user: { id: new Uint8Array([116, 101, 115, 116, 45, 117, 115, 101, 114]), name: "user", displayName: "user" },
|
|
@@ -59,10 +64,10 @@ const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
|
59
64
|
const packed = (0, ctap_model_1.packGetAssertionResponse)(testResponse);
|
|
60
65
|
const unpacked = (0, ctap_model_1.unpackGetAssertionResponse)(packed);
|
|
61
66
|
const rePacked = (0, ctap_model_1.packGetAssertionResponse)(unpacked);
|
|
62
|
-
(
|
|
63
|
-
(
|
|
67
|
+
strict_1.default.deepEqual(packed, rePacked);
|
|
68
|
+
strict_1.default.deepEqual(unpacked, testResponse);
|
|
64
69
|
});
|
|
65
|
-
(0,
|
|
70
|
+
(0, node_test_1.test)("GetInfoResponse CTAP Object pack and unpack", async () => {
|
|
66
71
|
const testResponse = {
|
|
67
72
|
versions: ["U2F_V2"],
|
|
68
73
|
extensions: ["credProtect"],
|
|
@@ -74,48 +79,48 @@ const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
|
74
79
|
const packed = (0, ctap_model_1.packGetInfoResponse)(testResponse);
|
|
75
80
|
const unpacked = (0, ctap_model_1.unpackGetInfoResponse)(packed);
|
|
76
81
|
const rePacked = (0, ctap_model_1.packGetInfoResponse)(unpacked);
|
|
77
|
-
(
|
|
78
|
-
(
|
|
82
|
+
strict_1.default.deepEqual(packed, rePacked);
|
|
83
|
+
strict_1.default.deepEqual(unpacked, testResponse);
|
|
79
84
|
});
|
|
80
|
-
(0,
|
|
85
|
+
(0, node_test_1.test)("Illegal data parse test _ failed to unpack", async () => {
|
|
81
86
|
const response = {};
|
|
82
87
|
const expected = "CTAP error: CTAP2_ERR_INVALID_CBOR (18)";
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
strict_1.default.throws(() => (0, ctap_model_1.unpackMakeCredentialResponse)(response), { message: expected });
|
|
89
|
+
strict_1.default.throws(() => (0, ctap_model_1.unpackGetAssertionResponse)(response), { message: expected });
|
|
90
|
+
strict_1.default.throws(() => (0, ctap_model_1.unpackGetInfoResponse)(response), { message: expected });
|
|
86
91
|
});
|
|
87
|
-
(0,
|
|
92
|
+
(0, node_test_1.test)("Illegal cbor data parse test _ failed to unpack", async () => {
|
|
88
93
|
const request = {
|
|
89
94
|
command: ctap_model_1.CTAP_COMMAND.authenticatorMakeCredential,
|
|
90
95
|
data: new Uint8Array([1, 2, 3, 4, 5]),
|
|
91
96
|
};
|
|
92
|
-
|
|
97
|
+
strict_1.default.throws(() => (0, ctap_model_1.unpackRequest)(request), { message: "CTAP error: CTAP2_ERR_INVALID_CBOR (18)" });
|
|
93
98
|
});
|
|
94
|
-
(0,
|
|
99
|
+
(0, node_test_1.test)("unpackRequest default branch returns undefined request for unhandled command", () => {
|
|
95
100
|
const req = { command: ctap_model_1.CTAP_COMMAND.authenticatorClientPIN }; // not explicitly handled in switch
|
|
96
101
|
const unpacked = (0, ctap_model_1.unpackRequest)(req);
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
strict_1.default.equal(unpacked.command, ctap_model_1.CTAP_COMMAND.authenticatorClientPIN);
|
|
103
|
+
strict_1.default.equal(unpacked.request, undefined);
|
|
99
104
|
});
|
|
100
|
-
(0,
|
|
105
|
+
(0, node_test_1.test)("credential management request with undefined optional params round-trips", () => {
|
|
101
106
|
const packed = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
102
107
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
103
108
|
// subCommandParams and pinUvAuth* omitted intentionally
|
|
104
109
|
});
|
|
105
110
|
const unpacked = (0, ctap_model_1.unpackCredentialManagementRequest)(packed);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
111
|
+
strict_1.default.equal(unpacked.subCommand, ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin);
|
|
112
|
+
strict_1.default.equal(unpacked.subCommandParams, undefined);
|
|
113
|
+
strict_1.default.equal(unpacked.pinUvAuthParam, undefined);
|
|
114
|
+
strict_1.default.equal(unpacked.pinUvAuthProtocol, undefined);
|
|
110
115
|
});
|
|
111
|
-
(0,
|
|
116
|
+
(0, node_test_1.test)("credential management response optional binary fields handled when absent/present", () => {
|
|
112
117
|
// Absent case
|
|
113
118
|
const packedAbsent = (0, ctap_model_1.packCredentialManagementResponse)({});
|
|
114
119
|
const absent = (0, ctap_model_1.unpackCredentialManagementResponse)(packedAbsent);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
120
|
+
strict_1.default.equal(absent.credentialID, undefined);
|
|
121
|
+
strict_1.default.equal(absent.publicKey, undefined);
|
|
122
|
+
strict_1.default.equal(absent.rpIDHash, undefined);
|
|
123
|
+
strict_1.default.equal(absent.largeBlobKey, undefined);
|
|
119
124
|
// Present case
|
|
120
125
|
const bin = new Uint8Array([1, 2, 3]);
|
|
121
126
|
const packedPresent = (0, ctap_model_1.packCredentialManagementResponse)({
|
|
@@ -125,32 +130,32 @@ const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
|
125
130
|
largeBlobKey: bin,
|
|
126
131
|
});
|
|
127
132
|
const present = (0, ctap_model_1.unpackCredentialManagementResponse)(packedPresent);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
133
|
+
strict_1.default.notEqual(present.credentialID, undefined);
|
|
134
|
+
strict_1.default.notEqual(present.publicKey, undefined);
|
|
135
|
+
strict_1.default.notEqual(present.rpIDHash, undefined);
|
|
136
|
+
strict_1.default.notEqual(present.largeBlobKey, undefined);
|
|
132
137
|
if (present.credentialID && present.publicKey && present.rpIDHash && present.largeBlobKey) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
strict_1.default.deepEqual(Array.from(present.credentialID), [1, 2, 3]);
|
|
139
|
+
strict_1.default.deepEqual(Array.from(present.publicKey), [1, 2, 3]);
|
|
140
|
+
strict_1.default.deepEqual(Array.from(present.rpIDHash), [1, 2, 3]);
|
|
141
|
+
strict_1.default.deepEqual(Array.from(present.largeBlobKey), [1, 2, 3]);
|
|
137
142
|
}
|
|
138
143
|
});
|
|
139
|
-
(0,
|
|
144
|
+
(0, node_test_1.test)("unpackCredentialManagementRequest throws on invalid CBOR", () => {
|
|
140
145
|
const bad = {
|
|
141
146
|
command: ctap_model_1.CTAP_COMMAND.authenticatorCredentialManagement,
|
|
142
147
|
data: new Uint8Array([1, 2, 3]),
|
|
143
148
|
};
|
|
144
|
-
|
|
149
|
+
strict_1.default.throws(() => (0, ctap_model_1.unpackCredentialManagementRequest)(bad), /CTAP2_ERR_INVALID_CBOR/);
|
|
145
150
|
});
|
|
146
|
-
(0,
|
|
151
|
+
(0, node_test_1.test)("unpackCredentialManagementResponse throws on invalid CBOR", () => {
|
|
147
152
|
const bad = {
|
|
148
153
|
status: ctap_model_1.CTAP_STATUS_CODE.CTAP2_OK,
|
|
149
154
|
data: new Uint8Array([1, 2, 3]),
|
|
150
155
|
};
|
|
151
|
-
|
|
156
|
+
strict_1.default.throws(() => (0, ctap_model_1.unpackCredentialManagementResponse)(bad), /CTAP2_ERR_INVALID_CBOR/);
|
|
152
157
|
});
|
|
153
|
-
(0,
|
|
158
|
+
(0, node_test_1.test)("unpackRequest CM includes pinUvAuthParam when present", () => {
|
|
154
159
|
const pin = new Uint8Array([7, 7]);
|
|
155
160
|
const packed = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
156
161
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
@@ -160,12 +165,12 @@ const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
|
160
165
|
const unpacked = (0, ctap_model_1.unpackRequest)(packed);
|
|
161
166
|
const req = unpacked.request;
|
|
162
167
|
const isMgmt = (x) => typeof x === "object" && x !== null && "subCommand" in x;
|
|
163
|
-
|
|
168
|
+
strict_1.default.equal(isMgmt(req), true);
|
|
164
169
|
if (isMgmt(req)) {
|
|
165
|
-
|
|
170
|
+
strict_1.default.deepEqual(Array.from(req.pinUvAuthParam ?? new Uint8Array()), [7, 7]);
|
|
166
171
|
}
|
|
167
172
|
});
|
|
168
|
-
(0,
|
|
173
|
+
(0, node_test_1.test)("unpackCredentialManagementRequest includes pinUvAuthParam when present", () => {
|
|
169
174
|
const pin = new Uint8Array([9, 9]);
|
|
170
175
|
const packed = (0, ctap_model_1.packCredentialManagementRequest)({
|
|
171
176
|
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
@@ -173,6 +178,6 @@ const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
|
173
178
|
pinUvAuthParam: pin,
|
|
174
179
|
});
|
|
175
180
|
const req = (0, ctap_model_1.unpackCredentialManagementRequest)(packed);
|
|
176
|
-
|
|
181
|
+
strict_1.default.deepEqual(Array.from(req.pinUvAuthParam ?? new Uint8Array()), [9, 9]);
|
|
177
182
|
});
|
|
178
183
|
});
|
|
@@ -3,11 +3,14 @@ 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
|
-
const
|
|
6
|
+
const node_test_1 = require("node:test");
|
|
7
7
|
const index_1 = __importDefault(require("../../src/index"));
|
|
8
8
|
const webauthn_io_1 = require("./webauthn-io");
|
|
9
|
-
(0,
|
|
10
|
-
|
|
9
|
+
(0, node_test_1.describe)("Passkeys Integration Test by webauthn.io", () => {
|
|
10
|
+
(0, node_test_1.test)("Registration Ceremony and Authentication Ceremony", {
|
|
11
|
+
timeout: 60_000,
|
|
12
|
+
skip: process.env.RUN_WEBAUTHN_IO !== "1",
|
|
13
|
+
}, async () => {
|
|
11
14
|
const origin = "https://webauthn.io";
|
|
12
15
|
const emulator = new index_1.default();
|
|
13
16
|
const webauthnIO = await webauthn_io_1.WebAuthnIO.create();
|
|
@@ -28,5 +31,5 @@ const webauthn_io_1 = require("./webauthn-io");
|
|
|
28
31
|
console.log("Authentication credential", requestCredential);
|
|
29
32
|
await webauthnIO.getAuthenticationVerification(requestCredential);
|
|
30
33
|
console.log("Authentication verification completed");
|
|
31
|
-
}
|
|
34
|
+
});
|
|
32
35
|
});
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { AuthenticationResponseJSON, PublicKeyCredentialCreationOptionsJSON, PublicKeyCredentialRequestOptionsJSON, RegistrationResponseJSON } from "../../src/webauthn/webauthn-model-json";
|
|
2
1
|
import type { PasskeysApiClient, PasskeysUser } from "./passkeys-api-client";
|
|
3
2
|
/**
|
|
4
3
|
* Passkeys Api Client implementation for webauthn.io
|