@atproto/lexicon-resolver 0.2.5 → 0.2.6

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @atproto/lexicon-resolver
2
2
 
3
+ ## 0.2.6
4
+
5
+ ### Patch Changes
6
+
7
+ - [#4577](https://github.com/bluesky-social/atproto/pull/4577) [`78e8ec2`](https://github.com/bluesky-social/atproto/commit/78e8ec25df860f6d383f3a0e38a2c3a5670bfe01) Thanks [@devinivy](https://github.com/devinivy)! - Do not require at:// handle for lexicon resolution
8
+
3
9
  ## 0.2.5
4
10
 
5
11
  ### Patch Changes
package/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  Dual MIT/Apache-2.0 License
2
2
 
3
- Copyright (c) 2022-2025 Bluesky Social PBC, and Contributors
3
+ Copyright (c) 2022-2026 Bluesky Social PBC, and Contributors
4
4
 
5
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
6
 
@@ -1 +1 @@
1
- {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EACL,MAAM,EAMP,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,KAAK,EAAkB,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAKtE;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAE/E;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,GAAG,EAAE,KAAK,GAAG,MAAM,EACnB,OAAO,CAAC,EAAE,oBAAoB,KAC3B,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAE9B,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,GAAG,YAAY,CAAA;CACvD,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,KAAK,CAAA;IACV,GAAG,EAAE,GAAG,CAAA;IACR,MAAM,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,MAAM,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,CAAA;AAE/D;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,0BAA+B,GACvC,qBAAqB,CAyCvB;AAED,eAAO,MAAM,aAAa,uBAAwB,CAAA;AAElD,eAAO,MAAM,SAAS,sFAIpB,CAAA;AAEF,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIrD"}
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,EAAE,UAAU,EAA0B,MAAM,mBAAmB,CAAA;AACtE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EACL,MAAM,EAMP,MAAM,eAAe,CAAA;AACtB,OAAO,EAAE,KAAK,EAAkB,MAAM,iBAAiB,CAAA;AACvD,OAAO,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAKtE;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,KAAK,GAAG,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAE/E;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,GAAG,EAAE,KAAK,GAAG,MAAM,EACnB,OAAO,CAAC,EAAE,oBAAoB,KAC3B,OAAO,CAAC,gBAAgB,CAAC,CAAA;AAE9B,MAAM,MAAM,0BAA0B,GAAG;IACvC,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,GAAG,CAAC,EAAE,OAAO,CAAC,wBAAwB,CAAC,GAAG,YAAY,CAAA;CACvD,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,KAAK,CAAA;IACV,GAAG,EAAE,GAAG,CAAA;IACR,MAAM,EAAE,UAAU,CAAA;CACnB,CAAA;AAED,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,MAAM,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,CAAA;AAE/D;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,0BAA+B,GACvC,qBAAqB,CAoDvB;AAED,eAAO,MAAM,aAAa,uBAAwB,CAAA;AAElD,eAAO,MAAM,SAAS,sFAIpB,CAAA;AAEF,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIrD"}
package/dist/record.js CHANGED
@@ -1,5 +1,4 @@
1
1
  "use strict";
2
- /* eslint-disable import/no-deprecated */
3
2
  Object.defineProperty(exports, "__esModule", { value: true });
4
3
  exports.RecordResolutionError = exports.safeFetch = exports.resolveRecord = exports.IdResolver = exports.CID = exports.AtUri = void 0;
5
4
  exports.buildRecordResolver = buildRecordResolver;
@@ -21,18 +20,25 @@ function buildRecordResolver(options = {}) {
21
20
  return async function resolveRecord(uriStr, opts = {}) {
22
21
  const uri = typeof uriStr === 'string' ? new syntax_1.AtUri(uriStr) : uriStr;
23
22
  const did = await getDidFromUri(uri, { idResolver });
24
- const identity = await idResolver.did
25
- .resolveAtprotoData(did, opts.forceRefresh)
23
+ const identityDoc = await idResolver.did
24
+ .ensureResolve(did, opts.forceRefresh)
26
25
  .catch((err) => {
27
26
  throw new RecordResolutionError('Could not resolve DID identity data', {
28
27
  cause: err,
29
28
  });
30
29
  });
30
+ const { pds, signingKey } = (0, identity_1.parseToAtprotoDocument)(identityDoc);
31
+ if (!pds) {
32
+ throw new RecordResolutionError('Incomplete DID identity data: missing pds');
33
+ }
34
+ if (!signingKey) {
35
+ throw new RecordResolutionError('Incomplete DID identity data: missing signing key');
36
+ }
31
37
  const client = new index_js_1.AtpBaseClient(typeof rpc === 'function'
32
38
  ? rpc
33
39
  : {
34
40
  ...rpc,
35
- service: rpc?.service ?? identity.pds,
41
+ service: rpc?.service ?? pds,
36
42
  fetch: rpc?.fetch ?? exports.safeFetch,
37
43
  });
38
44
  const { data: proofBytes } = await client.com.atproto.sync
@@ -48,7 +54,7 @@ function buildRecordResolver(options = {}) {
48
54
  });
49
55
  const verified = await verifyRecordProof(proofBytes, {
50
56
  uri: syntax_1.AtUri.make(did, uri.collection, uri.rkey),
51
- signingKey: identity.signingKey,
57
+ signingKey,
52
58
  });
53
59
  return verified;
54
60
  };
@@ -1 +1 @@
1
- {"version":3,"file":"record.js","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":";AAAA,yCAAyC;;;AAqDzC,kDA2CC;AA9FD,0CAAsC;AA8CtB,oFA9CP,SAAG,OA8CO;AA7CnB,gDAA8C;AA6CZ,2FA7CzB,qBAAU,OA6CyB;AA3C5C,wCAOsB;AACtB,4CAAuD;AAmC9C,sFAnCA,cAAK,OAmCA;AAjCd,yDAAwD;AACxD,gDAA2D;AAC3D,uCAAsC;AAiCtC;;GAEG;AACH,SAAgB,mBAAmB,CACjC,UAAsC,EAAE;IAExC,MAAM,EAAE,UAAU,GAAG,IAAI,qBAAU,EAAE,EAAE,GAAG,EAAE,GAAG,OAAO,CAAA;IACtD,OAAO,KAAK,UAAU,aAAa,CACjC,MAAsB,EACtB,OAA6B,EAAE;QAE/B,MAAM,GAAG,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,cAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;QACnE,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,CAAC,CAAA;QACpD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,GAAG;aAClC,kBAAkB,CAAC,GAAG,EAAE,IAAI,CAAC,YAAY,CAAC;aAC1C,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,MAAM,IAAI,qBAAqB,CAAC,qCAAqC,EAAE;gBACrE,KAAK,EAAE,GAAG;aACX,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QACJ,MAAM,MAAM,GAAG,IAAI,wBAAM,CACvB,OAAO,GAAG,KAAK,UAAU;YACvB,CAAC,CAAC,GAAG;YACL,CAAC,CAAC;gBACE,GAAG,GAAG;gBACN,OAAO,EAAE,GAAG,EAAE,OAAO,IAAI,QAAQ,CAAC,GAAG;gBACrC,KAAK,EAAE,GAAG,EAAE,KAAK,IAAI,iBAAS;aAC/B,CACN,CAAA;QACD,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI;aACvD,SAAS,CAAC;YACT,GAAG;YACH,UAAU,EAAE,GAAG,CAAC,UAAU;YAC1B,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC;aACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACb,MAAM,IAAI,qBAAqB,CAAC,8BAA8B,EAAE;gBAC9D,KAAK,EAAE,GAAG;aACX,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QACJ,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,UAAU,EAAE;YACnD,GAAG,EAAE,cAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,IAAI,CAAC;YAC9C,UAAU,EAAE,QAAQ,CAAC,UAAU;SAChC,CAAC,CAAA;QACF,OAAO,QAAQ,CAAA;IACjB,CAAC,CAAA;AACH,CAAC;AAEY,QAAA,aAAa,GAAG,mBAAmB,EAAE,CAAA;AAErC,QAAA,SAAS,GAAG,IAAA,0BAAa,EAAC;IACrC,WAAW,EAAE,KAAK;IAClB,qBAAqB,EAAE,IAAI;IAC3B,eAAe,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,qDAAqD;CAC3F,CAAC,CAAA;AAEF,MAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAgB,EAAE,OAAsB;QAClD,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QACvB,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAA;IACrC,CAAC;CACF;AALD,sDAKC;AAED,KAAK,UAAU,aAAa,CAC1B,GAAU,EACV,EAAE,UAAU,EAA8B;IAE1C,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAChC,IAAA,uBAAc,EAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACxB,OAAO,GAAG,CAAC,IAAI,CAAA;IACjB,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAC1D,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAA,oBAAU,EAAC,QAAQ,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,qBAAqB,CAAC,0CAA0C,CAAC,CAAA;IAC7E,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,KAAK,UAAU,iBAAiB,CAC9B,UAAsB,EACtB,EAAE,GAAG,EAAE,UAAU,EAAsC;IAEvD,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,IAAA,sBAAe,EAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACvE,MAAM,IAAI,qBAAqB,CAAC,wBAAwB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAA;IAC3E,CAAC,CAAC,CAAA;IACF,MAAM,UAAU,GAAG,IAAI,uBAAgB,CAAC,MAAM,CAAC,CAAA;IAC/C,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,UAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QAC1E,MAAM,IAAI,qBAAqB,CAAC,gCAAgC,EAAE;YAChE,KAAK,EAAE,GAAG;SACX,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IACF,IAAI,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,IAAI,EAAE,CAAC;QAC5B,MAAM,IAAI,qBAAqB,CAAC,qBAAqB,MAAM,CAAC,GAAG,EAAE,CAAC,CAAA;IACpE,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,IAAA,sBAAe,EAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAC1D,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,qBAAqB,CAC7B,gCAAgC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAClD,CAAA;IACH,CAAC;IACD,MAAM,GAAG,GAAG,UAAG,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,CAAA;IAC7C,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,CAAA;IAC1D,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,qBAAqB,CAAC,2BAA2B,CAAC,CAAA;IAC9D,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;IAC/C,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,CAAA;AACrC,CAAC","sourcesContent":["/* eslint-disable import/no-deprecated */\n\nimport { CID } from 'multiformats/cid'\nimport { IdResolver } from '@atproto/identity'\nimport { RepoRecord } from '@atproto/lexicon'\nimport {\n Commit,\n MST,\n MemoryBlockstore,\n def as repoDef,\n readCarWithRoot,\n verifyCommitSig,\n} from '@atproto/repo'\nimport { AtUri, ensureValidDid } from '@atproto/syntax'\nimport { BuildFetchHandlerOptions, FetchHandler } from '@atproto/xrpc'\nimport { safeFetchWrap } from '@atproto-labs/fetch-node'\nimport { AtpBaseClient as Client } from './client/index.js'\nimport { isValidDid } from './util.js'\n\n/**\n * Resolve a record from the network.\n */\nexport type RecordResolver = (uri: AtUri | string) => Promise<RecordResolution>\n\n/**\n * Resolve a record from the network, verifying its authenticity.\n */\nexport type AtprotoRecordResolver = (\n uri: AtUri | string,\n options?: ResolveRecordOptions,\n) => Promise<RecordResolution>\n\nexport type BuildRecordResolverOptions = {\n idResolver?: IdResolver\n rpc?: Partial<BuildFetchHandlerOptions> | FetchHandler\n}\n\nexport type ResolveRecordOptions = {\n forceRefresh?: boolean\n}\n\nexport type RecordResolution = {\n commit: Commit\n uri: AtUri\n cid: CID\n record: RepoRecord\n}\n\nexport { AtUri, CID, type Commit, IdResolver, type RepoRecord }\n\n/**\n * Build a record resolver function.\n */\nexport function buildRecordResolver(\n options: BuildRecordResolverOptions = {},\n): AtprotoRecordResolver {\n const { idResolver = new IdResolver(), rpc } = options\n return async function resolveRecord(\n uriStr: AtUri | string,\n opts: ResolveRecordOptions = {},\n ): Promise<RecordResolution> {\n const uri = typeof uriStr === 'string' ? new AtUri(uriStr) : uriStr\n const did = await getDidFromUri(uri, { idResolver })\n const identity = await idResolver.did\n .resolveAtprotoData(did, opts.forceRefresh)\n .catch((err) => {\n throw new RecordResolutionError('Could not resolve DID identity data', {\n cause: err,\n })\n })\n const client = new Client(\n typeof rpc === 'function'\n ? rpc\n : {\n ...rpc,\n service: rpc?.service ?? identity.pds,\n fetch: rpc?.fetch ?? safeFetch,\n },\n )\n const { data: proofBytes } = await client.com.atproto.sync\n .getRecord({\n did,\n collection: uri.collection,\n rkey: uri.rkey,\n })\n .catch((err) => {\n throw new RecordResolutionError('Could not fetch record proof', {\n cause: err,\n })\n })\n const verified = await verifyRecordProof(proofBytes, {\n uri: AtUri.make(did, uri.collection, uri.rkey),\n signingKey: identity.signingKey,\n })\n return verified\n }\n}\n\nexport const resolveRecord = buildRecordResolver()\n\nexport const safeFetch = safeFetchWrap({\n allowIpHost: false,\n allowImplicitRedirect: true,\n responseMaxSize: (1024 + 10) * 1024, // 1MB + 10kB, just a bit larger than max record size\n})\n\nexport class RecordResolutionError extends Error {\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'RecordResolutionError'\n }\n}\n\nasync function getDidFromUri(\n uri: AtUri,\n { idResolver }: { idResolver: IdResolver },\n) {\n if (uri.host.startsWith('did:')) {\n ensureValidDid(uri.host)\n return uri.host\n }\n const resolved = await idResolver.handle.resolve(uri.host)\n if (!resolved || !isValidDid(resolved)) {\n throw new RecordResolutionError('Could not resolve handle found in AT-URI')\n }\n return resolved\n}\n\nasync function verifyRecordProof(\n proofBytes: Uint8Array,\n { uri, signingKey }: { uri: AtUri; signingKey: string },\n) {\n const { root, blocks } = await readCarWithRoot(proofBytes).catch((err) => {\n throw new RecordResolutionError('Malformed record proof', { cause: err })\n })\n const blockstore = new MemoryBlockstore(blocks)\n const commit = await blockstore.readObj(root, repoDef.commit).catch((err) => {\n throw new RecordResolutionError('Invalid commit in record proof', {\n cause: err,\n })\n })\n if (commit.did !== uri.host) {\n throw new RecordResolutionError(`Invalid repo did: ${commit.did}`)\n }\n const validSig = await verifyCommitSig(commit, signingKey)\n if (!validSig) {\n throw new RecordResolutionError(\n `Invalid signature on commit: ${root.toString()}`,\n )\n }\n const mst = MST.load(blockstore, commit.data)\n const cid = await mst.get(`${uri.collection}/${uri.rkey}`)\n if (!cid) {\n throw new RecordResolutionError('Record not found in proof')\n }\n const record = await blockstore.readRecord(cid)\n return { commit, uri, cid, record }\n}\n"]}
1
+ {"version":3,"file":"record.js","sourceRoot":"","sources":["../src/record.ts"],"names":[],"mappings":"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","sourcesContent":["import { CID } from 'multiformats/cid'\nimport { IdResolver, parseToAtprotoDocument } from '@atproto/identity'\nimport { RepoRecord } from '@atproto/lexicon'\nimport {\n Commit,\n MST,\n MemoryBlockstore,\n def as repoDef,\n readCarWithRoot,\n verifyCommitSig,\n} from '@atproto/repo'\nimport { AtUri, ensureValidDid } from '@atproto/syntax'\nimport { BuildFetchHandlerOptions, FetchHandler } from '@atproto/xrpc'\nimport { safeFetchWrap } from '@atproto-labs/fetch-node'\nimport { AtpBaseClient as Client } from './client/index.js'\nimport { isValidDid } from './util.js'\n\n/**\n * Resolve a record from the network.\n */\nexport type RecordResolver = (uri: AtUri | string) => Promise<RecordResolution>\n\n/**\n * Resolve a record from the network, verifying its authenticity.\n */\nexport type AtprotoRecordResolver = (\n uri: AtUri | string,\n options?: ResolveRecordOptions,\n) => Promise<RecordResolution>\n\nexport type BuildRecordResolverOptions = {\n idResolver?: IdResolver\n rpc?: Partial<BuildFetchHandlerOptions> | FetchHandler\n}\n\nexport type ResolveRecordOptions = {\n forceRefresh?: boolean\n}\n\nexport type RecordResolution = {\n commit: Commit\n uri: AtUri\n cid: CID\n record: RepoRecord\n}\n\nexport { AtUri, CID, type Commit, IdResolver, type RepoRecord }\n\n/**\n * Build a record resolver function.\n */\nexport function buildRecordResolver(\n options: BuildRecordResolverOptions = {},\n): AtprotoRecordResolver {\n const { idResolver = new IdResolver(), rpc } = options\n return async function resolveRecord(\n uriStr: AtUri | string,\n opts: ResolveRecordOptions = {},\n ): Promise<RecordResolution> {\n const uri = typeof uriStr === 'string' ? new AtUri(uriStr) : uriStr\n const did = await getDidFromUri(uri, { idResolver })\n const identityDoc = await idResolver.did\n .ensureResolve(did, opts.forceRefresh)\n .catch((err) => {\n throw new RecordResolutionError('Could not resolve DID identity data', {\n cause: err,\n })\n })\n const { pds, signingKey } = parseToAtprotoDocument(identityDoc)\n if (!pds) {\n throw new RecordResolutionError(\n 'Incomplete DID identity data: missing pds',\n )\n }\n if (!signingKey) {\n throw new RecordResolutionError(\n 'Incomplete DID identity data: missing signing key',\n )\n }\n const client = new Client(\n typeof rpc === 'function'\n ? rpc\n : {\n ...rpc,\n service: rpc?.service ?? pds,\n fetch: rpc?.fetch ?? safeFetch,\n },\n )\n const { data: proofBytes } = await client.com.atproto.sync\n .getRecord({\n did,\n collection: uri.collection,\n rkey: uri.rkey,\n })\n .catch((err) => {\n throw new RecordResolutionError('Could not fetch record proof', {\n cause: err,\n })\n })\n const verified = await verifyRecordProof(proofBytes, {\n uri: AtUri.make(did, uri.collection, uri.rkey),\n signingKey,\n })\n return verified\n }\n}\n\nexport const resolveRecord = buildRecordResolver()\n\nexport const safeFetch = safeFetchWrap({\n allowIpHost: false,\n allowImplicitRedirect: true,\n responseMaxSize: (1024 + 10) * 1024, // 1MB + 10kB, just a bit larger than max record size\n})\n\nexport class RecordResolutionError extends Error {\n constructor(message?: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'RecordResolutionError'\n }\n}\n\nasync function getDidFromUri(\n uri: AtUri,\n { idResolver }: { idResolver: IdResolver },\n) {\n if (uri.host.startsWith('did:')) {\n ensureValidDid(uri.host)\n return uri.host\n }\n const resolved = await idResolver.handle.resolve(uri.host)\n if (!resolved || !isValidDid(resolved)) {\n throw new RecordResolutionError('Could not resolve handle found in AT-URI')\n }\n return resolved\n}\n\nasync function verifyRecordProof(\n proofBytes: Uint8Array,\n { uri, signingKey }: { uri: AtUri; signingKey: string },\n) {\n const { root, blocks } = await readCarWithRoot(proofBytes).catch((err) => {\n throw new RecordResolutionError('Malformed record proof', { cause: err })\n })\n const blockstore = new MemoryBlockstore(blocks)\n const commit = await blockstore.readObj(root, repoDef.commit).catch((err) => {\n throw new RecordResolutionError('Invalid commit in record proof', {\n cause: err,\n })\n })\n if (commit.did !== uri.host) {\n throw new RecordResolutionError(`Invalid repo did: ${commit.did}`)\n }\n const validSig = await verifyCommitSig(commit, signingKey)\n if (!validSig) {\n throw new RecordResolutionError(\n `Invalid signature on commit: ${root.toString()}`,\n )\n }\n const mst = MST.load(blockstore, commit.data)\n const cid = await mst.get(`${uri.collection}/${uri.rkey}`)\n if (!cid) {\n throw new RecordResolutionError('Record not found in proof')\n }\n const record = await blockstore.readRecord(cid)\n return { commit, uri, cid, record }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/lexicon-resolver",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "type": "commonjs",
5
5
  "license": "MIT",
6
6
  "description": "ATProto Lexicon resolution",
@@ -26,15 +26,15 @@
26
26
  "multiformats": "^9.9.0",
27
27
  "@atproto-labs/fetch-node": "^0.2.0",
28
28
  "@atproto/identity": "^0.4.10",
29
- "@atproto/lexicon": "^0.6.0",
29
+ "@atproto/lexicon": "^0.6.1",
30
30
  "@atproto/repo": "^0.8.12",
31
- "@atproto/syntax": "^0.4.2",
31
+ "@atproto/syntax": "^0.4.3",
32
32
  "@atproto/xrpc": "^0.7.7"
33
33
  },
34
34
  "devDependencies": {
35
35
  "jest": "^28.1.2",
36
36
  "typescript": "^5.6.3",
37
- "@atproto/common": "^0.5.3",
37
+ "@atproto/common": "^0.5.9",
38
38
  "@atproto/lex-cli": "^0.9.8"
39
39
  },
40
40
  "scripts": {
package/src/record.ts CHANGED
@@ -1,7 +1,5 @@
1
- /* eslint-disable import/no-deprecated */
2
-
3
1
  import { CID } from 'multiformats/cid'
4
- import { IdResolver } from '@atproto/identity'
2
+ import { IdResolver, parseToAtprotoDocument } from '@atproto/identity'
5
3
  import { RepoRecord } from '@atproto/lexicon'
6
4
  import {
7
5
  Commit,
@@ -61,19 +59,30 @@ export function buildRecordResolver(
61
59
  ): Promise<RecordResolution> {
62
60
  const uri = typeof uriStr === 'string' ? new AtUri(uriStr) : uriStr
63
61
  const did = await getDidFromUri(uri, { idResolver })
64
- const identity = await idResolver.did
65
- .resolveAtprotoData(did, opts.forceRefresh)
62
+ const identityDoc = await idResolver.did
63
+ .ensureResolve(did, opts.forceRefresh)
66
64
  .catch((err) => {
67
65
  throw new RecordResolutionError('Could not resolve DID identity data', {
68
66
  cause: err,
69
67
  })
70
68
  })
69
+ const { pds, signingKey } = parseToAtprotoDocument(identityDoc)
70
+ if (!pds) {
71
+ throw new RecordResolutionError(
72
+ 'Incomplete DID identity data: missing pds',
73
+ )
74
+ }
75
+ if (!signingKey) {
76
+ throw new RecordResolutionError(
77
+ 'Incomplete DID identity data: missing signing key',
78
+ )
79
+ }
71
80
  const client = new Client(
72
81
  typeof rpc === 'function'
73
82
  ? rpc
74
83
  : {
75
84
  ...rpc,
76
- service: rpc?.service ?? identity.pds,
85
+ service: rpc?.service ?? pds,
77
86
  fetch: rpc?.fetch ?? safeFetch,
78
87
  },
79
88
  )
@@ -90,7 +99,7 @@ export function buildRecordResolver(
90
99
  })
91
100
  const verified = await verifyRecordProof(proofBytes, {
92
101
  uri: AtUri.make(did, uri.collection, uri.rkey),
93
- signingKey: identity.signingKey,
102
+ signingKey,
94
103
  })
95
104
  return verified
96
105
  }
@@ -1,5 +1,3 @@
1
- /* eslint-disable import/no-deprecated */
2
-
3
1
  import { dataToCborBlock } from '@atproto/common'
4
2
  import { SeedClient, TestNetworkNoAppView, usersSeed } from '@atproto/dev-env'
5
3
  import { AtprotoRecordResolver, buildRecordResolver } from '../src/index.js'
@@ -97,4 +95,96 @@ describe('Record resolution', () => {
97
95
  }),
98
96
  ).rejects.toThrow('Malformed record proof')
99
97
  })
98
+
99
+ it('does not resolve record with missing signing key.', async () => {
100
+ const post = await sc.post(sc.dids.alice, 'post5')
101
+ await network.pds.ctx.plcClient.updateData(
102
+ sc.dids.alice,
103
+ network.pds.ctx.plcRotationKey,
104
+ (doc) => {
105
+ doc.verificationMethods = {
106
+ not_atproto: doc.verificationMethods.atproto,
107
+ }
108
+ return doc
109
+ },
110
+ )
111
+ await expect(
112
+ resolveRecord(post.ref.uri, {
113
+ forceRefresh: true,
114
+ }),
115
+ ).rejects.toThrow('Incomplete DID identity data: missing signing key')
116
+ // reset alice's key
117
+ await network.pds.ctx.plcClient.updateData(
118
+ sc.dids.alice,
119
+ network.pds.ctx.plcRotationKey,
120
+ (doc) => {
121
+ doc.verificationMethods = {
122
+ atproto: doc.verificationMethods.not_atproto,
123
+ }
124
+ return doc
125
+ },
126
+ )
127
+ })
128
+
129
+ it('does not resolve record with missing pds.', async () => {
130
+ const post = await sc.post(sc.dids.alice, 'post6')
131
+ await network.pds.ctx.plcClient.updateData(
132
+ sc.dids.alice,
133
+ network.pds.ctx.plcRotationKey,
134
+ (doc) => {
135
+ doc.services = {
136
+ not_atproto_pds: doc.services.atproto_pds,
137
+ }
138
+ return doc
139
+ },
140
+ )
141
+ await expect(
142
+ resolveRecord(post.ref.uri, {
143
+ forceRefresh: true,
144
+ }),
145
+ ).rejects.toThrow('Incomplete DID identity data: missing pds')
146
+ // reset alice's pds
147
+ await network.pds.ctx.plcClient.updateData(
148
+ sc.dids.alice,
149
+ network.pds.ctx.plcRotationKey,
150
+ (doc) => {
151
+ doc.services = {
152
+ atproto_pds: doc.services.not_atproto_pds,
153
+ }
154
+ return doc
155
+ },
156
+ )
157
+ })
158
+
159
+ it('resolves record despite missing at:// handle.', async () => {
160
+ const post = await sc.post(sc.dids.alice, 'post7')
161
+ await network.pds.ctx.plcClient.updateData(
162
+ sc.dids.alice,
163
+ network.pds.ctx.plcRotationKey,
164
+ (doc) => {
165
+ doc.alsoKnownAs = doc.alsoKnownAs.map((aka) =>
166
+ aka.replace('at://', 'notat://'),
167
+ )
168
+ return doc
169
+ },
170
+ )
171
+ const result = await resolveRecord(post.ref.uriStr, {
172
+ forceRefresh: true,
173
+ })
174
+ expect(result.commit.did).toEqual(sc.dids.alice)
175
+ expect(result.cid.toString()).toEqual(post.ref.cidStr)
176
+ expect(result.uri.toString()).toEqual(post.ref.uriStr)
177
+ expect(result.record.text).toEqual('post7')
178
+ // reset alice's handle
179
+ await network.pds.ctx.plcClient.updateData(
180
+ sc.dids.alice,
181
+ network.pds.ctx.plcRotationKey,
182
+ (doc) => {
183
+ doc.alsoKnownAs = doc.alsoKnownAs.map((aka) =>
184
+ aka.replace('notat://', 'at://'),
185
+ )
186
+ return doc
187
+ },
188
+ )
189
+ })
100
190
  })
@@ -1 +0,0 @@
1
- {"root":["./tests/lexicon.test.ts","./tests/record.test.ts"],"version":"5.8.3"}