nid-webauthn-emulator 0.2.16 → 0.2.17
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.
|
@@ -8,6 +8,62 @@ const node_test_1 = require("node:test");
|
|
|
8
8
|
const encode_utils_1 = __importDefault(require("../../src/libs/encode-utils"));
|
|
9
9
|
const webauthn_emulator_1 = require("../../src/webauthn/webauthn-emulator");
|
|
10
10
|
const webauthn_model_1 = require("../../src/webauthn/webauthn-model");
|
|
11
|
+
(0, node_test_1.describe)("RP ID validation", () => {
|
|
12
|
+
for (const [rpId, origin] of [
|
|
13
|
+
["example.com", "https://example.com"],
|
|
14
|
+
["example.com", "https://login.example.com"],
|
|
15
|
+
["login.example.com", "https://nested.login.example.com"],
|
|
16
|
+
["example.co.jp", "https://login.example.co.jp"],
|
|
17
|
+
["localhost", "http://localhost:3000"],
|
|
18
|
+
["localhost", "http://app.localhost:3000"],
|
|
19
|
+
["EXAMPLE.COM", "https://LOGIN.EXAMPLE.COM:8443"],
|
|
20
|
+
["example.com.", "https://login.example.com."],
|
|
21
|
+
["bücher.de", "https://login.xn--bcher-kva.de"],
|
|
22
|
+
]) {
|
|
23
|
+
(0, node_test_1.test)(`accepts ${origin} for ${rpId}`, () => {
|
|
24
|
+
strict_1.default.equal(new webauthn_model_1.RpId(rpId).validate(origin), true);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
for (const [rpId, origin] of [
|
|
28
|
+
["example.com", "https://other.com"],
|
|
29
|
+
["example.com", "https://notexample.com"],
|
|
30
|
+
["example.com", "https://example.com.evil.com"],
|
|
31
|
+
["login.example.com", "https://evil-login.example.com"],
|
|
32
|
+
["login.example.com", "https://other.example.com"],
|
|
33
|
+
["login.example.com", "https://example.com"],
|
|
34
|
+
["com", "https://example.com"],
|
|
35
|
+
["127.0.0.1", "http://127.0.0.1:3000"],
|
|
36
|
+
["[::1]", "http://[::1]:3000"],
|
|
37
|
+
["0.0.1", "http://127.0.0.1"],
|
|
38
|
+
["example.com", "https://[invalid]"],
|
|
39
|
+
["example.com", ""],
|
|
40
|
+
["", "https://example.com"],
|
|
41
|
+
["invalid host", "https://example.com"],
|
|
42
|
+
]) {
|
|
43
|
+
(0, node_test_1.test)(`rejects ${origin} for ${rpId}`, () => {
|
|
44
|
+
strict_1.default.equal(new webauthn_model_1.RpId(rpId).validate(origin), false);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
(0, node_test_1.test)("accepts related hosts and their subdomains", () => {
|
|
48
|
+
const rpId = new webauthn_model_1.RpId("example.com");
|
|
49
|
+
const relatedOrigins = ["https://login.other.com"];
|
|
50
|
+
strict_1.default.equal(rpId.validate("https://login.other.com"), false);
|
|
51
|
+
strict_1.default.equal(rpId.validate("https://login.other.com", relatedOrigins), true);
|
|
52
|
+
strict_1.default.equal(rpId.validate("https://nested.login.other.com", relatedOrigins), true);
|
|
53
|
+
strict_1.default.equal(rpId.validate("https://evil-login.other.com", relatedOrigins), false);
|
|
54
|
+
strict_1.default.equal(rpId.validate("https://other.com", relatedOrigins), false);
|
|
55
|
+
});
|
|
56
|
+
(0, node_test_1.test)("preserves hostname-only matching for related origins", () => {
|
|
57
|
+
strict_1.default.equal(new webauthn_model_1.RpId("example.com").validate("http://other.com:3000", ["https://other.com"]), true);
|
|
58
|
+
});
|
|
59
|
+
(0, node_test_1.test)("ignores malformed related origins", () => {
|
|
60
|
+
strict_1.default.equal(new webauthn_model_1.RpId("example.com").validate("https://other.com", ["https://[invalid]", "https://other.com"]), true);
|
|
61
|
+
});
|
|
62
|
+
(0, node_test_1.test)("does not distinguish multi-label public suffixes from registrable domains", () => {
|
|
63
|
+
strict_1.default.equal(new webauthn_model_1.RpId("co.jp").validate("https://example.co.jp"), true);
|
|
64
|
+
strict_1.default.equal(new webauthn_model_1.RpId("co.uk").validate("https://example.co.uk"), true);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
11
67
|
(0, node_test_1.describe)("WebAuthn Model Test", () => {
|
|
12
68
|
const webauthnEmulator = new webauthn_emulator_1.WebAuthnEmulator();
|
|
13
69
|
(0, node_test_1.test)("Attestation Object pack and unpack", async () => {
|
|
@@ -4,7 +4,7 @@ export declare class RpId {
|
|
|
4
4
|
readonly value: string;
|
|
5
5
|
constructor(value: string);
|
|
6
6
|
get hash(): Uint8Array<ArrayBuffer>;
|
|
7
|
-
/**
|
|
7
|
+
/** Matches hostnames without Public Suffix List validation. */
|
|
8
8
|
validate(origin: string, relatedOrigins?: string[]): boolean;
|
|
9
9
|
}
|
|
10
10
|
/** @see https://www.w3.org/TR/webauthn-3/#public-key-credential-source */
|
|
@@ -13,7 +13,7 @@ exports.parsePublicKeyCredentialSourceFromJSON = parsePublicKeyCredentialSourceF
|
|
|
13
13
|
exports.toFido2CreateOptions = toFido2CreateOptions;
|
|
14
14
|
exports.toFido2RequestOptions = toFido2RequestOptions;
|
|
15
15
|
const node_crypto_1 = require("node:crypto");
|
|
16
|
-
const
|
|
16
|
+
const node_net_1 = require("node:net");
|
|
17
17
|
const encode_utils_1 = __importDefault(require("../libs/encode-utils"));
|
|
18
18
|
const cose_key_1 = require("./cose-key");
|
|
19
19
|
class RpId {
|
|
@@ -24,26 +24,29 @@ class RpId {
|
|
|
24
24
|
get hash() {
|
|
25
25
|
return new Uint8Array((0, node_crypto_1.createHash)("sha256").update(this.value).digest());
|
|
26
26
|
}
|
|
27
|
-
/**
|
|
27
|
+
/** Matches hostnames without Public Suffix List validation. */
|
|
28
28
|
validate(origin, relatedOrigins = []) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
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;
|
|
29
|
+
const hostname = parseHostname(origin);
|
|
30
|
+
if (!hostname)
|
|
31
|
+
return false;
|
|
32
|
+
return [this.value, ...relatedOrigins].some((expectedOrigin) => {
|
|
33
|
+
const expectedHostname = parseHostname(expectedOrigin);
|
|
34
|
+
return Boolean(expectedHostname && (hostname === expectedHostname || hostname.endsWith(`.${expectedHostname}`)));
|
|
35
|
+
});
|
|
44
36
|
}
|
|
45
37
|
}
|
|
46
38
|
exports.RpId = RpId;
|
|
39
|
+
function parseHostname(value) {
|
|
40
|
+
try {
|
|
41
|
+
const hostname = new URL(value.includes("://") ? value : `https://${value}`).hostname.replace(/\.$/, "");
|
|
42
|
+
if ((0, node_net_1.isIP)(hostname) || hostname.startsWith("["))
|
|
43
|
+
return undefined;
|
|
44
|
+
return hostname === "localhost" || hostname.includes(".") ? hostname : undefined;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
47
50
|
function packAttestationObject(attestationObject) {
|
|
48
51
|
return encode_utils_1.default.encodeCbor({
|
|
49
52
|
fmt: attestationObject.fmt,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nid-webauthn-emulator",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"keywords": [
|
|
5
5
|
"test",
|
|
6
6
|
"e2e",
|
|
@@ -18,15 +18,12 @@
|
|
|
18
18
|
"main": "dist/src/index.js",
|
|
19
19
|
"types": "dist/src/index.d.ts",
|
|
20
20
|
"devDependencies": {
|
|
21
|
-
"@biomejs/biome": "2.
|
|
21
|
+
"@biomejs/biome": "2.6.3",
|
|
22
22
|
"@simplewebauthn/server": "14.0.3",
|
|
23
23
|
"@tsconfig/recommended": "1.0.13",
|
|
24
|
-
"@types/node": "26.6.
|
|
24
|
+
"@types/node": "26.6.5",
|
|
25
25
|
"typescript": "7.0.2"
|
|
26
26
|
},
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"tldts": "7.4.16"
|
|
29
|
-
},
|
|
30
27
|
"scripts": {
|
|
31
28
|
"check": "biome check .",
|
|
32
29
|
"test": "pnpm build && node --test --test-concurrency=4 \"dist/spec/**/*.spec.js\"",
|