@atproto/identity 0.1.0 → 0.2.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/jest.config.js CHANGED
@@ -2,5 +2,5 @@ const base = require('../../jest.config.base.js')
2
2
 
3
3
  module.exports = {
4
4
  ...base,
5
- displayName: 'Identity'
5
+ displayName: 'Identity',
6
6
  }
package/package.json CHANGED
@@ -1,34 +1,25 @@
1
1
  {
2
2
  "name": "@atproto/identity",
3
- "version": "0.1.0",
4
- "main": "dist/index.js",
3
+ "version": "0.2.1",
5
4
  "license": "MIT",
5
+ "description": "Library for decentralized identities in atproto using DIDs and handles",
6
+ "keywords": [
7
+ "atproto",
8
+ "did",
9
+ "identity"
10
+ ],
11
+ "homepage": "https://atproto.com",
6
12
  "repository": {
7
13
  "type": "git",
8
- "url": "https://github.com/bluesky-social/atproto.git",
14
+ "url": "https://github.com/bluesky-social/atproto",
9
15
  "directory": "packages/identity"
10
16
  },
11
- "scripts": {
12
- "test": "jest",
13
- "test:log": "cat test.log | pino-pretty",
14
- "prettier": "prettier --check src/ tests/",
15
- "prettier:fix": "prettier --write src/ tests/",
16
- "lint": "eslint . --ext .ts,.tsx",
17
- "lint:fix": "yarn lint --fix",
18
- "verify": "run-p prettier lint",
19
- "verify:fix": "yarn prettier:fix && yarn lint:fix",
20
- "build": "node ./build.js",
21
- "postbuild": "tsc --build tsconfig.build.json",
22
- "update-main-to-dist": "node ./update-pkg.js --update-main-to-dist",
23
- "update-main-to-src": "node ./update-pkg.js --update-main-to-src",
24
- "prepublish": "npm run update-main-to-dist",
25
- "postpublish": "npm run update-main-to-src"
26
- },
17
+ "main": "dist/index.js",
27
18
  "dependencies": {
28
- "@atproto/common-web": "*",
29
- "@atproto/crypto": "*",
30
19
  "axios": "^0.27.2",
31
- "zod": "^3.21.4"
20
+ "zod": "^3.21.4",
21
+ "@atproto/common-web": "^0.2.1",
22
+ "@atproto/crypto": "^0.2.2"
32
23
  },
33
24
  "devDependencies": {
34
25
  "@did-plc/lib": "^0.0.1",
@@ -36,5 +27,13 @@
36
27
  "cors": "^2.8.5",
37
28
  "express": "^4.18.2",
38
29
  "get-port": "^6.1.2"
39
- }
40
- }
30
+ },
31
+ "scripts": {
32
+ "test": "jest",
33
+ "test:log": "cat test.log | pino-pretty",
34
+ "build": "node ./build.js",
35
+ "postbuild": "tsc --build tsconfig.build.json",
36
+ "update-main-to-dist": "node ../../update-main-to-dist.js packages/identity"
37
+ },
38
+ "types": "dist/index.d.ts"
39
+ }
@@ -10,13 +10,16 @@ export const getDid = (doc: DidDocument): string => {
10
10
  }
11
11
 
12
12
  export const getKey = (doc: DidDocument): string | undefined => {
13
+ const did = getDid(doc)
13
14
  let keys = doc.verificationMethod
14
15
  if (!keys) return undefined
15
16
  if (typeof keys !== 'object') return undefined
16
17
  if (!Array.isArray(keys)) {
17
18
  keys = [keys]
18
19
  }
19
- const found = keys.find((key) => key.id === '#atproto')
20
+ const found = keys.find(
21
+ (key) => key.id === '#atproto' || key.id === `${did}#atproto`,
22
+ )
20
23
  if (!found) return undefined
21
24
 
22
25
  // @TODO support jwk
@@ -28,6 +31,9 @@ export const getKey = (doc: DidDocument): string | undefined => {
28
31
  didKey = crypto.formatDidKey(crypto.P256_JWT_ALG, keyBytes)
29
32
  } else if (found.type === 'EcdsaSecp256k1VerificationKey2019') {
30
33
  didKey = crypto.formatDidKey(crypto.SECP256K1_JWT_ALG, keyBytes)
34
+ } else if (found.type === 'Multikey') {
35
+ const parsed = crypto.parseMultikey(found.publicKeyMultibase)
36
+ didKey = crypto.formatDidKey(parsed.jwtAlg, parsed.keyBytes)
31
37
  }
32
38
  return didKey
33
39
  }
@@ -42,41 +48,24 @@ export const getHandle = (doc: DidDocument): string | undefined => {
42
48
  }
43
49
 
44
50
  export const getPds = (doc: DidDocument): string | undefined => {
45
- let services = doc.service
46
- if (!services) return undefined
47
- if (typeof services !== 'object') return undefined
48
- if (!Array.isArray(services)) {
49
- services = [services]
50
- }
51
- const found = services.find((service) => service.id === '#atproto_pds')
52
- if (!found) return undefined
53
- if (found.type !== 'AtprotoPersonalDataServer') {
54
- return undefined
55
- }
56
- if (typeof found.serviceEndpoint !== 'string') {
57
- return undefined
58
- }
59
- validateUrl(found.serviceEndpoint)
60
- return found.serviceEndpoint
51
+ return getServiceEndpoint(doc, {
52
+ id: '#atproto_pds',
53
+ type: 'AtprotoPersonalDataServer',
54
+ })
61
55
  }
62
56
 
63
57
  export const getFeedGen = (doc: DidDocument): string | undefined => {
64
- let services = doc.service
65
- if (!services) return undefined
66
- if (typeof services !== 'object') return undefined
67
- if (!Array.isArray(services)) {
68
- services = [services]
69
- }
70
- const found = services.find((service) => service.id === '#bsky_fg')
71
- if (!found) return undefined
72
- if (found.type !== 'BskyFeedGenerator') {
73
- return undefined
74
- }
75
- if (typeof found.serviceEndpoint !== 'string') {
76
- return undefined
77
- }
78
- validateUrl(found.serviceEndpoint)
79
- return found.serviceEndpoint
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
+ })
80
69
  }
81
70
 
82
71
  export const parseToAtprotoDocument = (
@@ -118,3 +107,28 @@ const validateUrl = (url: string) => {
118
107
  throw new Error('Invalid pds hostname')
119
108
  }
120
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
131
+ }
132
+ validateUrl(found.serviceEndpoint)
133
+ return found.serviceEndpoint
134
+ }
@@ -0,0 +1,103 @@
1
+ import { DidResolver, ensureAtpDocument } from '../src'
2
+
3
+ describe('did parsing', () => {
4
+ it('throws on bad DID document', async () => {
5
+ const did = 'did:plc:yk4dd2qkboz2yv6tpubpc6co'
6
+ const docJson = `{
7
+ "ideep": "did:plc:yk4dd2qkboz2yv6tpubpc6co",
8
+ "blah": [
9
+ "https://dholms.xyz"
10
+ ],
11
+ "zoot": [
12
+ {
13
+ "id": "#elsewhere",
14
+ "type": "EcdsaSecp256k1VerificationKey2019",
15
+ "controller": "did:plc:yk4dd2qkboz2yv6tpubpc6co",
16
+ "publicKeyMultibase": "zQYEBzXeuTM9UR3rfvNag6L3RNAs5pQZyYPsomTsgQhsxLdEgCrPTLgFna8yqCnxPpNT7DBk6Ym3dgPKNu86vt9GR"
17
+ }
18
+ ],
19
+ "yarg": [ ]
20
+ }`
21
+ const resolver = new DidResolver({})
22
+ expect(() => {
23
+ resolver.validateDidDoc(did, JSON.parse(docJson))
24
+ }).toThrow()
25
+ })
26
+
27
+ it('parses legacy DID format, extracts atpData', async () => {
28
+ const did = 'did:plc:yk4dd2qkboz2yv6tpubpc6co'
29
+ const docJson = `{
30
+ "@context": [
31
+ "https://www.w3.org/ns/did/v1",
32
+ "https://w3id.org/security/suites/secp256k1-2019/v1"
33
+ ],
34
+ "id": "did:plc:yk4dd2qkboz2yv6tpubpc6co",
35
+ "alsoKnownAs": [
36
+ "at://dholms.xyz"
37
+ ],
38
+ "verificationMethod": [
39
+ {
40
+ "id": "#atproto",
41
+ "type": "EcdsaSecp256k1VerificationKey2019",
42
+ "controller": "did:plc:yk4dd2qkboz2yv6tpubpc6co",
43
+ "publicKeyMultibase": "zQYEBzXeuTM9UR3rfvNag6L3RNAs5pQZyYPsomTsgQhsxLdEgCrPTLgFna8yqCnxPpNT7DBk6Ym3dgPKNu86vt9GR"
44
+ }
45
+ ],
46
+ "service": [
47
+ {
48
+ "id": "#atproto_pds",
49
+ "type": "AtprotoPersonalDataServer",
50
+ "serviceEndpoint": "https://bsky.social"
51
+ }
52
+ ]
53
+ }`
54
+ const resolver = new DidResolver({})
55
+ const doc = resolver.validateDidDoc(did, JSON.parse(docJson))
56
+ const atpData = ensureAtpDocument(doc)
57
+ expect(atpData.did).toEqual(did)
58
+ expect(atpData.handle).toEqual('dholms.xyz')
59
+ expect(atpData.pds).toEqual('https://bsky.social')
60
+ expect(atpData.signingKey).toEqual(
61
+ 'did:key:zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF',
62
+ )
63
+ })
64
+
65
+ it('parses newer Multikey DID format, extracts atpData', async () => {
66
+ const did = 'did:plc:yk4dd2qkboz2yv6tpubpc6co'
67
+ const docJson = `{
68
+ "@context": [
69
+ "https://www.w3.org/ns/did/v1",
70
+ "https://w3id.org/security/multikey/v1",
71
+ "https://w3id.org/security/suites/secp256k1-2019/v1"
72
+ ],
73
+ "id": "did:plc:yk4dd2qkboz2yv6tpubpc6co",
74
+ "alsoKnownAs": [
75
+ "at://dholms.xyz"
76
+ ],
77
+ "verificationMethod": [
78
+ {
79
+ "id": "did:plc:yk4dd2qkboz2yv6tpubpc6co#atproto",
80
+ "type": "Multikey",
81
+ "controller": "did:plc:yk4dd2qkboz2yv6tpubpc6co",
82
+ "publicKeyMultibase": "zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF"
83
+ }
84
+ ],
85
+ "service": [
86
+ {
87
+ "id": "#atproto_pds",
88
+ "type": "AtprotoPersonalDataServer",
89
+ "serviceEndpoint": "https://bsky.social"
90
+ }
91
+ ]
92
+ }`
93
+ const resolver = new DidResolver({})
94
+ const doc = resolver.validateDidDoc(did, JSON.parse(docJson))
95
+ const atpData = ensureAtpDocument(doc)
96
+ expect(atpData.did).toEqual(did)
97
+ expect(atpData.handle).toEqual('dholms.xyz')
98
+ expect(atpData.pds).toEqual('https://bsky.social')
99
+ expect(atpData.signingKey).toEqual(
100
+ 'did:key:zQ3shXjHeiBuRCKmM36cuYnm7YEMzhGnCmCyW92sRJ9pribSF',
101
+ )
102
+ })
103
+ })
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "extends": "./tsconfig.json",
3
3
  "exclude": ["**/*.spec.ts", "**/*.test.ts"]
4
- }
4
+ }
package/tsconfig.json CHANGED
@@ -5,9 +5,9 @@
5
5
  "outDir": "./dist", // Your outDir,
6
6
  "emitDeclarationOnly": true
7
7
  },
8
- "include": ["./src","__tests__/**/**.ts"],
8
+ "include": ["./src", "__tests__/**/**.ts"],
9
9
  "references": [
10
10
  { "path": "../common/tsconfig.build.json" },
11
- { "path": "../crypto/tsconfig.build.json" },
11
+ { "path": "../crypto/tsconfig.build.json" }
12
12
  ]
13
- }
13
+ }
package/dist/atp-did.d.ts DELETED
@@ -1,14 +0,0 @@
1
- import { DIDDocument } from 'did-resolver';
2
- export declare type AtpData = {
3
- did: string;
4
- signingKey: string;
5
- recoveryKey: string;
6
- handle: string;
7
- atpPds: string;
8
- };
9
- export declare const getDid: (doc: DIDDocument) => string;
10
- export declare const getKey: (doc: DIDDocument, id: string) => string | undefined;
11
- export declare const getHandle: (doc: DIDDocument) => string | undefined;
12
- export declare const getAtpPds: (doc: DIDDocument) => string | undefined;
13
- export declare const parseToAtpDocument: (doc: DIDDocument) => Partial<AtpData>;
14
- export declare const ensureAtpDocument: (doc: DIDDocument) => AtpData;
@@ -1,7 +0,0 @@
1
- import { DidDocument, AtprotoData } from './types';
2
- export declare const getDid: (doc: DidDocument) => string;
3
- export declare const getKey: (doc: DidDocument) => string | undefined;
4
- export declare const getHandle: (doc: DidDocument) => string | undefined;
5
- export declare const getPds: (doc: DidDocument) => string | undefined;
6
- export declare const parseToAtprotoDocument: (doc: DidDocument) => Partial<AtprotoData>;
7
- export declare const ensureAtpDocument: (doc: DidDocument) => AtprotoData;
@@ -1,16 +0,0 @@
1
- import { AtprotoData, DidDocument } from './types';
2
- import { DidCache } from './did-cache';
3
- export declare abstract class BaseResolver {
4
- cache?: DidCache | undefined;
5
- constructor(cache?: DidCache | undefined);
6
- abstract resolveDidNoCheck(did: string): Promise<unknown | null>;
7
- validateDidDoc(did: string, val: unknown): DidDocument;
8
- resolveDidNoCache(did: string): Promise<DidDocument | null>;
9
- refreshCache(did: string): Promise<void>;
10
- resolveDid(did: string, forceRefresh?: boolean): Promise<DidDocument | null>;
11
- ensureResolveDid(did: string, forceRefresh?: boolean): Promise<DidDocument>;
12
- resolveAtprotoData(did: string, forceRefresh?: boolean): Promise<AtprotoData>;
13
- resolveAtprotoKey(did: string, forceRefresh?: boolean): Promise<string>;
14
- verifySignature(did: string, data: Uint8Array, sig: Uint8Array, forceRefresh?: boolean): Promise<boolean>;
15
- }
16
- export default BaseResolver;
@@ -1,2 +0,0 @@
1
- export declare const logger: import("pino").default.Logger<import("pino").default.LoggerOptions>;
2
- export default logger;
@@ -1,8 +0,0 @@
1
- import { CacheResult, DidDocument } from './types';
2
- export declare abstract class DidCache {
3
- abstract cacheDid(did: string, doc: DidDocument): Promise<void>;
4
- abstract checkCache(did: string): Promise<CacheResult | null>;
5
- abstract refreshCache(did: string, getDoc: () => Promise<DidDocument | null>): Promise<void>;
6
- abstract clearEntry(did: string): Promise<void>;
7
- abstract clear(): Promise<void>;
8
- }
@@ -1,8 +0,0 @@
1
- import { HandleResolver } from './handle';
2
- import DidResolver from './did/did-resolver';
3
- import { IdentityResolverOpts } from './types';
4
- export declare class IdentityResolver {
5
- handle: HandleResolver;
6
- did: DidResolver;
7
- constructor(opts?: Partial<IdentityResolverOpts>);
8
- }
package/dist/logger.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export declare const logger: import("pino").default.Logger<import("pino").default.LoggerOptions>;
2
- export default logger;
@@ -1,18 +0,0 @@
1
- import { DidCache } from './did-cache';
2
- import { CacheResult, DidDocument } from './types';
3
- declare type CacheVal = {
4
- doc: DidDocument;
5
- updatedAt: number;
6
- };
7
- export declare class MemoryCache extends DidCache {
8
- staleTTL: number;
9
- maxTTL: number;
10
- constructor(staleTTL?: number, maxTTL?: number);
11
- cache: Map<string, CacheVal>;
12
- cacheDid(did: string, doc: DidDocument): Promise<void>;
13
- refreshCache(did: string, getDoc: () => Promise<DidDocument | null>): Promise<void>;
14
- checkCache(did: string): Promise<CacheResult | null>;
15
- clearEntry(did: string): Promise<void>;
16
- clear(): Promise<void>;
17
- }
18
- export {};
@@ -1,9 +0,0 @@
1
- import BaseResolver from './base-resolver';
2
- import { PlcResolverOpts } from './types';
3
- import { DidCache } from './did-cache';
4
- export declare class DidPlcResolver extends BaseResolver {
5
- opts: PlcResolverOpts;
6
- cache?: DidCache | undefined;
7
- constructor(opts: PlcResolverOpts, cache?: DidCache | undefined);
8
- resolveDidNoCheck(did: string): Promise<unknown>;
9
- }
@@ -1,9 +0,0 @@
1
- import { DidResolverOpts } from './types';
2
- import BaseResolver from './base-resolver';
3
- import { DidCache } from './did-cache';
4
- export declare class DidResolver extends BaseResolver {
5
- methods: Record<string, BaseResolver>;
6
- constructor(opts?: Partial<DidResolverOpts>, cache?: DidCache);
7
- resolveDidNoCheck(did: string): Promise<unknown>;
8
- }
9
- export default DidResolver;
@@ -1,14 +0,0 @@
1
- import { DIDDocument } from 'did-resolver';
2
- export declare type AtpData = {
3
- did: string;
4
- signingKey: string;
5
- recoveryKey: string;
6
- handle: string;
7
- atpPds: string;
8
- };
9
- export declare const getDid: (doc: DIDDocument) => string;
10
- export declare const getKey: (doc: DIDDocument, id: string) => string | undefined;
11
- export declare const getHandle: (doc: DIDDocument) => string | undefined;
12
- export declare const getAtpPds: (doc: DIDDocument) => string | undefined;
13
- export declare const parseToAtpDocument: (doc: DIDDocument) => Partial<AtpData>;
14
- export declare const ensureAtpDocument: (doc: DIDDocument) => AtpData;
@@ -1,5 +0,0 @@
1
- import { DIDResolutionResult } from 'did-resolver';
2
- export declare const error: (errorType: string) => DIDResolutionResult;
3
- export declare const notFound: () => DIDResolutionResult;
4
- export declare const invalidDid: () => DIDResolutionResult;
5
- export declare const unsupported: () => DIDResolutionResult;
@@ -1,4 +0,0 @@
1
- export * as web from './web-resolver';
2
- export * as plc from './plc-resolver';
3
- export * from './resolver';
4
- export * from './atp-did';
@@ -1,2 +0,0 @@
1
- export declare const logger: import("pino").default.Logger<import("pino").default.LoggerOptions>;
2
- export default logger;
@@ -1,6 +0,0 @@
1
- import { DIDResolver } from 'did-resolver';
2
- export declare type PlcResolverOptions = {
3
- timeout: number;
4
- plcUrl: string;
5
- };
6
- export declare const makeResolver: (opts: PlcResolverOptions) => DIDResolver;
@@ -1,6 +0,0 @@
1
- import { DIDResolver } from 'did-resolver';
2
- export declare type PlcResolverOptions = {
3
- timeout: number;
4
- plcUrl: string;
5
- };
6
- export declare const makeResolver: (opts: PlcResolverOptions) => DIDResolver;
@@ -1,14 +0,0 @@
1
- import { Resolver, DIDDocument, DIDResolutionOptions, DIDResolutionResult } from 'did-resolver';
2
- import * as atpDid from './atp-did';
3
- export declare type DidResolverOptions = {
4
- timeout: number;
5
- plcUrl: string;
6
- };
7
- export declare class DidResolver {
8
- resolver: Resolver;
9
- constructor(opts?: Partial<DidResolverOptions>);
10
- resolveDid(did: string, options?: DIDResolutionOptions): Promise<DIDResolutionResult>;
11
- ensureResolveDid(did: string, options?: DIDResolutionOptions): Promise<DIDDocument>;
12
- resolveAtpData(did: string): Promise<atpDid.AtpData>;
13
- }
14
- export declare const resolver: DidResolver;
@@ -1,17 +0,0 @@
1
- import { DIDDocument } from 'did-resolver';
2
- interface DidStore {
3
- put(key: string, val: string): Promise<void>;
4
- del(key: string): Promise<void>;
5
- get(key: string): Promise<string>;
6
- }
7
- export declare class DidWebDb {
8
- private store;
9
- constructor(store: DidStore);
10
- static persistent(location?: string): DidWebDb;
11
- static memory(): DidWebDb;
12
- put(didPath: string, didDoc: DIDDocument): Promise<void>;
13
- get(didPath: string): Promise<DIDDocument | null>;
14
- has(didPath: string): Promise<boolean>;
15
- del(didPath: string): Promise<void>;
16
- }
17
- export default DidWebDb;
@@ -1,6 +0,0 @@
1
- import { DIDResolver } from 'did-resolver';
2
- export declare const DOC_PATH = "/.well-known/did.json";
3
- export declare type WebResolverOptions = {
4
- timeout: number;
5
- };
6
- export declare const makeResolver: (opts: WebResolverOptions) => DIDResolver;
@@ -1,18 +0,0 @@
1
- /// <reference types="node" />
2
- import express from 'express';
3
- import http from 'http';
4
- import { DIDDocument } from 'did-resolver';
5
- import DidWebDb from './db';
6
- export declare class DidWebServer {
7
- port: number;
8
- private _db;
9
- _app: express.Application;
10
- _httpServer: http.Server | null;
11
- constructor(_app: express.Application, _db: DidWebDb, port: number);
12
- static create(db: DidWebDb, port: number): DidWebServer;
13
- getByPath(didPath?: string): Promise<DIDDocument | null>;
14
- getById(did?: string): Promise<DIDDocument | null>;
15
- put(didDoc: DIDDocument): Promise<void>;
16
- delete(didOrDoc: string | DIDDocument): Promise<void>;
17
- close(): Promise<void>;
18
- }
@@ -1,6 +0,0 @@
1
- import { DIDResolver } from 'did-resolver';
2
- export declare const DOC_PATH = "/.well-known/did.json";
3
- export declare type WebResolverOptions = {
4
- timeout: number;
5
- };
6
- export declare const makeResolver: (opts: WebResolverOptions) => DIDResolver;