@atproto/identity 0.2.1 → 0.3.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/dist/types.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- import * as z from 'zod';
1
+ import { DidDocument } from '@atproto/common-web';
2
+ export { didDocument } from '@atproto/common-web';
3
+ export type { DidDocument } from '@atproto/common-web';
2
4
  export declare type IdentityResolverOpts = {
3
5
  timeout?: number;
4
6
  plcUrl?: string;
@@ -25,102 +27,12 @@ export declare type CacheResult = {
25
27
  doc: DidDocument;
26
28
  updatedAt: number;
27
29
  stale: boolean;
30
+ expired: boolean;
28
31
  };
29
32
  export interface DidCache {
30
- cacheDid(did: string, doc: DidDocument): Promise<void>;
33
+ cacheDid(did: string, doc: DidDocument, prevResult?: CacheResult): Promise<void>;
31
34
  checkCache(did: string): Promise<CacheResult | null>;
32
- refreshCache(did: string, getDoc: () => Promise<DidDocument | null>): Promise<void>;
35
+ refreshCache(did: string, getDoc: () => Promise<DidDocument | null>, prevResult?: CacheResult): Promise<void>;
33
36
  clearEntry(did: string): Promise<void>;
34
37
  clear(): Promise<void>;
35
38
  }
36
- export declare const verificationMethod: z.ZodObject<{
37
- id: z.ZodString;
38
- type: z.ZodString;
39
- controller: z.ZodString;
40
- publicKeyMultibase: z.ZodOptional<z.ZodString>;
41
- }, "strip", z.ZodTypeAny, {
42
- id: string;
43
- type: string;
44
- controller: string;
45
- publicKeyMultibase?: string | undefined;
46
- }, {
47
- id: string;
48
- type: string;
49
- controller: string;
50
- publicKeyMultibase?: string | undefined;
51
- }>;
52
- export declare const service: z.ZodObject<{
53
- id: z.ZodString;
54
- type: z.ZodString;
55
- serviceEndpoint: z.ZodUnion<[z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
56
- }, "strip", z.ZodTypeAny, {
57
- id: string;
58
- type: string;
59
- serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
60
- }, {
61
- id: string;
62
- type: string;
63
- serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
64
- }>;
65
- export declare const didDocument: z.ZodObject<{
66
- id: z.ZodString;
67
- alsoKnownAs: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
68
- verificationMethod: z.ZodOptional<z.ZodArray<z.ZodObject<{
69
- id: z.ZodString;
70
- type: z.ZodString;
71
- controller: z.ZodString;
72
- publicKeyMultibase: z.ZodOptional<z.ZodString>;
73
- }, "strip", z.ZodTypeAny, {
74
- id: string;
75
- type: string;
76
- controller: string;
77
- publicKeyMultibase?: string | undefined;
78
- }, {
79
- id: string;
80
- type: string;
81
- controller: string;
82
- publicKeyMultibase?: string | undefined;
83
- }>, "many">>;
84
- service: z.ZodOptional<z.ZodArray<z.ZodObject<{
85
- id: z.ZodString;
86
- type: z.ZodString;
87
- serviceEndpoint: z.ZodUnion<[z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
88
- }, "strip", z.ZodTypeAny, {
89
- id: string;
90
- type: string;
91
- serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
92
- }, {
93
- id: string;
94
- type: string;
95
- serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
96
- }>, "many">>;
97
- }, "strip", z.ZodTypeAny, {
98
- id: string;
99
- alsoKnownAs?: string[] | undefined;
100
- verificationMethod?: {
101
- id: string;
102
- type: string;
103
- controller: string;
104
- publicKeyMultibase?: string | undefined;
105
- }[] | undefined;
106
- service?: {
107
- id: string;
108
- type: string;
109
- serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
110
- }[] | undefined;
111
- }, {
112
- id: string;
113
- alsoKnownAs?: string[] | undefined;
114
- verificationMethod?: {
115
- id: string;
116
- type: string;
117
- controller: string;
118
- publicKeyMultibase?: string | undefined;
119
- }[] | undefined;
120
- service?: {
121
- id: string;
122
- type: string;
123
- serviceEndpoint: (string | Record<string, unknown>) & (string | Record<string, unknown> | undefined);
124
- }[] | undefined;
125
- }>;
126
- export declare type DidDocument = z.infer<typeof didDocument>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/identity",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "license": "MIT",
5
5
  "description": "Library for decentralized identities in atproto using DIDs and handles",
6
6
  "keywords": [
@@ -17,9 +17,8 @@
17
17
  "main": "dist/index.js",
18
18
  "dependencies": {
19
19
  "axios": "^0.27.2",
20
- "zod": "^3.21.4",
21
- "@atproto/common-web": "^0.2.1",
22
- "@atproto/crypto": "^0.2.2"
20
+ "@atproto/common-web": "^0.2.3",
21
+ "@atproto/crypto": "^0.2.3"
23
22
  },
24
23
  "devDependencies": {
25
24
  "@did-plc/lib": "^0.0.1",
@@ -1,73 +1,39 @@
1
1
  import * as crypto from '@atproto/crypto'
2
2
  import { DidDocument, AtprotoData } from '../types'
3
+ import {
4
+ getDid,
5
+ getHandle,
6
+ getPdsEndpoint,
7
+ getFeedGenEndpoint,
8
+ getNotifEndpoint,
9
+ getSigningKey,
10
+ } from '@atproto/common-web'
3
11
 
4
- export const getDid = (doc: DidDocument): string => {
5
- const id = doc.id
6
- if (typeof id !== 'string') {
7
- throw new Error('No `id` on document')
8
- }
9
- return id
12
+ export {
13
+ getDid,
14
+ getHandle,
15
+ getPdsEndpoint as getPds,
16
+ getFeedGenEndpoint as getFeedGen,
17
+ getNotifEndpoint as getNotif,
10
18
  }
11
19
 
12
20
  export const getKey = (doc: DidDocument): string | undefined => {
13
- const did = getDid(doc)
14
- let keys = doc.verificationMethod
15
- if (!keys) return undefined
16
- if (typeof keys !== 'object') return undefined
17
- if (!Array.isArray(keys)) {
18
- keys = [keys]
19
- }
20
- const found = keys.find(
21
- (key) => key.id === '#atproto' || key.id === `${did}#atproto`,
22
- )
23
- if (!found) return undefined
21
+ const key = getSigningKey(doc)
22
+ if (!key) return undefined
24
23
 
25
- // @TODO support jwk
26
- // should we be surfacing errors here or returning undefined?
27
- if (!found.publicKeyMultibase) return undefined
28
- const keyBytes = crypto.multibaseToBytes(found.publicKeyMultibase)
24
+ const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase)
29
25
  let didKey: string | undefined = undefined
30
- if (found.type === 'EcdsaSecp256r1VerificationKey2019') {
26
+ if (key.type === 'EcdsaSecp256r1VerificationKey2019') {
31
27
  didKey = crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes)
32
- } else if (found.type === 'EcdsaSecp256k1VerificationKey2019') {
28
+ } else if (key.type === 'EcdsaSecp256k1VerificationKey2019') {
33
29
  didKey = crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes)
34
- } else if (found.type === 'Multikey') {
35
- const parsed = crypto.parseMultikey(found.publicKeyMultibase)
30
+ } else if (key.type === 'Multikey') {
31
+ const parsed = crypto.parseMultikey(key.publicKeyMultibase)
36
32
  didKey = crypto.formatDidKey(parsed.jwtAlg, parsed.keyBytes)
37
33
  }
38
34
  return didKey
39
35
  }
40
36
 
41
- export const getHandle = (doc: DidDocument): string | undefined => {
42
- const aka = doc.alsoKnownAs
43
- if (!aka) return undefined
44
- const found = aka.find((name) => name.startsWith('at://'))
45
- if (!found) return undefined
46
- // strip off at:// prefix
47
- return found.slice(5)
48
- }
49
-
50
- export const getPds = (doc: DidDocument): string | undefined => {
51
- return getServiceEndpoint(doc, {
52
- id: '#atproto_pds',
53
- type: 'AtprotoPersonalDataServer',
54
- })
55
- }
56
-
57
- export const getFeedGen = (doc: DidDocument): string | undefined => {
58
- return getServiceEndpoint(doc, {
59
- id: '#bsky_fg',
60
- type: 'BskyFeedGenerator',
61
- })
62
- }
63
-
64
- export const getNotif = (doc: DidDocument): string | undefined => {
65
- return getServiceEndpoint(doc, {
66
- id: '#bsky_notif',
67
- type: 'BskyNotificationService',
68
- })
69
- }
70
-
71
37
  export const parseToAtprotoDocument = (
72
38
  doc: DidDocument,
73
39
  ): Partial<AtprotoData> => {
@@ -76,7 +42,7 @@ export const parseToAtprotoDocument = (
76
42
  did,
77
43
  signingKey: getKey(doc),
78
44
  handle: getHandle(doc),
79
- pds: getPds(doc),
45
+ pds: getPdsEndpoint(doc),
80
46
  }
81
47
  }
82
48
 
@@ -97,38 +63,10 @@ export const ensureAtpDocument = (doc: DidDocument): AtprotoData => {
97
63
  return { did, signingKey, handle, pds }
98
64
  }
99
65
 
100
- // Check protocol and hostname to prevent potential SSRF
101
- const validateUrl = (url: string) => {
102
- const { hostname, protocol } = new URL(url)
103
- if (!['http:', 'https:'].includes(protocol)) {
104
- throw new Error('Invalid pds protocol')
105
- }
106
- if (!hostname) {
107
- throw new Error('Invalid pds hostname')
108
- }
109
- }
110
-
111
- const getServiceEndpoint = (
112
- doc: DidDocument,
113
- opts: { id: string; type: string },
114
- ) => {
115
- const did = getDid(doc)
116
- let services = doc.service
117
- if (!services) return undefined
118
- if (typeof services !== 'object') return undefined
119
- if (!Array.isArray(services)) {
120
- services = [services]
121
- }
122
- const found = services.find(
123
- (service) => service.id === opts.id || service.id === `${did}${opts.id}`,
124
- )
125
- if (!found) return undefined
126
- if (found.type !== opts.type) {
127
- return undefined
128
- }
129
- if (typeof found.serviceEndpoint !== 'string') {
130
- return undefined
66
+ export const ensureAtprotoKey = (doc: DidDocument): string => {
67
+ const { signingKey } = parseToAtprotoDocument(doc)
68
+ if (!signingKey) {
69
+ throw new Error(`Could not parse signingKey from doc: ${doc}`)
131
70
  }
132
- validateUrl(found.serviceEndpoint)
133
- return found.serviceEndpoint
71
+ return signingKey
134
72
  }
@@ -1,6 +1,12 @@
1
1
  import * as crypto from '@atproto/crypto'
2
2
  import { check } from '@atproto/common-web'
3
- import { DidCache, AtprotoData, DidDocument, didDocument } from '../types'
3
+ import {
4
+ DidCache,
5
+ AtprotoData,
6
+ DidDocument,
7
+ didDocument,
8
+ CacheResult,
9
+ } from '../types'
4
10
  import * as atprotoData from './atproto-data'
5
11
  import { DidNotFoundError, PoorlyFormattedDidDocumentError } from '../errors'
6
12
 
@@ -25,20 +31,25 @@ export abstract class BaseResolver {
25
31
  return this.validateDidDoc(did, got)
26
32
  }
27
33
 
28
- async refreshCache(did: string): Promise<void> {
29
- await this.cache?.refreshCache(did, () => this.resolveNoCache(did))
34
+ async refreshCache(did: string, prevResult?: CacheResult): Promise<void> {
35
+ await this.cache?.refreshCache(
36
+ did,
37
+ () => this.resolveNoCache(did),
38
+ prevResult,
39
+ )
30
40
  }
31
41
 
32
42
  async resolve(
33
43
  did: string,
34
44
  forceRefresh = false,
35
45
  ): Promise<DidDocument | null> {
46
+ let fromCache: CacheResult | null = null
36
47
  if (this.cache && !forceRefresh) {
37
- const fromCache = await this.cache.checkCache(did)
38
- if (fromCache?.stale) {
39
- await this.refreshCache(did)
40
- }
41
- if (fromCache) {
48
+ fromCache = await this.cache.checkCache(did)
49
+ if (fromCache && !fromCache.expired) {
50
+ if (fromCache?.stale) {
51
+ await this.refreshCache(did, fromCache)
52
+ }
42
53
  return fromCache.doc
43
54
  }
44
55
  }
@@ -48,7 +59,7 @@ export abstract class BaseResolver {
48
59
  await this.cache?.clearEntry(did)
49
60
  return null
50
61
  }
51
- await this.cache?.cacheDid(did, got)
62
+ await this.cache?.cacheDid(did, got, fromCache ?? undefined)
52
63
  return got
53
64
  }
54
65
 
@@ -72,8 +83,8 @@ export abstract class BaseResolver {
72
83
  if (did.startsWith('did:key:')) {
73
84
  return did
74
85
  } else {
75
- const data = await this.resolveAtprotoData(did, forceRefresh)
76
- return data.signingKey
86
+ const didDocument = await this.ensureResolve(did, forceRefresh)
87
+ return atprotoData.ensureAtprotoKey(didDocument)
77
88
  }
78
89
  }
79
90
 
@@ -35,13 +35,12 @@ export class MemoryCache implements DidCache {
35
35
  if (!val) return null
36
36
  const now = Date.now()
37
37
  const expired = now > val.updatedAt + this.maxTTL
38
- if (expired) return null
39
-
40
38
  const stale = now > val.updatedAt + this.staleTTL
41
39
  return {
42
40
  ...val,
43
41
  did,
44
42
  stale,
43
+ expired,
45
44
  }
46
45
  }
47
46
 
@@ -50,7 +50,7 @@ export class HandleResolver {
50
50
  const url = new URL('/.well-known/atproto-did', `https://${handle}`)
51
51
  try {
52
52
  const res = await fetch(url, { signal })
53
- const did = await res.text()
53
+ const did = (await res.text()).split('\n')[0].trim()
54
54
  if (typeof did === 'string' && did.startsWith('did:')) {
55
55
  return did
56
56
  }
package/src/types.ts CHANGED
@@ -1,4 +1,7 @@
1
- import * as z from 'zod'
1
+ import { DidDocument } from '@atproto/common-web'
2
+
3
+ export { didDocument } from '@atproto/common-web'
4
+ export type { DidDocument } from '@atproto/common-web'
2
5
 
3
6
  export type IdentityResolverOpts = {
4
7
  timeout?: number
@@ -30,37 +33,21 @@ export type CacheResult = {
30
33
  doc: DidDocument
31
34
  updatedAt: number
32
35
  stale: boolean
36
+ expired: boolean
33
37
  }
34
38
 
35
39
  export interface DidCache {
36
- cacheDid(did: string, doc: DidDocument): Promise<void>
40
+ cacheDid(
41
+ did: string,
42
+ doc: DidDocument,
43
+ prevResult?: CacheResult,
44
+ ): Promise<void>
37
45
  checkCache(did: string): Promise<CacheResult | null>
38
46
  refreshCache(
39
47
  did: string,
40
48
  getDoc: () => Promise<DidDocument | null>,
49
+ prevResult?: CacheResult,
41
50
  ): Promise<void>
42
51
  clearEntry(did: string): Promise<void>
43
52
  clear(): Promise<void>
44
53
  }
45
-
46
- export const verificationMethod = z.object({
47
- id: z.string(),
48
- type: z.string(),
49
- controller: z.string(),
50
- publicKeyMultibase: z.string().optional(),
51
- })
52
-
53
- export const service = z.object({
54
- id: z.string(),
55
- type: z.string(),
56
- serviceEndpoint: z.union([z.string(), z.record(z.unknown())]),
57
- })
58
-
59
- export const didDocument = z.object({
60
- id: z.string(),
61
- alsoKnownAs: z.array(z.string()).optional(),
62
- verificationMethod: z.array(verificationMethod).optional(),
63
- service: z.array(service).optional(),
64
- })
65
-
66
- export type DidDocument = z.infer<typeof didDocument>
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2022-2023 Bluesky PBC
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.