Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Commit

Permalink
Update web5/dids, web5/credentials, web5/crypto, web5/common to latest (
Browse files Browse the repository at this point in the history
#177)

* Update http-client

* Update http-server

* Update protocol

* update web5/common, web5/credentials, web5/crypto, web5/dids to latest

* remove ion, replace key with jwk, remove commented lines, refactor tests, address pr comments

* update tbdex

* cleanup console log

* update changeset
  • Loading branch information
kirahsapong authored Feb 16, 2024
1 parent 589edc3 commit eba04b8
Show file tree
Hide file tree
Showing 23 changed files with 304 additions and 660 deletions.
11 changes: 11 additions & 0 deletions .changeset/breezy-islands-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@tbdex/http-client": minor
"@tbdex/http-server": minor
"@tbdex/protocol": minor
---

Upgrade packages web5/[email protected], web5/[email protected], web5/[email protected], web5/[email protected]

* Deprecate did:ion and did:key in favour of did:jwk and did:dht
* Migrate from `PortableDid` to `BearerDid` with the latest @web5/dids upgrade
* Replaces dependency on `Web5Crypto` with `BearerDid` signer abstraction for signing operations
8 changes: 4 additions & 4 deletions packages/http-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@
},
"dependencies": {
"@tbdex/protocol": "workspace:*",
"@web5/common": "0.2.2",
"@web5/credentials": "0.4.1",
"@web5/crypto": "0.2.4",
"@web5/dids": "0.2.4",
"@web5/common": "0.2.3",
"@web5/credentials": "0.4.2",
"@web5/crypto": "0.4.0",
"@web5/dids": "0.4.0",
"ms": "2.1.3",
"query-string": "8.1.0",
"typeid-js": "0.3.0"
Expand Down
15 changes: 8 additions & 7 deletions packages/http-client/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { JwtPayload } from '@web5/crypto'
import type { ErrorDetail } from './types.js'
import type { DidDocument, PortableDid } from '@web5/dids'
import type { DidDocument, BearerDid } from '@web5/dids'
import {
MessageModel,
Parser,
Expand Down Expand Up @@ -30,7 +30,7 @@ import ms from 'ms'
* @beta
*/
export type GenerateRequestTokenParams = {
requesterDid: PortableDid
requesterDid: BearerDid
pfiDid: string
}

Expand Down Expand Up @@ -251,19 +251,20 @@ export class TbdexHttpClient {
* @throws {@link RequestTokenSigningError} If an error occurs during the token generation.
*/
static async generateRequestToken(params: GenerateRequestTokenParams): Promise<string> {
const { pfiDid, requesterDid } = params
const now = Date.now()
const exp = (now + ms('1m'))

const jwtPayload: JwtPayload = {
aud : params.pfiDid,
iss : params.requesterDid.did,
aud : pfiDid,
iss : requesterDid.uri,
exp : Math.floor(exp / 1000),
iat : Math.floor(now / 1000),
jti : typeid().getSuffix()
}

try {
return await Jwt.sign({ signerDid: params.requesterDid, payload: jwtPayload })
return await Jwt.sign({ signerDid: requesterDid, payload: jwtPayload })
} catch(e) {
throw new RequestTokenSigningError({ message: e.message, cause: e })
}
Expand Down Expand Up @@ -351,7 +352,7 @@ export type GetExchangeOptions = {
/** the exchange you want to fetch */
exchangeId: string
/** the message author's DID */
did: PortableDid
did: BearerDid
}

/**
Expand All @@ -361,7 +362,7 @@ export type GetExchangeOptions = {
export type GetExchangesOptions = {
/** the DID of the PFI from whom you want to get offerings */
pfiDid: string
did: PortableDid,
did: BearerDid,
filter?: {
id: string | string[]
}
Expand Down
112 changes: 54 additions & 58 deletions packages/http-client/tests/client.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai'
import { DidDhtMethod, DidKeyMethod, PortableDid } from '@web5/dids'
import { DidDht, DidJwk, BearerDid } from '@web5/dids'
import { TbdexHttpClient, requestTokenRequiredClaims } from '../src/client.js'
import {
RequestError,ResponseError,
Expand All @@ -12,19 +12,22 @@ import {
} from '../src/errors/index.js'
import { DevTools } from '@tbdex/protocol'
import * as sinon from 'sinon'
import { JwtHeaderParams, JwtPayload, PrivateKeyJwk, Secp256k1 } from '@web5/crypto'
import { JwtHeaderParams, JwtPayload } from '@web5/crypto'
import { Convert } from '@web5/common'
import { Jwt } from '@web5/credentials'

const dhtDid = await DidDhtMethod.create({
publish : true,
services : [{
type : 'PFI',
id : 'pfi',
serviceEndpoint : 'https://localhost:9000'
}]
const pfiDid: BearerDid = await DidDht.create({
options: {
services: [{
type : 'PFI',
id : 'pfi',
serviceEndpoint : 'https://localhost:9000'
}]
}
})

const aliceDid: BearerDid = await DidJwk.create()

// TODO : Instead of stubbing fetch, consider using libraries like msw
const fetchStub = sinon.stub(globalThis, 'fetch')
const getPfiServiceEndpointStub = sinon.stub(TbdexHttpClient, 'getPfiServiceEndpoint')
Expand All @@ -33,13 +36,6 @@ describe('client', () => {
beforeEach(() => getPfiServiceEndpointStub.resolves('https://localhost:9000'))

describe('sendMessage', () => {
let aliceDid: PortableDid
let pfiDid: PortableDid

beforeEach(async () => {
aliceDid = await DevTools.createDid()
pfiDid = await DevTools.createDid()
})

it('throws RequestError if service endpoint url is garbage', async () => {
getPfiServiceEndpointStub.resolves('garbage')
Expand Down Expand Up @@ -80,7 +76,7 @@ describe('client', () => {
expect(e).to.be.instanceof(ResponseError)
expect(e.statusCode).to.exist
expect(e.details).to.exist
expect(e.recipientDid).to.equal(pfiDid.did)
expect(e.recipientDid).to.equal(pfiDid.uri)
expect(e.url).to.equal(`https://localhost:9000/exchanges/${rfq.metadata.exchangeId}/rfq`)
}
})
Expand Down Expand Up @@ -124,7 +120,7 @@ describe('client', () => {
fetchStub.rejects({message: 'Failed to fetch on URL'})

try {
await TbdexHttpClient.getOfferings({ pfiDid: dhtDid.did })
await TbdexHttpClient.getOfferings({ pfiDid: pfiDid.uri })
expect.fail()
} catch(e) {
expect(e.name).to.equal('RequestError')
Expand All @@ -145,14 +141,14 @@ describe('client', () => {
} as Response)

try {
await TbdexHttpClient.getOfferings({ pfiDid: dhtDid.did })
await TbdexHttpClient.getOfferings({ pfiDid: pfiDid.uri })
expect.fail()
} catch(e) {
expect(e.name).to.equal('ResponseError')
expect(e).to.be.instanceof(ResponseError)
expect(e.statusCode).to.exist
expect(e.details).to.exist
expect(e.recipientDid).to.equal(dhtDid.did)
expect(e.recipientDid).to.equal(pfiDid.uri)
expect(e.url).to.equal('https://localhost:9000/offerings')
}
})
Expand All @@ -163,7 +159,7 @@ describe('client', () => {
json : () => Promise.resolve({ data: [] })
} as Response)

const offerings = await TbdexHttpClient.getOfferings({ pfiDid: dhtDid.did })
const offerings = await TbdexHttpClient.getOfferings({ pfiDid: pfiDid.uri })
expect(offerings).to.have.length(0)
})
})
Expand All @@ -174,7 +170,7 @@ describe('client', () => {
fetchStub.rejects({message: 'Failed to fetch on URL'})

try {
await TbdexHttpClient.getExchange({ pfiDid: dhtDid.did, exchangeId: '123', did: dhtDid })
await TbdexHttpClient.getExchange({ pfiDid: pfiDid.uri, exchangeId: '123', did: pfiDid })
expect.fail()
} catch(e) {
expect(e.name).to.equal('RequestError')
Expand All @@ -195,14 +191,14 @@ describe('client', () => {
} as Response)

try {
await TbdexHttpClient.getExchange({ pfiDid: dhtDid.did, exchangeId: '123', did: dhtDid })
await TbdexHttpClient.getExchange({ pfiDid: pfiDid.uri, exchangeId: '123', did: pfiDid })
expect.fail()
} catch(e) {
expect(e.name).to.equal('ResponseError')
expect(e).to.be.instanceof(ResponseError)
expect(e.statusCode).to.exist
expect(e.details).to.exist
expect(e.recipientDid).to.equal(dhtDid.did)
expect(e.recipientDid).to.equal(pfiDid.uri)
expect(e.url).to.equal('https://localhost:9000/exchanges/123')
}
})
Expand All @@ -213,7 +209,7 @@ describe('client', () => {
json : () => Promise.resolve({ data: [] })
} as Response)

const exchanges = await TbdexHttpClient.getExchange({ pfiDid: dhtDid.did, exchangeId: '123', did: dhtDid })
const exchanges = await TbdexHttpClient.getExchange({ pfiDid: pfiDid.uri, exchangeId: '123', did: pfiDid })
expect(exchanges).to.have.length(0)
})
})
Expand All @@ -224,7 +220,7 @@ describe('client', () => {
fetchStub.rejects({message: 'Failed to fetch on URL'})

try {
await TbdexHttpClient.getExchanges({ pfiDid: dhtDid.did, did: dhtDid })
await TbdexHttpClient.getExchanges({ pfiDid: pfiDid.uri, did: pfiDid })
expect.fail()
} catch(e) {
expect(e.name).to.equal('RequestError')
Expand All @@ -245,14 +241,14 @@ describe('client', () => {
} as Response)

try {
await TbdexHttpClient.getExchanges({ pfiDid: dhtDid.did, did: dhtDid })
await TbdexHttpClient.getExchanges({ pfiDid: pfiDid.uri, did: pfiDid })
expect.fail()
} catch(e) {
expect(e.name).to.equal('ResponseError')
expect(e).to.be.instanceof(ResponseError)
expect(e.statusCode).to.exist
expect(e.details).to.exist
expect(e.recipientDid).to.equal(dhtDid.did)
expect(e.recipientDid).to.equal(pfiDid.uri)
expect(e.url).to.equal('https://localhost:9000/exchanges')
}
})
Expand All @@ -263,7 +259,7 @@ describe('client', () => {
json : () => Promise.resolve({ data: [] })
} as Response)

const exchanges = await TbdexHttpClient.getExchanges({ pfiDid: dhtDid.did, did: dhtDid })
const exchanges = await TbdexHttpClient.getExchanges({ pfiDid: pfiDid.uri, did: pfiDid })
expect(exchanges).to.have.length(0)
})
})
Expand All @@ -285,10 +281,8 @@ describe('client', () => {
}
})
it('throws MissingServiceEndpointError if did has no PFI service endpoint', async () => {
const keyDid = await DidKeyMethod.create()

try {
await TbdexHttpClient.getPfiServiceEndpoint(keyDid.did)
await TbdexHttpClient.getPfiServiceEndpoint(aliceDid.uri)
expect.fail()
} catch(e) {
expect(e.name).to.equal('MissingServiceEndpointError')
Expand All @@ -297,73 +291,75 @@ describe('client', () => {
}
})
it('returns pfi service endpoint if all is well', async () => {
const serviceEndpoint = await TbdexHttpClient.getPfiServiceEndpoint(dhtDid.did)
const serviceEndpoint = await TbdexHttpClient.getPfiServiceEndpoint(pfiDid.uri)
expect(serviceEndpoint).to.equal('https://localhost:9000')
})
})

describe('generateRequestToken', () => {
let requesterPortableDid: PortableDid
let requesterBearerDid: BearerDid
before(async () => {
requesterPortableDid = await DidKeyMethod.create({ keyAlgorithm: 'secp256k1' })
})
it('throws a RequestTokenSigningError if requesterDid is not a valid PortableDid', async () => {
try {
await TbdexHttpClient.generateRequestToken({ requesterDid: {did: '', document: { id: '' }, keySet: {}}, pfiDid: '' })
expect.fail()
} catch (e) {
expect(e).to.be.instanceOf(RequestTokenSigningError)
}
requesterBearerDid = await DidJwk.create()
})
it('includes all expected claims', async () => {
const requestToken = await TbdexHttpClient.generateRequestToken({ requesterDid: requesterPortableDid, pfiDid: 'did:key:1234' })
const requestToken = await TbdexHttpClient.generateRequestToken({ requesterDid: requesterBearerDid, pfiDid: 'did:key:1234' })
const decodedToken = await Jwt.verify({ jwt: requestToken })
expect(decodedToken.payload).to.have.all.keys(requestTokenRequiredClaims)
})
// TODO: decide if we want to ensure that the expiration date is not longer than 1 minute after the issuance date
it('sets expiration seconds to 1 minute after the time at which it was issued', async () => {
const requestToken = await TbdexHttpClient.generateRequestToken({ requesterDid: requesterPortableDid, pfiDid: 'did:key:1234' })
const requestToken = await TbdexHttpClient.generateRequestToken({ requesterDid: requesterBearerDid, pfiDid: 'did:key:1234' })
const decodedToken = await Jwt.verify({ jwt: requestToken })
expect(decodedToken.payload.exp! - decodedToken.payload.iat!).to.equal(60)
})
})

describe('verifyRequestToken', () => {
let pfiPortableDid: PortableDid
let header: JwtHeaderParams
let payload: JwtPayload

/*
** helper function to help alice generate a valid request token to send to a pfi
*/
async function createRequestTokenFromPayload(payload: JwtPayload) {
const privateKeyJwk = pfiPortableDid.keySet.verificationMethodKeys![0].privateKeyJwk
const signer = await pfiDid.getSigner()
header = { typ: 'JWT', alg: signer.algorithm, kid: signer.keyId }
const base64UrlEncodedHeader = Convert.object(header).toBase64Url()
const base64UrlEncodedPayload = Convert.object(payload).toBase64Url()

const toSign = `${base64UrlEncodedHeader}.${base64UrlEncodedPayload}`
const toSignBytes = Convert.string(toSign).toUint8Array()
const signatureBytes = await Secp256k1.sign({ key: privateKeyJwk as PrivateKeyJwk, data: toSignBytes })
const signatureBytes = await signer.sign({ data: toSignBytes })
const base64UrlEncodedSignature = Convert.uint8Array(signatureBytes).toBase64Url()

return `${toSign}.${base64UrlEncodedSignature}`
}

before(async () => {
pfiPortableDid = await DidKeyMethod.create({ keyAlgorithm: 'secp256k1' })
header = { typ: 'JWT', alg: 'ES256K', kid: pfiPortableDid.document.verificationMethod![0].id }
})

beforeEach(() => {
payload = {
iat : Math.floor(Date.now() / 1000),
aud : pfiPortableDid.did,
aud : pfiDid.uri,
iss : 'did:key:1234',
exp : Math.floor(Date.now() / 1000 + 60),
jti : 'randomnonce'
}
})

it('throws a RequestTokenSigningError if token cannot be signed', async () => {
const jwtSigner = sinon.stub(Jwt, 'sign')
jwtSigner.throws()
try {
await TbdexHttpClient.generateRequestToken({ requesterDid: aliceDid, pfiDid: ''})
expect.fail()
} catch (e) {
expect(e).to.be.instanceOf(RequestTokenSigningError)
}
jwtSigner.restore()
})

it('throws RequestTokenVerificationError if request token is not a valid jwt', async () => {
try {
await TbdexHttpClient.verifyRequestToken({ requestToken: '', pfiDid: pfiPortableDid.did })
await TbdexHttpClient.verifyRequestToken({ requestToken: '', pfiDid: pfiDid.uri })
expect.fail()
} catch(e) {
expect(e).to.be.instanceof(RequestTokenVerificationError)
Expand All @@ -375,7 +371,7 @@ describe('client', () => {
try {
delete payload[claim]
const requestToken = await createRequestTokenFromPayload(payload)
await TbdexHttpClient.verifyRequestToken({ requestToken, pfiDid: pfiPortableDid.did })
await TbdexHttpClient.verifyRequestToken({ requestToken, pfiDid: pfiDid.uri })
expect.fail()
} catch(e) {
expect(e).to.be.instanceof(RequestTokenMissingClaimsError)
Expand All @@ -388,7 +384,7 @@ describe('client', () => {
try {
payload.aud = 'squirtle'
const requestToken = await createRequestTokenFromPayload(payload)
await TbdexHttpClient.verifyRequestToken({ requestToken, pfiDid: pfiPortableDid.did })
await TbdexHttpClient.verifyRequestToken({ requestToken, pfiDid: pfiDid.uri })
expect.fail()
} catch(e) {
expect(e).to.be.instanceof(RequestTokenAudienceMismatchError)
Expand All @@ -397,7 +393,7 @@ describe('client', () => {
})
it('returns requester\'s DID if request token is valid', async () => {
const requestToken = await createRequestTokenFromPayload(payload)
const iss = await TbdexHttpClient.verifyRequestToken({ requestToken, pfiDid: pfiPortableDid.did })
const iss = await TbdexHttpClient.verifyRequestToken({ requestToken, pfiDid: pfiDid.uri })
expect(iss).to.equal('did:key:1234')
})
})
Expand Down
2 changes: 1 addition & 1 deletion packages/http-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"dependencies": {
"@tbdex/http-client": "workspace:*",
"@tbdex/protocol": "workspace:*",
"@web5/dids": "0.2.2",
"@web5/dids": "0.4.0",
"cors": "2.8.5",
"express": "4.18.2"
},
Expand Down
Loading

0 comments on commit eba04b8

Please sign in to comment.