caa 9.0.3 → 9.2.0
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/dist/index.d.ts +11 -0
- package/dist/index.js +126 -0
- package/package.json +20 -10
- package/index.js +0 -140
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type CaaOpts = {
|
|
2
|
+
ignoreTLDs?: boolean;
|
|
3
|
+
recursions?: number;
|
|
4
|
+
retries?: number;
|
|
5
|
+
port?: number;
|
|
6
|
+
servers?: string[];
|
|
7
|
+
dnsSocket?: any;
|
|
8
|
+
};
|
|
9
|
+
export declare function caa(name: any, opts?: CaaOpts): Promise<any>;
|
|
10
|
+
export declare function caaMatches(name: any, ca: any, opts?: {}): Promise<any>;
|
|
11
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { getServers } from "node:dns";
|
|
2
|
+
import { promisify } from "node:util";
|
|
3
|
+
import dnsSocket from "dns-socket";
|
|
4
|
+
import tlds from "tlds";
|
|
5
|
+
const defaults = {
|
|
6
|
+
ignoreTLDs: false,
|
|
7
|
+
recursions: 50,
|
|
8
|
+
retries: 12,
|
|
9
|
+
port: 53,
|
|
10
|
+
servers: ["8.8.8.8", "8.8.4.4"],
|
|
11
|
+
// these are only used as fallback to system's servers
|
|
12
|
+
dnsSocket: void 0
|
|
13
|
+
};
|
|
14
|
+
const tldSet = new Set(tlds);
|
|
15
|
+
const isTLD = (name) => tldSet.has(name);
|
|
16
|
+
const isWildcard = (name) => /\*/.test(name);
|
|
17
|
+
const parent = (name) => name.split(".").splice(1).join(".");
|
|
18
|
+
const selectServer = (servers, retries, tries) => servers[(tries - retries) % servers.length];
|
|
19
|
+
function normalizeName(name = "") {
|
|
20
|
+
name = name.toLowerCase();
|
|
21
|
+
return name.endsWith(".") && name.length > 1 ? name.substring(0, name.length - 1) : name;
|
|
22
|
+
}
|
|
23
|
+
const resolve = async ({ name, query, servers, port, recursions, retries, tries, ignoreTLDs }) => {
|
|
24
|
+
var _a, _b, _c, _d, _e;
|
|
25
|
+
name = normalizeName(name);
|
|
26
|
+
if (!name)
|
|
27
|
+
return [];
|
|
28
|
+
if (ignoreTLDs && isTLD(name))
|
|
29
|
+
return [];
|
|
30
|
+
if (recursions <= 0 || retries <= 0)
|
|
31
|
+
return [];
|
|
32
|
+
name = name.replace(/^\*\./, "");
|
|
33
|
+
let res;
|
|
34
|
+
try {
|
|
35
|
+
res = await query({ questions: [{ name, type: "CAA" }] }, port, selectServer(servers, retries, tries));
|
|
36
|
+
} catch {
|
|
37
|
+
if (retries <= 0)
|
|
38
|
+
return [];
|
|
39
|
+
retries -= 1;
|
|
40
|
+
return await resolve({ name, query, servers, port, recursions, retries, tries, ignoreTLDs });
|
|
41
|
+
}
|
|
42
|
+
if (!res || !res.answers && !["NXDOMAIN", "NOERROR"].includes(res.rcode)) {
|
|
43
|
+
if (retries <= 0)
|
|
44
|
+
return [];
|
|
45
|
+
retries -= 1;
|
|
46
|
+
return await resolve({ name, query, servers, port, recursions, retries, tries, ignoreTLDs });
|
|
47
|
+
}
|
|
48
|
+
const records = {};
|
|
49
|
+
if ((_a = res == null ? void 0 : res.answers) == null ? void 0 : _a.length) {
|
|
50
|
+
for (const { name: name2, type, data } of res.answers || {}) {
|
|
51
|
+
if (!name2 || !type || !data)
|
|
52
|
+
continue;
|
|
53
|
+
if (!records[type])
|
|
54
|
+
records[type] = [];
|
|
55
|
+
records[type].push({ name: name2, data });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if ((_b = records.CAA) == null ? void 0 : _b.length) {
|
|
59
|
+
const caas = records.CAA.filter((record) => record.name === name).map((record) => record.data);
|
|
60
|
+
if (caas.length)
|
|
61
|
+
return caas;
|
|
62
|
+
}
|
|
63
|
+
let alias;
|
|
64
|
+
if ((_c = records.CNAME) == null ? void 0 : _c.length) {
|
|
65
|
+
const dest = records.CNAME.find((record) => record.name === name);
|
|
66
|
+
alias = dest.data;
|
|
67
|
+
} else if ((_d = records.DNAME) == null ? void 0 : _d.length) {
|
|
68
|
+
const dest = records.DNAME.find((record) => record.name === name);
|
|
69
|
+
alias = name.replace(dest.name, dest.data);
|
|
70
|
+
}
|
|
71
|
+
if (alias && ((_e = records.CAA) == null ? void 0 : _e.length)) {
|
|
72
|
+
return records.CAA.filter((record) => record.name === alias && record.data).map((record) => record.data);
|
|
73
|
+
}
|
|
74
|
+
if (!isTLD(name)) {
|
|
75
|
+
recursions -= 1;
|
|
76
|
+
return await resolve({ name: parent(name), query, servers, port, recursions, retries, tries, ignoreTLDs });
|
|
77
|
+
} else {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
async function caa(name, opts = {}) {
|
|
82
|
+
if (typeof name !== "string")
|
|
83
|
+
throw new Error(`Expected a string for 'name', got ${name}`);
|
|
84
|
+
name = normalizeName(name);
|
|
85
|
+
if (!opts.servers) {
|
|
86
|
+
const systemServers = getServers();
|
|
87
|
+
opts.servers = (systemServers == null ? void 0 : systemServers.length) ? systemServers : defaults.servers;
|
|
88
|
+
}
|
|
89
|
+
opts = { ...defaults, ...opts };
|
|
90
|
+
const socket = opts.dnsSocket || dnsSocket();
|
|
91
|
+
const query = promisify(socket.query.bind(socket));
|
|
92
|
+
const caa2 = await resolve({
|
|
93
|
+
name,
|
|
94
|
+
query,
|
|
95
|
+
servers: opts.servers,
|
|
96
|
+
port: opts.port,
|
|
97
|
+
recursions: opts.recursions,
|
|
98
|
+
retries: opts.retries,
|
|
99
|
+
tries: opts.retries,
|
|
100
|
+
ignoreTLDs: opts.ignoreTLDs
|
|
101
|
+
});
|
|
102
|
+
if (!opts.dnsSocket)
|
|
103
|
+
socket.destroy();
|
|
104
|
+
return caa2 || [];
|
|
105
|
+
}
|
|
106
|
+
async function caaMatches(name, ca, opts = {}) {
|
|
107
|
+
if (typeof name !== "string")
|
|
108
|
+
throw new Error(`Expected a string for 'name', got ${name}`);
|
|
109
|
+
if (typeof ca !== "string")
|
|
110
|
+
throw new Error(`Expected a string for 'ca', got ${ca}`);
|
|
111
|
+
name = normalizeName(name);
|
|
112
|
+
ca = normalizeName(ca);
|
|
113
|
+
const caas = await caa(name, opts);
|
|
114
|
+
if (!caas.length)
|
|
115
|
+
return true;
|
|
116
|
+
const issueNames = caas.filter((caa2) => (caa2 == null ? void 0 : caa2.tag) === "issue").map((name2) => normalizeName(name2.value.split(";")[0].trim()));
|
|
117
|
+
const issueWildNames = caas.filter((caa2) => (caa2 == null ? void 0 : caa2.tag) === "issuewild").map((name2) => normalizeName(name2.value.split(";")[0].trim()));
|
|
118
|
+
const names = isWildcard(name) ? issueWildNames.length ? issueWildNames : issueNames : issueNames;
|
|
119
|
+
if (names.includes(";"))
|
|
120
|
+
return false;
|
|
121
|
+
return !names.length || names.includes(ca);
|
|
122
|
+
}
|
|
123
|
+
export {
|
|
124
|
+
caa,
|
|
125
|
+
caaMatches
|
|
126
|
+
};
|
package/package.json
CHANGED
|
@@ -1,28 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "caa",
|
|
3
|
-
"version": "9.0
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "rfc6844-conform CAA record lookup and validation",
|
|
5
5
|
"author": "silverwind <me@silverwind.io>",
|
|
6
6
|
"repository": "silverwind/caa",
|
|
7
7
|
"license": "BSD-2-Clause",
|
|
8
8
|
"type": "module",
|
|
9
|
-
"
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"exports": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
10
16
|
"engines": {
|
|
11
17
|
"node": ">=18"
|
|
12
18
|
},
|
|
13
|
-
"files": [
|
|
14
|
-
"index.js"
|
|
15
|
-
],
|
|
16
19
|
"dependencies": {
|
|
17
20
|
"dns-socket": "^4.2.2",
|
|
18
21
|
"tlds": "^1.252.0"
|
|
19
22
|
},
|
|
20
23
|
"devDependencies": {
|
|
24
|
+
"@babel/eslint-parser": "7.24.5",
|
|
25
|
+
"@babel/plugin-syntax-import-assertions": "7.24.1",
|
|
26
|
+
"@types/node": "20.12.12",
|
|
21
27
|
"eslint": "8.57.0",
|
|
22
|
-
"eslint-config-silverwind": "
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
28
|
+
"eslint-config-silverwind": "85.1.3",
|
|
29
|
+
"eslint-config-silverwind-typescript": "3.2.5",
|
|
30
|
+
"typescript-config-silverwind": "4.3.1",
|
|
31
|
+
"updates": "16.1.1",
|
|
32
|
+
"versions": "12.0.2",
|
|
33
|
+
"vite": "5.2.11",
|
|
34
|
+
"vite-plugin-dts": "3.9.1",
|
|
35
|
+
"vitest": "1.6.0",
|
|
36
|
+
"vitest-config-silverwind": "9.0.6"
|
|
27
37
|
}
|
|
28
38
|
}
|
package/index.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import {getServers} from "node:dns";
|
|
2
|
-
import {promisify} from "node:util";
|
|
3
|
-
import dnsSocket from "dns-socket";
|
|
4
|
-
import tlds from "tlds";
|
|
5
|
-
|
|
6
|
-
const defaults = {
|
|
7
|
-
ignoreTLDs: false,
|
|
8
|
-
recursions: 50,
|
|
9
|
-
retries: 12,
|
|
10
|
-
port: 53,
|
|
11
|
-
servers: ["8.8.8.8", "8.8.4.4"], // these are only used as fallback to system's servers
|
|
12
|
-
dnsSocket: undefined,
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
const tldSet = new Set(tlds);
|
|
16
|
-
const isTLD = name => tldSet.has(name);
|
|
17
|
-
const isWildcard = name => /\*/.test(name);
|
|
18
|
-
const parent = name => name.split(".").splice(1).join(".");
|
|
19
|
-
const selectServer = (servers, retries, tries) => servers[(tries - retries) % servers.length];
|
|
20
|
-
|
|
21
|
-
function normalizeName(name = "") {
|
|
22
|
-
name = name.toLowerCase();
|
|
23
|
-
return (name.endsWith(".") && name.length > 1) ? name.substring(0, name.length - 1) : name;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// resolve a CAA record, possibly via recursion
|
|
27
|
-
const resolve = async ({name, query, servers, port, recursions, retries, tries, ignoreTLDs}) => {
|
|
28
|
-
name = normalizeName(name);
|
|
29
|
-
if (!name) return [];
|
|
30
|
-
if (ignoreTLDs && isTLD(name)) return [];
|
|
31
|
-
if (recursions <= 0 || retries <= 0) return [];
|
|
32
|
-
|
|
33
|
-
// Given a request for a specific domain X, or a request for a wildcard
|
|
34
|
-
// domain *.X, the relevant record set R(X) is determined ...
|
|
35
|
-
name = name.replace(/^\*\./, "");
|
|
36
|
-
|
|
37
|
-
let res;
|
|
38
|
-
try {
|
|
39
|
-
res = await query({questions: [{name, type: "CAA"}]}, port, selectServer(servers, retries, tries));
|
|
40
|
-
} catch {
|
|
41
|
-
if (retries <= 0) return [];
|
|
42
|
-
retries -= 1;
|
|
43
|
-
return await resolve({name, query, servers, port, recursions, retries, tries, ignoreTLDs});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!res || (!res.answers && !["NXDOMAIN", "NOERROR"].includes(res.rcode))) {
|
|
47
|
-
if (retries <= 0) return [];
|
|
48
|
-
retries -= 1;
|
|
49
|
-
return await resolve({name, query, servers, port, recursions, retries, tries, ignoreTLDs});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// parse DNS answers to {type: [{name, data}]}
|
|
53
|
-
const records = {};
|
|
54
|
-
if (res?.answers?.length) {
|
|
55
|
-
for (const {name, type, data} of res.answers || {}) {
|
|
56
|
-
if (!name || !type || !data) continue;
|
|
57
|
-
if (!records[type]) records[type] = [];
|
|
58
|
-
records[type].push({name, data});
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// If CAA(X) is not empty, R(X) = CAA (X)
|
|
63
|
-
if (records.CAA?.length) {
|
|
64
|
-
const caas = records.CAA.filter(record => record.name === name).map(record => record.data);
|
|
65
|
-
if (caas.length) return caas;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
let alias;
|
|
69
|
-
if (records.CNAME?.length) {
|
|
70
|
-
const dest = records.CNAME.find(record => record.name === name);
|
|
71
|
-
alias = dest.data;
|
|
72
|
-
} else if (records.DNAME?.length) {
|
|
73
|
-
const dest = records.DNAME.find(record => record.name === name);
|
|
74
|
-
alias = name.replace(dest.name, dest.data);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// If A(X) is not null, and CAA(A(X)) is not empty, then R(X) = CAA(A(X)), otherwise
|
|
78
|
-
if (alias && records.CAA?.length) {
|
|
79
|
-
return records.CAA.filter(record => record.name === alias && record.data).map(record => record.data);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// If X is not a top-level domain, then R(X) = R(P(X)
|
|
83
|
-
if (!isTLD(name)) {
|
|
84
|
-
recursions -= 1;
|
|
85
|
-
return await resolve({name: parent(name), query, servers, port, recursions, retries, tries, ignoreTLDs});
|
|
86
|
-
} else {
|
|
87
|
-
return [];
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
export async function caa(name, opts = {}) {
|
|
92
|
-
if (typeof name !== "string") throw new Error(`Expected a string for 'name', got ${name}`);
|
|
93
|
-
name = normalizeName(name);
|
|
94
|
-
|
|
95
|
-
if (!opts.servers) {
|
|
96
|
-
const systemServers = getServers();
|
|
97
|
-
opts.servers = systemServers?.length ? systemServers : defaults.servers;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
opts = {...defaults, ...opts};
|
|
101
|
-
|
|
102
|
-
const socket = opts.dnsSocket || dnsSocket();
|
|
103
|
-
const query = promisify(socket.query.bind(socket));
|
|
104
|
-
|
|
105
|
-
const caa = await resolve({
|
|
106
|
-
name, query,
|
|
107
|
-
servers: opts.servers,
|
|
108
|
-
port: opts.port,
|
|
109
|
-
recursions: opts.recursions,
|
|
110
|
-
retries: opts.retries,
|
|
111
|
-
tries: opts.retries,
|
|
112
|
-
ignoreTLDs: opts.ignoreTLDs,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
if (!opts.dnsSocket) socket.destroy();
|
|
116
|
-
return caa || [];
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export async function caaMatches(name, ca, opts = {}) {
|
|
120
|
-
if (typeof name !== "string") throw new Error(`Expected a string for 'name', got ${name}`);
|
|
121
|
-
if (typeof ca !== "string") throw new Error(`Expected a string for 'ca', got ${ca}`);
|
|
122
|
-
|
|
123
|
-
name = normalizeName(name);
|
|
124
|
-
ca = normalizeName(ca);
|
|
125
|
-
|
|
126
|
-
const caas = await caa(name, opts);
|
|
127
|
-
if (!caas.length) return true;
|
|
128
|
-
|
|
129
|
-
const issueNames = caas
|
|
130
|
-
.filter(caa => caa?.tag === "issue")
|
|
131
|
-
.map(name => normalizeName(name.value.split(";")[0].trim()));
|
|
132
|
-
const issueWildNames = caas
|
|
133
|
-
.filter(caa => caa?.tag === "issuewild")
|
|
134
|
-
.map(name => normalizeName(name.value.split(";")[0].trim()));
|
|
135
|
-
|
|
136
|
-
const names = isWildcard(name) ? (issueWildNames.length ? issueWildNames : issueNames) : issueNames;
|
|
137
|
-
|
|
138
|
-
if (names.includes(";")) return false;
|
|
139
|
-
return !names.length || names.includes(ca);
|
|
140
|
-
}
|