From 4df802bb059097546f21178505aff35d89a36757 Mon Sep 17 00:00:00 2001 From: Brandon Wilson Date: Thu, 2 Feb 2023 08:35:39 -0600 Subject: [PATCH] fix(backend): support SPSP queries (#1032) * fix(backend): allow SPSP query to payment pointer * feat(backend): allow SPSP query to Open Payments connection Generalize SPSP routes. * chore(backend): add connection middleware Add incoming payment to ctx for both SPSP and connection query. * chore(backend): add SPSP middleware --- packages/backend/src/app.ts | 22 ++- .../connection/middleware.test.ts | 70 +++++++ .../open_payments/connection/middleware.ts | 23 +++ .../open_payments/connection/routes.test.ts | 50 ++--- .../src/open_payments/connection/routes.ts | 15 +- packages/backend/src/spsp/middleware.test.ts | 123 ++++++++++++ packages/backend/src/spsp/middleware.ts | 31 +++ packages/backend/src/spsp/routes.test.ts | 180 ++++++++---------- packages/backend/src/spsp/routes.ts | 18 +- packages/openapi/src/middleware.ts | 1 + 10 files changed, 362 insertions(+), 171 deletions(-) create mode 100644 packages/backend/src/open_payments/connection/middleware.test.ts create mode 100644 packages/backend/src/open_payments/connection/middleware.ts create mode 100644 packages/backend/src/spsp/middleware.test.ts create mode 100644 packages/backend/src/spsp/middleware.ts diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 84c06cd708..cd68070d08 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -20,9 +20,10 @@ import { loadSchemaSync } from '@graphql-tools/load' import { resolvers } from './graphql/resolvers' import { HttpTokenService } from './httpToken/service' -import { AssetService } from './asset/service' +import { AssetService, AssetOptions } from './asset/service' import { AccountingService } from './accounting/service' import { PeerService } from './peer/service' +import { connectionMiddleware } from './open_payments/connection/middleware' import { createPaymentPointerMiddleware } from './open_payments/payment_pointer/middleware' import { PaymentPointer } from './open_payments/payment_pointer/model' import { PaymentPointerService } from './open_payments/payment_pointer/service' @@ -33,6 +34,7 @@ import { RequestAction } from './open_payments/auth/middleware' import { RatesService } from './rates/service' +import { spspMiddleware } from './spsp/middleware' import { SPSPRoutes } from './spsp/routes' import { IncomingPaymentRoutes } from './open_payments/payment/incoming/routes' import { PaymentPointerKeyRoutes } from './open_payments/payment_pointer/key/routes' @@ -138,6 +140,11 @@ export type ReadContext = SubresourceContext export type CompleteContext = SubresourceContext export type ListContext = CollectionContext +export interface SPSPContext extends AppContext { + paymentTag: string + asset: AssetOptions +} + type ContextType = T extends ( ctx: infer Context // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -284,7 +291,6 @@ export class App { ctx.status = 200 }) - const spspRoutes = await this.container.use('spspRoutes') const paymentPointerKeyRoutes = await this.container.use( 'paymentPointerKeyRoutes' ) @@ -355,6 +361,8 @@ export class App { route = connectionRoutes.get router[method]( toRouterPath(path), + connectionMiddleware, + spspMiddleware, createValidatorMiddleware>( resourceServerSpec, { @@ -411,18 +419,12 @@ export class App { router.get( PAYMENT_POINTER_PATH, createPaymentPointerMiddleware(), + spspMiddleware, createValidatorMiddleware(resourceServerSpec, { path: '/', method: HttpMethod.GET }), - async (ctx: PaymentPointerContext): Promise => { - // Fall back to legacy protocols if client doesn't support Open Payments. - if (ctx.accepts('application/json')) await paymentPointerRoutes.get(ctx) - //else if (ctx.accepts('application/ilp-stream+json')) // TODO https://docs.openpayments.dev/accounts#payment-details - else if (ctx.accepts('application/spsp4+json')) - await spspRoutes.get(ctx) - else ctx.throw(406, 'no accepted Content-Type available') - } + paymentPointerRoutes.get ) koa.use(router.routes()) diff --git a/packages/backend/src/open_payments/connection/middleware.test.ts b/packages/backend/src/open_payments/connection/middleware.test.ts new file mode 100644 index 0000000000..1f305f47bd --- /dev/null +++ b/packages/backend/src/open_payments/connection/middleware.test.ts @@ -0,0 +1,70 @@ +import assert from 'assert' +import { v4 as uuid } from 'uuid' +import { connectionMiddleware, ConnectionContext } from './middleware' +import { Config } from '../../config/app' +import { IocContract } from '@adonisjs/fold' +import { initIocContainer } from '../../' +import { AppServices } from '../../app' +import { createTestApp, TestContainer } from '../../tests/app' +import { createAsset } from '../../tests/asset' +import { createContext } from '../../tests/context' +import { createIncomingPayment } from '../../tests/incomingPayment' +import { createPaymentPointer } from '../../tests/paymentPointer' +import { truncateTables } from '../../tests/tableManager' + +describe('Connection Middleware', (): void => { + let deps: IocContract + let appContainer: TestContainer + let ctx: ConnectionContext + let next: jest.MockedFunction<() => Promise> + + beforeAll(async (): Promise => { + deps = await initIocContainer(Config) + appContainer = await createTestApp(deps) + }) + + beforeEach((): void => { + ctx = createContext( + { + headers: { + Accept: 'application/json' + } + }, + {} + ) + ctx.container = deps + next = jest.fn() + }) + + afterEach(async (): Promise => { + await truncateTables(appContainer.knex) + }) + + afterAll(async (): Promise => { + await appContainer.shutdown() + }) + + test('returns 404 for unknown connection id', async (): Promise => { + ctx.params.id = uuid() + await expect(connectionMiddleware(ctx, next)).rejects.toMatchObject({ + status: 404, + message: 'Not Found' + }) + expect(next).not.toHaveBeenCalled() + }) + + test('sets the context incomingPayment and calls next', async (): Promise => { + const asset = await createAsset(deps) + const { id: paymentPointerId } = await createPaymentPointer(deps, { + assetId: asset.id + }) + const incomingPayment = await createIncomingPayment(deps, { + paymentPointerId + }) + assert.ok(incomingPayment.connectionId) + ctx.params.id = incomingPayment.connectionId + await expect(connectionMiddleware(ctx, next)).resolves.toBeUndefined() + expect(next).toHaveBeenCalled() + expect(ctx.incomingPayment).toEqual(incomingPayment) + }) +}) diff --git a/packages/backend/src/open_payments/connection/middleware.ts b/packages/backend/src/open_payments/connection/middleware.ts new file mode 100644 index 0000000000..d517f4da5d --- /dev/null +++ b/packages/backend/src/open_payments/connection/middleware.ts @@ -0,0 +1,23 @@ +import { AppContext } from '../../app' +import { IncomingPayment } from '../payment/incoming/model' + +export interface ConnectionContext extends AppContext { + incomingPayment: IncomingPayment +} + +export const connectionMiddleware = async ( + ctx: Omit & { + incomingPayment: Partial + }, + next: () => Promise +): Promise => { + const incomingPaymentService = await ctx.container.use( + 'incomingPaymentService' + ) + const incomingPayment = await incomingPaymentService.getByConnection( + ctx.params.id + ) + if (!incomingPayment) return ctx.throw(404) + ctx.incomingPayment = incomingPayment + await next() +} diff --git a/packages/backend/src/open_payments/connection/routes.test.ts b/packages/backend/src/open_payments/connection/routes.test.ts index 8d3126d938..ffe1b3ab04 100644 --- a/packages/backend/src/open_payments/connection/routes.test.ts +++ b/packages/backend/src/open_payments/connection/routes.test.ts @@ -1,13 +1,13 @@ import { IocContract } from '@adonisjs/fold' import { Knex } from 'knex' import jestOpenAPI from 'jest-openapi' -import { v4 as uuid } from 'uuid' -import { AppServices, ReadContext } from '../../app' +import { AppServices } from '../../app' import { Config, IAppConfig } from '../../config/app' import { createTestApp, TestContainer } from '../../tests/app' import { truncateTables } from '../../tests/tableManager' import { initIocContainer } from '../../' +import { ConnectionContext } from './middleware' import { ConnectionRoutes } from './routes' import { createAsset } from '../../tests/asset' import { createContext } from '../../tests/context' @@ -67,22 +67,6 @@ describe('Connection Routes', (): void => { }) describe('get', (): void => { - test('returns 404 for nonexistent connection id on incoming payment', async (): Promise => { - const ctx = createContext( - { - headers: { Accept: 'application/json' }, - url: `/connections/${incomingPayment.connectionId}` - }, - { - id: uuid() - } - ) - await expect(connectionRoutes.get(ctx)).rejects.toHaveProperty( - 'status', - 404 - ) - }) - test.each` state ${IncomingPaymentState.Completed} @@ -95,15 +79,11 @@ describe('Connection Routes', (): void => { expiresAt: state === IncomingPaymentState.Expired ? new Date() : undefined }) - const ctx = createContext( - { - headers: { Accept: 'application/json' }, - url: `/connections/${incomingPayment.connectionId}` - }, - { - id: incomingPayment.connectionId as string - } - ) + const ctx = createContext({ + headers: { Accept: 'application/json' }, + url: `/connections/${incomingPayment.connectionId}` + }) + ctx.incomingPayment = incomingPayment await expect(connectionRoutes.get(ctx)).rejects.toHaveProperty( 'status', 404 @@ -111,16 +91,12 @@ describe('Connection Routes', (): void => { } ) - test('returns 200 for correct connection id', async (): Promise => { - const ctx = createContext( - { - headers: { Accept: 'application/json' }, - url: `/connections/${incomingPayment.connectionId}` - }, - { - id: incomingPayment.connectionId as string - } - ) + test('returns 200 with connection', async (): Promise => { + const ctx = createContext({ + headers: { Accept: 'application/json' }, + url: `/connections/${incomingPayment.connectionId}` + }) + ctx.incomingPayment = incomingPayment await expect(connectionRoutes.get(ctx)).resolves.toBeUndefined() expect(ctx.response).toSatisfyApiSpec() diff --git a/packages/backend/src/open_payments/connection/routes.ts b/packages/backend/src/open_payments/connection/routes.ts index 1d7492c628..80f0f1cc80 100644 --- a/packages/backend/src/open_payments/connection/routes.ts +++ b/packages/backend/src/open_payments/connection/routes.ts @@ -1,6 +1,6 @@ import { Logger } from 'pino' -import { ReadContext } from '../../app' import { IncomingPaymentService } from '../payment/incoming/service' +import { ConnectionContext } from './middleware' import { ConnectionService } from './service' interface ServiceDependencies { @@ -10,7 +10,7 @@ interface ServiceDependencies { } export interface ConnectionRoutes { - get(ctx: ReadContext): Promise + get(ctx: ConnectionContext): Promise } export function createConnectionRoutes( @@ -21,20 +21,15 @@ export function createConnectionRoutes( }) const deps = { ...deps_, logger } return { - get: (ctx: ReadContext) => getConnection(deps, ctx) + get: (ctx: ConnectionContext) => getConnection(deps, ctx) } } async function getConnection( deps: ServiceDependencies, - ctx: ReadContext + ctx: ConnectionContext ): Promise { - const incomingPayment = await deps.incomingPaymentService.getByConnection( - ctx.params.id - ) - if (!incomingPayment) return ctx.throw(404) - - const connection = deps.connectionService.get(incomingPayment) + const connection = deps.connectionService.get(ctx.incomingPayment) if (!connection) return ctx.throw(404) ctx.body = connection.toJSON() } diff --git a/packages/backend/src/spsp/middleware.test.ts b/packages/backend/src/spsp/middleware.test.ts new file mode 100644 index 0000000000..db478d6c72 --- /dev/null +++ b/packages/backend/src/spsp/middleware.test.ts @@ -0,0 +1,123 @@ +import { + spspMiddleware, + SPSPConnectionContext, + SPSPPaymentPointerContext +} from './middleware' +import { setup } from '../open_payments/payment_pointer/model.test' +import { Config } from '../config/app' +import { IocContract } from '@adonisjs/fold' +import { initIocContainer } from '../' +import { AppServices } from '../app' +import { createTestApp, TestContainer } from '../tests/app' +import { createAsset } from '../tests/asset' +import { createContext } from '../tests/context' +import { createIncomingPayment } from '../tests/incomingPayment' +import { createPaymentPointer } from '../tests/paymentPointer' +import { truncateTables } from '../tests/tableManager' + +describe('SPSP Middleware', (): void => { + let deps: IocContract + let appContainer: TestContainer + let next: jest.MockedFunction<() => Promise> + + beforeAll(async (): Promise => { + deps = await initIocContainer(Config) + appContainer = await createTestApp(deps) + }) + + beforeEach((): void => { + next = jest.fn() + }) + + afterEach(async (): Promise => { + await truncateTables(appContainer.knex) + }) + + afterAll(async (): Promise => { + await appContainer.shutdown() + }) + + describe('Payment Pointer', (): void => { + let ctx: SPSPPaymentPointerContext + + beforeEach(async (): Promise => { + const asset = await createAsset(deps) + const paymentPointer = await createPaymentPointer(deps, { + assetId: asset.id + }) + ctx = setup({ + reqOpts: {}, + paymentPointer + }) + ctx.container = deps + }) + + test('calls next for non-SPSP request', async (): Promise => { + await expect(spspMiddleware(ctx, next)).resolves.toBeUndefined() + expect(next).toHaveBeenCalled() + }) + + test('calls SPSP route for SPSP query', async (): Promise => { + const spspRoutes = await ctx.container.use('spspRoutes') + const spspSpy = jest + .spyOn(spspRoutes, 'get') + .mockResolvedValueOnce(undefined) + + ctx.headers['accept'] = 'application/spsp4+json' + await expect(spspMiddleware(ctx, next)).resolves.toBeUndefined() + expect(spspSpy).toHaveBeenCalledTimes(1) + expect(next).not.toHaveBeenCalled() + expect(ctx.paymentTag).toEqual(ctx.paymentPointer.id) + expect(ctx.asset).toEqual({ + code: ctx.paymentPointer.asset.code, + scale: ctx.paymentPointer.asset.scale + }) + }) + }) + + describe('Incoming Payment Connection', (): void => { + let ctx: SPSPConnectionContext + + beforeEach(async (): Promise => { + ctx = createContext( + { + headers: { + Accept: 'application/json' + } + }, + {} + ) + const asset = await createAsset(deps) + const { id: paymentPointerId } = await createPaymentPointer(deps, { + assetId: asset.id + }) + const incomingPayment = await createIncomingPayment(deps, { + paymentPointerId + }) + ctx.incomingPayment = incomingPayment + ctx.container = deps + }) + + test('calls next for non-SPSP request', async (): Promise => { + await expect(spspMiddleware(ctx, next)).resolves.toBeUndefined() + expect(next).toHaveBeenCalled() + }) + + test('calls SPSP route for SPSP query', async (): Promise => { + const spspRoutes = await ctx.container.use('spspRoutes') + const spspSpy = jest + .spyOn(spspRoutes, 'get') + .mockResolvedValueOnce(undefined) + + ctx.headers['accept'] = 'application/spsp4+json' + await expect(spspMiddleware(ctx, next)).resolves.toBeUndefined() + expect(spspSpy).toHaveBeenCalledTimes(1) + expect(next).not.toHaveBeenCalled() + expect(ctx.paymentTag).toEqual(ctx.incomingPayment.id) + expect(ctx.asset).toEqual({ + code: ctx.incomingPayment.asset.code, + scale: ctx.incomingPayment.asset.scale + }) + }) + }) +}) diff --git a/packages/backend/src/spsp/middleware.ts b/packages/backend/src/spsp/middleware.ts new file mode 100644 index 0000000000..ad71867883 --- /dev/null +++ b/packages/backend/src/spsp/middleware.ts @@ -0,0 +1,31 @@ +import { PaymentPointerContext, SPSPContext } from '../app' +import { ConnectionContext } from '../open_payments/connection/middleware' + +export type SPSPConnectionContext = ConnectionContext & + SPSPContext & { + paymentPointer?: never + } + +export type SPSPPaymentPointerContext = PaymentPointerContext & + SPSPContext & { + incomingPayment?: never + } + +export const spspMiddleware = async ( + ctx: SPSPConnectionContext | SPSPPaymentPointerContext, + next: () => Promise +): Promise => { + // Fall back to legacy protocols if client doesn't support Open Payments. + if (ctx.accepts('application/spsp4+json')) { + const receiver = ctx.paymentPointer ?? ctx.incomingPayment + ctx.paymentTag = receiver.id + ctx.asset = { + code: receiver.asset.code, + scale: receiver.asset.scale + } + const spspRoutes = await ctx.container.use('spspRoutes') + await spspRoutes.get(ctx) + } else { + await next() + } +} diff --git a/packages/backend/src/spsp/routes.test.ts b/packages/backend/src/spsp/routes.test.ts index cfff3e38f9..c8baea0f71 100644 --- a/packages/backend/src/spsp/routes.test.ts +++ b/packages/backend/src/spsp/routes.test.ts @@ -1,17 +1,17 @@ import * as crypto from 'crypto' -import { AppServices } from '../app' +import { v4 as uuid } from 'uuid' +import { AppServices, SPSPContext } from '../app' import { SPSPRoutes } from './routes' import { createTestApp, TestContainer } from '../tests/app' import { initIocContainer } from '../' +import { AssetOptions } from '../asset/service' import { Config } from '../config/app' -import { PaymentPointer } from '../open_payments/payment_pointer/model' -import { setup } from '../open_payments/payment_pointer/model.test' import { IocContract } from '@adonisjs/fold' import { StreamServer } from '@interledger/stream-receiver' -import { createAsset } from '../tests/asset' -import { createPaymentPointer } from '../tests/paymentPointer' +import { randomAsset } from '../tests/asset' +import { createContext } from '../tests/context' import { truncateTables } from '../tests/tableManager' describe('SPSP Routes', (): void => { @@ -38,126 +38,100 @@ describe('SPSP Routes', (): void => { }) describe('GET /:id handler', (): void => { - let paymentPointer: PaymentPointer - - beforeEach(async (): Promise => { - const { id: assetId } = await createAsset(deps) - paymentPointer = await createPaymentPointer(deps, { - assetId + const setup = ({ + paymentTag, + asset, + nonce, + secret + }: { + paymentTag?: string + asset?: AssetOptions + nonce?: string + secret?: string + } = {}): SPSPContext => { + const headers = { + Accept: 'application/spsp4+json' + } + if (nonce) { + headers['Receipt-Nonce'] = nonce + } + if (secret) { + headers['Receipt-Secret'] = secret + } + const ctx = createContext({ + headers }) - }) + ctx.paymentTag = paymentTag || uuid() + ctx.asset = asset || randomAsset() + return ctx + } test('wrong Accept; returns 406', async () => { - const ctx = setup({ - reqOpts: { - headers: { Accept: 'application/json' } - }, - paymentPointer + const ctx = createContext({ + headers: { Accept: 'application/json' } }) await expect(spspRoutes.get(ctx)).rejects.toHaveProperty('status', 406) }) test('nonce, no secret; returns 400', async () => { - const ctx = setup({ - reqOpts: { - headers: { Accept: 'application/spsp4+json', 'Receipt-Nonce': nonce } - }, - paymentPointer - }) + const ctx = setup({ nonce }) await expect(spspRoutes.get(ctx)).rejects.toHaveProperty('status', 400) }) test('secret; no nonce; returns 400', async () => { - const ctx = setup({ - reqOpts: { - headers: { - Accept: 'application/spsp4+json', - 'Receipt-Secret': secret - } - }, - paymentPointer - }) + const ctx = setup({ secret }) await expect(spspRoutes.get(ctx)).rejects.toHaveProperty('status', 400) }) test('malformed nonce; returns 400', async () => { const ctx = setup({ - reqOpts: { - headers: { - Accept: 'application/spsp4+json', - 'Receipt-Nonce': Buffer.alloc(15).toString('base64'), - 'Receipt-Secret': secret - } - }, - paymentPointer + nonce: Buffer.alloc(15).toString('base64'), + secret }) await expect(spspRoutes.get(ctx)).rejects.toHaveProperty('status', 400) }) - test('receipts disabled', async () => { - const ctx = setup({ - reqOpts: { - headers: { Accept: 'application/spsp4+json' } - }, - paymentPointer - }) - await expect(spspRoutes.get(ctx)).resolves.toBeUndefined() - expect(ctx.response.get('Content-Type')).toBe('application/spsp4+json') - - const res = JSON.parse(ctx.body as string) - expect(res.destination_account).toEqual( - expect.stringMatching(/^test\.rafiki\.[a-zA-Z0-9_-]{95}$/) - ) - expect(Buffer.from(res.shared_secret, 'base64')).toHaveLength(32) - expect(res.receipts_enabled).toBe(false) - const connectionDetails = await decryptConnectionDetails( - res.destination_account - ) - expect(connectionDetails).toEqual({ - paymentTag: paymentPointer.id, - asset: { - code: paymentPointer.asset.code, - scale: paymentPointer.asset.scale - } - }) - }) + const receiptSetup = { + nonce: Buffer.from(nonce, 'base64'), + secret: Buffer.from(secret, 'base64') + } - test('receipts enabled', async () => { - const ctx = setup({ - reqOpts: { - headers: { - Accept: 'application/spsp4+json', - 'Receipt-Nonce': nonce, - 'Receipt-Secret': secret - } - }, - paymentPointer - }) - await expect(spspRoutes.get(ctx)).resolves.toBeUndefined() - expect(ctx.response.get('Content-Type')).toBe('application/spsp4+json') - - const res = JSON.parse(ctx.body as string) - expect(ctx.status).toBe(200) - expect(res.destination_account).toEqual( - expect.stringMatching(/^test\.rafiki\.[a-zA-Z0-9_-]{159}$/) - ) - expect(Buffer.from(res.shared_secret, 'base64')).toHaveLength(32) - expect(res.receipts_enabled).toBe(true) - const connectionDetails = await decryptConnectionDetails( - res.destination_account - ) - expect(connectionDetails).toEqual({ - paymentTag: paymentPointer.id, - asset: { - code: paymentPointer.asset.code, - scale: paymentPointer.asset.scale - }, - receiptSetup: { - nonce: Buffer.from(nonce, 'base64'), - secret: Buffer.from(secret, 'base64') - } - }) - }) + test.each` + nonce | secret | addressLength | receiptSetup | description + ${undefined} | ${undefined} | ${95} | ${undefined} | ${'receipts disabled'} + ${nonce} | ${secret} | ${159} | ${receiptSetup} | ${'receipts enabled'} + `( + 'generates payment details ($description)', + async ({ nonce, secret, addressLength, receiptSetup }): Promise => { + const paymentTag = uuid() + const asset = randomAsset() + const ctx = setup({ + paymentTag, + asset, + nonce, + secret + }) + await expect(spspRoutes.get(ctx)).resolves.toBeUndefined() + expect(ctx.response.get('Content-Type')).toBe('application/spsp4+json') + + const res = JSON.parse(ctx.body as string) + const regex = new RegExp( + `^test.rafiki.[a-zA-Z0-9_-]{${addressLength}}$` + ) + + expect(res.destination_account).toEqual(expect.stringMatching(regex)) + expect(Buffer.from(res.shared_secret, 'base64')).toHaveLength(32) + expect(res.receipts_enabled).toBe(!!receiptSetup) + const connectionDetails = await decryptConnectionDetails( + res.destination_account + ) + expect(connectionDetails).toEqual({ + paymentTag, + asset, + receiptSetup + }) + } + ) /** * Utility functions diff --git a/packages/backend/src/spsp/routes.ts b/packages/backend/src/spsp/routes.ts index 3874eb22f3..69ed0f6501 100644 --- a/packages/backend/src/spsp/routes.ts +++ b/packages/backend/src/spsp/routes.ts @@ -1,12 +1,12 @@ import { BaseService } from '../shared/baseService' -import { PaymentPointerContext } from '../app' +import { SPSPContext } from '../app' import base64url from 'base64url' import { StreamServer } from '@interledger/stream-receiver' const CONTENT_TYPE_V4 = 'application/spsp4+json' export interface SPSPRoutes { - get(ctx: PaymentPointerContext): Promise + get(ctx: SPSPContext): Promise } interface ServiceDependencies extends Omit { @@ -26,16 +26,15 @@ export async function createSPSPRoutes({ streamServer } return { - get: (ctx) => getPay(deps, ctx) + get: (ctx) => getSPSP(deps, ctx) } } -async function getPay( +async function getSPSP( deps: ServiceDependencies, - ctx: PaymentPointerContext + ctx: SPSPContext ): Promise { ctx.assert(ctx.accepts(CONTENT_TYPE_V4), 406) - const nonce = ctx.request.headers['receipt-nonce'] const secret = ctx.request.headers['receipt-secret'] ctx.assert( @@ -46,7 +45,7 @@ async function getPay( try { const { ilpAddress, sharedSecret } = deps.streamServer.generateCredentials({ - paymentTag: ctx.paymentPointer.id, + paymentTag: ctx.paymentTag, receiptSetup: nonce && secret ? { @@ -54,10 +53,7 @@ async function getPay( secret: Buffer.from(secret.toString(), 'base64') } : undefined, - asset: { - code: ctx.paymentPointer.asset.code, - scale: ctx.paymentPointer.asset.scale - } + asset: ctx.asset }) ctx.set('Content-Type', CONTENT_TYPE_V4) diff --git a/packages/openapi/src/middleware.ts b/packages/openapi/src/middleware.ts index 24a755a317..ef83f13929 100644 --- a/packages/openapi/src/middleware.ts +++ b/packages/openapi/src/middleware.ts @@ -15,6 +15,7 @@ export function createValidatorMiddleware( ctx: Koa.Context, next: () => Promise ): Promise => { + // TODO: Allow 'application/*+json' ctx.assert(ctx.accepts('application/json'), 406, 'must accept json') try { if (validateRequest(ctx.request)) {