Fork of github.com/did-method-plc/did-method-plc
1import { P256Keypair } from '@atproto/crypto' 2import * as plc from '@did-plc/lib' 3import { CloseFn, runTestServer } from './_util' 4import { check } from '@atproto/common' 5import { Database } from '../src' 6import { didForCreateOp, PlcClientError } from '@did-plc/lib' 7 8describe('PLC server', () => { 9 let handle = 'at://alice.example.com' 10 let atpPds = 'https://example.com' 11 12 let close: CloseFn 13 let db: Database 14 let client: plc.Client 15 16 let signingKey: P256Keypair 17 let rotationKey1: P256Keypair 18 let rotationKey2: P256Keypair 19 20 let did: string 21 22 beforeAll(async () => { 23 const server = await runTestServer({ 24 dbSchema: 'server', 25 }) 26 27 db = server.db 28 close = server.close 29 client = new plc.Client(server.url) 30 signingKey = await P256Keypair.create() 31 rotationKey1 = await P256Keypair.create() 32 rotationKey2 = await P256Keypair.create() 33 }) 34 35 afterAll(async () => { 36 if (close) { 37 await close() 38 } 39 }) 40 41 const verifyDoc = (doc: plc.DocumentData | null) => { 42 if (!doc) { 43 throw new Error('expected doc') 44 } 45 expect(doc.did).toEqual(did) 46 expect(doc.verificationMethods).toEqual({ atproto: signingKey.did() }) 47 expect(doc.rotationKeys).toEqual([rotationKey1.did(), rotationKey2.did()]) 48 expect(doc.alsoKnownAs).toEqual([handle]) 49 expect(doc.services).toEqual({ 50 atproto_pds: { 51 type: 'AtprotoPersonalDataServer', 52 endpoint: atpPds, 53 }, 54 }) 55 } 56 57 it('registers a did', async () => { 58 did = await client.createDid({ 59 signingKey: signingKey.did(), 60 rotationKeys: [rotationKey1.did(), rotationKey2.did()], 61 handle, 62 pds: atpPds, 63 signer: rotationKey1, 64 }) 65 }) 66 67 it('retrieves did doc data', async () => { 68 const doc = await client.getDocumentData(did) 69 verifyDoc(doc) 70 }) 71 72 it('can perform some updates', async () => { 73 const newRotationKey = await P256Keypair.create() 74 signingKey = await P256Keypair.create() 75 handle = 'at://ali.example2.com' 76 atpPds = 'https://example2.com' 77 78 await client.updateAtprotoKey(did, rotationKey1, signingKey.did()) 79 await client.updateRotationKeys(did, rotationKey1, [ 80 newRotationKey.did(), 81 rotationKey2.did(), 82 ]) 83 rotationKey1 = newRotationKey 84 85 await client.updateHandle(did, rotationKey1, handle) 86 await client.updatePds(did, rotationKey1, atpPds) 87 88 const doc = await client.getDocumentData(did) 89 verifyDoc(doc) 90 }) 91 92 it('does not allow key types that we do not support', async () => { 93 // an ed25519 key which we don't yet support 94 const newSigningKey = 95 'did:key:z6MkjwbBXZnFqL8su24wGL2Fdjti6GSLv9SWdYGswfazUPm9' 96 97 const promise = client.updateAtprotoKey(did, rotationKey1, newSigningKey) 98 await expect(promise).rejects.toThrow(PlcClientError) 99 100 const promise2 = client.updateRotationKeys(did, rotationKey1, [ 101 newSigningKey, 102 ]) 103 await expect(promise2).rejects.toThrow(PlcClientError) 104 }) 105 106 it('retrieves the operation log', async () => { 107 const doc = await client.getDocumentData(did) 108 const ops = await client.getOperationLog(did) 109 const computedDoc = await plc.validateOperationLog(did, ops) 110 expect(computedDoc).toEqual(doc) 111 }) 112 113 it('rejects on bad updates', async () => { 114 const newKey = await P256Keypair.create() 115 const operation = client.updateAtprotoKey(did, newKey, newKey.did()) 116 await expect(operation).rejects.toThrow() 117 }) 118 119 it('allows for recovery through a forked history', async () => { 120 const attackerKey = await P256Keypair.create() 121 await client.updateRotationKeys(did, rotationKey2, [attackerKey.did()]) 122 123 const newKey = await P256Keypair.create() 124 const ops = await client.getOperationLog(did) 125 const forkPoint = ops.at(-2) 126 if (!check.is(forkPoint, plc.def.operation)) { 127 throw new Error('Could not find fork point') 128 } 129 const op = await plc.updateRotationKeysOp(forkPoint, rotationKey1, [ 130 rotationKey1.did(), 131 newKey.did(), 132 ]) 133 await client.sendOperation(did, op) 134 135 rotationKey2 = newKey 136 137 const doc = await client.getDocumentData(did) 138 verifyDoc(doc) 139 }) 140 141 it('retrieves the auditable operation log', async () => { 142 const log = await client.getOperationLog(did) 143 const auditable = await client.getAuditableLog(did) 144 // has one nullifed op 145 expect(auditable.length).toBe(log.length + 1) 146 expect(auditable.filter((op) => op.nullified).length).toBe(1) 147 expect(auditable.at(-2)?.nullified).toBe(true) 148 expect( 149 auditable.every((op) => check.is(op, plc.def.exportedOp)), 150 ).toBeTruthy() 151 }) 152 153 it('retrieves the did doc', async () => { 154 const data = await client.getDocumentData(did) 155 const doc = await client.getDocument(did) 156 expect(doc).toEqual(plc.formatDidDoc(data)) 157 }) 158 159 it('handles concurrent requests to many docs', async () => { 160 const COUNT = 20 161 const keys: P256Keypair[] = [] 162 for (let i = 0; i < COUNT; i++) { 163 keys.push(await P256Keypair.create()) 164 } 165 await Promise.all( 166 keys.map(async (key, index) => { 167 await client.createDid({ 168 signingKey: key.did(), 169 rotationKeys: [key.did()], 170 handle: `user${index}`, 171 pds: `example.com`, 172 signer: key, 173 }) 174 }), 175 ) 176 }) 177 178 it('resolves races into a coherent history with no forks', async () => { 179 const COUNT = 20 180 const keys: P256Keypair[] = [] 181 for (let i = 0; i < COUNT; i++) { 182 keys.push(await P256Keypair.create()) 183 } 184 // const prev = await client.getPrev(did) 185 186 let successes = 0 187 let failures = 0 188 await Promise.all( 189 keys.map(async (key) => { 190 try { 191 await client.updateAtprotoKey(did, rotationKey1, key.did()) 192 successes++ 193 } catch (err) { 194 failures++ 195 } 196 }), 197 ) 198 expect(successes).toBe(1) 199 expect(failures).toBe(19) 200 201 const ops = await client.getOperationLog(did) 202 await plc.validateOperationLog(did, ops) 203 }) 204 205 it('tombstones the did', async () => { 206 await client.tombstone(did, rotationKey1) 207 208 const promise = client.getDocument(did) 209 await expect(promise).rejects.toThrow(PlcClientError) 210 const promise2 = client.getDocumentData(did) 211 await expect(promise2).rejects.toThrow(PlcClientError) 212 }) 213 214 it('exports the data set', async () => { 215 const data = await client.export() 216 expect(data.every((row) => check.is(row, plc.def.exportedOp))).toBeTruthy() 217 expect(data.length).toBe(29) 218 for (let i = 1; i < data.length; i++) { 219 expect(data[i].createdAt >= data[i - 1].createdAt).toBeTruthy() 220 } 221 }) 222 223 it('disallows create v1s', async () => { 224 const createV1 = await plc.deprecatedSignCreate( 225 { 226 type: 'create', 227 signingKey: signingKey.did(), 228 recoveryKey: rotationKey1.did(), 229 handle, 230 service: atpPds, 231 prev: null, 232 }, 233 signingKey, 234 ) 235 const did = await didForCreateOp(createV1) 236 const attempt = client.sendOperation(did, createV1 as any) 237 await expect(attempt).rejects.toThrow() 238 }) 239 240 it('healthcheck succeeds when database is available.', async () => { 241 const res = await client.health() 242 expect(res).toEqual({ version: '0.0.0' }) 243 }) 244 245 it('healthcheck fails when database is unavailable.', async () => { 246 await db.db.destroy() 247 let error: PlcClientError 248 try { 249 await client.health() 250 throw new Error('Healthcheck should have failed') 251 } catch (err) { 252 if (err instanceof PlcClientError) { 253 error = err 254 } else { 255 throw err 256 } 257 } 258 expect(error.status).toEqual(503) 259 expect(error.data).toEqual({ 260 version: '0.0.0', 261 error: 'Service Unavailable', 262 }) 263 }) 264})