@atproto-labs/handle-resolver-node 0.0.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/CHANGELOG.md +13 -0
- package/LICENSE.txt +7 -0
- package/dist/dns-handle-resolver.d.ts +15 -0
- package/dist/dns-handle-resolver.d.ts.map +1 -0
- package/dist/dns-handle-resolver.js +58 -0
- package/dist/dns-handle-resolver.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/node-handle-resolver.d.ts +29 -0
- package/dist/node-handle-resolver.d.ts.map +1 -0
- package/dist/node-handle-resolver.js +49 -0
- package/dist/node-handle-resolver.js.map +1 -0
- package/package.json +40 -0
- package/src/dns-handle-resolver.ts +76 -0
- package/src/index.ts +6 -0
- package/src/node-handle-resolver.ts +96 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +4 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @atproto-labs/handle-resolver-node
|
|
2
|
+
|
|
3
|
+
## 0.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`e134c79a0`](https://github.com/bluesky-social/atproto/commit/e134c79a0ffb000b2cb36437815673fa6bda664b) Thanks [@devinivy](https://github.com/devinivy)! - Initial publish of experimental oauth packages to @atproto-labs
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`e134c79a0`](https://github.com/bluesky-social/atproto/commit/e134c79a0ffb000b2cb36437815673fa6bda664b)]:
|
|
10
|
+
- @atproto-labs/handle-resolver@0.0.1
|
|
11
|
+
- @atproto-labs/fetch-node@0.0.1
|
|
12
|
+
- @atproto-labs/fetch@0.0.1
|
|
13
|
+
- @atproto-labs/did@0.0.1
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Dual MIT/Apache-2.0 License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-2024 Bluesky PBC, and Contributors
|
|
4
|
+
|
|
5
|
+
Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
|
|
6
|
+
|
|
7
|
+
Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { resolveTxt } from 'node:dns/promises';
|
|
3
|
+
import { HandleResolveOptions, HandleResolver, ResolvedHandle } from '@atproto-labs/handle-resolver';
|
|
4
|
+
export type DnsHandleResolverOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Nameservers to use in place of the system's default nameservers.
|
|
7
|
+
*/
|
|
8
|
+
nameservers?: string[];
|
|
9
|
+
};
|
|
10
|
+
export declare class DnsHandleResolver implements HandleResolver {
|
|
11
|
+
protected resolveTxt: typeof resolveTxt;
|
|
12
|
+
constructor(options?: DnsHandleResolverOptions);
|
|
13
|
+
resolve(handle: string, _options?: HandleResolveOptions): Promise<ResolvedHandle>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=dns-handle-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dns-handle-resolver.d.ts","sourceRoot":"","sources":["../src/dns-handle-resolver.ts"],"names":[],"mappings":";AAAA,OAAO,EAAoB,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEhE,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,cAAc,EAEf,MAAM,+BAA+B,CAAA;AAKtC,MAAM,MAAM,wBAAwB,GAAG;IACrC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CACvB,CAAA;AAED,qBAAa,iBAAkB,YAAW,cAAc;IACtD,SAAS,CAAC,UAAU,EAAE,OAAO,UAAU,CAAA;gBAE3B,OAAO,CAAC,EAAE,wBAAwB;IAMjC,OAAO,CAClB,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,oBAAoB,GAC9B,OAAO,CAAC,cAAc,CAAC;CAO3B"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DnsHandleResolver = void 0;
|
|
4
|
+
const promises_1 = require("node:dns/promises");
|
|
5
|
+
const handle_resolver_1 = require("@atproto-labs/handle-resolver");
|
|
6
|
+
const SUBDOMAIN = '_atproto';
|
|
7
|
+
const PREFIX = 'did=';
|
|
8
|
+
class DnsHandleResolver {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
Object.defineProperty(this, "resolveTxt", {
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
writable: true,
|
|
14
|
+
value: void 0
|
|
15
|
+
});
|
|
16
|
+
this.resolveTxt = options?.nameservers
|
|
17
|
+
? buildResolveTxt(options.nameservers)
|
|
18
|
+
: promises_1.resolveTxt.bind(null);
|
|
19
|
+
}
|
|
20
|
+
async resolve(handle, _options) {
|
|
21
|
+
try {
|
|
22
|
+
return parseDnsResult(await this.resolveTxt(`${SUBDOMAIN}.${handle}`));
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.DnsHandleResolver = DnsHandleResolver;
|
|
30
|
+
function buildResolveTxt(nameservers) {
|
|
31
|
+
// Build the resolver asynchronously
|
|
32
|
+
const resolverPromise = Promise.allSettled(nameservers.map((h) => (0, promises_1.lookup)(h)))
|
|
33
|
+
.then((responses) => responses.flatMap((r) => r.status === 'fulfilled' ? [r.value.address] : []))
|
|
34
|
+
.then((backupIps) => {
|
|
35
|
+
if (!backupIps.length)
|
|
36
|
+
return null;
|
|
37
|
+
const resolver = new promises_1.Resolver();
|
|
38
|
+
resolver.setServers(backupIps);
|
|
39
|
+
return resolver;
|
|
40
|
+
});
|
|
41
|
+
resolverPromise.catch(() => {
|
|
42
|
+
// Should never happen
|
|
43
|
+
});
|
|
44
|
+
return async (hostname) => {
|
|
45
|
+
const resolver = await resolverPromise;
|
|
46
|
+
return resolver ? resolver.resolveTxt(hostname) : [];
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function parseDnsResult(chunkedResults) {
|
|
50
|
+
const results = chunkedResults.map((chunks) => chunks.join(''));
|
|
51
|
+
const found = results.filter((i) => i.startsWith(PREFIX));
|
|
52
|
+
if (found.length !== 1) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const did = found[0].slice(PREFIX.length);
|
|
56
|
+
return (0, handle_resolver_1.isResolvedHandle)(did) ? did : null;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=dns-handle-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dns-handle-resolver.js","sourceRoot":"","sources":["../src/dns-handle-resolver.ts"],"names":[],"mappings":";;;AAAA,gDAAgE;AAEhE,mEAKsC;AAEtC,MAAM,SAAS,GAAG,UAAU,CAAA;AAC5B,MAAM,MAAM,GAAG,MAAM,CAAA;AASrB,MAAa,iBAAiB;IAG5B,YAAY,OAAkC;QAFpC;;;;;WAA6B;QAGrC,IAAI,CAAC,UAAU,GAAG,OAAO,EAAE,WAAW;YACpC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,WAAW,CAAC;YACtC,CAAC,CAAC,qBAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC3B,CAAC;IAEM,KAAK,CAAC,OAAO,CAClB,MAAc,EACd,QAA+B;QAE/B,IAAI,CAAC;YACH,OAAO,cAAc,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,SAAS,IAAI,MAAM,EAAE,CAAC,CAAC,CAAA;QACxE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;CACF;AAnBD,8CAmBC;AAED,SAAS,eAAe,CAAC,WAAqB;IAC5C,oCAAoC;IACpC,MAAM,eAAe,GAA6B,OAAO,CAAC,UAAU,CAClE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAA,iBAAM,EAAC,CAAC,CAAC,CAAC,CAClC;SACE,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAClB,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACtB,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAClD,CACF;SACA,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;QAClB,IAAI,CAAC,SAAS,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QAClC,MAAM,QAAQ,GAAG,IAAI,mBAAQ,EAAE,CAAA;QAC/B,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA;QAC9B,OAAO,QAAQ,CAAA;IACjB,CAAC,CAAC,CAAA;IAEJ,eAAe,CAAC,KAAK,CAAC,GAAG,EAAE;QACzB,sBAAsB;IACxB,CAAC,CAAC,CAAA;IAEF,OAAO,KAAK,EAAE,QAAQ,EAAE,EAAE;QACxB,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAA;QACtC,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IACtD,CAAC,CAAA;AACH,CAAC;AAED,SAAS,cAAc,CAAC,cAA0B;IAChD,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAA;IAC/D,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;IACzD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CAAA;IACb,CAAC;IACD,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACzC,OAAO,IAAA,kCAAgB,EAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;AAC3C,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,2BAA2B,CAAA;AACzC,OAAO,EAAE,kBAAkB,IAAI,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAGzE,cAAc,0BAA0B,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.default = void 0;
|
|
18
|
+
// Main export
|
|
19
|
+
__exportStar(require("./node-handle-resolver.js"), exports);
|
|
20
|
+
var node_handle_resolver_js_1 = require("./node-handle-resolver.js");
|
|
21
|
+
Object.defineProperty(exports, "default", { enumerable: true, get: function () { return node_handle_resolver_js_1.NodeHandleResolver; } });
|
|
22
|
+
// Utilities
|
|
23
|
+
__exportStar(require("./dns-handle-resolver.js"), exports);
|
|
24
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,cAAc;AACd,4DAAyC;AACzC,qEAAyE;AAAhE,kHAAA,kBAAkB,OAAW;AAEtC,YAAY;AACZ,2DAAwC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Fetch } from '@atproto-labs/fetch';
|
|
2
|
+
import { SafeFetchWrapOptions } from '@atproto-labs/fetch-node';
|
|
3
|
+
import { CachedHandleResolver, CachedHandleResolverOptions, HandleResolver } from '@atproto-labs/handle-resolver';
|
|
4
|
+
import { DnsHandleResolverOptions } from './dns-handle-resolver.js';
|
|
5
|
+
export type NodeHandleResolverOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* List of domain names that are forbidden to be resolved using the
|
|
8
|
+
* well-known/atproto-did method.
|
|
9
|
+
*/
|
|
10
|
+
wellKnownExclude?: SafeFetchWrapOptions['forbiddenDomainNames'];
|
|
11
|
+
/**
|
|
12
|
+
* List of backup nameservers to use for DNS resolution.
|
|
13
|
+
*/
|
|
14
|
+
fallbackNameservers?: DnsHandleResolverOptions['nameservers'];
|
|
15
|
+
cache?: CachedHandleResolverOptions['cache'];
|
|
16
|
+
/**
|
|
17
|
+
* Fetch function to use for HTTP requests. Allows customizing the request
|
|
18
|
+
* behavior, e.g. adding headers, setting a timeout, mocking, etc. The
|
|
19
|
+
* provided fetch function will be wrapped with a safeFetchWrap function that
|
|
20
|
+
* adds SSRF protection.
|
|
21
|
+
*
|
|
22
|
+
* @default `globalThis.fetch`
|
|
23
|
+
*/
|
|
24
|
+
fetch?: Fetch;
|
|
25
|
+
};
|
|
26
|
+
export declare class NodeHandleResolver extends CachedHandleResolver implements HandleResolver {
|
|
27
|
+
constructor({ cache, fetch, fallbackNameservers, wellKnownExclude, }?: NodeHandleResolverOptions);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=node-handle-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-handle-resolver.d.ts","sourceRoot":"","sources":["../src/node-handle-resolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAA;AAC3C,OAAO,EAAE,oBAAoB,EAAiB,MAAM,0BAA0B,CAAA;AAC9E,OAAO,EACL,oBAAoB,EACpB,2BAA2B,EAC3B,cAAc,EAEf,MAAM,+BAA+B,CAAA;AAEtC,OAAO,EAEL,wBAAwB,EACzB,MAAM,0BAA0B,CAAA;AAEjC,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;OAGG;IACH,gBAAgB,CAAC,EAAE,oBAAoB,CAAC,sBAAsB,CAAC,CAAA;IAE/D;;OAEG;IACH,mBAAmB,CAAC,EAAE,wBAAwB,CAAC,aAAa,CAAC,CAAA;IAE7D,KAAK,CAAC,EAAE,2BAA2B,CAAC,OAAO,CAAC,CAAA;IAE5C;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,KAAK,CAAA;CACd,CAAA;AAED,qBAAa,kBACX,SAAQ,oBACR,YAAW,cAAc;gBAEb,EACV,KAAK,EACL,KAAwB,EACxB,mBAAmB,EACnB,gBAAgB,GACjB,GAAE,yBAA8B;CA+ClC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NodeHandleResolver = void 0;
|
|
4
|
+
const fetch_node_1 = require("@atproto-labs/fetch-node");
|
|
5
|
+
const handle_resolver_1 = require("@atproto-labs/handle-resolver");
|
|
6
|
+
const dns_handle_resolver_js_1 = require("./dns-handle-resolver.js");
|
|
7
|
+
class NodeHandleResolver extends handle_resolver_1.CachedHandleResolver {
|
|
8
|
+
constructor({ cache, fetch = globalThis.fetch, fallbackNameservers, wellKnownExclude, } = {}) {
|
|
9
|
+
const safeFetch = (0, fetch_node_1.safeFetchWrap)({
|
|
10
|
+
fetch,
|
|
11
|
+
timeout: 3000, // 3 seconds
|
|
12
|
+
forbiddenDomainNames: wellKnownExclude,
|
|
13
|
+
ssrfProtection: true,
|
|
14
|
+
responseMaxSize: 10 * 1048, // DID are max 2048 characters, 10kb for safety
|
|
15
|
+
});
|
|
16
|
+
const httpResolver = new handle_resolver_1.WellKnownHandleResolver({
|
|
17
|
+
fetch: safeFetch,
|
|
18
|
+
});
|
|
19
|
+
const dnsResolver = new dns_handle_resolver_js_1.DnsHandleResolver();
|
|
20
|
+
const fallbackResolver = new dns_handle_resolver_js_1.DnsHandleResolver({
|
|
21
|
+
nameservers: fallbackNameservers,
|
|
22
|
+
});
|
|
23
|
+
super({
|
|
24
|
+
cache,
|
|
25
|
+
resolver: {
|
|
26
|
+
resolve: async (handle) => {
|
|
27
|
+
const abortController = new AbortController();
|
|
28
|
+
const dnsPromise = dnsResolver.resolve(handle);
|
|
29
|
+
const httpPromise = httpResolver.resolve(handle, {
|
|
30
|
+
signal: abortController.signal,
|
|
31
|
+
});
|
|
32
|
+
// Will be awaited later
|
|
33
|
+
httpPromise.catch(() => { });
|
|
34
|
+
const dnsRes = await dnsPromise;
|
|
35
|
+
if (dnsRes) {
|
|
36
|
+
abortController.abort();
|
|
37
|
+
return dnsRes;
|
|
38
|
+
}
|
|
39
|
+
const res = await httpPromise;
|
|
40
|
+
if (res)
|
|
41
|
+
return res;
|
|
42
|
+
return fallbackResolver.resolve(handle);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.NodeHandleResolver = NodeHandleResolver;
|
|
49
|
+
//# sourceMappingURL=node-handle-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-handle-resolver.js","sourceRoot":"","sources":["../src/node-handle-resolver.ts"],"names":[],"mappings":";;;AACA,yDAA8E;AAC9E,mEAKsC;AAEtC,qEAGiC;AA2BjC,MAAa,kBACX,SAAQ,sCAAoB;IAG5B,YAAY,EACV,KAAK,EACL,KAAK,GAAG,UAAU,CAAC,KAAK,EACxB,mBAAmB,EACnB,gBAAgB,MACa,EAAE;QAC/B,MAAM,SAAS,GAAG,IAAA,0BAAa,EAAC;YAC9B,KAAK;YACL,OAAO,EAAE,IAAI,EAAE,YAAY;YAC3B,oBAAoB,EAAE,gBAAgB;YACtC,cAAc,EAAE,IAAI;YACpB,eAAe,EAAE,EAAE,GAAG,IAAI,EAAE,+CAA+C;SAC5E,CAAC,CAAA;QAEF,MAAM,YAAY,GAAG,IAAI,yCAAuB,CAAC;YAC/C,KAAK,EAAE,SAAS;SACjB,CAAC,CAAA;QAEF,MAAM,WAAW,GAAG,IAAI,0CAAiB,EAAE,CAAA;QAE3C,MAAM,gBAAgB,GAAG,IAAI,0CAAiB,CAAC;YAC7C,WAAW,EAAE,mBAAmB;SACjC,CAAC,CAAA;QAEF,KAAK,CAAC;YACJ,KAAK;YACL,QAAQ,EAAE;gBACR,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;oBACxB,MAAM,eAAe,GAAG,IAAI,eAAe,EAAE,CAAA;oBAE7C,MAAM,UAAU,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;oBAC9C,MAAM,WAAW,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE;wBAC/C,MAAM,EAAE,eAAe,CAAC,MAAM;qBAC/B,CAAC,CAAA;oBAEF,wBAAwB;oBACxB,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;oBAE3B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAA;oBAC/B,IAAI,MAAM,EAAE,CAAC;wBACX,eAAe,CAAC,KAAK,EAAE,CAAA;wBACvB,OAAO,MAAM,CAAA;oBACf,CAAC;oBAED,MAAM,GAAG,GAAG,MAAM,WAAW,CAAA;oBAC7B,IAAI,GAAG;wBAAE,OAAO,GAAG,CAAA;oBAEnB,OAAO,gBAAgB,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;gBACzC,CAAC;aACF;SACF,CAAC,CAAA;IACJ,CAAC;CACF;AAxDD,gDAwDC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atproto-labs/handle-resolver-node",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Node specific ATProto handle to DID resolver",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"atproto",
|
|
8
|
+
"oauth",
|
|
9
|
+
"handle",
|
|
10
|
+
"identity",
|
|
11
|
+
"node"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://atproto.com",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/bluesky-social/atproto",
|
|
17
|
+
"directory": "packages/handle-resolver-node"
|
|
18
|
+
},
|
|
19
|
+
"type": "commonjs",
|
|
20
|
+
"main": "dist/index.js",
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"default": "./dist/index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@atproto-labs/handle-resolver": "0.0.1",
|
|
30
|
+
"@atproto-labs/fetch": "0.0.1",
|
|
31
|
+
"@atproto-labs/fetch-node": "0.0.1",
|
|
32
|
+
"@atproto-labs/did": "0.0.1"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"typescript": "^5.3.3"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc --build tsconfig.build.json"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Resolver, lookup, resolveTxt } from 'node:dns/promises'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
HandleResolveOptions,
|
|
5
|
+
HandleResolver,
|
|
6
|
+
ResolvedHandle,
|
|
7
|
+
isResolvedHandle,
|
|
8
|
+
} from '@atproto-labs/handle-resolver'
|
|
9
|
+
|
|
10
|
+
const SUBDOMAIN = '_atproto'
|
|
11
|
+
const PREFIX = 'did='
|
|
12
|
+
|
|
13
|
+
export type DnsHandleResolverOptions = {
|
|
14
|
+
/**
|
|
15
|
+
* Nameservers to use in place of the system's default nameservers.
|
|
16
|
+
*/
|
|
17
|
+
nameservers?: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class DnsHandleResolver implements HandleResolver {
|
|
21
|
+
protected resolveTxt: typeof resolveTxt
|
|
22
|
+
|
|
23
|
+
constructor(options?: DnsHandleResolverOptions) {
|
|
24
|
+
this.resolveTxt = options?.nameservers
|
|
25
|
+
? buildResolveTxt(options.nameservers)
|
|
26
|
+
: resolveTxt.bind(null)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public async resolve(
|
|
30
|
+
handle: string,
|
|
31
|
+
_options?: HandleResolveOptions,
|
|
32
|
+
): Promise<ResolvedHandle> {
|
|
33
|
+
try {
|
|
34
|
+
return parseDnsResult(await this.resolveTxt(`${SUBDOMAIN}.${handle}`))
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildResolveTxt(nameservers: string[]): typeof resolveTxt {
|
|
42
|
+
// Build the resolver asynchronously
|
|
43
|
+
const resolverPromise: Promise<Resolver | null> = Promise.allSettled(
|
|
44
|
+
nameservers.map((h) => lookup(h)),
|
|
45
|
+
)
|
|
46
|
+
.then((responses) =>
|
|
47
|
+
responses.flatMap((r) =>
|
|
48
|
+
r.status === 'fulfilled' ? [r.value.address] : [],
|
|
49
|
+
),
|
|
50
|
+
)
|
|
51
|
+
.then((backupIps) => {
|
|
52
|
+
if (!backupIps.length) return null
|
|
53
|
+
const resolver = new Resolver()
|
|
54
|
+
resolver.setServers(backupIps)
|
|
55
|
+
return resolver
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
resolverPromise.catch(() => {
|
|
59
|
+
// Should never happen
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
return async (hostname) => {
|
|
63
|
+
const resolver = await resolverPromise
|
|
64
|
+
return resolver ? resolver.resolveTxt(hostname) : []
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function parseDnsResult(chunkedResults: string[][]): ResolvedHandle {
|
|
69
|
+
const results = chunkedResults.map((chunks) => chunks.join(''))
|
|
70
|
+
const found = results.filter((i) => i.startsWith(PREFIX))
|
|
71
|
+
if (found.length !== 1) {
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
74
|
+
const did = found[0].slice(PREFIX.length)
|
|
75
|
+
return isResolvedHandle(did) ? did : null
|
|
76
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Fetch } from '@atproto-labs/fetch'
|
|
2
|
+
import { SafeFetchWrapOptions, safeFetchWrap } from '@atproto-labs/fetch-node'
|
|
3
|
+
import {
|
|
4
|
+
CachedHandleResolver,
|
|
5
|
+
CachedHandleResolverOptions,
|
|
6
|
+
HandleResolver,
|
|
7
|
+
WellKnownHandleResolver,
|
|
8
|
+
} from '@atproto-labs/handle-resolver'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
DnsHandleResolver,
|
|
12
|
+
DnsHandleResolverOptions,
|
|
13
|
+
} from './dns-handle-resolver.js'
|
|
14
|
+
|
|
15
|
+
export type NodeHandleResolverOptions = {
|
|
16
|
+
/**
|
|
17
|
+
* List of domain names that are forbidden to be resolved using the
|
|
18
|
+
* well-known/atproto-did method.
|
|
19
|
+
*/
|
|
20
|
+
wellKnownExclude?: SafeFetchWrapOptions['forbiddenDomainNames']
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* List of backup nameservers to use for DNS resolution.
|
|
24
|
+
*/
|
|
25
|
+
fallbackNameservers?: DnsHandleResolverOptions['nameservers']
|
|
26
|
+
|
|
27
|
+
cache?: CachedHandleResolverOptions['cache']
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Fetch function to use for HTTP requests. Allows customizing the request
|
|
31
|
+
* behavior, e.g. adding headers, setting a timeout, mocking, etc. The
|
|
32
|
+
* provided fetch function will be wrapped with a safeFetchWrap function that
|
|
33
|
+
* adds SSRF protection.
|
|
34
|
+
*
|
|
35
|
+
* @default `globalThis.fetch`
|
|
36
|
+
*/
|
|
37
|
+
fetch?: Fetch
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class NodeHandleResolver
|
|
41
|
+
extends CachedHandleResolver
|
|
42
|
+
implements HandleResolver
|
|
43
|
+
{
|
|
44
|
+
constructor({
|
|
45
|
+
cache,
|
|
46
|
+
fetch = globalThis.fetch,
|
|
47
|
+
fallbackNameservers,
|
|
48
|
+
wellKnownExclude,
|
|
49
|
+
}: NodeHandleResolverOptions = {}) {
|
|
50
|
+
const safeFetch = safeFetchWrap({
|
|
51
|
+
fetch,
|
|
52
|
+
timeout: 3000, // 3 seconds
|
|
53
|
+
forbiddenDomainNames: wellKnownExclude,
|
|
54
|
+
ssrfProtection: true,
|
|
55
|
+
responseMaxSize: 10 * 1048, // DID are max 2048 characters, 10kb for safety
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const httpResolver = new WellKnownHandleResolver({
|
|
59
|
+
fetch: safeFetch,
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const dnsResolver = new DnsHandleResolver()
|
|
63
|
+
|
|
64
|
+
const fallbackResolver = new DnsHandleResolver({
|
|
65
|
+
nameservers: fallbackNameservers,
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
super({
|
|
69
|
+
cache,
|
|
70
|
+
resolver: {
|
|
71
|
+
resolve: async (handle) => {
|
|
72
|
+
const abortController = new AbortController()
|
|
73
|
+
|
|
74
|
+
const dnsPromise = dnsResolver.resolve(handle)
|
|
75
|
+
const httpPromise = httpResolver.resolve(handle, {
|
|
76
|
+
signal: abortController.signal,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// Will be awaited later
|
|
80
|
+
httpPromise.catch(() => {})
|
|
81
|
+
|
|
82
|
+
const dnsRes = await dnsPromise
|
|
83
|
+
if (dnsRes) {
|
|
84
|
+
abortController.abort()
|
|
85
|
+
return dnsRes
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const res = await httpPromise
|
|
89
|
+
if (res) return res
|
|
90
|
+
|
|
91
|
+
return fallbackResolver.resolve(handle)
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
}
|
package/tsconfig.json
ADDED