nid-webauthn-emulator 0.1.5 → 0.2.1
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.md +23 -0
- package/dist/spec/authenticator/authenticator-emulator.spec.js +229 -2
- package/dist/spec/libs/browser-injection.spec.js +5 -1
- package/dist/spec/webauthn/webauthn-emulator.spec.js +166 -0
- package/dist/src/authenticator/authenticator-emulator.d.ts +10 -1
- package/dist/src/authenticator/authenticator-emulator.js +114 -1
- package/dist/src/authenticator/ctap-model.d.ts +38 -1
- package/dist/src/authenticator/ctap-model.js +113 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/test-utils/browser-injection.d.ts +3 -0
- package/dist/src/test-utils/browser-injection.js +20 -1
- package/dist/src/webauthn/webauthn-emulator.d.ts +16 -1
- package/dist/src/webauthn/webauthn-emulator.js +80 -0
- package/dist/src/webauthn/webauthn-model-json.d.ts +15 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -143,6 +143,27 @@ async function startWebAuthnEmulator(page: Page, origin: string, debug = false)
|
|
|
143
143
|
return response;
|
|
144
144
|
},
|
|
145
145
|
);
|
|
146
|
+
|
|
147
|
+
await page.exposeFunction(
|
|
148
|
+
BrowserInjection.WebAuthnEmulatorSignalUnknownCredential,
|
|
149
|
+
async (options: UnknownCredentialOptionsJSON) => {
|
|
150
|
+
emulator.signalUnknownCredential(options);
|
|
151
|
+
},
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
await page.exposeFunction(
|
|
155
|
+
BrowserInjection.WebAuthnEmulatorSignalAllAcceptedCredentials,
|
|
156
|
+
async (options: AllAcceptedCredentialsOptionsJSON) => {
|
|
157
|
+
emulator.signalAllAcceptedCredentials(options);
|
|
158
|
+
};
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
await page.exposeFunction(
|
|
162
|
+
BrowserInjection.WebAuthnEmulatorSignalCurrentUserDetails,
|
|
163
|
+
async (options: CurrentUserDetailsOptionsJSON) => {
|
|
164
|
+
emulator.signalCurrentUserDetails(options);
|
|
165
|
+
};
|
|
166
|
+
);
|
|
146
167
|
}
|
|
147
168
|
|
|
148
169
|
test.describe("Passkeys Tests", { tag: ["@daily"] }, () => {
|
|
@@ -162,6 +183,7 @@ test.describe("Passkeys Tests", { tag: ["@daily"] }, () => {
|
|
|
162
183
|
|
|
163
184
|
- `window.webAuthnEmulatorGet`: `WebAuthnEmulator.getJSON` の Exposed Function
|
|
164
185
|
- `window.webAuthnEmulatorCreate`: `WebAuthnEmulator.createJSON` の Exposed Function
|
|
186
|
+
- `window.webAuthnEmulatorSignalUnknownCredential`: `WebAuthnEmulator.signalUnknownCredential` の Exposed Function
|
|
165
187
|
|
|
166
188
|
これらは Page グローバルに定義されるため、Page インスタンスにつき 1 回だけ定義する必要があります。
|
|
167
189
|
|
|
@@ -175,6 +197,7 @@ await page.evaluate(BrowserInjection.HookWebAuthnApis);
|
|
|
175
197
|
|
|
176
198
|
- `navigator.credentials.get` の定義を上書きし、`window.webAuthnEmulatorGet` を呼び出す
|
|
177
199
|
- `navigator.credentials.create` の定義を上書きし、`window.webAuthnEmulatorCreate` を呼び出す
|
|
200
|
+
- `PublicKeyCredential.signalUnknownCredential` の定義を追加し、`window.webAuthnEmulatorSignalUnknownCredential` を呼び出す
|
|
178
201
|
|
|
179
202
|
これにより先ほどの `exposeFunction` で定義した `WebAuthnEmulator` のメソッドが、`navigator.credentials.get` および `navigator.credentials.create` 呼び出し時に実行されるようになります。これらの処理中にはテストコンテキストと Playwright のコンテキスト間の通信のためにデータのシリアライズおよびデシリアライズが行われるため、そのための処理も含まれています。
|
|
180
203
|
|
|
@@ -1,9 +1,14 @@
|
|
|
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
6
|
const globals_1 = require("@jest/globals");
|
|
4
|
-
const globals_2 = require("@jest/globals");
|
|
5
7
|
const src_1 = require("../../src");
|
|
6
8
|
const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
9
|
+
const ctap_model_2 = require("../../src/authenticator/ctap-model");
|
|
10
|
+
const encode_utils_1 = __importDefault(require("../../src/libs/encode-utils"));
|
|
11
|
+
const credentials_memory_repository_1 = require("../../src/repository/credentials-memory-repository");
|
|
7
12
|
(0, globals_1.describe)("Authenticator Emulator Exceptional Test", () => {
|
|
8
13
|
// Success case has been tested in the webauthn-emulator.spec.ts
|
|
9
14
|
(0, globals_1.test)("Unknown command _ CTAP Error", async () => {
|
|
@@ -11,8 +16,230 @@ const ctap_model_1 = require("../../src/authenticator/ctap-model");
|
|
|
11
16
|
command: ctap_model_1.CTAP_COMMAND.authenticatorReset,
|
|
12
17
|
};
|
|
13
18
|
const authenticator = new src_1.AuthenticatorEmulator();
|
|
14
|
-
(0,
|
|
19
|
+
(0, globals_1.expect)(() => {
|
|
15
20
|
authenticator.command(testRequest);
|
|
16
21
|
}).toThrowError("CTAP error: CTAP1_ERR_INVALID_COMMAND (1)");
|
|
17
22
|
});
|
|
18
23
|
});
|
|
24
|
+
(0, globals_1.describe)("Authenticator Credential Management Tests", () => {
|
|
25
|
+
let authenticator;
|
|
26
|
+
let repository;
|
|
27
|
+
// Test data
|
|
28
|
+
const rpId1 = "example.com";
|
|
29
|
+
const rpId2 = "test.com";
|
|
30
|
+
const user1 = {
|
|
31
|
+
id: encode_utils_1.default.strToUint8Array("user1-id"),
|
|
32
|
+
name: "user1",
|
|
33
|
+
displayName: "User One",
|
|
34
|
+
};
|
|
35
|
+
const user2 = {
|
|
36
|
+
id: encode_utils_1.default.strToUint8Array("user2-id"),
|
|
37
|
+
name: "user2",
|
|
38
|
+
displayName: "User Two",
|
|
39
|
+
};
|
|
40
|
+
const user3 = {
|
|
41
|
+
id: encode_utils_1.default.strToUint8Array("user3-id"),
|
|
42
|
+
name: "user3",
|
|
43
|
+
displayName: "User Three",
|
|
44
|
+
};
|
|
45
|
+
(0, globals_1.beforeEach)(() => {
|
|
46
|
+
// Create a new repository for each test
|
|
47
|
+
repository = new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository();
|
|
48
|
+
// Create authenticator with the repository
|
|
49
|
+
authenticator = new src_1.AuthenticatorEmulator({
|
|
50
|
+
credentialsRepository: repository,
|
|
51
|
+
});
|
|
52
|
+
// Create test credentials
|
|
53
|
+
createTestCredential(rpId1, user1);
|
|
54
|
+
createTestCredential(rpId1, user2);
|
|
55
|
+
createTestCredential(rpId2, user3);
|
|
56
|
+
});
|
|
57
|
+
// Helper function to create a test credential
|
|
58
|
+
function createTestCredential(rpId, user) {
|
|
59
|
+
const clientDataHash = new Uint8Array(32).fill(1);
|
|
60
|
+
const makeCredentialRequest = {
|
|
61
|
+
clientDataHash,
|
|
62
|
+
rp: { id: rpId, name: rpId },
|
|
63
|
+
user,
|
|
64
|
+
pubKeyCredParams: [{ type: "public-key", alg: -7 }],
|
|
65
|
+
};
|
|
66
|
+
authenticator.authenticatorMakeCredential(makeCredentialRequest);
|
|
67
|
+
}
|
|
68
|
+
(0, globals_1.describe)("enumerateCredentialsBegin", () => {
|
|
69
|
+
(0, globals_1.test)("should return the total number of credentials for a specific RP", () => {
|
|
70
|
+
// Create request for enumerateCredentialsBegin
|
|
71
|
+
const request = {
|
|
72
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
73
|
+
subCommandParams: {
|
|
74
|
+
rpId: rpId1,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
// Execute the command
|
|
78
|
+
const response = authenticator.authenticatorCredentialManagement(request);
|
|
79
|
+
// Verify the response
|
|
80
|
+
(0, globals_1.expect)(response.totalCredentials).toBe(2); // We created 2 credentials for rpId1
|
|
81
|
+
});
|
|
82
|
+
(0, globals_1.test)("should throw an error if rpId is not provided", () => {
|
|
83
|
+
// Create request without rpId
|
|
84
|
+
const request = {
|
|
85
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
86
|
+
};
|
|
87
|
+
// Execute the command and expect an error
|
|
88
|
+
(0, globals_1.expect)(() => {
|
|
89
|
+
authenticator.authenticatorCredentialManagement(request);
|
|
90
|
+
}).toThrow(new ctap_model_2.AuthenticationEmulatorError(ctap_model_2.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER));
|
|
91
|
+
});
|
|
92
|
+
(0, globals_1.test)("should return 0 credentials for an RP with no credentials", () => {
|
|
93
|
+
// Create request for a non-existent RP
|
|
94
|
+
const request = {
|
|
95
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
96
|
+
subCommandParams: {
|
|
97
|
+
rpId: "nonexistent.com",
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
// Execute the command
|
|
101
|
+
const response = authenticator.authenticatorCredentialManagement(request);
|
|
102
|
+
// Verify the response
|
|
103
|
+
(0, globals_1.expect)(response.totalCredentials).toBe(0);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
(0, globals_1.describe)("enumerateCredentialsGetNextCredential", () => {
|
|
107
|
+
(0, globals_1.test)("should return credentials one by one", () => {
|
|
108
|
+
// First, call enumerateCredentialsBegin
|
|
109
|
+
const beginRequest = {
|
|
110
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
111
|
+
subCommandParams: {
|
|
112
|
+
rpId: rpId1,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
authenticator.authenticatorCredentialManagement(beginRequest);
|
|
116
|
+
// Now call enumerateCredentialsGetNextCredential twice to get both credentials
|
|
117
|
+
const getNextRequest = {
|
|
118
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsGetNextCredential,
|
|
119
|
+
};
|
|
120
|
+
// Get first credential
|
|
121
|
+
const response1 = authenticator.authenticatorCredentialManagement(getNextRequest);
|
|
122
|
+
(0, globals_1.expect)(response1.user).toBeDefined();
|
|
123
|
+
(0, globals_1.expect)(response1.credentialID).toBeDefined();
|
|
124
|
+
(0, globals_1.expect)(response1.publicKey).toBeDefined();
|
|
125
|
+
// Get second credential
|
|
126
|
+
const response2 = authenticator.authenticatorCredentialManagement(getNextRequest);
|
|
127
|
+
(0, globals_1.expect)(response2.user).toBeDefined();
|
|
128
|
+
(0, globals_1.expect)(response2.credentialID).toBeDefined();
|
|
129
|
+
(0, globals_1.expect)(response2.publicKey).toBeDefined();
|
|
130
|
+
// Verify we got different credentials
|
|
131
|
+
if (response1.credentialID && response2.credentialID) {
|
|
132
|
+
(0, globals_1.expect)(encode_utils_1.default.encodeBase64Url(response1.credentialID)).not.toBe(encode_utils_1.default.encodeBase64Url(response2.credentialID));
|
|
133
|
+
}
|
|
134
|
+
// Verify we can't get more credentials
|
|
135
|
+
(0, globals_1.expect)(() => {
|
|
136
|
+
authenticator.authenticatorCredentialManagement(getNextRequest);
|
|
137
|
+
}).toThrow(new ctap_model_2.AuthenticationEmulatorError(ctap_model_2.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS));
|
|
138
|
+
});
|
|
139
|
+
(0, globals_1.test)("should throw an error if called without calling enumerateCredentialsBegin first", () => {
|
|
140
|
+
const getNextRequest = {
|
|
141
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsGetNextCredential,
|
|
142
|
+
};
|
|
143
|
+
(0, globals_1.expect)(() => {
|
|
144
|
+
authenticator.authenticatorCredentialManagement(getNextRequest);
|
|
145
|
+
}).toThrow(new ctap_model_2.AuthenticationEmulatorError(ctap_model_2.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS));
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
(0, globals_1.describe)("updateUserInformation", () => {
|
|
149
|
+
(0, globals_1.test)("should update user information for all credentials of a user", () => {
|
|
150
|
+
// Create updated user information
|
|
151
|
+
const updatedUser = {
|
|
152
|
+
id: user1.id, // Same ID
|
|
153
|
+
name: "user1-updated",
|
|
154
|
+
displayName: "Updated User One",
|
|
155
|
+
};
|
|
156
|
+
// Create request for updateUserInformation
|
|
157
|
+
const request = {
|
|
158
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
159
|
+
subCommandParams: {
|
|
160
|
+
rpId: rpId1,
|
|
161
|
+
user: updatedUser,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
// Execute the command
|
|
165
|
+
authenticator.authenticatorCredentialManagement(request);
|
|
166
|
+
// Verify the user information was updated by enumerating credentials
|
|
167
|
+
const beginRequest = {
|
|
168
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
169
|
+
subCommandParams: {
|
|
170
|
+
rpId: rpId1,
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
authenticator.authenticatorCredentialManagement(beginRequest);
|
|
174
|
+
const getNextRequest = {
|
|
175
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsGetNextCredential,
|
|
176
|
+
};
|
|
177
|
+
// Get credentials and check if user info was updated
|
|
178
|
+
const nextResponse = authenticator.authenticatorCredentialManagement(getNextRequest);
|
|
179
|
+
// Find the credential for user1
|
|
180
|
+
if (nextResponse.user &&
|
|
181
|
+
encode_utils_1.default.encodeBase64Url(nextResponse.user.id) === encode_utils_1.default.encodeBase64Url(user1.id)) {
|
|
182
|
+
(0, globals_1.expect)(nextResponse.user.name).toBe(updatedUser.name);
|
|
183
|
+
(0, globals_1.expect)(nextResponse.user.displayName).toBe(updatedUser.displayName);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
(0, globals_1.test)("should throw an error if rpId is not provided", () => {
|
|
187
|
+
const request = {
|
|
188
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
189
|
+
subCommandParams: {
|
|
190
|
+
user: user1,
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
(0, globals_1.expect)(() => {
|
|
194
|
+
authenticator.authenticatorCredentialManagement(request);
|
|
195
|
+
}).toThrow(new ctap_model_2.AuthenticationEmulatorError(ctap_model_2.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER));
|
|
196
|
+
});
|
|
197
|
+
(0, globals_1.test)("should throw an error if user is not provided", () => {
|
|
198
|
+
const request = {
|
|
199
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
200
|
+
subCommandParams: {
|
|
201
|
+
rpId: rpId1,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
(0, globals_1.expect)(() => {
|
|
205
|
+
authenticator.authenticatorCredentialManagement(request);
|
|
206
|
+
}).toThrow(new ctap_model_2.AuthenticationEmulatorError(ctap_model_2.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER));
|
|
207
|
+
});
|
|
208
|
+
(0, globals_1.test)("should throw an error if no credentials exist for the user", () => {
|
|
209
|
+
const nonExistentUser = {
|
|
210
|
+
id: encode_utils_1.default.strToUint8Array("nonexistent-id"),
|
|
211
|
+
name: "nonexistent",
|
|
212
|
+
displayName: "Non Existent User",
|
|
213
|
+
};
|
|
214
|
+
const request = {
|
|
215
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
216
|
+
subCommandParams: {
|
|
217
|
+
rpId: rpId1,
|
|
218
|
+
user: nonExistentUser,
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
(0, globals_1.expect)(() => {
|
|
222
|
+
authenticator.authenticatorCredentialManagement(request);
|
|
223
|
+
}).toThrow(new ctap_model_2.AuthenticationEmulatorError(ctap_model_2.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS));
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
(0, globals_1.describe)("authenticatorCredentialManagement with stateless authenticator", () => {
|
|
227
|
+
(0, globals_1.test)("should throw an error if authenticator is stateless", () => {
|
|
228
|
+
// Create a stateless authenticator
|
|
229
|
+
const statelessAuthenticator = new src_1.AuthenticatorEmulator({
|
|
230
|
+
stateless: true,
|
|
231
|
+
});
|
|
232
|
+
// Create request for enumerateCredentialsBegin
|
|
233
|
+
const request = {
|
|
234
|
+
subCommand: ctap_model_2.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
235
|
+
subCommandParams: {
|
|
236
|
+
rpId: rpId1,
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
// Execute the command and expect an error
|
|
240
|
+
(0, globals_1.expect)(() => {
|
|
241
|
+
statelessAuthenticator.authenticatorCredentialManagement(request);
|
|
242
|
+
}).toThrow(new ctap_model_2.AuthenticationEmulatorError(ctap_model_2.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED));
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -5,11 +5,15 @@ const browser_injection_1 = require("../../src/test-utils/browser-injection");
|
|
|
5
5
|
(0, globals_1.describe)("Browser Injection Test", () => {
|
|
6
6
|
(0, globals_1.test)("Browser Injection Test", async () => {
|
|
7
7
|
const window = { navigator: { credentials: { create: undefined, get: undefined } } };
|
|
8
|
-
const PublicKeyCredential = {
|
|
8
|
+
const PublicKeyCredential = {
|
|
9
|
+
isConditionalMediationAvailable: async () => false,
|
|
10
|
+
signalUnknownCredential: undefined,
|
|
11
|
+
};
|
|
9
12
|
// biome-ignore lint/security/noGlobalEval: This is a test code.
|
|
10
13
|
eval(browser_injection_1.HookWebAuthnApis);
|
|
11
14
|
(0, globals_1.expect)(await PublicKeyCredential.isConditionalMediationAvailable()).toBeTruthy();
|
|
12
15
|
(0, globals_1.expect)(window.navigator.credentials.create).toBeDefined();
|
|
13
16
|
(0, globals_1.expect)(window.navigator.credentials.get).toBeDefined();
|
|
17
|
+
(0, globals_1.expect)(PublicKeyCredential.signalUnknownCredential).toBeDefined();
|
|
14
18
|
});
|
|
15
19
|
});
|
|
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
const globals_1 = require("@jest/globals");
|
|
40
40
|
const src_1 = __importStar(require("../../src"));
|
|
41
41
|
const encode_utils_1 = __importDefault(require("../../src/libs/encode-utils"));
|
|
42
|
+
const credentials_memory_repository_1 = require("../../src/repository/credentials-memory-repository");
|
|
42
43
|
const webauthn_test_server_1 = require("./webauthn-test-server");
|
|
43
44
|
(0, globals_1.describe)("WebAuthnEmulator Registration Passkeys Test", () => {
|
|
44
45
|
const user = { username: "test", id: "test" };
|
|
@@ -230,3 +231,168 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
|
|
|
230
231
|
});
|
|
231
232
|
});
|
|
232
233
|
});
|
|
234
|
+
(0, globals_1.describe)("WebAuthnEmulator signalUnknownCredential Test", () => {
|
|
235
|
+
(0, globals_1.test)("Signal Unknown Credential _ OK", async () => {
|
|
236
|
+
const user = { username: "test-signal", id: "test-signal" };
|
|
237
|
+
// Create a new emulator with a clean repository
|
|
238
|
+
const repository = new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository();
|
|
239
|
+
const authenticator = new src_1.AuthenticatorEmulator({
|
|
240
|
+
credentialsRepository: repository,
|
|
241
|
+
});
|
|
242
|
+
const emulator = new src_1.default(authenticator);
|
|
243
|
+
const testServer = new webauthn_test_server_1.WebAuthnTestServer();
|
|
244
|
+
// Create a credential
|
|
245
|
+
const options = await testServer.getRegistrationOptions(user);
|
|
246
|
+
const credential = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
|
|
247
|
+
await testServer.getRegistrationVerification(user, credential);
|
|
248
|
+
// Verify the credential exists
|
|
249
|
+
const credentialsBefore = repository.loadCredentials();
|
|
250
|
+
(0, globals_1.expect)(credentialsBefore.length).toBe(1);
|
|
251
|
+
// Signal unknown credential
|
|
252
|
+
emulator.signalUnknownCredential({
|
|
253
|
+
rpId: webauthn_test_server_1.TEST_RP_ORIGIN.replace("https://", ""),
|
|
254
|
+
credentialId: credential.id,
|
|
255
|
+
});
|
|
256
|
+
// Verify the credential was deleted
|
|
257
|
+
const credentialsAfter = repository.loadCredentials();
|
|
258
|
+
(0, globals_1.expect)(credentialsAfter.length).toBe(0);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
(0, globals_1.describe)("WebAuthnEmulator signalAllAcceptedCredentials Test", () => {
|
|
262
|
+
(0, globals_1.test)("Signal All Accepted Credentials _ OK", async () => {
|
|
263
|
+
// Create users
|
|
264
|
+
const user = { username: "user1", id: "user1-id" };
|
|
265
|
+
// Create a new emulator with a clean repository
|
|
266
|
+
const repository = new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository();
|
|
267
|
+
const authenticator = new src_1.AuthenticatorEmulator({
|
|
268
|
+
credentialsRepository: repository,
|
|
269
|
+
});
|
|
270
|
+
const emulator = new src_1.default(authenticator);
|
|
271
|
+
const testServer = new webauthn_test_server_1.WebAuthnTestServer();
|
|
272
|
+
const options = await testServer.getRegistrationOptions(user);
|
|
273
|
+
// Create credential1 for user
|
|
274
|
+
const credential1 = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
|
|
275
|
+
const backupCredential1 = repository.loadCredentials()[0];
|
|
276
|
+
// Create credential2 for user (credential1 is overwritten)
|
|
277
|
+
const credential2 = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
|
|
278
|
+
// restore credential1
|
|
279
|
+
repository.saveCredential(backupCredential1);
|
|
280
|
+
// Verify all credentials exist
|
|
281
|
+
const credentialsBefore = repository.loadCredentials();
|
|
282
|
+
(0, globals_1.expect)(credentialsBefore.length).toBe(2);
|
|
283
|
+
// Get the user1 credentials
|
|
284
|
+
const user1Credentials = credentialsBefore.filter((cred) => cred.user.name === user.username);
|
|
285
|
+
// Get the actual user ID from the credential
|
|
286
|
+
const actualUserId = encode_utils_1.default.encodeBase64Url(user1Credentials[0].user.id);
|
|
287
|
+
// Get the IDs of all credentials
|
|
288
|
+
const allCredentialIds = credentialsBefore.map((cred) => encode_utils_1.default.encodeBase64Url(cred.publicKeyCredentialSource.id));
|
|
289
|
+
// Signal that only the first credential is accepted for user1
|
|
290
|
+
emulator.signalAllAcceptedCredentials({
|
|
291
|
+
rpId: webauthn_test_server_1.TEST_RP_ORIGIN.replace("https://", ""),
|
|
292
|
+
userId: actualUserId,
|
|
293
|
+
allAcceptedCredentialIds: [credential1.id],
|
|
294
|
+
});
|
|
295
|
+
// Verify that one credential was deleted
|
|
296
|
+
const credentialsAfter = repository.loadCredentials();
|
|
297
|
+
(0, globals_1.expect)(credentialsAfter.length).toBe(1);
|
|
298
|
+
// Check that credential1 and credential3 still exist
|
|
299
|
+
const remainingIds = credentialsAfter.map((cred) => encode_utils_1.default.encodeBase64Url(cred.publicKeyCredentialSource.id));
|
|
300
|
+
(0, globals_1.expect)(remainingIds).toContain(credential1.id);
|
|
301
|
+
(0, globals_1.expect)(remainingIds).not.toContain(credential2.id);
|
|
302
|
+
// Verify one credential was deleted
|
|
303
|
+
(0, globals_1.expect)(remainingIds.length).toBe(1);
|
|
304
|
+
(0, globals_1.expect)(allCredentialIds.length).toBe(2);
|
|
305
|
+
const deletedIds = allCredentialIds.filter((id) => !remainingIds.includes(id));
|
|
306
|
+
(0, globals_1.expect)(deletedIds.length).toBe(1);
|
|
307
|
+
});
|
|
308
|
+
(0, globals_1.test)("Signal All Accepted Credentials with empty list _ Delete all user credentials", async () => {
|
|
309
|
+
// Create user
|
|
310
|
+
const user = { username: "user-test", id: "user-test-id" };
|
|
311
|
+
// Create a new emulator with a clean repository
|
|
312
|
+
const repository = new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository();
|
|
313
|
+
const authenticator = new src_1.AuthenticatorEmulator({
|
|
314
|
+
credentialsRepository: repository,
|
|
315
|
+
});
|
|
316
|
+
const emulator = new src_1.default(authenticator);
|
|
317
|
+
const testServer = new webauthn_test_server_1.WebAuthnTestServer();
|
|
318
|
+
// Create a credential for the user
|
|
319
|
+
const options = await testServer.getRegistrationOptions(user);
|
|
320
|
+
const credential1 = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
|
|
321
|
+
await testServer.getRegistrationVerification(user, credential1);
|
|
322
|
+
// Verify credentials exist
|
|
323
|
+
const credentialsBefore = repository.loadCredentials();
|
|
324
|
+
(0, globals_1.expect)(credentialsBefore.length).toBe(1);
|
|
325
|
+
// Get the actual user ID from the credential
|
|
326
|
+
const actualUserId = encode_utils_1.default.encodeBase64Url(credentialsBefore[0].user.id);
|
|
327
|
+
// Signal empty accepted credentials list
|
|
328
|
+
emulator.signalAllAcceptedCredentials({
|
|
329
|
+
rpId: webauthn_test_server_1.TEST_RP_ORIGIN.replace("https://", ""),
|
|
330
|
+
userId: actualUserId,
|
|
331
|
+
allAcceptedCredentialIds: [],
|
|
332
|
+
});
|
|
333
|
+
// Verify all user credentials were deleted
|
|
334
|
+
const userCredentialsAfter = repository
|
|
335
|
+
.loadCredentials()
|
|
336
|
+
.filter((cred) => encode_utils_1.default.encodeBase64Url(cred.user.id) === actualUserId);
|
|
337
|
+
(0, globals_1.expect)(userCredentialsAfter.length).toBe(0);
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
(0, globals_1.describe)("WebAuthnEmulator signalCurrentUserDetails Test", () => {
|
|
341
|
+
(0, globals_1.test)("Signal Current User Details _ OK", async () => {
|
|
342
|
+
// Create user
|
|
343
|
+
const user = { username: "user-details", id: "user-details-id" };
|
|
344
|
+
// Create a new emulator with a clean repository
|
|
345
|
+
const repository = new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository();
|
|
346
|
+
const authenticator = new src_1.AuthenticatorEmulator({
|
|
347
|
+
credentialsRepository: repository,
|
|
348
|
+
});
|
|
349
|
+
const emulator = new src_1.default(authenticator);
|
|
350
|
+
const testServer = new webauthn_test_server_1.WebAuthnTestServer();
|
|
351
|
+
// Create a credential
|
|
352
|
+
const options = await testServer.getRegistrationOptions(user);
|
|
353
|
+
const credential = emulator.createJSON(webauthn_test_server_1.TEST_RP_ORIGIN, options);
|
|
354
|
+
await testServer.getRegistrationVerification(user, credential);
|
|
355
|
+
// Verify the credential exists with original user details
|
|
356
|
+
const credentialsBefore = repository.loadCredentials();
|
|
357
|
+
(0, globals_1.expect)(credentialsBefore.length).toBe(1);
|
|
358
|
+
(0, globals_1.expect)(credentialsBefore[0].user.name).toBe(user.username);
|
|
359
|
+
// Note: We don't check displayName as it's not consistently set by the test server
|
|
360
|
+
// Get the actual user ID from the credential
|
|
361
|
+
const actualUserId = encode_utils_1.default.encodeBase64Url(credentialsBefore[0].user.id);
|
|
362
|
+
// Update user details using the actual user ID from the credential
|
|
363
|
+
const updatedName = "Updated User Name";
|
|
364
|
+
const updatedDisplayName = "Updated Display Name";
|
|
365
|
+
emulator.signalCurrentUserDetails({
|
|
366
|
+
rpId: webauthn_test_server_1.TEST_RP_ORIGIN.replace("https://", ""),
|
|
367
|
+
userId: actualUserId,
|
|
368
|
+
name: updatedName,
|
|
369
|
+
displayName: updatedDisplayName,
|
|
370
|
+
});
|
|
371
|
+
// Verify the user details were updated
|
|
372
|
+
const credentialsAfter = repository.loadCredentials();
|
|
373
|
+
(0, globals_1.expect)(credentialsAfter.length).toBe(1);
|
|
374
|
+
(0, globals_1.expect)(credentialsAfter[0].user.name).toBe(updatedName);
|
|
375
|
+
(0, globals_1.expect)(credentialsAfter[0].user.displayName).toBe(updatedDisplayName);
|
|
376
|
+
// Verify the credential ID and user ID remain unchanged
|
|
377
|
+
(0, globals_1.expect)(encode_utils_1.default.encodeBase64Url(credentialsAfter[0].publicKeyCredentialSource.id)).toBe(credential.id);
|
|
378
|
+
(0, globals_1.expect)(encode_utils_1.default.encodeBase64Url(credentialsAfter[0].user.id)).toBe(actualUserId);
|
|
379
|
+
});
|
|
380
|
+
(0, globals_1.test)("Signal Current User Details with non-existent user _ Error", async () => {
|
|
381
|
+
// Create a new emulator with a clean repository
|
|
382
|
+
const repository = new credentials_memory_repository_1.PasskeysCredentialsMemoryRepository();
|
|
383
|
+
const authenticator = new src_1.AuthenticatorEmulator({
|
|
384
|
+
credentialsRepository: repository,
|
|
385
|
+
});
|
|
386
|
+
const emulator = new src_1.default(authenticator);
|
|
387
|
+
// Try to update non-existent user
|
|
388
|
+
const nonExistentUserId = "non-existent-user-id";
|
|
389
|
+
(0, globals_1.expect)(() => {
|
|
390
|
+
emulator.signalCurrentUserDetails({
|
|
391
|
+
rpId: webauthn_test_server_1.TEST_RP_ORIGIN.replace("https://", ""),
|
|
392
|
+
userId: encode_utils_1.default.encodeBase64Url(encode_utils_1.default.strToUint8Array(nonExistentUserId)),
|
|
393
|
+
name: "New Name",
|
|
394
|
+
displayName: "New Display Name",
|
|
395
|
+
});
|
|
396
|
+
}).toThrow(); // Should throw CTAP2_ERR_NO_CREDENTIALS
|
|
397
|
+
});
|
|
398
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PasskeysCredentialsRepository } from "../repository/credentials-repository";
|
|
2
2
|
import { type AuthenticatorData, type PublicKeyCredentialSource } from "../webauthn/webauthn-model";
|
|
3
|
-
import { type AuthenticatorGetAssertionRequest, type AuthenticatorGetAssertionResponse, type AuthenticatorGetInfoResponse, type AuthenticatorMakeCredentialRequest, type AuthenticatorMakeCredentialResponse, type AuthenticatorOptions, type CTAPAuthenticatorRequest, type CTAPAuthenticatorResponse } from "./ctap-model";
|
|
3
|
+
import { type AuthenticatorCredentialManagementRequest, type AuthenticatorCredentialManagementResponse, type AuthenticatorGetAssertionRequest, type AuthenticatorGetAssertionResponse, type AuthenticatorGetInfoResponse, type AuthenticatorMakeCredentialRequest, type AuthenticatorMakeCredentialResponse, type AuthenticatorOptions, type CTAPAuthenticatorRequest, type CTAPAuthenticatorResponse } from "./ctap-model";
|
|
4
4
|
type InteractionResponse = {
|
|
5
5
|
options: {
|
|
6
6
|
uv: boolean;
|
|
@@ -52,6 +52,15 @@ export declare class AuthenticatorEmulator {
|
|
|
52
52
|
constructor(params?: Partial<AuthenticatorParameters>);
|
|
53
53
|
/** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticator-api */
|
|
54
54
|
command(request: CTAPAuthenticatorRequest): CTAPAuthenticatorResponse;
|
|
55
|
+
/**
|
|
56
|
+
* @see https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorCredentialManagement
|
|
57
|
+
*/
|
|
58
|
+
authenticatorCredentialManagement(request: AuthenticatorCredentialManagementRequest): AuthenticatorCredentialManagementResponse;
|
|
59
|
+
private enumerationState;
|
|
60
|
+
private authenticatorEnumerateCredentialsBegin;
|
|
61
|
+
private authenticatorEnumerateCredentialsGetNextCredential;
|
|
62
|
+
private authenticatorUpdateUserInformation;
|
|
63
|
+
private authenticatorDeleteCredential;
|
|
55
64
|
/** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetInfo */
|
|
56
65
|
authenticatorGetInfo(): AuthenticatorGetInfoResponse;
|
|
57
66
|
/**
|
|
@@ -48,7 +48,9 @@ class AuthenticatorEmulator {
|
|
|
48
48
|
verifications: params.verifications ?? AuthenticatorEmulator.DEFAULT_VERIFICATIONS,
|
|
49
49
|
userMakeCredentialInteraction: params.userMakeCredentialInteraction ?? AuthenticatorEmulator.DEFAULT_MAKE_CREDENTIAL_INTERACTION,
|
|
50
50
|
userGetAssertionInteraction: params.userGetAssertionInteraction ?? AuthenticatorEmulator.DEFAULT_GET_ASSERTION_INTERACTION,
|
|
51
|
-
credentialsRepository: params.stateless
|
|
51
|
+
credentialsRepository: params.stateless
|
|
52
|
+
? undefined
|
|
53
|
+
: (params.credentialsRepository ?? AuthenticatorEmulator.DEFAULT_CREDENTIALS_REPOSITORY),
|
|
52
54
|
stateless: params.stateless ?? AuthenticatorEmulator.DEFAULT_STATELESS,
|
|
53
55
|
};
|
|
54
56
|
}
|
|
@@ -68,8 +70,119 @@ class AuthenticatorEmulator {
|
|
|
68
70
|
if (unpackedRequest.command === ctap_model_1.CTAP_COMMAND.authenticatorGetInfo) {
|
|
69
71
|
return (0, ctap_model_1.packGetInfoResponse)(this.authenticatorGetInfo());
|
|
70
72
|
}
|
|
73
|
+
if (unpackedRequest.command === ctap_model_1.CTAP_COMMAND.authenticatorCredentialManagement) {
|
|
74
|
+
const credentialManagementRequest = (0, ctap_model_1.unpackCredentialManagementRequest)(request);
|
|
75
|
+
const credentialManagementResponse = this.authenticatorCredentialManagement(credentialManagementRequest);
|
|
76
|
+
return (0, ctap_model_1.packCredentialManagementResponse)(credentialManagementResponse);
|
|
77
|
+
}
|
|
71
78
|
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_COMMAND);
|
|
72
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* @see https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#authenticatorCredentialManagement
|
|
82
|
+
*/
|
|
83
|
+
authenticatorCredentialManagement(request) {
|
|
84
|
+
const repository = this.params.credentialsRepository;
|
|
85
|
+
if (!repository) {
|
|
86
|
+
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED);
|
|
87
|
+
}
|
|
88
|
+
switch (request.subCommand) {
|
|
89
|
+
case ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential:
|
|
90
|
+
return this.authenticatorDeleteCredential(request);
|
|
91
|
+
case ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin:
|
|
92
|
+
return this.authenticatorEnumerateCredentialsBegin(request);
|
|
93
|
+
case ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsGetNextCredential:
|
|
94
|
+
return this.authenticatorEnumerateCredentialsGetNextCredential();
|
|
95
|
+
case ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation:
|
|
96
|
+
return this.authenticatorUpdateUserInformation(request);
|
|
97
|
+
default:
|
|
98
|
+
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_COMMAND);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Store the current enumeration state
|
|
102
|
+
enumerationState = null;
|
|
103
|
+
authenticatorEnumerateCredentialsBegin(request) {
|
|
104
|
+
const repository = this.params.credentialsRepository;
|
|
105
|
+
if (!repository) {
|
|
106
|
+
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED);
|
|
107
|
+
}
|
|
108
|
+
const rpId = request.subCommandParams?.rpId;
|
|
109
|
+
if (!rpId) {
|
|
110
|
+
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER);
|
|
111
|
+
}
|
|
112
|
+
// Get all credentials for the RP
|
|
113
|
+
const allCredentials = repository.loadCredentials();
|
|
114
|
+
const rpCredentials = allCredentials.filter((cred) => cred.publicKeyCredentialSource.rpId.value === rpId);
|
|
115
|
+
// Store the enumeration state
|
|
116
|
+
this.enumerationState = {
|
|
117
|
+
rpId,
|
|
118
|
+
credentials: rpCredentials,
|
|
119
|
+
currentIndex: 0,
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
totalCredentials: rpCredentials.length,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
authenticatorEnumerateCredentialsGetNextCredential() {
|
|
126
|
+
if (!this.enumerationState || this.enumerationState.currentIndex >= this.enumerationState.credentials.length) {
|
|
127
|
+
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS);
|
|
128
|
+
}
|
|
129
|
+
const credential = this.enumerationState.credentials[this.enumerationState.currentIndex++];
|
|
130
|
+
return {
|
|
131
|
+
user: credential.user,
|
|
132
|
+
credentialID: encode_utils_1.default.bufferSourceToUint8Array(credential.publicKeyCredentialDescriptor.id),
|
|
133
|
+
publicKey: credential.authenticatorData.attestedCredentialData?.credentialPublicKey.toDer(),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
authenticatorUpdateUserInformation(request) {
|
|
137
|
+
const repository = this.params.credentialsRepository;
|
|
138
|
+
if (!repository) {
|
|
139
|
+
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED);
|
|
140
|
+
}
|
|
141
|
+
const rpId = request.subCommandParams?.rpId;
|
|
142
|
+
const user = request.subCommandParams?.user;
|
|
143
|
+
if (!rpId || !user) {
|
|
144
|
+
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER);
|
|
145
|
+
}
|
|
146
|
+
// Find all credentials for this user and RP
|
|
147
|
+
const allCredentials = repository.loadCredentials();
|
|
148
|
+
const userCredentials = allCredentials.filter((cred) => cred.publicKeyCredentialSource.rpId.value === rpId &&
|
|
149
|
+
encode_utils_1.default.encodeBase64Url(cred.user.id) === encode_utils_1.default.encodeBase64Url(user.id));
|
|
150
|
+
if (userCredentials.length === 0) {
|
|
151
|
+
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS);
|
|
152
|
+
}
|
|
153
|
+
// Update user information for all matching credentials
|
|
154
|
+
for (const credential of userCredentials) {
|
|
155
|
+
const updatedCredential = {
|
|
156
|
+
...credential,
|
|
157
|
+
user: {
|
|
158
|
+
...user,
|
|
159
|
+
id: user.id, // Keep the same ID
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
repository.deleteCredential(credential);
|
|
163
|
+
repository.saveCredential(updatedCredential);
|
|
164
|
+
}
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
167
|
+
authenticatorDeleteCredential(request) {
|
|
168
|
+
const repository = this.params.credentialsRepository;
|
|
169
|
+
if (!repository) {
|
|
170
|
+
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NOT_ALLOWED);
|
|
171
|
+
}
|
|
172
|
+
if (!request.subCommandParams?.credentialId) {
|
|
173
|
+
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP1_ERR_INVALID_PARAMETER);
|
|
174
|
+
}
|
|
175
|
+
const credentialId = request.subCommandParams.credentialId;
|
|
176
|
+
const credentials = repository.loadCredentials();
|
|
177
|
+
const credential = credentials.find((cred) => encode_utils_1.default.encodeBase64Url(cred.publicKeyCredentialSource.id) === encode_utils_1.default.encodeBase64Url(credentialId));
|
|
178
|
+
if (!credential) {
|
|
179
|
+
throw new ctap_model_1.AuthenticationEmulatorError(ctap_model_1.CTAP_STATUS_CODE.CTAP2_ERR_NO_CREDENTIALS);
|
|
180
|
+
}
|
|
181
|
+
repository.deleteCredential(credential);
|
|
182
|
+
return {
|
|
183
|
+
existingResidentCredentialsCount: repository.loadCredentials().length,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
73
186
|
/** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetInfo */
|
|
74
187
|
authenticatorGetInfo() {
|
|
75
188
|
return {
|
|
@@ -76,7 +76,40 @@ export declare enum CTAP_COMMAND {
|
|
|
76
76
|
authenticatorGetInfo = 4,
|
|
77
77
|
authenticatorClientPIN = 6,
|
|
78
78
|
authenticatorReset = 7,
|
|
79
|
-
authenticatorGetNextAssertion = 8
|
|
79
|
+
authenticatorGetNextAssertion = 8,
|
|
80
|
+
authenticatorCredentialManagement = 10
|
|
81
|
+
}
|
|
82
|
+
export declare enum CREDENTIAL_MANAGEMENT_SUBCOMMAND {
|
|
83
|
+
getCredsMetadata = 1,
|
|
84
|
+
enumerateRPsBegin = 2,
|
|
85
|
+
enumerateRPsGetNextRP = 3,
|
|
86
|
+
enumerateCredentialsBegin = 4,
|
|
87
|
+
enumerateCredentialsGetNextCredential = 5,
|
|
88
|
+
deleteCredential = 6,
|
|
89
|
+
updateUserInformation = 7
|
|
90
|
+
}
|
|
91
|
+
export interface AuthenticatorCredentialManagementRequest {
|
|
92
|
+
subCommand: CREDENTIAL_MANAGEMENT_SUBCOMMAND;
|
|
93
|
+
subCommandParams?: {
|
|
94
|
+
credentialId?: Uint8Array;
|
|
95
|
+
rpId?: string;
|
|
96
|
+
user?: PublicKeyCredentialUserEntity;
|
|
97
|
+
};
|
|
98
|
+
pinUvAuthProtocol?: number;
|
|
99
|
+
pinUvAuthParam?: Uint8Array;
|
|
100
|
+
}
|
|
101
|
+
export interface AuthenticatorCredentialManagementResponse {
|
|
102
|
+
existingResidentCredentialsCount?: number;
|
|
103
|
+
maxPossibleRemainingResidentCredentialsCount?: number;
|
|
104
|
+
rp?: PublicKeyCredentialRpEntity;
|
|
105
|
+
rpIDHash?: Uint8Array;
|
|
106
|
+
totalRPs?: number;
|
|
107
|
+
user?: PublicKeyCredentialUserEntity;
|
|
108
|
+
credentialID?: Uint8Array;
|
|
109
|
+
publicKey?: Uint8Array;
|
|
110
|
+
totalCredentials?: number;
|
|
111
|
+
credProtect?: number;
|
|
112
|
+
largeBlobKey?: Uint8Array;
|
|
80
113
|
}
|
|
81
114
|
/** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#responses */
|
|
82
115
|
export interface CTAPAuthenticatorResponse {
|
|
@@ -99,9 +132,13 @@ export declare function unpackRequest(request: CTAPAuthenticatorRequest): {
|
|
|
99
132
|
};
|
|
100
133
|
export declare function packMakeCredentialRequest(request: AuthenticatorMakeCredentialRequest): CTAPAuthenticatorRequest;
|
|
101
134
|
export declare function packGetAssertionRequest(request: AuthenticatorGetAssertionRequest): CTAPAuthenticatorRequest;
|
|
135
|
+
export declare function packCredentialManagementRequest(request: AuthenticatorCredentialManagementRequest): CTAPAuthenticatorRequest;
|
|
136
|
+
export declare function unpackCredentialManagementRequest(request: CTAPAuthenticatorRequest): AuthenticatorCredentialManagementRequest;
|
|
102
137
|
export declare function unpackMakeCredentialResponse(response: CTAPAuthenticatorResponse): AuthenticatorMakeCredentialResponse;
|
|
103
138
|
export declare function packMakeCredentialResponse(response: AuthenticatorMakeCredentialResponse): CTAPAuthenticatorResponse;
|
|
104
139
|
export declare function unpackGetAssertionResponse(response: CTAPAuthenticatorResponse): AuthenticatorGetAssertionResponse;
|
|
105
140
|
export declare function packGetAssertionResponse(response: AuthenticatorGetAssertionResponse): CTAPAuthenticatorResponse;
|
|
106
141
|
export declare function unpackGetInfoResponse(response: CTAPAuthenticatorResponse): AuthenticatorGetInfoResponse;
|
|
107
142
|
export declare function packGetInfoResponse(response: AuthenticatorGetInfoResponse): CTAPAuthenticatorResponse;
|
|
143
|
+
export declare function packCredentialManagementResponse(response: AuthenticatorCredentialManagementResponse): CTAPAuthenticatorResponse;
|
|
144
|
+
export declare function unpackCredentialManagementResponse(response: CTAPAuthenticatorResponse): AuthenticatorCredentialManagementResponse;
|
|
@@ -3,16 +3,20 @@ 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.AuthenticationEmulatorError = exports.CTAP_COMMAND = exports.CTAP_STATUS_CODE = void 0;
|
|
6
|
+
exports.AuthenticationEmulatorError = exports.CREDENTIAL_MANAGEMENT_SUBCOMMAND = exports.CTAP_COMMAND = exports.CTAP_STATUS_CODE = void 0;
|
|
7
7
|
exports.unpackRequest = unpackRequest;
|
|
8
8
|
exports.packMakeCredentialRequest = packMakeCredentialRequest;
|
|
9
9
|
exports.packGetAssertionRequest = packGetAssertionRequest;
|
|
10
|
+
exports.packCredentialManagementRequest = packCredentialManagementRequest;
|
|
11
|
+
exports.unpackCredentialManagementRequest = unpackCredentialManagementRequest;
|
|
10
12
|
exports.unpackMakeCredentialResponse = unpackMakeCredentialResponse;
|
|
11
13
|
exports.packMakeCredentialResponse = packMakeCredentialResponse;
|
|
12
14
|
exports.unpackGetAssertionResponse = unpackGetAssertionResponse;
|
|
13
15
|
exports.packGetAssertionResponse = packGetAssertionResponse;
|
|
14
16
|
exports.unpackGetInfoResponse = unpackGetInfoResponse;
|
|
15
17
|
exports.packGetInfoResponse = packGetInfoResponse;
|
|
18
|
+
exports.packCredentialManagementResponse = packCredentialManagementResponse;
|
|
19
|
+
exports.unpackCredentialManagementResponse = unpackCredentialManagementResponse;
|
|
16
20
|
const encode_utils_1 = __importDefault(require("../libs/encode-utils"));
|
|
17
21
|
/** @see https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#error-responses */
|
|
18
22
|
var CTAP_STATUS_CODE;
|
|
@@ -36,7 +40,18 @@ var CTAP_COMMAND;
|
|
|
36
40
|
CTAP_COMMAND[CTAP_COMMAND["authenticatorClientPIN"] = 6] = "authenticatorClientPIN";
|
|
37
41
|
CTAP_COMMAND[CTAP_COMMAND["authenticatorReset"] = 7] = "authenticatorReset";
|
|
38
42
|
CTAP_COMMAND[CTAP_COMMAND["authenticatorGetNextAssertion"] = 8] = "authenticatorGetNextAssertion";
|
|
43
|
+
CTAP_COMMAND[CTAP_COMMAND["authenticatorCredentialManagement"] = 10] = "authenticatorCredentialManagement";
|
|
39
44
|
})(CTAP_COMMAND || (exports.CTAP_COMMAND = CTAP_COMMAND = {}));
|
|
45
|
+
var CREDENTIAL_MANAGEMENT_SUBCOMMAND;
|
|
46
|
+
(function (CREDENTIAL_MANAGEMENT_SUBCOMMAND) {
|
|
47
|
+
CREDENTIAL_MANAGEMENT_SUBCOMMAND[CREDENTIAL_MANAGEMENT_SUBCOMMAND["getCredsMetadata"] = 1] = "getCredsMetadata";
|
|
48
|
+
CREDENTIAL_MANAGEMENT_SUBCOMMAND[CREDENTIAL_MANAGEMENT_SUBCOMMAND["enumerateRPsBegin"] = 2] = "enumerateRPsBegin";
|
|
49
|
+
CREDENTIAL_MANAGEMENT_SUBCOMMAND[CREDENTIAL_MANAGEMENT_SUBCOMMAND["enumerateRPsGetNextRP"] = 3] = "enumerateRPsGetNextRP";
|
|
50
|
+
CREDENTIAL_MANAGEMENT_SUBCOMMAND[CREDENTIAL_MANAGEMENT_SUBCOMMAND["enumerateCredentialsBegin"] = 4] = "enumerateCredentialsBegin";
|
|
51
|
+
CREDENTIAL_MANAGEMENT_SUBCOMMAND[CREDENTIAL_MANAGEMENT_SUBCOMMAND["enumerateCredentialsGetNextCredential"] = 5] = "enumerateCredentialsGetNextCredential";
|
|
52
|
+
CREDENTIAL_MANAGEMENT_SUBCOMMAND[CREDENTIAL_MANAGEMENT_SUBCOMMAND["deleteCredential"] = 6] = "deleteCredential";
|
|
53
|
+
CREDENTIAL_MANAGEMENT_SUBCOMMAND[CREDENTIAL_MANAGEMENT_SUBCOMMAND["updateUserInformation"] = 7] = "updateUserInformation";
|
|
54
|
+
})(CREDENTIAL_MANAGEMENT_SUBCOMMAND || (exports.CREDENTIAL_MANAGEMENT_SUBCOMMAND = CREDENTIAL_MANAGEMENT_SUBCOMMAND = {}));
|
|
40
55
|
// Not standard functions and interfaces
|
|
41
56
|
class AuthenticationEmulatorError extends Error {
|
|
42
57
|
status;
|
|
@@ -84,6 +99,24 @@ function unpackRequest(request) {
|
|
|
84
99
|
pinProtocol: data[0x07],
|
|
85
100
|
},
|
|
86
101
|
};
|
|
102
|
+
case CTAP_COMMAND.authenticatorCredentialManagement:
|
|
103
|
+
return {
|
|
104
|
+
command: request.command,
|
|
105
|
+
request: {
|
|
106
|
+
subCommand: data[0x01],
|
|
107
|
+
subCommandParams: data[0x02]
|
|
108
|
+
? {
|
|
109
|
+
credentialId: data[0x02][0x01]
|
|
110
|
+
? encode_utils_1.default.bufferSourceToUint8Array(data[0x02][0x01])
|
|
111
|
+
: undefined,
|
|
112
|
+
rpId: data[0x02][0x02],
|
|
113
|
+
user: data[0x02][0x03],
|
|
114
|
+
}
|
|
115
|
+
: undefined,
|
|
116
|
+
pinUvAuthProtocol: data[0x03],
|
|
117
|
+
pinUvAuthParam: data[0x04] ? encode_utils_1.default.bufferSourceToUint8Array(data[0x04]) : undefined,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
87
120
|
default:
|
|
88
121
|
return {
|
|
89
122
|
command: request.command,
|
|
@@ -121,6 +154,46 @@ function packGetAssertionRequest(request) {
|
|
|
121
154
|
}),
|
|
122
155
|
};
|
|
123
156
|
}
|
|
157
|
+
function packCredentialManagementRequest(request) {
|
|
158
|
+
return {
|
|
159
|
+
command: CTAP_COMMAND.authenticatorCredentialManagement,
|
|
160
|
+
data: encode_utils_1.default.encodeCbor({
|
|
161
|
+
[0x01]: request.subCommand,
|
|
162
|
+
[0x02]: request.subCommandParams
|
|
163
|
+
? {
|
|
164
|
+
[0x01]: request.subCommandParams.credentialId,
|
|
165
|
+
[0x02]: request.subCommandParams.rpId,
|
|
166
|
+
[0x03]: request.subCommandParams.user,
|
|
167
|
+
}
|
|
168
|
+
: undefined,
|
|
169
|
+
[0x03]: request.pinUvAuthProtocol,
|
|
170
|
+
[0x04]: request.pinUvAuthParam,
|
|
171
|
+
}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function unpackCredentialManagementRequest(request) {
|
|
175
|
+
try {
|
|
176
|
+
const data = encode_utils_1.default.decodeCbor(request.data);
|
|
177
|
+
const subCommandParams = data[0x02];
|
|
178
|
+
return {
|
|
179
|
+
subCommand: data[0x01],
|
|
180
|
+
subCommandParams: subCommandParams
|
|
181
|
+
? {
|
|
182
|
+
credentialId: subCommandParams[0x01]
|
|
183
|
+
? encode_utils_1.default.bufferSourceToUint8Array(subCommandParams[0x01])
|
|
184
|
+
: undefined,
|
|
185
|
+
rpId: subCommandParams[0x02],
|
|
186
|
+
user: subCommandParams[0x03],
|
|
187
|
+
}
|
|
188
|
+
: undefined,
|
|
189
|
+
pinUvAuthProtocol: data[0x03],
|
|
190
|
+
pinUvAuthParam: data[0x04] ? encode_utils_1.default.bufferSourceToUint8Array(data[0x04]) : undefined,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
throw new AuthenticationEmulatorError(CTAP_STATUS_CODE.CTAP2_ERR_INVALID_CBOR, { cause: error });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
124
197
|
function unpackMakeCredentialResponse(response) {
|
|
125
198
|
try {
|
|
126
199
|
const data = encode_utils_1.default.decodeCbor(response.data);
|
|
@@ -200,3 +273,42 @@ function packGetInfoResponse(response) {
|
|
|
200
273
|
}),
|
|
201
274
|
};
|
|
202
275
|
}
|
|
276
|
+
function packCredentialManagementResponse(response) {
|
|
277
|
+
return {
|
|
278
|
+
status: CTAP_STATUS_CODE.CTAP2_OK,
|
|
279
|
+
data: encode_utils_1.default.encodeCbor({
|
|
280
|
+
[0x01]: response.existingResidentCredentialsCount,
|
|
281
|
+
[0x02]: response.maxPossibleRemainingResidentCredentialsCount,
|
|
282
|
+
[0x03]: response.rp,
|
|
283
|
+
[0x04]: response.rpIDHash,
|
|
284
|
+
[0x05]: response.totalRPs,
|
|
285
|
+
[0x06]: response.user,
|
|
286
|
+
[0x07]: response.credentialID,
|
|
287
|
+
[0x08]: response.publicKey,
|
|
288
|
+
[0x09]: response.totalCredentials,
|
|
289
|
+
[0x0a]: response.credProtect,
|
|
290
|
+
[0x0b]: response.largeBlobKey,
|
|
291
|
+
}),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function unpackCredentialManagementResponse(response) {
|
|
295
|
+
try {
|
|
296
|
+
const data = encode_utils_1.default.decodeCbor(response.data);
|
|
297
|
+
return {
|
|
298
|
+
existingResidentCredentialsCount: data[0x01],
|
|
299
|
+
maxPossibleRemainingResidentCredentialsCount: data[0x02],
|
|
300
|
+
rp: data[0x03],
|
|
301
|
+
rpIDHash: data[0x04] ? encode_utils_1.default.bufferSourceToUint8Array(data[0x04]) : undefined,
|
|
302
|
+
totalRPs: data[0x05],
|
|
303
|
+
user: data[0x06],
|
|
304
|
+
credentialID: data[0x07] ? encode_utils_1.default.bufferSourceToUint8Array(data[0x07]) : undefined,
|
|
305
|
+
publicKey: data[0x08] ? encode_utils_1.default.bufferSourceToUint8Array(data[0x08]) : undefined,
|
|
306
|
+
totalCredentials: data[0x09],
|
|
307
|
+
credProtect: data[0x0a],
|
|
308
|
+
largeBlobKey: data[0x0b] ? encode_utils_1.default.bufferSourceToUint8Array(data[0x0b]) : undefined,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
throw new AuthenticationEmulatorError(CTAP_STATUS_CODE.CTAP2_ERR_INVALID_CBOR, { cause: error });
|
|
313
|
+
}
|
|
314
|
+
}
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -30,5 +30,6 @@ __exportStar(require("./repository/credentials-memory-repository"), exports);
|
|
|
30
30
|
exports.BrowserInjection = {
|
|
31
31
|
WebAuthnEmulatorGet: browser_injection_1.WebAuthnEmulatorGet,
|
|
32
32
|
WebAuthnEmulatorCreate: browser_injection_1.WebAuthnEmulatorCreate,
|
|
33
|
+
WebAuthnEmulatorSignalUnknownCredential: browser_injection_1.WebAuthnEmulatorSignalUnknownCredential,
|
|
33
34
|
HookWebAuthnApis: browser_injection_1.HookWebAuthnApis,
|
|
34
35
|
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
export declare const WebAuthnEmulatorGet = "webAuthnEmulatorGet";
|
|
2
2
|
export declare const WebAuthnEmulatorCreate = "webAuthnEmulatorCreate";
|
|
3
|
+
export declare const WebAuthnEmulatorSignalUnknownCredential = "webAuthnEmulatorSignalUnknownCredential";
|
|
4
|
+
export declare const WebAuthnEmulatorSignalAllAcceptedCredentials = "webAuthnEmulatorSignalAllAcceptedCredentials";
|
|
5
|
+
export declare const WebAuthnEmulatorSignalCurrentUserDetails = "webAuthnEmulatorSignalCurrentUserDetails";
|
|
3
6
|
export declare const HookWebAuthnApis: string;
|
|
@@ -33,10 +33,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.HookWebAuthnApis = exports.WebAuthnEmulatorCreate = exports.WebAuthnEmulatorGet = void 0;
|
|
36
|
+
exports.HookWebAuthnApis = exports.WebAuthnEmulatorSignalCurrentUserDetails = exports.WebAuthnEmulatorSignalAllAcceptedCredentials = exports.WebAuthnEmulatorSignalUnknownCredential = exports.WebAuthnEmulatorCreate = exports.WebAuthnEmulatorGet = void 0;
|
|
37
37
|
const Model = __importStar(require("../webauthn/webauthn-model-json"));
|
|
38
38
|
exports.WebAuthnEmulatorGet = "webAuthnEmulatorGet";
|
|
39
39
|
exports.WebAuthnEmulatorCreate = "webAuthnEmulatorCreate";
|
|
40
|
+
exports.WebAuthnEmulatorSignalUnknownCredential = "webAuthnEmulatorSignalUnknownCredential";
|
|
41
|
+
exports.WebAuthnEmulatorSignalAllAcceptedCredentials = "webAuthnEmulatorSignalAllAcceptedCredentials";
|
|
42
|
+
exports.WebAuthnEmulatorSignalCurrentUserDetails = "webAuthnEmulatorSignalCurrentUserDetails";
|
|
40
43
|
const webAuthnModelExports = Object.values(Model)
|
|
41
44
|
.map((m) => m.toString())
|
|
42
45
|
.join("\n ");
|
|
@@ -57,6 +60,22 @@ exports.HookWebAuthnApis = `
|
|
|
57
60
|
const responseJSON = await window.${exports.WebAuthnEmulatorGet}(optionsJSON);
|
|
58
61
|
return parseAuthenticationResponseFromJSON(responseJSON);
|
|
59
62
|
}
|
|
63
|
+
|
|
60
64
|
PublicKeyCredential.isConditionalMediationAvailable = async () => true;
|
|
65
|
+
|
|
66
|
+
PublicKeyCredential.signalUnknownCredential = async (options) => {
|
|
67
|
+
if (!options || !options.rpId || !options.credentialId) return;
|
|
68
|
+
await window.${exports.WebAuthnEmulatorSignalUnknownCredential}(options);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
PublicKeyCredential.signalAllAcceptedCredentials = async (options) => {
|
|
72
|
+
if (!options || !options.rpId || !options.userId || !options.allAcceptedCredentialIds) return;
|
|
73
|
+
await window.${exports.WebAuthnEmulatorSignalAllAcceptedCredentials}(options);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
PublicKeyCredential.signalCurrentUserDetails = async (options) => {
|
|
77
|
+
if (!options || !options.rpId || !options.userId) return;
|
|
78
|
+
await window.${exports.WebAuthnEmulatorSignalCurrentUserDetails}(options);
|
|
79
|
+
}
|
|
61
80
|
})();
|
|
62
81
|
`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AuthenticatorEmulator } from "../authenticator/authenticator-emulator";
|
|
2
|
-
import { type AuthenticationResponseJSON, type CreatePublicKeyCredential, type PublicKeyCredentialCreationOptionsJSON, type PublicKeyCredentialRequestOptionsJSON, type RegistrationResponseJSON, type RequestPublicKeyCredential } from "./webauthn-model-json";
|
|
2
|
+
import { type AllAcceptedCredentialsOptionsJSON, type AuthenticationResponseJSON, type CreatePublicKeyCredential, type CurrentUserDetailsOptionsJSON, type PublicKeyCredentialCreationOptionsJSON, type PublicKeyCredentialRequestOptionsJSON, type RegistrationResponseJSON, type RequestPublicKeyCredential, type UnknownCredentialOptionsJSON } from "./webauthn-model-json";
|
|
3
3
|
export type AuthenticatorInfo = {
|
|
4
4
|
version: string;
|
|
5
5
|
aaguid: string;
|
|
@@ -23,6 +23,21 @@ export declare class WebAuthnEmulator {
|
|
|
23
23
|
getJSON(origin: string, optionsJSON: PublicKeyCredentialRequestOptionsJSON): AuthenticationResponseJSON;
|
|
24
24
|
createJSON(origin: string, optionsJSON: PublicKeyCredentialCreationOptionsJSON): RegistrationResponseJSON;
|
|
25
25
|
getAuthenticatorInfo(): AuthenticatorInfo;
|
|
26
|
+
signalUnknownCredential(options: UnknownCredentialOptionsJSON): void;
|
|
27
|
+
/**
|
|
28
|
+
* Signal all accepted credentials for a user
|
|
29
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalAllAcceptedCredentials_static
|
|
30
|
+
*/
|
|
31
|
+
signalAllAcceptedCredentials(options: AllAcceptedCredentialsOptionsJSON): void;
|
|
32
|
+
/**
|
|
33
|
+
* Process credentials for an RP and delete those not in the accepted list
|
|
34
|
+
*/
|
|
35
|
+
private processCredentials;
|
|
36
|
+
/**
|
|
37
|
+
* Signal current user details
|
|
38
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalCurrentUserDetails_static
|
|
39
|
+
*/
|
|
40
|
+
signalCurrentUserDetails(options: CurrentUserDetailsOptionsJSON): void;
|
|
26
41
|
/** @see https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get */
|
|
27
42
|
get(origin: string, options: CredentialRequestOptions): RequestPublicKeyCredential;
|
|
28
43
|
/** @see https://developer.mozilla.org/ja/docs/Web/API/CredentialsContainer/create */
|
|
@@ -48,6 +48,86 @@ class WebAuthnEmulator {
|
|
|
48
48
|
},
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
|
+
signalUnknownCredential(options) {
|
|
52
|
+
const credentialId = (0, webauthn_model_json_1.decodeBase64Url)(options.credentialId);
|
|
53
|
+
this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
54
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential,
|
|
55
|
+
subCommandParams: {
|
|
56
|
+
credentialId: encode_utils_1.default.bufferSourceToUint8Array(credentialId),
|
|
57
|
+
rpId: options.rpId,
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Signal all accepted credentials for a user
|
|
63
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalAllAcceptedCredentials_static
|
|
64
|
+
*/
|
|
65
|
+
signalAllAcceptedCredentials(options) {
|
|
66
|
+
// Create a set of accepted credential IDs for quick lookup
|
|
67
|
+
const acceptedCredentialIds = new Set(options.allAcceptedCredentialIds);
|
|
68
|
+
// Start enumerating credentials for the specified RP
|
|
69
|
+
const beginResponse = (0, ctap_model_1.unpackCredentialManagementResponse)(this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
70
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsBegin,
|
|
71
|
+
subCommandParams: {
|
|
72
|
+
rpId: options.rpId,
|
|
73
|
+
},
|
|
74
|
+
})));
|
|
75
|
+
// Get the total number of credentials
|
|
76
|
+
const totalCredentials = beginResponse.totalCredentials ?? 0;
|
|
77
|
+
// Get all credentials for the RP and delete those not in the accepted list
|
|
78
|
+
this.processCredentials(options.rpId, options.userId, acceptedCredentialIds, totalCredentials);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Process credentials for an RP and delete those not in the accepted list
|
|
82
|
+
*/
|
|
83
|
+
processCredentials(rpId, userId, acceptedCredentialIds, totalCredentials) {
|
|
84
|
+
// Process each credential
|
|
85
|
+
for (let i = 0; i < totalCredentials; i++) {
|
|
86
|
+
// Get next credential and unpack the response
|
|
87
|
+
const credResponse = (0, ctap_model_1.unpackCredentialManagementResponse)(this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
88
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.enumerateCredentialsGetNextCredential,
|
|
89
|
+
})));
|
|
90
|
+
// Skip if this is not for the user we're looking for
|
|
91
|
+
if (credResponse.user && encode_utils_1.default.encodeBase64Url(credResponse.user.id) !== userId) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// Delete credential if it's not in the accepted list
|
|
95
|
+
if (credResponse.credentialID) {
|
|
96
|
+
const credentialId = encode_utils_1.default.encodeBase64Url(credResponse.credentialID);
|
|
97
|
+
if (!acceptedCredentialIds.has(credentialId)) {
|
|
98
|
+
// Delete the credential directly
|
|
99
|
+
this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
100
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.deleteCredential,
|
|
101
|
+
subCommandParams: {
|
|
102
|
+
credentialId: credResponse.credentialID,
|
|
103
|
+
rpId: rpId,
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Signal current user details
|
|
112
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/signalCurrentUserDetails_static
|
|
113
|
+
*/
|
|
114
|
+
signalCurrentUserDetails(options) {
|
|
115
|
+
const userId = (0, webauthn_model_json_1.decodeBase64Url)(options.userId);
|
|
116
|
+
// Create user object with updated information
|
|
117
|
+
const user = {
|
|
118
|
+
id: userId,
|
|
119
|
+
name: options.name,
|
|
120
|
+
displayName: options.displayName,
|
|
121
|
+
};
|
|
122
|
+
// Update user information using the credential management API
|
|
123
|
+
this.authenticator.command((0, ctap_model_1.packCredentialManagementRequest)({
|
|
124
|
+
subCommand: ctap_model_1.CREDENTIAL_MANAGEMENT_SUBCOMMAND.updateUserInformation,
|
|
125
|
+
subCommandParams: {
|
|
126
|
+
rpId: options.rpId,
|
|
127
|
+
user: user,
|
|
128
|
+
},
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
51
131
|
/** @see https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get */
|
|
52
132
|
get(origin, options) {
|
|
53
133
|
if (!options.publicKey)
|
|
@@ -91,6 +91,21 @@ export declare function toAuthenticationResponseJSON(credential: PublicKeyCreden
|
|
|
91
91
|
export declare function toCreationOptionsJSON(options: PublicKeyCredentialCreationOptions): PublicKeyCredentialCreationOptionsJSON;
|
|
92
92
|
export declare function toRequestOptionsJSON(options: PublicKeyCredentialRequestOptions): PublicKeyCredentialRequestOptionsJSON;
|
|
93
93
|
export declare function parseRegistrationResponseFromJSON(options: RegistrationResponseJSON): CreatePublicKeyCredential;
|
|
94
|
+
export interface UnknownCredentialOptionsJSON {
|
|
95
|
+
rpId: string;
|
|
96
|
+
credentialId: Base64urlString;
|
|
97
|
+
}
|
|
98
|
+
export interface AllAcceptedCredentialsOptionsJSON {
|
|
99
|
+
rpId: string;
|
|
100
|
+
userId: Base64urlString;
|
|
101
|
+
allAcceptedCredentialIds: Base64urlString[];
|
|
102
|
+
}
|
|
103
|
+
export interface CurrentUserDetailsOptionsJSON {
|
|
104
|
+
rpId: string;
|
|
105
|
+
userId: Base64urlString;
|
|
106
|
+
name: string;
|
|
107
|
+
displayName: string;
|
|
108
|
+
}
|
|
94
109
|
export declare function parseAuthenticationResponseFromJSON(options: AuthenticationResponseJSON): RequestPublicKeyCredential;
|
|
95
110
|
export declare function toPublicKeyCredentialDescriptorJSON(credential: PublicKeyCredentialDescriptor): PublicKeyCredentialDescriptorJSON;
|
|
96
111
|
export declare function parsePublicKeyCredentialDescriptorFromJSON(credential: PublicKeyCredentialDescriptorJSON): PublicKeyCredentialDescriptor;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nid-webauthn-emulator",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"test",
|
|
6
6
|
"e2e",
|
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
"@jest/globals": "^29.7.0",
|
|
20
20
|
"@simplewebauthn/server": "^13.1.1",
|
|
21
21
|
"@tsconfig/recommended": "^1.0.8",
|
|
22
|
-
"@types/node": "^22.13.
|
|
22
|
+
"@types/node": "^22.13.10",
|
|
23
23
|
"jest": "^29.7.0",
|
|
24
|
-
"ts-jest": "^29.2.
|
|
24
|
+
"ts-jest": "^29.2.6",
|
|
25
25
|
"ts-node": "^10.9.2",
|
|
26
|
-
"typescript": "^5.
|
|
26
|
+
"typescript": "^5.8.2"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"cbor": "^10.0.3",
|
|
30
|
-
"tldts": "^6.1.
|
|
30
|
+
"tldts": "^6.1.84"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"check": "biome check .",
|