nid-webauthn-emulator 0.2.2 → 0.2.4

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 CHANGED
@@ -125,13 +125,13 @@ import WebAuthnEmulator, {
125
125
  type PublicKeyCredentialRequestOptionsJSON,
126
126
  } from "nid-webauthn-emulator";
127
127
 
128
- async function startWebAuthnEmulator(page: Page, origin: string, debug = false) {
128
+ async function startWebAuthnEmulator(page: Page, origin: string, debug = false, relatedOrigins: string[] = []) {
129
129
  const emulator = new WebAuthnEmulator();
130
130
 
131
131
  await page.exposeFunction(
132
132
  BrowserInjection.WebAuthnEmulatorCreate,
133
133
  async (optionsJSON: PublicKeyCredentialCreationOptionsJSON) => {
134
- const response = emulator.createJSON(origin, optionsJSON);
134
+ const response = emulator.createJSON(origin, optionsJSON, relatedOrigins);
135
135
  return response;
136
136
  },
137
137
  );
@@ -139,7 +139,7 @@ async function startWebAuthnEmulator(page: Page, origin: string, debug = false)
139
139
  await page.exposeFunction(
140
140
  BrowserInjection.WebAuthnEmulatorGet,
141
141
  async (optionsJSON: PublicKeyCredentialRequestOptionsJSON) => {
142
- const response = emulator.getJSON(origin, optionsJSON);
142
+ const response = emulator.getJSON(origin, optionsJSON, relatedOrigins);
143
143
  return response;
144
144
  },
145
145
  );
@@ -155,21 +155,23 @@ async function startWebAuthnEmulator(page: Page, origin: string, debug = false)
155
155
  BrowserInjection.WebAuthnEmulatorSignalAllAcceptedCredentials,
156
156
  async (options: AllAcceptedCredentialsOptionsJSON) => {
157
157
  emulator.signalAllAcceptedCredentials(options);
158
- };
158
+ },
159
159
  );
160
160
 
161
161
  await page.exposeFunction(
162
162
  BrowserInjection.WebAuthnEmulatorSignalCurrentUserDetails,
163
163
  async (options: CurrentUserDetailsOptionsJSON) => {
164
164
  emulator.signalCurrentUserDetails(options);
165
- };
165
+ },
166
166
  );
167
167
  }
168
168
 
169
169
  test.describe("Passkeys Tests", { tag: ["@daily"] }, () => {
170
170
  test("Passkeys login test", async ({ page }) => {
171
171
  // Page内で最初に1回だけ定義する exposed functions
172
- await startWebAuthnEmulator(page, env, true);
172
+ // 必要に応じて関連オリジンを指定できます
173
+ const relatedOrigins = ["https://sub.example.com", "https://alt.example.com"];
174
+ await startWebAuthnEmulator(page, env, true, relatedOrigins);
173
175
  await page.goto("https://example.com/passkeys/login");
174
176
 
175
177
  // Passkeys の WebAuthn API をフック開始
@@ -184,6 +186,10 @@ test.describe("Passkeys Tests", { tag: ["@daily"] }, () => {
184
186
  - `window.webAuthnEmulatorGet`: `WebAuthnEmulator.getJSON` の Exposed Function
185
187
  - `window.webAuthnEmulatorCreate`: `WebAuthnEmulator.createJSON` の Exposed Function
186
188
  - `window.webAuthnEmulatorSignalUnknownCredential`: `WebAuthnEmulator.signalUnknownCredential` の Exposed Function
189
+ - `window.webAuthnEmulatorSignalAllAcceptedCredentials`: `WebAuthnEmulator.signalAllAcceptedCredentials` の Exposed Function
190
+ - `window.webAuthnEmulatorSignalCurrentUserDetails`: `WebAuthnEmulator.signalCurrentUserDetails` の Exposed Function
191
+
192
+ さらに`startWebAuthnEmulator`関数は`relatedOrigins`パラメータをサポートしています。これにより、異なるオリジンからのリクエストでも同じRP IDを使用できるようになります。例えば、マルチドメイン環境(`example.com`と`sub.example.com`など)でPasskeysを使用する場合に便利です。`relatedOrigins` の値はRP IDで指定されたドメインでホストされた `/.well-known/webauthn` の内容と同じです。
187
193
 
188
194
  これらは Page グローバルに定義されるため、Page インスタンスにつき 1 回だけ定義する必要があります。
189
195
 
@@ -337,6 +337,29 @@ const webauthn_test_server_1 = require("./webauthn-test-server");
337
337
  (0, globals_1.expect)(userCredentialsAfter.length).toBe(0);
338
338
  });
339
339
  });
340
+ (0, globals_1.describe)("WebAuthnEmulator relatedOrigins Test", () => {
341
+ (0, globals_1.test)("Get with relatedOrigins _ OK with origin not matching rpId", async () => {
342
+ const user = { username: "test-related", id: "test-related" };
343
+ const emulator = new src_1.default();
344
+ // Origin is different from the RP ID
345
+ const testServer = new webauthn_test_server_1.WebAuthnTestServer("https://test-rp.com", "test-rp2.com");
346
+ const relatedOrigins = ["https://test-rp-a.com", "https://test-rp-b.com", "https://test-rp.com"];
347
+ // Register a credential
348
+ const regOptions = await testServer.getRegistrationOptions(user);
349
+ const credential = emulator.createJSON(testServer.origin, regOptions, relatedOrigins);
350
+ await testServer.getRegistrationVerification(user, credential);
351
+ // Create authentication options
352
+ const authOptions = await testServer.getAuthenticationOptions();
353
+ // Standard RP ID validation only
354
+ await (0, globals_1.expect)(async () => {
355
+ emulator.getJSON(testServer.origin, authOptions);
356
+ }).rejects.toThrow(src_1.InvalidRpIdError);
357
+ // Related origins validation
358
+ const authResponse = emulator.getJSON(testServer.origin, authOptions, relatedOrigins);
359
+ (0, globals_1.expect)(authResponse).toBeDefined();
360
+ (0, globals_1.expect)(authResponse.id).toBe(credential.id);
361
+ });
362
+ });
340
363
  (0, globals_1.describe)("WebAuthnEmulator signalCurrentUserDetails Test", () => {
341
364
  (0, globals_1.test)("Signal Current User Details _ OK", async () => {
342
365
  // Create user
@@ -20,8 +20,8 @@ export declare class InvalidRpIdError extends WebAuthnEmulatorError {
20
20
  export declare class WebAuthnEmulator {
21
21
  authenticator: AuthenticatorEmulator;
22
22
  constructor(authenticator?: AuthenticatorEmulator);
23
- getJSON(origin: string, optionsJSON: PublicKeyCredentialRequestOptionsJSON): AuthenticationResponseJSON;
24
- createJSON(origin: string, optionsJSON: PublicKeyCredentialCreationOptionsJSON): RegistrationResponseJSON;
23
+ getJSON(origin: string, optionsJSON: PublicKeyCredentialRequestOptionsJSON, relatedOrigins?: string[]): AuthenticationResponseJSON;
24
+ createJSON(origin: string, optionsJSON: PublicKeyCredentialCreationOptionsJSON, relatedOrigins?: string[]): RegistrationResponseJSON;
25
25
  getAuthenticatorInfo(): AuthenticatorInfo;
26
26
  signalUnknownCredential(options: UnknownCredentialOptionsJSON): void;
27
27
  /**
@@ -39,7 +39,7 @@ export declare class WebAuthnEmulator {
39
39
  */
40
40
  signalCurrentUserDetails(options: CurrentUserDetailsOptionsJSON): void;
41
41
  /** @see https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get */
42
- get(origin: string, options: CredentialRequestOptions): RequestPublicKeyCredential;
42
+ get(origin: string, options: CredentialRequestOptions, relatedOrigins?: string[]): RequestPublicKeyCredential;
43
43
  /** @see https://developer.mozilla.org/ja/docs/Web/API/CredentialsContainer/create */
44
- create(origin: string, options: CredentialCreationOptions): CreatePublicKeyCredential;
44
+ create(origin: string, options: CredentialCreationOptions, relatedOrigins?: string[]): CreatePublicKeyCredential;
45
45
  }
@@ -27,14 +27,14 @@ class WebAuthnEmulator {
27
27
  constructor(authenticator = new authenticator_emulator_1.AuthenticatorEmulator()) {
28
28
  this.authenticator = authenticator;
29
29
  }
30
- getJSON(origin, optionsJSON) {
30
+ getJSON(origin, optionsJSON, relatedOrigins = []) {
31
31
  const options = (0, webauthn_model_json_1.parseRequestOptionsFromJSON)(optionsJSON);
32
- const response = this.get(origin, { publicKey: options });
32
+ const response = this.get(origin, { publicKey: options }, relatedOrigins);
33
33
  return response.toJSON();
34
34
  }
35
- createJSON(origin, optionsJSON) {
35
+ createJSON(origin, optionsJSON, relatedOrigins = []) {
36
36
  const options = (0, webauthn_model_json_1.parseCreationOptionsFromJSON)(optionsJSON);
37
- const response = this.create(origin, { publicKey: options });
37
+ const response = this.create(origin, { publicKey: options }, relatedOrigins);
38
38
  return response.toJSON();
39
39
  }
40
40
  getAuthenticatorInfo() {
@@ -129,11 +129,11 @@ class WebAuthnEmulator {
129
129
  }));
130
130
  }
131
131
  /** @see https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get */
132
- get(origin, options) {
132
+ get(origin, options, relatedOrigins = []) {
133
133
  if (!options.publicKey)
134
134
  throw new NoPublicKeyError("PublicKeyCredentialCreationOptions are required");
135
135
  const rpId = new webauthn_model_1.RpId(options.publicKey.rpId ?? new URL(origin).hostname);
136
- if (!rpId.validate(origin))
136
+ if (!rpId.validate(origin, relatedOrigins))
137
137
  throw new InvalidRpIdError(`Invalid rpId: RP_ID=${rpId.value}, ORIGIN=${origin}`);
138
138
  const clientData = {
139
139
  type: "webauthn.get",
@@ -170,11 +170,11 @@ class WebAuthnEmulator {
170
170
  return publicKeyCredential;
171
171
  }
172
172
  /** @see https://developer.mozilla.org/ja/docs/Web/API/CredentialsContainer/create */
173
- create(origin, options) {
173
+ create(origin, options, relatedOrigins = []) {
174
174
  if (!options.publicKey)
175
175
  throw new NoPublicKeyError("PublicKeyCredentialCreationOptions are required");
176
176
  const rpId = new webauthn_model_1.RpId(options.publicKey.rp.id ?? new URL(origin).hostname);
177
- if (!rpId.validate(origin))
177
+ if (!rpId.validate(origin, relatedOrigins))
178
178
  throw new InvalidRpIdError(`Invalid rpId: RP_ID=${rpId.value}, ORIGIN=${origin}`);
179
179
  const clientData = {
180
180
  challenge: encode_utils_1.default.encodeBase64Url(options.publicKey.challenge),
@@ -5,7 +5,7 @@ export declare class RpId {
5
5
  constructor(value: string);
6
6
  get hash(): Uint8Array;
7
7
  /** @see https://www.w3.org/TR/webauthn-3/#sctn-validating-origin */
8
- validate(origin: string): boolean;
8
+ validate(origin: string, relatedOrigins?: string[]): boolean;
9
9
  }
10
10
  /** @see https://www.w3.org/TR/webauthn-3/#public-key-credential-source */
11
11
  export type PublicKeyCredentialSource = {
@@ -25,14 +25,22 @@ class RpId {
25
25
  return new Uint8Array((0, node_crypto_1.createHash)("sha256").update(this.value).digest());
26
26
  }
27
27
  /** @see https://www.w3.org/TR/webauthn-3/#sctn-validating-origin */
28
- validate(origin) {
28
+ validate(origin, relatedOrigins = []) {
29
29
  const parsedOrigin = (0, tldts_1.parse)(origin, { validHosts: ["localhost"] });
30
- const parsedRpId = (0, tldts_1.parse)(this.value, { validHosts: ["localhost"] });
31
- return Boolean(parsedOrigin.domain &&
32
- parsedOrigin.subdomain !== null &&
33
- parsedRpId.subdomain !== null &&
34
- parsedOrigin.domain === parsedRpId.domain &&
35
- parsedOrigin.subdomain.endsWith(parsedRpId.subdomain));
30
+ const expectedOrigins = [this.value, ...relatedOrigins];
31
+ // Check each origin using standard validation
32
+ for (const expectedOrigin of expectedOrigins) {
33
+ const parsedExpectedOrigin = (0, tldts_1.parse)(expectedOrigin, { validHosts: ["localhost"] });
34
+ const isValid = Boolean(parsedOrigin.domain &&
35
+ parsedOrigin.subdomain !== null &&
36
+ parsedExpectedOrigin.subdomain !== null &&
37
+ parsedOrigin.domain === parsedExpectedOrigin.domain &&
38
+ parsedOrigin.subdomain.endsWith(parsedExpectedOrigin.subdomain));
39
+ if (isValid) {
40
+ return true;
41
+ }
42
+ }
43
+ return false;
36
44
  }
37
45
  }
38
46
  exports.RpId = RpId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nid-webauthn-emulator",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "keywords": [
5
5
  "test",
6
6
  "e2e",
@@ -19,7 +19,7 @@
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.14.1",
22
+ "@types/node": "^22.15.17",
23
23
  "jest": "^29.7.0",
24
24
  "ts-jest": "^29.3.2",
25
25
  "ts-node": "^10.9.2",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "cbor": "^10.0.3",
30
- "tldts": "^7.0.0"
30
+ "tldts": "^7.0.7"
31
31
  },
32
32
  "scripts": {
33
33
  "check": "biome check .",