@bskyprism/atproto-oauth-client-cloudflare-workers 0.2.2

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.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +69 -0
  3. package/lib/did-cache-kv.d.ts +18 -0
  4. package/lib/did-cache-kv.js +26 -0
  5. package/lib/did-resolver/did-cache-memory.d.ts +7 -0
  6. package/lib/did-resolver/did-cache-memory.js +10 -0
  7. package/lib/did-resolver/did-cache.d.ts +14 -0
  8. package/lib/did-resolver/did-cache.js +10 -0
  9. package/lib/did-resolver/did-method.d.ts +11 -0
  10. package/lib/did-resolver/did-method.js +1 -0
  11. package/lib/did-resolver/did-resolver-base.d.ts +9 -0
  12. package/lib/did-resolver/did-resolver-base.js +36 -0
  13. package/lib/did-resolver/did-resolver-common.d.ts +8 -0
  14. package/lib/did-resolver/did-resolver-common.js +11 -0
  15. package/lib/did-resolver/did-resolver.d.ts +6 -0
  16. package/lib/did-resolver/did-resolver.js +1 -0
  17. package/lib/did-resolver/index.d.ts +6 -0
  18. package/lib/did-resolver/index.js +7 -0
  19. package/lib/did-resolver/methods/plc.d.ts +43 -0
  20. package/lib/did-resolver/methods/plc.js +22 -0
  21. package/lib/did-resolver/methods/web.d.ts +43 -0
  22. package/lib/did-resolver/methods/web.js +42 -0
  23. package/lib/did-resolver/methods.d.ts +2 -0
  24. package/lib/did-resolver/methods.js +2 -0
  25. package/lib/did-resolver/util.d.ts +3 -0
  26. package/lib/did-resolver/util.js +1 -0
  27. package/lib/dpop-store.d.ts +21 -0
  28. package/lib/dpop-store.js +25 -0
  29. package/lib/handle-cache-kv.d.ts +17 -0
  30. package/lib/handle-cache-kv.js +31 -0
  31. package/lib/handle-resolver/atproto-doh-handle-resolver.d.ts +8 -0
  32. package/lib/handle-resolver/atproto-doh-handle-resolver.js +94 -0
  33. package/lib/handle-resolver/atproto-handle-resolver.d.ts +21 -0
  34. package/lib/handle-resolver/atproto-handle-resolver.js +46 -0
  35. package/lib/handle-resolver/cached-handle-resolver.d.ts +12 -0
  36. package/lib/handle-resolver/cached-handle-resolver.js +17 -0
  37. package/lib/handle-resolver/handle-resolver-error.d.ts +3 -0
  38. package/lib/handle-resolver/handle-resolver-error.js +6 -0
  39. package/lib/handle-resolver/index.d.ts +6 -0
  40. package/lib/handle-resolver/index.js +8 -0
  41. package/lib/handle-resolver/internal-resolvers/dns-handle-resolver.d.ts +11 -0
  42. package/lib/handle-resolver/internal-resolvers/dns-handle-resolver.js +28 -0
  43. package/lib/handle-resolver/internal-resolvers/well-known-handler-resolver.d.ts +17 -0
  44. package/lib/handle-resolver/internal-resolvers/well-known-handler-resolver.js +28 -0
  45. package/lib/handle-resolver/types.d.ts +25 -0
  46. package/lib/handle-resolver/types.js +10 -0
  47. package/lib/handle-resolver/xrpc-handle-resolver.d.ts +31 -0
  48. package/lib/handle-resolver/xrpc-handle-resolver.js +45 -0
  49. package/lib/handle-resolver.d.ts +20 -0
  50. package/lib/handle-resolver.js +19 -0
  51. package/lib/identity-resolver/atproto-identity-resolver.d.ts +20 -0
  52. package/lib/identity-resolver/atproto-identity-resolver.js +72 -0
  53. package/lib/identity-resolver/constants.d.ts +1 -0
  54. package/lib/identity-resolver/constants.js +1 -0
  55. package/lib/identity-resolver/identity-resolver-error.d.ts +3 -0
  56. package/lib/identity-resolver/identity-resolver-error.js +6 -0
  57. package/lib/identity-resolver/identity-resolver.d.ts +19 -0
  58. package/lib/identity-resolver/identity-resolver.js +1 -0
  59. package/lib/identity-resolver/index.d.ts +5 -0
  60. package/lib/identity-resolver/index.js +5 -0
  61. package/lib/identity-resolver/util.d.ts +12 -0
  62. package/lib/identity-resolver/util.js +35 -0
  63. package/lib/index.d.ts +7 -0
  64. package/lib/index.js +6 -0
  65. package/lib/oauth-client/atproto-token-response.d.ts +100 -0
  66. package/lib/oauth-client/atproto-token-response.js +15 -0
  67. package/lib/oauth-client/constants.d.ts +4 -0
  68. package/lib/oauth-client/constants.js +4 -0
  69. package/lib/oauth-client/errors/auth-method-unsatisfiable-error.d.ts +2 -0
  70. package/lib/oauth-client/errors/auth-method-unsatisfiable-error.js +2 -0
  71. package/lib/oauth-client/errors/token-invalid-error.d.ts +6 -0
  72. package/lib/oauth-client/errors/token-invalid-error.js +6 -0
  73. package/lib/oauth-client/errors/token-refresh-error.d.ts +6 -0
  74. package/lib/oauth-client/errors/token-refresh-error.js +6 -0
  75. package/lib/oauth-client/errors/token-revoked-error.d.ts +6 -0
  76. package/lib/oauth-client/errors/token-revoked-error.js +6 -0
  77. package/lib/oauth-client/fetch-dpop.d.ts +19 -0
  78. package/lib/oauth-client/fetch-dpop.js +176 -0
  79. package/lib/oauth-client/identity-resolver.d.ts +15 -0
  80. package/lib/oauth-client/identity-resolver.js +33 -0
  81. package/lib/oauth-client/index.d.ts +17 -0
  82. package/lib/oauth-client/index.js +17 -0
  83. package/lib/oauth-client/lock.d.ts +2 -0
  84. package/lib/oauth-client/lock.js +28 -0
  85. package/lib/oauth-client/oauth-authorization-server-metadata-resolver.d.ts +18 -0
  86. package/lib/oauth-client/oauth-authorization-server-metadata-resolver.js +53 -0
  87. package/lib/oauth-client/oauth-callback-error.d.ts +6 -0
  88. package/lib/oauth-client/oauth-callback-error.js +13 -0
  89. package/lib/oauth-client/oauth-client-auth.d.ts +22 -0
  90. package/lib/oauth-client/oauth-client-auth.js +127 -0
  91. package/lib/oauth-client/oauth-client.d.ts +311 -0
  92. package/lib/oauth-client/oauth-client.js +276 -0
  93. package/lib/oauth-client/oauth-protected-resource-metadata-resolver.d.ts +18 -0
  94. package/lib/oauth-client/oauth-protected-resource-metadata-resolver.js +49 -0
  95. package/lib/oauth-client/oauth-resolver-error.d.ts +6 -0
  96. package/lib/oauth-client/oauth-resolver-error.js +18 -0
  97. package/lib/oauth-client/oauth-resolver.d.ts +71 -0
  98. package/lib/oauth-client/oauth-resolver.js +117 -0
  99. package/lib/oauth-client/oauth-response-error.d.ts +10 -0
  100. package/lib/oauth-client/oauth-response-error.js +22 -0
  101. package/lib/oauth-client/oauth-server-agent.d.ts +54 -0
  102. package/lib/oauth-client/oauth-server-agent.js +250 -0
  103. package/lib/oauth-client/oauth-server-factory.d.ts +32 -0
  104. package/lib/oauth-client/oauth-server-factory.js +37 -0
  105. package/lib/oauth-client/oauth-session.d.ts +33 -0
  106. package/lib/oauth-client/oauth-session.js +122 -0
  107. package/lib/oauth-client/runtime-implementation.d.ts +16 -0
  108. package/lib/oauth-client/runtime-implementation.js +1 -0
  109. package/lib/oauth-client/runtime.d.ts +25 -0
  110. package/lib/oauth-client/runtime.js +99 -0
  111. package/lib/oauth-client/session-getter.d.ts +54 -0
  112. package/lib/oauth-client/session-getter.js +260 -0
  113. package/lib/oauth-client/state-store.d.ts +12 -0
  114. package/lib/oauth-client/state-store.js +1 -0
  115. package/lib/oauth-client/types.d.ts +1365 -0
  116. package/lib/oauth-client/types.js +8 -0
  117. package/lib/oauth-client/util.d.ts +25 -0
  118. package/lib/oauth-client/util.js +139 -0
  119. package/lib/oauth-client/validate-client-metadata.d.ts +4 -0
  120. package/lib/oauth-client/validate-client-metadata.js +68 -0
  121. package/lib/oauth-client.d.ts +27 -0
  122. package/lib/oauth-client.js +30 -0
  123. package/lib/resolve-txt-factory.d.ts +3 -0
  124. package/lib/resolve-txt-factory.js +80 -0
  125. package/lib/session-store-kv.d.ts +9 -0
  126. package/lib/session-store-kv.js +20 -0
  127. package/lib/state-store-kv.d.ts +9 -0
  128. package/lib/state-store-kv.js +20 -0
  129. package/lib/util.d.ts +18 -0
  130. package/lib/util.js +5 -0
  131. package/package.json +58 -0
@@ -0,0 +1,94 @@
1
+ import { AtprotoHandleResolver, } from "./atproto-handle-resolver.js";
2
+ import { HandleResolverError } from "./handle-resolver-error.js";
3
+ export class AtprotoDohHandleResolver extends AtprotoHandleResolver {
4
+ constructor(options) {
5
+ super({
6
+ ...options,
7
+ resolveTxt: dohResolveTxtFactory(options),
8
+ resolveTxtFallback: undefined,
9
+ });
10
+ }
11
+ }
12
+ /**
13
+ * Resolver for DNS-over-HTTPS (DoH) handles. Only works with servers supporting
14
+ * Google Flavoured "application/dns-json" queries.
15
+ *
16
+ * @see {@link https://developers.google.com/speed/public-dns/docs/doh/json}
17
+ * @see {@link https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/make-api-requests/dns-json/}
18
+ * @todo Add support for DoH using application/dns-message (?)
19
+ */
20
+ function dohResolveTxtFactory({ dohEndpoint, fetch = globalThis.fetch, }) {
21
+ return async (hostname) => {
22
+ const url = new URL(dohEndpoint);
23
+ url.searchParams.set("type", "TXT");
24
+ url.searchParams.set("name", hostname);
25
+ const response = await fetch(url, {
26
+ method: "GET",
27
+ headers: { accept: "application/dns-json" },
28
+ redirect: "follow",
29
+ });
30
+ try {
31
+ const contentType = response.headers.get("content-type")?.trim();
32
+ if (!response.ok) {
33
+ const message = contentType?.startsWith("text/plain")
34
+ ? await response.text()
35
+ : `Failed to resolve ${hostname}`;
36
+ throw new HandleResolverError(message);
37
+ }
38
+ else if (contentType?.match(/application\/(dns-)?json/i) == null) {
39
+ throw new HandleResolverError("Unexpected response from DoH server");
40
+ }
41
+ const result = asResult(await response.json());
42
+ return result.Answer?.filter(isAnswerTxt).map(extractTxtData) ?? null;
43
+ }
44
+ finally {
45
+ // Make sure to always cancel the response body as some engines (Node 👀)
46
+ // do not do this automatically.
47
+ // https://undici.nodejs.org/#/?id=garbage-collection
48
+ if (response.bodyUsed === false) {
49
+ // Handle rejection asynchronously
50
+ void response.body?.cancel().catch(onCancelError);
51
+ }
52
+ }
53
+ };
54
+ }
55
+ function onCancelError(err) {
56
+ if (!(err instanceof DOMException) || err.name !== "AbortError") {
57
+ console.error("An error occurred while cancelling the response body:", err);
58
+ }
59
+ }
60
+ function isResult(result) {
61
+ if (typeof result !== "object" || result === null)
62
+ return false;
63
+ if (!("Status" in result) || typeof result.Status !== "number")
64
+ return false;
65
+ if ("Answer" in result && !isArrayOf(result.Answer, isAnswer))
66
+ return false;
67
+ return true;
68
+ }
69
+ function asResult(result) {
70
+ if (isResult(result))
71
+ return result;
72
+ throw new HandleResolverError("Invalid DoH response");
73
+ }
74
+ function isArrayOf(value, predicate) {
75
+ return Array.isArray(value) && value.every(predicate);
76
+ }
77
+ function isAnswer(answer) {
78
+ return (typeof answer === "object" &&
79
+ answer !== null &&
80
+ "name" in answer &&
81
+ typeof answer.name === "string" &&
82
+ "type" in answer &&
83
+ typeof answer.type === "number" &&
84
+ "data" in answer &&
85
+ typeof answer.data === "string" &&
86
+ "TTL" in answer &&
87
+ typeof answer.TTL === "number");
88
+ }
89
+ function isAnswerTxt(answer) {
90
+ return answer.type === 16;
91
+ }
92
+ function extractTxtData(answer) {
93
+ return answer.data.replace(/^"|"$/g, "").replace(/\\"/g, '"');
94
+ }
@@ -0,0 +1,21 @@
1
+ import { ResolveTxt } from "./internal-resolvers/dns-handle-resolver.js";
2
+ import { WellKnownHandleResolverOptions } from "./internal-resolvers/well-known-handler-resolver.js";
3
+ import { HandleResolver, ResolveHandleOptions, ResolvedHandle } from "./types.js";
4
+ export type { ResolveTxt };
5
+ export type AtprotoHandleResolverOptions = WellKnownHandleResolverOptions & {
6
+ resolveTxt: ResolveTxt;
7
+ resolveTxtFallback?: ResolveTxt;
8
+ };
9
+ /**
10
+ * Implementation of the official ATPROTO handle resolution strategy.
11
+ * This implementation relies on two primitives:
12
+ * - HTTP Well-Known URI resolution (requires a `fetch()` implementation)
13
+ * - DNS TXT record resolution (requires a `resolveTxt()` function)
14
+ */
15
+ export declare class AtprotoHandleResolver implements HandleResolver {
16
+ private readonly httpResolver;
17
+ private readonly dnsResolver;
18
+ private readonly dnsResolverFallback?;
19
+ constructor(options: AtprotoHandleResolverOptions);
20
+ resolve(handle: string, options?: ResolveHandleOptions): Promise<ResolvedHandle>;
21
+ }
@@ -0,0 +1,46 @@
1
+ import { DnsHandleResolver, } from "./internal-resolvers/dns-handle-resolver.js";
2
+ import { WellKnownHandleResolver, } from "./internal-resolvers/well-known-handler-resolver.js";
3
+ const noop = () => { };
4
+ /**
5
+ * Implementation of the official ATPROTO handle resolution strategy.
6
+ * This implementation relies on two primitives:
7
+ * - HTTP Well-Known URI resolution (requires a `fetch()` implementation)
8
+ * - DNS TXT record resolution (requires a `resolveTxt()` function)
9
+ */
10
+ export class AtprotoHandleResolver {
11
+ constructor(options) {
12
+ this.httpResolver = new WellKnownHandleResolver(options);
13
+ this.dnsResolver = new DnsHandleResolver(options.resolveTxt);
14
+ this.dnsResolverFallback = options.resolveTxtFallback
15
+ ? new DnsHandleResolver(options.resolveTxtFallback)
16
+ : undefined;
17
+ }
18
+ async resolve(handle, options) {
19
+ options?.signal?.throwIfAborted();
20
+ const abortController = new AbortController();
21
+ const { signal } = abortController;
22
+ options?.signal?.addEventListener("abort", () => abortController.abort(), {
23
+ signal,
24
+ });
25
+ const wrappedOptions = { ...options, signal };
26
+ try {
27
+ const dnsPromise = this.dnsResolver.resolve(handle, wrappedOptions);
28
+ const httpPromise = this.httpResolver.resolve(handle, wrappedOptions);
29
+ // Prevent uncaught promise rejection
30
+ httpPromise.catch(noop);
31
+ const dnsRes = await dnsPromise;
32
+ if (dnsRes)
33
+ return dnsRes;
34
+ signal.throwIfAborted();
35
+ const res = await httpPromise;
36
+ if (res)
37
+ return res;
38
+ signal.throwIfAborted();
39
+ return this.dnsResolverFallback?.resolve(handle, wrappedOptions) ?? null;
40
+ }
41
+ finally {
42
+ // Cancel pending requests, and remove "abort" listener on incoming signal
43
+ abortController.abort();
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,12 @@
1
+ import { SimpleStore } from "@atproto-labs/simple-store";
2
+ import { HandleResolver, ResolveHandleOptions, ResolvedHandle } from "./types.js";
3
+ export type HandleCache = SimpleStore<string, ResolvedHandle>;
4
+ export declare class CachedHandleResolver implements HandleResolver {
5
+ private getter;
6
+ constructor(
7
+ /**
8
+ * The resolver that will be used to resolve handles.
9
+ */
10
+ resolver: HandleResolver, cache?: HandleCache);
11
+ resolve(handle: string, options?: ResolveHandleOptions): Promise<ResolvedHandle>;
12
+ }
@@ -0,0 +1,17 @@
1
+ import { CachedGetter } from "@atproto-labs/simple-store";
2
+ import { SimpleStoreMemory } from "@atproto-labs/simple-store-memory";
3
+ export class CachedHandleResolver {
4
+ constructor(
5
+ /**
6
+ * The resolver that will be used to resolve handles.
7
+ */
8
+ resolver, cache = new SimpleStoreMemory({
9
+ max: 1000,
10
+ ttl: 10 * 60e3,
11
+ })) {
12
+ this.getter = new CachedGetter((handle, options) => resolver.resolve(handle, options), cache);
13
+ }
14
+ async resolve(handle, options) {
15
+ return this.getter.get(handle, options);
16
+ }
17
+ }
@@ -0,0 +1,3 @@
1
+ export declare class HandleResolverError extends Error {
2
+ name: string;
3
+ }
@@ -0,0 +1,6 @@
1
+ export class HandleResolverError extends Error {
2
+ constructor() {
3
+ super(...arguments);
4
+ this.name = "HandleResolverError";
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./handle-resolver-error.js";
2
+ export * from "./types.js";
3
+ export * from "./xrpc-handle-resolver.js";
4
+ export * from "./atproto-doh-handle-resolver.js";
5
+ export * from "./atproto-handle-resolver.js";
6
+ export * from "./cached-handle-resolver.js";
@@ -0,0 +1,8 @@
1
+ export * from "./handle-resolver-error.js";
2
+ export * from "./types.js";
3
+ // Main Handle Resolvers strategies
4
+ export * from "./xrpc-handle-resolver.js";
5
+ export * from "./atproto-doh-handle-resolver.js";
6
+ export * from "./atproto-handle-resolver.js";
7
+ // Handle Resolver Caching utility
8
+ export * from "./cached-handle-resolver.js";
@@ -0,0 +1,11 @@
1
+ import { HandleResolver, ResolvedHandle } from "../types.js";
2
+ /**
3
+ * DNS TXT record resolver. Return `null` if the hostname successfully does not
4
+ * resolve to a valid DID. Throw an error if an unexpected error occurs.
5
+ */
6
+ export type ResolveTxt = (hostname: string) => Promise<null | string[]>;
7
+ export declare class DnsHandleResolver implements HandleResolver {
8
+ protected resolveTxt: ResolveTxt;
9
+ constructor(resolveTxt: ResolveTxt);
10
+ resolve(handle: string): Promise<ResolvedHandle>;
11
+ }
@@ -0,0 +1,28 @@
1
+ import { isResolvedHandle } from "../types.js";
2
+ const SUBDOMAIN = "_atproto";
3
+ const PREFIX = "did=";
4
+ export class DnsHandleResolver {
5
+ constructor(resolveTxt) {
6
+ this.resolveTxt = resolveTxt;
7
+ }
8
+ async resolve(handle) {
9
+ const results = await this.resolveTxt.call(null, `${SUBDOMAIN}.${handle}`);
10
+ if (!results)
11
+ return null;
12
+ for (let i = 0; i < results.length; i++) {
13
+ // If the line does not start with "did=", skip it
14
+ if (!results[i].startsWith(PREFIX))
15
+ continue;
16
+ // Ensure no other entry starting with "did=" follows
17
+ for (let j = i + 1; j < results.length; j++) {
18
+ if (results[j].startsWith(PREFIX))
19
+ return null;
20
+ }
21
+ // Note: No trimming (to be consistent with spec)
22
+ const did = results[i].slice(PREFIX.length);
23
+ // Invalid DBS record
24
+ return isResolvedHandle(did) ? did : null;
25
+ }
26
+ return null;
27
+ }
28
+ }
@@ -0,0 +1,17 @@
1
+ import { HandleResolver, ResolveHandleOptions, ResolvedHandle } from "../types.js";
2
+ export type WellKnownHandleResolverOptions = {
3
+ /**
4
+ * Fetch function to use for HTTP requests. Allows customizing the request
5
+ * behavior, e.g. adding headers, setting a timeout, mocking, etc. The
6
+ * provided fetch function will be wrapped with a safeFetchWrap function that
7
+ * adds SSRF protection.
8
+ *
9
+ * @default `globalThis.fetch`
10
+ */
11
+ fetch?: typeof globalThis.fetch;
12
+ };
13
+ export declare class WellKnownHandleResolver implements HandleResolver {
14
+ protected readonly fetch: typeof globalThis.fetch;
15
+ constructor(options?: WellKnownHandleResolverOptions);
16
+ resolve(handle: string, options?: ResolveHandleOptions): Promise<ResolvedHandle>;
17
+ }
@@ -0,0 +1,28 @@
1
+ import { isResolvedHandle, } from "../types.js";
2
+ export class WellKnownHandleResolver {
3
+ constructor(options) {
4
+ this.fetch = options?.fetch ?? globalThis.fetch;
5
+ }
6
+ async resolve(handle, options) {
7
+ const url = new URL("/.well-known/atproto-did", `https://${handle}`);
8
+ try {
9
+ const response = await this.fetch.call(null, url, {
10
+ // cache: options?.noCache ? "no-cache" : undefined,
11
+ signal: options?.signal,
12
+ redirect: "follow",
13
+ headers: { "cache-control": "no-cache" },
14
+ });
15
+ const text = await response.text();
16
+ const firstLine = text.split("\n")[0].trim();
17
+ if (isResolvedHandle(firstLine))
18
+ return firstLine;
19
+ return null;
20
+ }
21
+ catch (err) {
22
+ // The the request failed, assume the handle does not resolve to a DID,
23
+ // unless the failure was due to the signal being aborted.
24
+ options?.signal?.throwIfAborted();
25
+ return null;
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,25 @@
1
+ import { AtprotoDid } from "@atproto/did";
2
+ export type { AtprotoDid, AtprotoIdentityDidMethods } from "@atproto/did";
3
+ export type ResolveHandleOptions = {
4
+ signal?: AbortSignal;
5
+ noCache?: boolean;
6
+ };
7
+ /**
8
+ * @see {@link https://atproto.com/specs/did#blessed-did-methods}
9
+ */
10
+ export type ResolvedHandle = null | AtprotoDid;
11
+ /**
12
+ * @see {@link https://atproto.com/specs/did#blessed-did-methods}
13
+ */
14
+ export declare function isResolvedHandle<T>(value: T): value is T & ResolvedHandle;
15
+ export declare function asResolvedHandle(value: string): ResolvedHandle;
16
+ export interface HandleResolver {
17
+ /**
18
+ * @returns the DID that corresponds to the given handle, or `null` if no DID
19
+ * is found. `null` should only be returned if no unexpected behavior occurred
20
+ * during the resolution process.
21
+ * @throws Error if the resolution method fails due to an unexpected error, or
22
+ * if the resolution is aborted ({@link ResolveHandleOptions}).
23
+ */
24
+ resolve(handle: string, options?: ResolveHandleOptions): Promise<ResolvedHandle>;
25
+ }
@@ -0,0 +1,10 @@
1
+ import { isAtprotoDid } from "@atproto/did";
2
+ /**
3
+ * @see {@link https://atproto.com/specs/did#blessed-did-methods}
4
+ */
5
+ export function isResolvedHandle(value) {
6
+ return value === null || isAtprotoDid(value);
7
+ }
8
+ export function asResolvedHandle(value) {
9
+ return isResolvedHandle(value) ? value : null;
10
+ }
@@ -0,0 +1,31 @@
1
+ import { z } from "zod";
2
+ import { HandleResolver, ResolveHandleOptions, ResolvedHandle } from "./types.js";
3
+ export declare const xrpcErrorSchema: z.ZodObject<{
4
+ error: z.ZodString;
5
+ message: z.ZodOptional<z.ZodString>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ error: string;
8
+ message?: string | undefined;
9
+ }, {
10
+ error: string;
11
+ message?: string | undefined;
12
+ }>;
13
+ export type XrpcHandleResolverOptions = {
14
+ /**
15
+ * Fetch function to use for HTTP requests. Allows customizing the request
16
+ * behavior, e.g. adding headers, setting a timeout, mocking, etc.
17
+ *
18
+ * @default globalThis.fetch
19
+ */
20
+ fetch?: typeof globalThis.fetch;
21
+ };
22
+ export declare class XrpcHandleResolver implements HandleResolver {
23
+ /**
24
+ * URL of the atproto lexicon server. This is the base URL where the
25
+ * `com.atproto.identity.resolveHandle` XRPC method is located.
26
+ */
27
+ protected readonly serviceUrl: URL;
28
+ protected readonly fetch: typeof globalThis.fetch;
29
+ constructor(service: URL | string, options?: XrpcHandleResolverOptions);
30
+ resolve(handle: string, options?: ResolveHandleOptions): Promise<ResolvedHandle>;
31
+ }
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ import { HandleResolverError } from "./handle-resolver-error.js";
3
+ import { isResolvedHandle, } from "./types.js";
4
+ export const xrpcErrorSchema = z.object({
5
+ error: z.string(),
6
+ message: z.string().optional(),
7
+ });
8
+ export class XrpcHandleResolver {
9
+ constructor(service, options) {
10
+ this.serviceUrl = new URL(service);
11
+ this.fetch = options?.fetch ?? globalThis.fetch;
12
+ }
13
+ async resolve(handle, options) {
14
+ const url = new URL("/xrpc/com.atproto.identity.resolveHandle", this.serviceUrl);
15
+ url.searchParams.set("handle", handle);
16
+ const response = await this.fetch.call(null, url, {
17
+ cache: options?.noCache ? "no-cache" : undefined,
18
+ signal: options?.signal,
19
+ redirect: "follow",
20
+ });
21
+ const payload = await response.json();
22
+ // The response should either be
23
+ // - 400 Bad Request with { error: 'InvalidRequest', message: 'Unable to resolve handle' }
24
+ // - 200 OK with { did: NonNullable<ResolvedHandle> }
25
+ // Any other response is considered unexpected behavior an should throw an error.
26
+ if (response.status === 400) {
27
+ const { error, data } = xrpcErrorSchema.safeParse(payload);
28
+ if (error) {
29
+ throw new HandleResolverError(`Invalid response from resolveHandle method: ${error.message}`, { cause: error });
30
+ }
31
+ if (data.error === "InvalidRequest" &&
32
+ data.message === "Unable to resolve handle") {
33
+ return null;
34
+ }
35
+ }
36
+ if (!response.ok) {
37
+ throw new HandleResolverError("Invalid status code from resolveHandle method");
38
+ }
39
+ const value = payload?.did;
40
+ if (!isResolvedHandle(value)) {
41
+ throw new HandleResolverError("Invalid DID returned from resolveHandle method");
42
+ }
43
+ return value;
44
+ }
45
+ }
@@ -0,0 +1,20 @@
1
+ import { AtprotoHandleResolver, HandleResolver } from "#handle-resolver";
2
+ export type AtprotoHandleResolverWorkersOptions = {
3
+ /**
4
+ * List of backup nameservers to use in case the primary ones fail. Will
5
+ * default to no fallback nameservers.
6
+ */
7
+ fallbackNameservers?: string[];
8
+ /**
9
+ * Fetch function to use for HTTP requests. Allows customizing the request
10
+ * behavior, e.g. adding headers, setting a timeout, mocking, etc. The
11
+ * provided fetch function will be wrapped with a safeFetchWrap function that
12
+ * adds SSRF protection.
13
+ *
14
+ * @default `globalThis.fetch`
15
+ */
16
+ fetch?: (typeof globalThis)["fetch"];
17
+ };
18
+ export declare class AtprotoHandleResolverWorkers extends AtprotoHandleResolver implements HandleResolver {
19
+ constructor({ fetch, fallbackNameservers, }?: AtprotoHandleResolverWorkersOptions);
20
+ }
@@ -0,0 +1,19 @@
1
+ import { AtprotoHandleResolver } from "#handle-resolver";
2
+ import { resolveTxtDefault, resolveTxtFactory } from "./resolve-txt-factory.js";
3
+ export class AtprotoHandleResolverWorkers extends AtprotoHandleResolver {
4
+ constructor({ fetch = globalThis.fetch, fallbackNameservers, } = {}) {
5
+ super({
6
+ fetch: fetch,
7
+ // fetch: safeFetchWrap({
8
+ // fetch,
9
+ // timeout: 3000, // 3 seconds
10
+ // ssrfProtection: true,
11
+ // responseMaxSize: 10 * 1048, // DID are max 2048 characters, 10kb for safety
12
+ // }),
13
+ resolveTxt: resolveTxtDefault,
14
+ resolveTxtFallback: fallbackNameservers?.length
15
+ ? resolveTxtFactory(fallbackNameservers)
16
+ : undefined,
17
+ });
18
+ }
19
+ }
@@ -0,0 +1,20 @@
1
+ import { AtprotoDid, AtprotoIdentityDidMethods, DidDocument } from "@atproto/did";
2
+ import { DidResolver, ResolveDidOptions } from "#did-resolver";
3
+ import { HandleResolver, ResolveHandleOptions } from "#handle-resolver";
4
+ import { IdentityInfo, IdentityResolver, ResolveIdentityOptions } from "./identity-resolver.js";
5
+ /**
6
+ * Implementation of the official ATPROTO identity resolution strategy.
7
+ * This implementation relies on two primitives:
8
+ * - DID resolution (using the `DidResolver` interface)
9
+ * - Handle resolution (using the `HandleResolver` interface)
10
+ */
11
+ export declare class AtprotoIdentityResolver implements IdentityResolver {
12
+ protected readonly didResolver: DidResolver<AtprotoIdentityDidMethods>;
13
+ protected readonly handleResolver: HandleResolver;
14
+ constructor(didResolver: DidResolver<AtprotoIdentityDidMethods>, handleResolver: HandleResolver);
15
+ resolve(input: string, options?: ResolveIdentityOptions): Promise<IdentityInfo>;
16
+ resolveFromDid(did: AtprotoDid, options?: ResolveDidOptions): Promise<IdentityInfo>;
17
+ resolveFromHandle(handle: string, options?: ResolveHandleOptions): Promise<IdentityInfo>;
18
+ getDocumentFromDid(did: AtprotoDid, options?: ResolveDidOptions): Promise<DidDocument<AtprotoIdentityDidMethods>>;
19
+ getDocumentFromHandle(input: string, options?: ResolveHandleOptions): Promise<DidDocument<AtprotoIdentityDidMethods>>;
20
+ }
@@ -0,0 +1,72 @@
1
+ import { isAtprotoDid, } from "@atproto/did";
2
+ import { HANDLE_INVALID } from "./constants.js";
3
+ import { IdentityResolverError } from "./identity-resolver-error.js";
4
+ import { asNormalizedHandle, extractNormalizedHandle } from "./util.js";
5
+ // @TODO Move this to its own package as soon as we have a distinct
6
+ // implementation based on XRPC calls to the
7
+ // "com.atproto.identity.resolveIdentity" method.
8
+ /**
9
+ * Implementation of the official ATPROTO identity resolution strategy.
10
+ * This implementation relies on two primitives:
11
+ * - DID resolution (using the `DidResolver` interface)
12
+ * - Handle resolution (using the `HandleResolver` interface)
13
+ */
14
+ export class AtprotoIdentityResolver {
15
+ constructor(didResolver, handleResolver) {
16
+ this.didResolver = didResolver;
17
+ this.handleResolver = handleResolver;
18
+ }
19
+ async resolve(input, options) {
20
+ return isAtprotoDid(input)
21
+ ? this.resolveFromDid(input, options)
22
+ : this.resolveFromHandle(input, options);
23
+ }
24
+ async resolveFromDid(did, options) {
25
+ const document = await this.getDocumentFromDid(did, options);
26
+ options?.signal?.throwIfAborted();
27
+ // We will only return the document's handle alias if it resolves to the
28
+ // same DID as the input.
29
+ const handle = extractNormalizedHandle(document);
30
+ const resolvedDid = handle
31
+ ? await this.handleResolver
32
+ .resolve(handle, options)
33
+ .catch(() => undefined) // Ignore errors (temporarily unavailable)
34
+ : undefined;
35
+ return {
36
+ did: document.id,
37
+ didDoc: document,
38
+ handle: handle && resolvedDid === did ? handle : HANDLE_INVALID,
39
+ };
40
+ }
41
+ async resolveFromHandle(handle, options) {
42
+ const document = await this.getDocumentFromHandle(handle, options);
43
+ // @NOTE bi-directional resolution enforced in getDocumentFromHandle()
44
+ return {
45
+ did: document.id,
46
+ didDoc: document,
47
+ handle: extractNormalizedHandle(document) || HANDLE_INVALID,
48
+ };
49
+ }
50
+ async getDocumentFromDid(did, options) {
51
+ return this.didResolver.resolve(did, options);
52
+ }
53
+ async getDocumentFromHandle(input, options) {
54
+ const handle = asNormalizedHandle(input);
55
+ if (!handle) {
56
+ throw new IdentityResolverError(`Invalid handle "${input}" provided.`);
57
+ }
58
+ const did = await this.handleResolver.resolve(handle, options);
59
+ if (!did) {
60
+ throw new IdentityResolverError(`Handle "${handle}" does not resolve to a DID`);
61
+ }
62
+ options?.signal?.throwIfAborted();
63
+ // Note: Not using "return this.resolveDid(did, options)" to make the extra
64
+ // check for the handle in the DID document:
65
+ const document = await this.didResolver.resolve(did, options);
66
+ // Enforce bi-directional resolution
67
+ if (handle !== extractNormalizedHandle(document)) {
68
+ throw new IdentityResolverError(`Did document for "${did}" does not include the handle "${handle}"`);
69
+ }
70
+ return document;
71
+ }
72
+ }
@@ -0,0 +1 @@
1
+ export declare const HANDLE_INVALID = "handle.invalid";
@@ -0,0 +1 @@
1
+ export const HANDLE_INVALID = "handle.invalid";
@@ -0,0 +1,3 @@
1
+ export declare class IdentityResolverError extends Error {
2
+ name: string;
3
+ }
@@ -0,0 +1,6 @@
1
+ export class IdentityResolverError extends Error {
2
+ constructor() {
3
+ super(...arguments);
4
+ this.name = "IdentityResolverError";
5
+ }
6
+ }
@@ -0,0 +1,19 @@
1
+ import { AtprotoDid, AtprotoIdentityDidMethods, DidDocument } from "@atproto/did";
2
+ import { HANDLE_INVALID } from "./constants.js";
3
+ export type IdentityInfo = {
4
+ did: AtprotoDid;
5
+ didDoc: DidDocument<AtprotoIdentityDidMethods>;
6
+ /**
7
+ * Will be 'handle.invalid' if the handle does not resolve to the
8
+ * same DID as the input, or if the handle is not present in the DID
9
+ * document.
10
+ */
11
+ handle: typeof HANDLE_INVALID | string;
12
+ };
13
+ export type ResolveIdentityOptions = {
14
+ signal?: AbortSignal;
15
+ noCache?: boolean;
16
+ };
17
+ export interface IdentityResolver {
18
+ resolve(identifier: string, options?: ResolveIdentityOptions): Promise<IdentityInfo>;
19
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export * from "./atproto-identity-resolver.js";
2
+ export * from "./constants.js";
3
+ export * from "./identity-resolver-error.js";
4
+ export * from "./identity-resolver.js";
5
+ export * from "./util.js";
@@ -0,0 +1,5 @@
1
+ export * from "./atproto-identity-resolver.js";
2
+ export * from "./constants.js";
3
+ export * from "./identity-resolver-error.js";
4
+ export * from "./identity-resolver.js";
5
+ export * from "./util.js";
@@ -0,0 +1,12 @@
1
+ import { AtprotoIdentityDidMethods, DidDocument } from "@atproto/did";
2
+ /**
3
+ * Extract the raw, un-validated, Atproto handle from a DID document.
4
+ */
5
+ export declare function extractAtprotoHandle(document: DidDocument<AtprotoIdentityDidMethods>): string | undefined;
6
+ /**
7
+ * Extracts a validated, normalized Atproto handle from a DID document.
8
+ */
9
+ export declare function extractNormalizedHandle(document: DidDocument<AtprotoIdentityDidMethods>): string | undefined;
10
+ export declare function asNormalizedHandle(input: string): string | undefined;
11
+ export declare function normalizeHandle(handle: string): string;
12
+ export declare function isValidHandle(handle: string): boolean;