From 01fc63669499f13c775823015333a0bb91d69537 Mon Sep 17 00:00:00 2001 From: Jiyoon Koo Date: Thu, 25 Jan 2024 19:30:19 -0500 Subject: [PATCH] JWT creation / verification (#150) * porting over changes related to jwt creation/verification in pr 122 * fixing rfq creation in client.spec.ts per protocol changes * using devtools in client.spec.ts * using devtools for tests * adding changeset * removing todos waiting on a merged PR * removing some stale / irrelevant todos * removing unused error from index import. changed DevTools.getOffering() to allow passing in of offering creator did * removing an error import * removing a test that is no longer needed * updating tbdex submodule commit pointer to latest in main Co-authored-by: Moe Jangda --- .changeset/soft-ravens-march.md | 6 + packages/http-client/src/client.ts | 147 +++++++++++++---- packages/http-client/src/errors/index.ts | 3 +- .../src/errors/request-token-error.ts | 68 ++++++++ packages/http-client/tests/client.spec.ts | 148 ++++++++++++++++-- packages/http-server/src/http-server.ts | 18 ++- .../src/request-handlers/get-exchanges.ts | 9 +- .../http-server/tests/get-exchanges.spec.ts | 5 +- tbdex | 2 +- 9 files changed, 344 insertions(+), 62 deletions(-) create mode 100644 .changeset/soft-ravens-march.md create mode 100644 packages/http-client/src/errors/request-token-error.ts diff --git a/.changeset/soft-ravens-march.md b/.changeset/soft-ravens-march.md new file mode 100644 index 00000000..1618c7ed --- /dev/null +++ b/.changeset/soft-ravens-march.md @@ -0,0 +1,6 @@ +--- +"@tbdex/http-client": minor +"@tbdex/http-server": minor +--- + +JWT creation and verification diff --git a/packages/http-client/src/client.ts b/packages/http-client/src/client.ts index 609609e6..9e3f8f05 100644 --- a/packages/http-client/src/client.ts +++ b/packages/http-client/src/client.ts @@ -1,3 +1,4 @@ +import type { JwtPayload } from '@web5/crypto' import type { ErrorDetail } from './types.js' import type { PortableDid } from '@web5/dids' import type { @@ -9,11 +10,47 @@ import type { MessageKindClass, } from '@tbdex/protocol' -import { resolveDid, Offering, Resource, Message, Crypto } from '@tbdex/protocol' +import { + RequestError, + ResponseError, + InvalidDidError, + MissingServiceEndpointError, + RequestTokenMissingClaimsError, + RequestTokenAudienceMismatchError, + RequestTokenSigningError, + RequestTokenVerificationError +} from './errors/index.js' +import { resolveDid, Offering, Resource, Message } from '@tbdex/protocol' import { utils as didUtils } from '@web5/dids' -import { Convert } from '@web5/common' -import { RequestError, ResponseError, InvalidDidError, MissingServiceEndpointError } from './errors/index.js' +import { typeid } from 'typeid-js' +import { Jwt } from '@web5/credentials' + import queryString from 'query-string' +import ms from 'ms' + +/** + * Parameters for generating a request token + * @beta + */ +export type GenerateRequestTokenParams = { + requesterDid: PortableDid + pfiDid: string +} + +/** + * Parameters for verifying a request token + * @beta + */ +export type VerifyRequestTokenParams = { + requestToken: string + pfiDid: string +} + +/** + * Required jwt claims expected in a request token + * @beta + */ +export const requestTokenRequiredClaims = ['aud', 'iss', 'exp', 'iat', 'jti'] /** * HTTP client for interacting with TBDex PFIs @@ -44,7 +81,7 @@ export class TbdexHttpClient { headers : { 'content-type': 'application/json' }, body : JSON.stringify(jsonMessage) }) - } catch(e) { + } catch (e) { throw new RequestError({ message: `Failed to send message to ${pfiDid}`, recipientDid: pfiDid, url: apiRoute, cause: e }) } @@ -110,16 +147,16 @@ export class TbdexHttpClient { * @beta */ static async getOfferings(opts: GetOfferingsOptions): Promise { - const { pfiDid , filter } = opts + const { pfiDid, filter } = opts const pfiServiceEndpoint = await TbdexHttpClient.getPfiServiceEndpoint(pfiDid) - const queryParams = filter ? `?${queryString.stringify(filter)}`: '' + const queryParams = filter ? `?${queryString.stringify(filter)}` : '' const apiRoute = `${pfiServiceEndpoint}/offerings${queryParams}` let response: Response try { response = await fetch(apiRoute) - } catch(e) { + } catch (e) { throw new RequestError({ message: `Failed to get offerings from ${pfiDid}`, recipientDid: pfiDid, url: apiRoute, cause: e }) } @@ -148,7 +185,7 @@ export class TbdexHttpClient { const pfiServiceEndpoint = await TbdexHttpClient.getPfiServiceEndpoint(pfiDid) const apiRoute = `${pfiServiceEndpoint}/exchanges/${exchangeId}` - const requestToken = await TbdexHttpClient.generateRequestToken(did) + const requestToken = await TbdexHttpClient.generateRequestToken({ requesterDid: did, pfiDid }) let response: Response try { @@ -157,7 +194,7 @@ export class TbdexHttpClient { authorization: `Bearer ${requestToken}` } }) - } catch(e) { + } catch (e) { throw new RequestError({ message: `Failed to get exchange from ${pfiDid}`, recipientDid: pfiDid, url: apiRoute, cause: e }) } @@ -186,9 +223,9 @@ export class TbdexHttpClient { const { pfiDid, filter, did } = opts const pfiServiceEndpoint = await TbdexHttpClient.getPfiServiceEndpoint(pfiDid) - const queryParams = filter ? `?${queryString.stringify(filter)}`: '' + const queryParams = filter ? `?${queryString.stringify(filter)}` : '' const apiRoute = `${pfiServiceEndpoint}/exchanges${queryParams}` - const requestToken = await TbdexHttpClient.generateRequestToken(did) + const requestToken = await TbdexHttpClient.generateRequestToken({ requesterDid: did, pfiDid }) let response: Response try { @@ -197,7 +234,7 @@ export class TbdexHttpClient { authorization: `Bearer ${requestToken}` } }) - } catch(e) { + } catch (e) { throw new RequestError({ message: `Failed to get exchanges from ${pfiDid}`, recipientDid: pfiDid, url: apiRoute, cause: e }) } @@ -230,7 +267,7 @@ export class TbdexHttpClient { static async getPfiServiceEndpoint(did: string) { try { const didDocument = await resolveDid(did) - const [ didService ] = didUtils.getServices({ didDocument, type: 'PFI' }) + const [didService] = didUtils.getServices({ didDocument, type: 'PFI' }) if (!didService?.serviceEndpoint) { throw new MissingServiceEndpointError(`${did} has no PFI service entry`) @@ -246,27 +283,75 @@ export class TbdexHttpClient { } /** - * generates a jws to be used to authenticate GET requests - * @param did - the requester's did - */ - static async generateRequestToken(did: PortableDid): Promise { - // TODO: include exp property. expires 1 minute from generation time - // TODO: include aud property. should be DID of receipient - // TODO: include nbf property. not before current time - // TODO: include iss property. should be requester's did - const payload = { timestamp: new Date().toISOString() } - const payloadBytes = Convert.object(payload).toUint8Array() - - return Crypto.sign({ did: did, payload: payloadBytes, detached: false }) + * Creates and signs a request token ([JWT](https://datatracker.ietf.org/doc/html/rfc7519)) + * that's included as the value of Authorization header for requests sent to a PFI API's + * endpoints that require authentication + * + * JWT payload with the following claims: + * * `aud` + * * `iss` + * * `exp` + * * `iat` + * * `jti`The JWT is then signed and returned. + * + * @returns the request token (JWT) + * @throws {RequestTokenError} If an error occurs during the token generation. + */ + static async generateRequestToken(params: GenerateRequestTokenParams): Promise { + const now = Date.now() + const exp = (now + ms('1m')) + + const jwtPayload: JwtPayload = { + aud : params.pfiDid, + iss : params.requesterDid.did, + exp : Math.floor(exp / 1000), + iat : Math.floor(now / 1000), + jti : typeid().getSuffix() + } + + try { + return await Jwt.sign({ signerDid: params.requesterDid, payload: jwtPayload }) + } catch(e) { + throw new RequestTokenSigningError({ message: e.message, cause: e }) + } } /** - * validates the bearer token and verifies the cryptographic signature - * @throws if the token is invalid - * @throws see {@link @tbdex/protocol#Crypto.verify} - */ - static async verify(requestToken: string): Promise { - return Crypto.verify({ signature: requestToken }) + * Validates and verifies the integrity of a request token ([JWT](https://datatracker.ietf.org/doc/html/rfc7519)) + * generated by {@link generateRequestToken}. Specifically: + * * verifies integrity of the JWT + * * ensures all required claims are present and valid. + * * ensures the token has not expired + * * ensures token audience matches the expected PFI DID. + * + * @returns the requester's DID as a string if the token is valid. + * @throws {RequestTokenError} If the token is invalid, expired, or has been tampered with + */ + static async verifyRequestToken(params: VerifyRequestTokenParams): Promise { + let result + + try { + result = await Jwt.verify({ jwt: params.requestToken }) + } catch(e) { + throw new RequestTokenVerificationError({ message: e.message, cause: e }) + } + + const { payload: requestTokenPayload } = result + + // check to ensure all expected claims are present + for (let claim of requestTokenRequiredClaims) { + if (!requestTokenPayload[claim]) { + throw new RequestTokenMissingClaimsError({ message: `Request token missing ${claim} claim. Expected ${requestTokenRequiredClaims}.` }) + } + } + + // TODO: decide if we want to ensure that the expiration date is not longer than 1 minute after the issuance date + + if (requestTokenPayload.aud !== params.pfiDid) { + throw new RequestTokenAudienceMismatchError({ message: 'Request token contains invalid audience. Expected aud property to be PFI DID.' }) + } + + return requestTokenPayload.iss } } diff --git a/packages/http-client/src/errors/index.ts b/packages/http-client/src/errors/index.ts index e66158ef..e3590c00 100644 --- a/packages/http-client/src/errors/index.ts +++ b/packages/http-client/src/errors/index.ts @@ -1,3 +1,4 @@ export { RequestError } from './request-error.js' export { ResponseError } from './response-error.js' -export { ValidationError, InvalidDidError, MissingServiceEndpointError } from './validation-error.js' \ No newline at end of file +export { ValidationError, InvalidDidError, MissingServiceEndpointError } from './validation-error.js' +export { RequestTokenError, RequestTokenSigningError, RequestTokenVerificationError, RequestTokenMissingClaimsError, RequestTokenAudienceMismatchError } from './request-token-error.js' \ No newline at end of file diff --git a/packages/http-client/src/errors/request-token-error.ts b/packages/http-client/src/errors/request-token-error.ts new file mode 100644 index 00000000..aa7df6c1 --- /dev/null +++ b/packages/http-client/src/errors/request-token-error.ts @@ -0,0 +1,68 @@ +// TODO: decide whether this should be a ValidationError + +export type RequestTokenErrorParams = { + message: string + cause?: unknown + } + +/** + * Error thrown for request token related things + * @beta + */ +export class RequestTokenError extends Error { + constructor(params: RequestTokenErrorParams) { + super(params.message, { cause: params.cause }) + + this.name = this.constructor.name + + Object.setPrototypeOf(this, RequestTokenError.prototype) + } +} + +/** + * Error thrown when a request token cannot be signed + * @beta + */ +export class RequestTokenSigningError extends RequestTokenError { + constructor(params: RequestTokenErrorParams) { + super(params) + + Object.setPrototypeOf(this, RequestTokenSigningError.prototype) + } +} + +/** + * Error thrown when a request token cannot be verified + * @beta + */ +export class RequestTokenVerificationError extends RequestTokenError { + constructor(params: RequestTokenErrorParams) { + super(params) + + Object.setPrototypeOf(this, RequestTokenVerificationError.prototype) + } +} + +/** + * Error thrown when a request token is missing required claims + * @beta + */ +export class RequestTokenMissingClaimsError extends RequestTokenError { + constructor(params: RequestTokenErrorParams) { + super(params) + + Object.setPrototypeOf(this, RequestTokenMissingClaimsError.prototype) + } +} + +/** + * Error thrown when a request token aud does not match the PFI did for which its intended + * @beta + */ +export class RequestTokenAudienceMismatchError extends RequestTokenError { + constructor(params: RequestTokenErrorParams) { + super(params) + + Object.setPrototypeOf(this, RequestTokenAudienceMismatchError.prototype) + } +} \ No newline at end of file diff --git a/packages/http-client/tests/client.spec.ts b/packages/http-client/tests/client.spec.ts index b72c536d..ad816a40 100644 --- a/packages/http-client/tests/client.spec.ts +++ b/packages/http-client/tests/client.spec.ts @@ -1,9 +1,20 @@ import { expect } from 'chai' -import { DidDhtMethod, DidKeyMethod } from '@web5/dids' -import { TbdexHttpClient } from '../src/client.js' -import { RequestError,ResponseError, InvalidDidError, MissingServiceEndpointError } from '../src/errors/index.js' -import { DevTools, Message, Rfq } from '@tbdex/protocol' +import { DidDhtMethod, DidKeyMethod, PortableDid } from '@web5/dids' +import { TbdexHttpClient, requestTokenRequiredClaims } from '../src/client.js' +import { + RequestError,ResponseError, + InvalidDidError, + MissingServiceEndpointError, + RequestTokenMissingClaimsError, + RequestTokenAudienceMismatchError, + RequestTokenVerificationError, + RequestTokenSigningError +} from '../src/errors/index.js' +import { DevTools, Message } from '@tbdex/protocol' import * as sinon from 'sinon' +import { JwtHeaderParams, JwtPayload, PrivateKeyJwk, Secp256k1 } from '@web5/crypto' +import { Convert } from '@web5/common' +import { Jwt } from '@web5/credentials' const dhtDid = await DidDhtMethod.create({ publish : true, @@ -20,11 +31,15 @@ sinon.stub(Message, 'verify').resolves('123') describe('client', () => { beforeEach(() => getPfiServiceEndpointStub.resolves('https://localhost:9000')) - describe('sendMessage', async () => { - let mockMessage: Rfq + describe('sendMessage', () => { - beforeEach(async () => { - mockMessage = await DevTools.createRfq({ sender: dhtDid, receiver: dhtDid }) + let mockRfq: Message<'rfq'> + beforeEach(async () => + { + mockRfq = await DevTools.createRfq({ + sender : await DevTools.createDid(), + receiver : dhtDid + }) }) it('throws RequestError if service endpoint url is garbage', async () => { @@ -32,7 +47,7 @@ describe('client', () => { fetchStub.rejects({message: 'Failed to fetch on URL'}) try { - await TbdexHttpClient.sendMessage({message: mockMessage}) + await TbdexHttpClient.sendMessage({message: mockRfq}) expect.fail() } catch(e) { expect(e.name).to.equal('RequestError') @@ -53,7 +68,7 @@ describe('client', () => { } as Response) try { - await TbdexHttpClient.sendMessage({message: mockMessage}) + await TbdexHttpClient.sendMessage({message: mockRfq}) expect.fail() } catch(e) { expect(e.name).to.equal('ResponseError') @@ -61,7 +76,7 @@ describe('client', () => { expect(e.statusCode).to.exist expect(e.details).to.exist expect(e.recipientDid).to.equal(dhtDid.did) - expect(e.url).to.equal(`https://localhost:9000/exchanges/${mockMessage.metadata.exchangeId}/rfq`) + expect(e.url).to.equal(`https://localhost:9000/exchanges/${mockRfq.metadata.exchangeId}/rfq`) } }) it('should not throw errors if all is well', async () => { @@ -71,14 +86,14 @@ describe('client', () => { } as Response) try { - await TbdexHttpClient.sendMessage({message: mockMessage}) + await TbdexHttpClient.sendMessage({message: mockRfq}) } catch (e) { expect.fail() } }) }) - describe('getOfferings', async () => { + describe('getOfferings', () => { it('throws RequestError if service endpoint url is garbage', async () => { getPfiServiceEndpointStub.resolves('garbage') fetchStub.rejects({message: 'Failed to fetch on URL'}) @@ -128,7 +143,7 @@ describe('client', () => { }) }) - describe('getExchange', async () => { + describe('getExchange', () => { it('throws RequestError if service endpoint url is garbage', async () => { getPfiServiceEndpointStub.resolves('garbage') fetchStub.rejects({message: 'Failed to fetch on URL'}) @@ -178,7 +193,7 @@ describe('client', () => { }) }) - describe('getExchanges', async () => { + describe('getExchanges', () => { it('throws RequestError if service endpoint url is garbage', async () => { getPfiServiceEndpointStub.resolves('garbage') fetchStub.rejects({message: 'Failed to fetch on URL'}) @@ -228,7 +243,7 @@ describe('client', () => { }) }) - describe('getPfiServiceEndpoint', async () => { + describe('getPfiServiceEndpoint', () => { before(() => { getPfiServiceEndpointStub.restore() fetchStub.restore() @@ -261,5 +276,104 @@ describe('client', () => { expect(serviceEndpoint).to.equal('https://localhost:9000') }) }) -}) + describe('generateRequestToken', () => { + let requesterPortableDid: PortableDid + 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) + } + }) + it('includes all expected claims', async () => { + const requestToken = await TbdexHttpClient.generateRequestToken({ requesterDid: requesterPortableDid, 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 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 + + async function createRequestTokenFromPayload(payload) { + const privateKeyJwk = pfiPortableDid.keySet.verificationMethodKeys![0].privateKeyJwk + 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 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, + iss : 'did:key:1234', + exp : Math.floor(Date.now() / 1000 + 60), + jti : 'randomnonce' + } + }) + + it('throws RequestTokenVerificationError if request token is not a valid jwt', async () => { + try { + await TbdexHttpClient.verifyRequestToken({ requestToken: '', pfiDid: pfiPortableDid.did }) + expect.fail() + } catch(e) { + expect(e).to.be.instanceof(RequestTokenVerificationError) + } + }) + it('throws RequestTokenMissingClaimsError if request token is missing any of the expected claims', async () => { + for (const claim of requestTokenRequiredClaims) { + const initialClaim = payload[claim] + try { + delete payload[claim] + const requestToken = await createRequestTokenFromPayload(payload) + await TbdexHttpClient.verifyRequestToken({ requestToken, pfiDid: pfiPortableDid.did }) + expect.fail() + } catch(e) { + expect(e).to.be.instanceof(RequestTokenMissingClaimsError) + expect(e.message).to.include(`Request token missing ${claim} claim.`) + } + payload[claim] = initialClaim + } + }) + it('throws RequestTokenAudienceMismatchError if aud claim does not match pfi did', async () => { + try { + payload.aud = 'squirtle' + const requestToken = await createRequestTokenFromPayload(payload) + await TbdexHttpClient.verifyRequestToken({ requestToken, pfiDid: pfiPortableDid.did }) + expect.fail() + } catch(e) { + expect(e).to.be.instanceof(RequestTokenAudienceMismatchError) + expect(e.message).to.include('Request token contains invalid audience') + } + }) + 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 }) + expect(iss).to.equal('did:key:1234') + }) + }) +}) diff --git a/packages/http-server/src/http-server.ts b/packages/http-server/src/http-server.ts index 3fd60cee..b7f0e126 100644 --- a/packages/http-server/src/http-server.ts +++ b/packages/http-server/src/http-server.ts @@ -40,12 +40,14 @@ type CallbackMap = { */ type NewHttpServerOptions = { offeringsApi?: OfferingsApi - exchangesApi?: ExchangesApi + exchangesApi?: ExchangesApi, + pfiDid?: string } const defaults: NewHttpServerOptions = { offeringsApi : fakeOfferingsApi, - exchangesApi : fakeExchangesApi + exchangesApi : fakeExchangesApi, + pfiDid : 'did:ex:pfi' } /** @@ -73,13 +75,19 @@ export class TbdexHttpServer { */ offeringsApi: OfferingsApi + /** + * PFI DID + */ + pfiDid: string + constructor(opts?: NewHttpServerOptions) { this.callbacks = {} opts = { ...defaults, ...opts } - const { offeringsApi, exchangesApi } = opts + const { offeringsApi, exchangesApi, pfiDid } = opts this.exchangesApi = exchangesApi this.offeringsApi = offeringsApi + this.pfiDid = pfiDid // initialize api here so that consumers can attach custom endpoints const api = express() @@ -115,7 +123,7 @@ export class TbdexHttpServer { * @param callback - to be called when the server is ready */ listen(port: number | string, callback?: () => void) { - const { offeringsApi, exchangesApi } = this + const { offeringsApi, exchangesApi, pfiDid } = this this.api.post('/exchanges/:exchangeId/rfq', submitRfq({ callback: this.callbacks['rfq'], offeringsApi, exchangesApi, @@ -130,7 +138,7 @@ export class TbdexHttpServer { })) this.api.get('/exchanges', getExchanges({ - callback: this.callbacks['exchanges'], exchangesApi + callback: this.callbacks['exchanges'], exchangesApi, pfiDid })) this.api.get('/offerings', getOfferings({ diff --git a/packages/http-server/src/request-handlers/get-exchanges.ts b/packages/http-server/src/request-handlers/get-exchanges.ts index 71f37f54..edc22088 100644 --- a/packages/http-server/src/request-handlers/get-exchanges.ts +++ b/packages/http-server/src/request-handlers/get-exchanges.ts @@ -4,11 +4,12 @@ import { TbdexHttpClient } from '@tbdex/http-client' type GetExchangesOpts = { callback: GetCallback<'exchanges'> - exchangesApi: ExchangesApi + exchangesApi: ExchangesApi, + pfiDid: string } export function getExchanges(opts: GetExchangesOpts): RequestHandler { - const { callback, exchangesApi } = opts + const { callback, exchangesApi, pfiDid } = opts return async function (request, response) { const authzHeader = request.headers['authorization'] if (!authzHeader) { @@ -21,9 +22,9 @@ export function getExchanges(opts: GetExchangesOpts): RequestHandler { return response.status(401).json({ errors: [{ detail: 'Malformed Authorization header. Expected: Bearer TOKEN_HERE' }] }) } - let requesterDid + let requesterDid: string try { - requesterDid = await TbdexHttpClient.verify(requestToken) + requesterDid = await TbdexHttpClient.verifyRequestToken({ requestToken: requestToken, pfiDid }) } catch(e) { return response.status(401).json({ errors: [{ detail: `Malformed Authorization header: ${e}` }] }) } diff --git a/packages/http-server/tests/get-exchanges.spec.ts b/packages/http-server/tests/get-exchanges.spec.ts index c0c7b0f7..fa2e1a6f 100644 --- a/packages/http-server/tests/get-exchanges.spec.ts +++ b/packages/http-server/tests/get-exchanges.spec.ts @@ -62,10 +62,9 @@ describe('GET /exchanges', () => { } } - const testApi = new TbdexHttpServer({ exchangesApi }) + const testApi = new TbdexHttpServer({ exchangesApi, pfiDid: 'did:ex:pfi' }) const server = testApi.listen(8001) - - const requestToken = await TbdexHttpClient.generateRequestToken(alice) + const requestToken = await TbdexHttpClient.generateRequestToken({ requesterDid: alice, pfiDid: 'did:ex:pfi' }) const resp = await fetch('http://localhost:8001/exchanges', { headers: { 'Authorization': `Bearer ${requestToken}` diff --git a/tbdex b/tbdex index c19cd468..e298dc21 160000 --- a/tbdex +++ b/tbdex @@ -1 +1 @@ -Subproject commit c19cd4680209e0d817a1f9299787d3ac0dcdc25a +Subproject commit e298dc21340964d26d336128bd7c55c4079f5824