Fork of github.com/did-method-plc/did-method-plc

server tests

dholms 7ed6378a f107f35c

Changed files
+187 -119
packages
lib
src
server
+29 -6
packages/lib/src/client.ts
···
import { cidForCbor } from '@atproto/common'
import { Keypair } from '@atproto/crypto'
import axios from 'axios'
-
import { signOperation } from './operations'
+
import { didForCreateOp, signOperation } from './operations'
import * as t from './types'
export class Client {
···
}
async getLastOp(did: string): Promise<t.Operation> {
-
const res = await axios.get(`${this.url}/log/${encodeURIComponent(did)}`)
+
const res = await axios.get(`${this.url}/last/${encodeURIComponent(did)}`)
return res.data
}
-
async applyPartialOp(did: string, delta: Partial<t.Operation>, key: Keypair) {
+
async applyPartialOp(
+
did: string,
+
delta: Partial<t.UnsignedOperation>,
+
key: Keypair,
+
) {
const lastOp = await this.getLastOp(did)
const prev = await cidForCbor(lastOp)
+
const { signingKey, rotationKeys, handles, services } = lastOp
const op = await signOperation(
{
-
...lastOp,
-
...delta,
+
signingKey,
+
rotationKeys,
+
handles,
+
services,
prev: prev.toString(),
+
...delta,
},
key,
)
-
console.log(op)
await this.sendOperation(did, op)
+
}
+
+
async create(
+
op: Omit<t.UnsignedOperation, 'prev'>,
+
key: Keypair,
+
): Promise<string> {
+
const createOp = await signOperation(
+
{
+
...op,
+
prev: null,
+
},
+
key,
+
)
+
const did = await didForCreateOp(createOp)
+
await this.sendOperation(did, createOp)
+
return did
}
async sendOperation(did: string, op: t.Operation) {
+13 -2
packages/server/src/logger.ts
···
import pino from 'pino'
import pinoHttp from 'pino-http'
-
// @TODO fix this up
-
export const logger = pino()
+
const enabledEnv = process.env.LOG_ENABLED
+
const enabled =
+
enabledEnv === 'true' || enabledEnv === 't' || enabledEnv === '1'
+
const level = process.env.LOG_LEVEL || 'info'
+
+
const config = {
+
enabled,
+
level,
+
}
+
+
const logger = process.env.LOG_DESTINATION
+
? pino(config, pino.destination(process.env.LOG_DESTINATION))
+
: pino(config)
export const loggerMiddleware = pinoHttp({
logger,
+3 -3
packages/server/src/routes.ts
···
throw new ServerError(404, `DID not registered: ${did}`)
}
const data = await plc.validateOperationLog(did, log)
-
res.send(data)
+
res.json(data)
})
// Get operation log for a DID
···
if (log.length === 0) {
throw new ServerError(404, `DID not registered: ${did}`)
}
-
res.send({ log })
+
res.json({ log })
})
// Get operation log for a DID
···
if (!curr) {
throw new ServerError(404, `DID not registered: ${did}`)
}
-
res.send(curr)
+
res.json(curr)
})
// Update or create a DID doc
+142 -108
packages/server/tests/server.test.ts
···
import { cidForCbor } from '@atproto/common'
import { AxiosError } from 'axios'
import { Database } from '../src'
-
import { didForCreateOp } from '@did-plc/lib'
+
import { signOperation } from '@did-plc/lib'
describe('PLC server', () => {
let handle = 'alice.example.com'
···
})
it('registers a did', async () => {
-
const op = await plc.signOperation(
+
did = await client.create(
{
signingKey: signingKey.did(),
rotationKeys: [rotationKey1.did(), rotationKey2.did()],
···
services: {
atpPds,
},
-
prev: null,
},
rotationKey1,
)
-
did = await didForCreateOp(op)
-
await client.sendOperation(did, op)
})
it('retrieves did doc data', async () => {
···
rotationKey1,
)
-
return
-
await client.applyPartialOp(
did,
{ rotationKeys: [newRotationKey.did(), rotationKey2.did()] },
-
newRotationKey,
+
rotationKey1,
)
rotationKey1 = newRotationKey
···
expect(doc.services).toEqual({ atpPds })
})
-
// it('does not allow key types that we do not support', async () => {
-
// // an ed25519 key which we don't yet support
-
// const newSigningKey =
-
// 'did:key:z6MkjwbBXZnFqL8su24wGL2Fdjti6GSLv9SWdYGswfazUPm9'
+
it('does not allow key types that we do not support', async () => {
+
// an ed25519 key which we don't yet support
+
const newSigningKey =
+
'did:key:z6MkjwbBXZnFqL8su24wGL2Fdjti6GSLv9SWdYGswfazUPm9'
-
// const promise = client.rotateSigningKey(did, newSigningKey, signingKey)
-
// await expect(promise).rejects.toThrow(AxiosError)
-
// })
+
const promise = client.applyPartialOp(
+
did,
+
{ signingKey: newSigningKey },
+
rotationKey1,
+
)
+
await expect(promise).rejects.toThrow(AxiosError)
+
})
-
// it('retrieves the operation log', async () => {
-
// const doc = await client.getDocumentData(did)
-
// const ops = await client.getOperationLog(did)
-
// const computedDoc = await document.validateOperationLog(did, ops)
-
// expect(computedDoc).toEqual(doc)
-
// })
+
it('retrieves the operation log', async () => {
+
const doc = await client.getDocumentData(did)
+
const ops = await client.getOperationLog(did)
+
const computedDoc = await plc.validateOperationLog(did, ops)
+
expect(computedDoc).toEqual(doc)
+
})
-
// it('rejects on bad updates', async () => {
-
// const newKey = await EcdsaKeypair.create()
-
// const operation = client.rotateRecoveryKey(did, newKey.did(), newKey)
-
// await expect(operation).rejects.toThrow()
-
// })
+
it('rejects on bad updates', async () => {
+
const newKey = await EcdsaKeypair.create()
+
const operation = client.applyPartialOp(
+
did,
+
{ signingKey: newKey.did() },
+
newKey,
+
)
+
await expect(operation).rejects.toThrow()
+
})
-
// it('allows for recovery through a forked history', async () => {
-
// const attackerKey = await EcdsaKeypair.create()
-
// await client.rotateSigningKey(did, attackerKey.did(), signingKey)
-
// await client.rotateRecoveryKey(did, attackerKey.did(), attackerKey)
+
it('allows for recovery through a forked history', async () => {
+
const attackerKey = await EcdsaKeypair.create()
+
await client.applyPartialOp(
+
did,
+
{ signingKey: attackerKey.did(), rotationKeys: [attackerKey.did()] },
+
rotationKey2,
+
)
-
// const newKey = await EcdsaKeypair.create()
-
// const ops = await client.getOperationLog(did)
-
// const forkPoint = ops[ops.length - 3]
-
// const forkCid = await cidForCbor(forkPoint)
-
// await client.rotateSigningKey(did, newKey.did(), recoveryKey, forkCid)
-
// signingKey = newKey
+
const newKey = await EcdsaKeypair.create()
+
const ops = await client.getOperationLog(did)
+
const forkPoint = ops.at(-2)
+
if (!forkPoint) {
+
throw new Error('Could not find fork point')
+
}
+
const forkCid = await cidForCbor(forkPoint)
+
const op = await signOperation(
+
{
+
signingKey: signingKey.did(),
+
rotationKeys: [newKey.did()],
+
handles: forkPoint.handles,
+
services: forkPoint.services,
+
prev: forkCid.toString(),
+
},
+
rotationKey1,
+
)
+
await client.sendOperation(did, op)
-
// const doc = await client.getDocumentData(did)
-
// expect(doc.did).toEqual(did)
-
// expect(doc.signingKey).toEqual(signingKey.did())
-
// expect(doc.recoveryKey).toEqual(recoveryKey.did())
-
// expect(doc.handle).toEqual(handle)
-
// expect(doc.atpPds).toEqual(atpPds)
-
// })
+
rotationKey1 = newKey
-
// it('retrieves the did doc', async () => {
-
// const data = await client.getDocumentData(did)
-
// const doc = await client.getDocument(did)
-
// expect(doc).toEqual(document.formatDidDoc(data))
-
// })
+
const doc = await client.getDocumentData(did)
+
expect(doc.did).toEqual(did)
+
expect(doc.signingKey).toEqual(signingKey.did())
+
expect(doc.rotationKeys).toEqual([newKey.did()])
+
expect(doc.handles).toEqual([handle])
+
expect(doc.services).toEqual({ atpPds })
+
})
-
// it('handles concurrent requests to many docs', async () => {
-
// const COUNT = 100
-
// const keys: EcdsaKeypair[] = []
-
// for (let i = 0; i < COUNT; i++) {
-
// keys.push(await EcdsaKeypair.create())
-
// }
-
// await Promise.all(
-
// keys.map(async (key, index) => {
-
// await client.createDid(key, key.did(), `user${index}`, `example.com`)
-
// }),
-
// )
-
// })
+
it('retrieves the did doc', async () => {
+
const data = await client.getDocumentData(did)
+
const doc = await client.getDocument(did)
+
expect(doc).toEqual(plc.formatDidDoc(data))
+
})
-
// it('resolves races into a coherent history with no forks', async () => {
-
// const COUNT = 100
-
// const keys: EcdsaKeypair[] = []
-
// for (let i = 0; i < COUNT; i++) {
-
// keys.push(await EcdsaKeypair.create())
-
// }
-
// const prev = await client.getPrev(did)
+
it('handles concurrent requests to many docs', async () => {
+
const COUNT = 100
+
const keys: EcdsaKeypair[] = []
+
for (let i = 0; i < COUNT; i++) {
+
keys.push(await EcdsaKeypair.create())
+
}
+
await Promise.all(
+
keys.map(async (key, index) => {
+
await client.create(
+
{
+
signingKey: key.did(),
+
rotationKeys: [key.did()],
+
handles: [`user${index}`],
+
services: {
+
atpPds: `example.com`,
+
},
+
},
+
key,
+
)
+
}),
+
)
+
})
+
+
it('resolves races into a coherent history with no forks', async () => {
+
const COUNT = 100
+
const keys: EcdsaKeypair[] = []
+
for (let i = 0; i < COUNT; i++) {
+
keys.push(await EcdsaKeypair.create())
+
}
+
// const prev = await client.getPrev(did)
-
// let successes = 0
-
// let failures = 0
-
// await Promise.all(
-
// keys.map(async (key) => {
-
// try {
-
// await client.rotateSigningKey(did, key.did(), signingKey, prev)
-
// successes++
-
// } catch (err) {
-
// failures++
-
// }
-
// }),
-
// )
-
// expect(successes).toBe(1)
-
// expect(failures).toBe(99)
+
let successes = 0
+
let failures = 0
+
await Promise.all(
+
keys.map(async (key) => {
+
try {
+
await client.applyPartialOp(
+
did,
+
{ signingKey: key.did() },
+
rotationKey1,
+
)
+
successes++
+
} catch (err) {
+
failures++
+
}
+
}),
+
)
+
expect(successes).toBe(1)
+
expect(failures).toBe(99)
-
// const ops = await client.getOperationLog(did)
-
// await document.validateOperationLog(did, ops)
-
// })
+
const ops = await client.getOperationLog(did)
+
await plc.validateOperationLog(did, ops)
+
})
-
// it('healthcheck succeeds when database is available.', async () => {
-
// const { data, status } = await client.health()
-
// expect(status).toEqual(200)
-
// expect(data).toEqual({ version: '0.0.0' })
-
// })
+
it('healthcheck succeeds when database is available.', async () => {
+
const { data, status } = await client.health()
+
expect(status).toEqual(200)
+
expect(data).toEqual({ version: '0.0.0' })
+
})
-
// it('healthcheck fails when database is unavailable.', async () => {
-
// await db.db.destroy()
-
// let error: AxiosError
-
// try {
-
// await client.health()
-
// throw new Error('Healthcheck should have failed')
-
// } catch (err) {
-
// if (err instanceof AxiosError) {
-
// error = err
-
// } else {
-
// throw err
-
// }
-
// }
-
// expect(error.response?.status).toEqual(503)
-
// expect(error.response?.data).toEqual({
-
// version: '0.0.0',
-
// error: 'Service Unavailable',
-
// })
-
// })
+
it('healthcheck fails when database is unavailable.', async () => {
+
await db.db.destroy()
+
let error: AxiosError
+
try {
+
await client.health()
+
throw new Error('Healthcheck should have failed')
+
} catch (err) {
+
if (err instanceof AxiosError) {
+
error = err
+
} else {
+
throw err
+
}
+
}
+
expect(error.response?.status).toEqual(503)
+
expect(error.response?.data).toEqual({
+
version: '0.0.0',
+
error: 'Service Unavailable',
+
})
+
})
})