From 31c265ea897a6a7a85b09a0d15047706469448b6 Mon Sep 17 00:00:00 2001 From: Nathan Lie Date: Fri, 15 Sep 2023 09:40:52 -0700 Subject: [PATCH 01/13] feat(backend): mount resource server routes explicitly --- packages/backend/src/app.ts | 304 ++++++++++++++++++++++++------------ 1 file changed, 201 insertions(+), 103 deletions(-) diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index c47bada6ec..9e9494a812 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -34,17 +34,17 @@ import { RequestAction } from './open_payments/auth/middleware' import { RatesService } from './rates/service' -import { spspMiddleware } from './spsp/middleware' +import { spspMiddleware, SPSPConnectionContext } from './spsp/middleware' import { SPSPRoutes } from './spsp/routes' -import { IncomingPaymentRoutes } from './open_payments/payment/incoming/routes' +import { IncomingPaymentRoutes, CreateBody as IncomingCreateBody } from './open_payments/payment/incoming/routes' import { PaymentPointerKeyRoutes } from './open_payments/payment_pointer/key/routes' import { PaymentPointerRoutes } from './open_payments/payment_pointer/routes' import { IncomingPaymentService } from './open_payments/payment/incoming/service' import { StreamServer } from '@interledger/stream-receiver' import { WebhookService } from './webhook/service' -import { QuoteRoutes } from './open_payments/quote/routes' +import { QuoteRoutes, CreateBody as QuoteCreateBody } from './open_payments/quote/routes' import { QuoteService } from './open_payments/quote/service' -import { OutgoingPaymentRoutes } from './open_payments/payment/outgoing/routes' +import { OutgoingPaymentRoutes, CreateBody as OutgoingCreateBody } from './open_payments/payment/outgoing/routes' import { OutgoingPaymentService } from './open_payments/payment/outgoing/service' import { IlpPlugin, IlpPluginOptions } from './shared/ilp_plugin' import { @@ -75,6 +75,7 @@ import { FeeService } from './fee/service' import { AutoPeeringService } from './auto-peering/service' import { AutoPeeringRoutes } from './auto-peering/routes' import { Rafiki as ConnectorApp } from './connector/core' +import { createPaymentPointer } from './tests/paymentPointer' export interface AppContextData { logger: Logger @@ -124,6 +125,7 @@ export type HttpSigContext = AppContext & { client: string } + // Payment pointer subresources type CollectionRequest = Omit< PaymentPointerContext['request'], @@ -142,6 +144,8 @@ type CollectionContext = Omit< accessAction: NonNullable } +type SignedCollectionContext = CollectionContext & HttpSigContext + type SubresourceRequest = Omit & { params: Record<'id', string> } @@ -155,6 +159,8 @@ type SubresourceContext = Omit< accessAction: NonNullable } +type SignedSubresourceContext = SubresourceContext & HttpSigContext + export type CreateContext = CollectionContext export type ReadContext = SubresourceContext export type CompleteContext = SubresourceContext @@ -343,109 +349,201 @@ export class App { const quoteRoutes = await this.container.use('quoteRoutes') const connectionRoutes = await this.container.use('connectionRoutes') const { resourceServerSpec } = await this.container.use('openApi') - const toRouterPath = (path: string): string => - path.replace(/{/g, ':').replace(/}/g, '') - - const toAction = ({ - path, - method - }: { - path: string - method: HttpMethod - }): RequestAction | undefined => { - switch (method) { - case HttpMethod.GET: - return path.endsWith('{id}') ? RequestAction.Read : RequestAction.List - case HttpMethod.POST: - return path.endsWith('/complete') - ? RequestAction.Complete - : RequestAction.Create - default: - return undefined - } - } - const actionToRoute: { - [key in RequestAction]: string - } = { - create: 'create', - read: 'get', - complete: 'complete', - list: 'list' - } + // GET /connections/{id} + router.get( + PAYMENT_POINTER_PATH + '/connections/:id', + connectionMiddleware, + spspMiddleware, + createValidatorMiddleware>( + resourceServerSpec, + { + path: '/connections/{id}', + method: HttpMethod.GET + } + ), + connectionRoutes.get + ) - for (const path in resourceServerSpec.paths) { - for (const method in resourceServerSpec.paths[path]) { - if (isHttpMethod(method)) { - const requestAction = toAction({ path, method }) - if (!requestAction) { - throw new Error() - } + // POST /incoming-payments + // Create incoming payment + router.post>( + PAYMENT_POINTER_PATH + '/incoming-payments', + createPaymentPointerMiddleware(), + createValidatorMiddleware>>( + resourceServerSpec, + { + path: '/incoming-payments', + method: HttpMethod.POST + } + ), + createTokenIntrospectionMiddleware({ + requestType: AccessType.IncomingPayment, + requestAction: RequestAction.Create + }), + httpsigMiddleware, + incomingPaymentRoutes.create + ) - let requestType: AccessType - let route: (ctx: AppContext) => Promise - if (path.includes('incoming-payments')) { - requestType = AccessType.IncomingPayment - // Use of ts-ignore will be obsolete once we get rid of this for-loop, see https://github.com/interledger/rafiki/issues/916 - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - route = incomingPaymentRoutes[actionToRoute[requestAction]] - } else if (path.includes('outgoing-payments')) { - requestType = AccessType.OutgoingPayment - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - route = outgoingPaymentRoutes[actionToRoute[requestAction]] - } else if (path.includes('quotes')) { - requestType = AccessType.Quote - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - route = quoteRoutes[actionToRoute[requestAction]] - } else { - if (path.includes('connections')) { - route = connectionRoutes.get - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - router[method]( - toRouterPath(path), - connectionMiddleware, - spspMiddleware, - createValidatorMiddleware>( - resourceServerSpec, - { - path, - method - } - ), - route - ) - } else if (path !== '/' || method !== HttpMethod.GET) { - // The payment pointer query route is added last below - this.logger.warn({ path, method }, 'unexpected path/method') - } - continue - } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - router[method]( - PAYMENT_POINTER_PATH + toRouterPath(path), - createPaymentPointerMiddleware(), - createValidatorMiddleware>( - resourceServerSpec, - { - path, - method - } - ), - createTokenIntrospectionMiddleware({ - requestType, - requestAction - }), - httpsigMiddleware, - route - ) + // GET /incoming-payments + // List incoming payments + router.get( + PAYMENT_POINTER_PATH + '/incoming-payments', + createPaymentPointerMiddleware(), + createValidatorMiddleware>( + resourceServerSpec, + { + path: '/incoming-payments', + method: HttpMethod.GET } - } - } + ), + createTokenIntrospectionMiddleware({ + requestType: AccessType.IncomingPayment, + requestAction: RequestAction.List + }), + httpsigMiddleware, + incomingPaymentRoutes.list + ) + + // POST /outgoing-payment + // Create outgoing payment + router.post>( + PAYMENT_POINTER_PATH + '/outgoing-payments', + createPaymentPointerMiddleware(), + createValidatorMiddleware>>( + resourceServerSpec, + { + path: '/outgoing-payments', + method: HttpMethod.POST + } + ), + createTokenIntrospectionMiddleware({ + requestType: AccessType.OutgoingPayment, + requestAction: RequestAction.Create + }), + httpsigMiddleware, + outgoingPaymentRoutes.create + ) + + // GET /outgoing-payment + // List outgoing payments + router.get( + PAYMENT_POINTER_PATH + '/outgoing-payments', + createPaymentPointerMiddleware(), + createValidatorMiddleware>( + resourceServerSpec, + { + path: '/outgoing-payments', + method: HttpMethod.GET + } + ), + createTokenIntrospectionMiddleware({ + requestType: AccessType.OutgoingPayment, + requestAction: RequestAction.List + }), + httpsigMiddleware, + outgoingPaymentRoutes.list + ) + + // POST /quotes + // Create quote + router.post>( + PAYMENT_POINTER_PATH + '/quotes', + createPaymentPointerMiddleware(), + createValidatorMiddleware>>( + resourceServerSpec, + { + path: '/quotes', + method: HttpMethod.POST + } + ), + createTokenIntrospectionMiddleware({ + requestType: AccessType.Quote, + requestAction: RequestAction.Create + }), + httpsigMiddleware, + quoteRoutes.create + ) + + // GET /incoming-payments/{id} + // Read incoming payment + router.get( + PAYMENT_POINTER_PATH + '/incoming-payments/:id', + createPaymentPointerMiddleware(), + createValidatorMiddleware>( + resourceServerSpec, + { + path: '/incoming-payments/{id}', + method: HttpMethod.GET + } + ), + createTokenIntrospectionMiddleware({ + requestType: AccessType.IncomingPayment, + requestAction: RequestAction.Read + }), + httpsigMiddleware, + incomingPaymentRoutes.get + ) + + // POST /incoming-payments/{id}/complete + // Complete incoming payment + router.post( + PAYMENT_POINTER_PATH + '/incoming-payments/:id/complete', + createPaymentPointerMiddleware(), + createValidatorMiddleware>( + resourceServerSpec, + { + path: '/incoming-payments/{id}/complete', + method: HttpMethod.POST + } + ), + createTokenIntrospectionMiddleware({ + requestType: AccessType.IncomingPayment, + requestAction: RequestAction.Complete + }), + httpsigMiddleware, + incomingPaymentRoutes.complete + ) + + // GET /outgoing-payments/{id} + // Read outgoing payment + router.get( + PAYMENT_POINTER_PATH + '/outgoing-payments/:id', + createPaymentPointerMiddleware(), + createValidatorMiddleware>( + resourceServerSpec, + { + path: '/outgoing-payments/{id}', + method: HttpMethod.GET + } + ), + createTokenIntrospectionMiddleware({ + requestType: AccessType.OutgoingPayment, + requestAction: RequestAction.Read + }), + httpsigMiddleware, + outgoingPaymentRoutes.get + ) + + // GET /quotes/{id} + // Read quote + router.get( + PAYMENT_POINTER_PATH + '/quotes/:id', + createPaymentPointerMiddleware(), + createValidatorMiddleware>( + resourceServerSpec, + { + path: '/quotes/{id}', + method: HttpMethod.GET + } + ), + createTokenIntrospectionMiddleware({ + requestType: AccessType.Quote, + requestAction: RequestAction.Read + }), + httpsigMiddleware, + quoteRoutes.get + ) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore From b329f45a9f60141d111954d337a9c0fd82dec484 Mon Sep 17 00:00:00 2001 From: Nathan Lie Date: Fri, 15 Sep 2023 10:18:52 -0700 Subject: [PATCH 02/13] chore: formatting --- packages/backend/src/app.ts | 67 +++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 9e9494a812..001762defb 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -36,22 +36,27 @@ import { import { RatesService } from './rates/service' import { spspMiddleware, SPSPConnectionContext } from './spsp/middleware' import { SPSPRoutes } from './spsp/routes' -import { IncomingPaymentRoutes, CreateBody as IncomingCreateBody } from './open_payments/payment/incoming/routes' +import { + IncomingPaymentRoutes, + CreateBody as IncomingCreateBody +} from './open_payments/payment/incoming/routes' import { PaymentPointerKeyRoutes } from './open_payments/payment_pointer/key/routes' import { PaymentPointerRoutes } from './open_payments/payment_pointer/routes' import { IncomingPaymentService } from './open_payments/payment/incoming/service' import { StreamServer } from '@interledger/stream-receiver' import { WebhookService } from './webhook/service' -import { QuoteRoutes, CreateBody as QuoteCreateBody } from './open_payments/quote/routes' +import { + QuoteRoutes, + CreateBody as QuoteCreateBody +} from './open_payments/quote/routes' import { QuoteService } from './open_payments/quote/service' -import { OutgoingPaymentRoutes, CreateBody as OutgoingCreateBody } from './open_payments/payment/outgoing/routes' +import { + OutgoingPaymentRoutes, + CreateBody as OutgoingCreateBody +} from './open_payments/payment/outgoing/routes' import { OutgoingPaymentService } from './open_payments/payment/outgoing/service' import { IlpPlugin, IlpPluginOptions } from './shared/ilp_plugin' -import { - createValidatorMiddleware, - HttpMethod, - isHttpMethod -} from '@interledger/openapi' +import { createValidatorMiddleware, HttpMethod } from '@interledger/openapi' import { PaymentPointerKeyService } from './open_payments/payment_pointer/key/service' import { AccessAction, @@ -75,7 +80,6 @@ import { FeeService } from './fee/service' import { AutoPeeringService } from './auto-peering/service' import { AutoPeeringRoutes } from './auto-peering/routes' import { Rafiki as ConnectorApp } from './connector/core' -import { createPaymentPointer } from './tests/paymentPointer' export interface AppContextData { logger: Logger @@ -125,7 +129,6 @@ export type HttpSigContext = AppContext & { client: string } - // Payment pointer subresources type CollectionRequest = Omit< PaymentPointerContext['request'], @@ -144,7 +147,10 @@ type CollectionContext = Omit< accessAction: NonNullable } -type SignedCollectionContext = CollectionContext & HttpSigContext +type SignedCollectionContext< + BodyT = never, + QueryT = ParsedUrlQuery +> = CollectionContext & HttpSigContext type SubresourceRequest = Omit & { params: Record<'id', string> @@ -370,13 +376,12 @@ export class App { router.post>( PAYMENT_POINTER_PATH + '/incoming-payments', createPaymentPointerMiddleware(), - createValidatorMiddleware>>( - resourceServerSpec, - { - path: '/incoming-payments', - method: HttpMethod.POST - } - ), + createValidatorMiddleware< + ContextType> + >(resourceServerSpec, { + path: '/incoming-payments', + method: HttpMethod.POST + }), createTokenIntrospectionMiddleware({ requestType: AccessType.IncomingPayment, requestAction: RequestAction.Create @@ -410,13 +415,12 @@ export class App { router.post>( PAYMENT_POINTER_PATH + '/outgoing-payments', createPaymentPointerMiddleware(), - createValidatorMiddleware>>( - resourceServerSpec, - { - path: '/outgoing-payments', - method: HttpMethod.POST - } - ), + createValidatorMiddleware< + ContextType> + >(resourceServerSpec, { + path: '/outgoing-payments', + method: HttpMethod.POST + }), createTokenIntrospectionMiddleware({ requestType: AccessType.OutgoingPayment, requestAction: RequestAction.Create @@ -450,13 +454,12 @@ export class App { router.post>( PAYMENT_POINTER_PATH + '/quotes', createPaymentPointerMiddleware(), - createValidatorMiddleware>>( - resourceServerSpec, - { - path: '/quotes', - method: HttpMethod.POST - } - ), + createValidatorMiddleware< + ContextType> + >(resourceServerSpec, { + path: '/quotes', + method: HttpMethod.POST + }), createTokenIntrospectionMiddleware({ requestType: AccessType.Quote, requestAction: RequestAction.Create From 1fea525ffccf694041c0726e90d327a204cef5eb Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:15:04 +0300 Subject: [PATCH 03/13] feat(backend): wip unauthenticated incoming payment get --- openapi/resource-server.yaml | 25 +++++- packages/backend/src/app.ts | 41 ++++++--- .../src/open_payments/auth/middleware.test.ts | 80 ++++++++++++++++- .../src/open_payments/auth/middleware.ts | 90 ++++++++++++++++++- .../open_payments/payment/incoming/model.ts | 7 ++ .../payment/incoming/routes.test.ts | 2 +- .../open_payments/payment/incoming/routes.ts | 48 +++++++++- .../payment_pointer/middleware.ts | 6 ++ .../payment_pointer/model.test.ts | 3 + 9 files changed, 277 insertions(+), 25 deletions(-) diff --git a/openapi/resource-server.yaml b/openapi/resource-server.yaml index fcfe71a097..2e83332d87 100644 --- a/openapi/resource-server.yaml +++ b/openapi/resource-server.yaml @@ -650,7 +650,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/incoming-payment-with-connection' + anyOf: + - $ref: '#/components/schemas/incoming-payment-with-connection' + - $ref: '#/components/schemas/public-incoming-payment' examples: Incoming Payment for $25 with $12.34 received so far: value: @@ -684,9 +686,9 @@ paths: '404': description: Incoming Payment Not Found description: A client can fetch the latest state of an incoming payment to determine the amount received into the payment pointer. - parameters: - - $ref: '#/components/parameters/signature-input' - - $ref: '#/components/parameters/signature' + # parameters: + # - $ref: '#/components/parameters/signature-input' + # - $ref: '#/components/parameters/signature' parameters: - $ref: '#/components/parameters/id' '/incoming-payments/{id}/complete': @@ -1062,6 +1064,21 @@ components: description: Endpoint that returns unique STREAM connection credentials to establish a STREAM connection to the underlying account. readOnly: true unevaluatedProperties: false + public-incoming-payment: + title: Public Incoming Payment + description: An **incoming payment** resource with public details. + type: object + examples: + - receivedAmount: + value: '0' + assetCode: USD + assetScale: 2 + properties: + receiveAmount: + $ref: ./schemas.yaml#/components/schemas/amount + # description: The total amount that should be received by the receiver when the associated outgoing payment has been paid. + unresolvedProperites: false + outgoing-payment: title: Outgoing Payment description: 'An **outgoing payment** resource represents a payment that will be, is currently being, or has previously been, sent from the payment pointer.' diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 7cf13e7b81..486b8d8444 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -31,7 +31,8 @@ import { createTokenIntrospectionMiddleware, httpsigMiddleware, Grant, - RequestAction + RequestAction, + authenticatedStatusMiddleware } from './open_payments/auth/middleware' import { RatesService } from './rates/service' import { spspMiddleware, SPSPConnectionContext } from './spsp/middleware' @@ -129,6 +130,9 @@ export type HttpSigContext = AppContext & { client: string } +export type HttpSigWithAuthenticatedStausContext = HttpSigContext & + AuthenticatedStatusContext + // Payment pointer subresources type CollectionRequest = Omit< PaymentPointerContext['request'], @@ -165,8 +169,14 @@ type SubresourceContext = Omit< accessAction: NonNullable } +export type AuthenticatedStatusContext = { authenticated: boolean } + type SignedSubresourceContext = SubresourceContext & HttpSigContext +type SubresourceContextWithAuthenticatedStatus = SubresourceContext & + HttpSigContext & + AuthenticatedStatusContext + export type CreateContext = CollectionContext export type ReadContext = SubresourceContext export type CompleteContext = SubresourceContext @@ -470,21 +480,30 @@ export class App { // GET /incoming-payments/{id} // Read incoming payment - router.get( + router.get( PAYMENT_POINTER_PATH + '/incoming-payments/:id', createPaymentPointerMiddleware(), - createValidatorMiddleware>( - resourceServerSpec, - { - path: '/incoming-payments/{id}', - method: HttpMethod.GET - } - ), + createValidatorMiddleware< + ContextType + >(resourceServerSpec, { + path: '/incoming-payments/{id}', + method: HttpMethod.GET + }), createTokenIntrospectionMiddleware({ requestType: AccessType.IncomingPayment, - requestAction: RequestAction.Read + requestAction: RequestAction.Read, + // authenticated path: + // - createTokenIntrospectionMiddleware succeeds + // - authenticatedStatusMiddleware sets ctx.authenticated to true + // - incomingPaymentRoutes.get calls private getIncomingPayment + + // non-authenticated path: + // - createTokenIntrospectionMiddleware fails, but error is bypassed + // - authenticatedStatusMiddleware sets ctx.authenticated to false + // - incomingPaymentRoutes.get calls public getIncomingPayment + bypassError: true }), - httpsigMiddleware, + authenticatedStatusMiddleware, incomingPaymentRoutes.get ) diff --git a/packages/backend/src/open_payments/auth/middleware.test.ts b/packages/backend/src/open_payments/auth/middleware.test.ts index 56309d5c45..e227aed7dd 100644 --- a/packages/backend/src/open_payments/auth/middleware.test.ts +++ b/packages/backend/src/open_payments/auth/middleware.test.ts @@ -10,13 +10,19 @@ import { } from '@interledger/http-signature-utils' import { + authenticatedStatusMiddleware, createTokenIntrospectionMiddleware, httpsigMiddleware } from './middleware' import { Config } from '../../config/app' import { IocContract } from '@adonisjs/fold' import { initIocContainer } from '../../' -import { AppServices, HttpSigContext, PaymentPointerContext } from '../../app' +import { + AppServices, + HttpSigContext, + HttpSigWithAuthenticatedStausContext, + PaymentPointerContext +} from '../../app' import { createTestApp, TestContainer } from '../../tests/app' import { createContext } from '../../tests/context' import { createPaymentPointer } from '../../tests/paymentPointer' @@ -348,6 +354,78 @@ describe('Auth Middleware', (): void => { ) }) +describe('authenticatedStatusMiddleware', (): void => { + let deps: IocContract + let appContainer: TestContainer + + beforeAll(async (): Promise => { + deps = await initIocContainer(Config) + appContainer = await createTestApp(deps) + }) + + afterAll(async (): Promise => { + await appContainer.shutdown() + }) + + test('sets ctx.authenticated to false if http signature is invalid', async (): Promise => { + const ctx = createContext({ + headers: { 'signature-input': '' } + }) + + expect(authenticatedStatusMiddleware(ctx, next)).resolves.toBeUndefined() + expect(next).not.toHaveBeenCalled() + expect(ctx.authenticated).toBe(false) + }) + + test('sets ctx.authenticated to true if http signature is valid', async (): Promise => { + const keyId = uuid() + const privateKey = generateKeyPairSync('ed25519').privateKey + const method = 'GET' + const url = faker.internet.url({ appendSlash: false }) + const request = { + method, + url, + headers: { + Accept: 'application/json', + Authorization: `GNAP ${token}` + } + } + const ctx = createContext({ + headers: { + Accept: 'application/json', + Authorization: `GNAP ${token}`, + ...(await createHeaders({ + request, + privateKey, + keyId + })) + }, + method, + url + }) + ctx.container = deps + ctx.client = faker.internet.url({ appendSlash: false }) + const key = generateJwk({ + keyId, + privateKey + }) + + const scope = nock(ctx.client) + .get('/jwks.json') + .reply(200, { + keys: [key] + }) + + await expect( + authenticatedStatusMiddleware(ctx, next) + ).resolves.toBeUndefined() + expect(next).toHaveBeenCalled() + expect(ctx.authenticated).toBe(true) + + scope.done() + }) +}) + describe('HTTP Signature Middleware', (): void => { let deps: IocContract let appContainer: TestContainer diff --git a/packages/backend/src/open_payments/auth/middleware.ts b/packages/backend/src/open_payments/auth/middleware.ts index 92fba9162b..67e13d089f 100644 --- a/packages/backend/src/open_payments/auth/middleware.ts +++ b/packages/backend/src/open_payments/auth/middleware.ts @@ -5,7 +5,11 @@ import { } from '@interledger/http-signature-utils' import Koa, { HttpError } from 'koa' import { Limits, parseLimits } from '../payment/outgoing/limits' -import { HttpSigContext, PaymentPointerContext } from '../../app' +import { + HttpSigContext, + HttpSigWithAuthenticatedStausContext, + PaymentPointerContext +} from '../../app' import { AccessAction, AccessType, JWKS } from '@interledger/open-payments' import { TokenInfo } from 'token-introspection' import { isActiveTokenInfo } from 'token-introspection' @@ -37,12 +41,17 @@ function contextToRequestLike(ctx: HttpSigContext): RequestLike { body: ctx.request.body ? JSON.stringify(ctx.request.body) : undefined } } + +// TODO: unauthed requests are getting screwed up here. +// unauthed requests work without this middleware, but with it im seeing an error: InternalServerError: response must be null. export function createTokenIntrospectionMiddleware({ requestType, - requestAction + requestAction, + bypassError = false }: { requestType: AccessType requestAction: RequestAction + bypassError?: boolean }) { return async ( ctx: PaymentPointerContext, @@ -119,6 +128,9 @@ export function createTokenIntrospectionMiddleware({ } await next() } catch (err) { + // TODO: narrow this to not include any error? maybe: bypassError && err instanceof HttpError && [401,403].includes(err.status) + if (bypassError) await next() + if (err instanceof HttpError && err.status === 401) { ctx.status = 401 ctx.message = err.message @@ -130,10 +142,23 @@ export function createTokenIntrospectionMiddleware({ } } -export const httpsigMiddleware = async ( - ctx: HttpSigContext, +export const authenticatedStatusMiddleware = async ( + ctx: HttpSigWithAuthenticatedStausContext, next: () => Promise ): Promise => { + ctx.authenticated = false + try { + await throwIfSignatureInvalid(ctx) + ctx.authenticated = true + } catch (err) { + if (err instanceof Koa.HttpError && err.status !== 401) { + throw err + } + } + await next() +} + +export const throwIfSignatureInvalid = async (ctx: HttpSigContext) => { const keyId = getKeyId(ctx.request.headers['signature-input']) if (!keyId) { ctx.throw(401, 'Invalid signature input') @@ -177,5 +202,62 @@ export const httpsigMiddleware = async ( ) ctx.throw(401, `Invalid signature`) } +} + +export const httpsigMiddleware = async ( + ctx: HttpSigContext, + next: () => Promise +): Promise => { + await throwIfSignatureInvalid(ctx) await next() } + +// export const httpsigMiddleware = async ( +// ctx: HttpSigContext, +// next: () => Promise +// ): Promise => { +// const keyId = getKeyId(ctx.request.headers['signature-input']) +// if (!keyId) { +// ctx.throw(401, 'Invalid signature input') +// } +// // TODO +// // cache client key(s) +// let jwks: JWKS | undefined +// try { +// const openPaymentsClient = await ctx.container.use('openPaymentsClient') +// jwks = await openPaymentsClient.paymentPointer.getKeys({ +// url: ctx.client +// }) +// } catch (error) { +// const logger = await ctx.container.use('logger') +// logger.debug( +// { +// error, +// client: ctx.client +// }, +// 'retrieving client key' +// ) +// } +// const key = jwks?.keys.find((key) => key.kid === keyId) +// if (!key) { +// ctx.throw(401, 'Invalid signature input') +// } +// try { +// if (!(await validateSignature(key, contextToRequestLike(ctx)))) { +// ctx.throw(401, 'Invalid signature') +// } +// } catch (err) { +// if (err instanceof Koa.HttpError) { +// throw err +// } +// const logger = await ctx.container.use('logger') +// logger.warn( +// { +// err +// }, +// 'httpsig error' +// ) +// ctx.throw(401, `Invalid signature`) +// } +// await next() +// } diff --git a/packages/backend/src/open_payments/payment/incoming/model.ts b/packages/backend/src/open_payments/payment/incoming/model.ts index 3b17c3f452..a8e4e589d9 100644 --- a/packages/backend/src/open_payments/payment/incoming/model.ts +++ b/packages/backend/src/open_payments/payment/incoming/model.ts @@ -265,4 +265,11 @@ export class IncomingPayment ilpStreamConnection: ilpStreamConnection.toOpenPaymentsType() } } + + public toPublicOpenPaymentsType(): Pick< + OpenPaymentsIncomingPayment, + 'receivedAmount' + > { + return { receivedAmount: serializeAmount(this.receivedAmount) } + } } diff --git a/packages/backend/src/open_payments/payment/incoming/routes.test.ts b/packages/backend/src/open_payments/payment/incoming/routes.test.ts index 2cb4b975df..8dbc322a9d 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.test.ts @@ -85,7 +85,7 @@ describe('Incoming Payment Routes', (): void => { incomingAmount, metadata }), - get: (ctx) => incomingPaymentRoutes.get(ctx), + get: (ctx) => incomingPaymentRoutes.get(ctx as any), // TODO: fix any getBody: (incomingPayment, list) => { return { id: incomingPayment.getUrl(paymentPointer), diff --git a/packages/backend/src/open_payments/payment/incoming/routes.ts b/packages/backend/src/open_payments/payment/incoming/routes.ts index 12e9b10e02..b83d92f886 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.ts @@ -4,7 +4,8 @@ import { ReadContext, CreateContext, CompleteContext, - ListContext + ListContext, + AuthenticatedStatusContext } from '../../../app' import { IAppConfig } from '../../../config/app' import { IncomingPaymentService } from './service' @@ -34,8 +35,11 @@ interface ServiceDependencies { connectionService: ConnectionService } +type ReadContextWithAuthenticatedStatus = ReadContext & + AuthenticatedStatusContext + export interface IncomingPaymentRoutes { - get(ctx: ReadContext): Promise + get(ctx: ReadContextWithAuthenticatedStatus): Promise create(ctx: CreateContext): Promise complete(ctx: CompleteContext): Promise list(ctx: ListContext): Promise @@ -49,7 +53,8 @@ export function createIncomingPaymentRoutes( }) const deps = { ...deps_, logger } return { - get: (ctx: ReadContext) => getIncomingPayment(deps, ctx), + get: (ctx: ReadContextWithAuthenticatedStatus) => + getIncomingPayment(deps, ctx), create: (ctx: CreateContext) => createIncomingPayment(deps, ctx), complete: (ctx: CompleteContext) => completeIncomingPayment(deps, ctx), @@ -59,7 +64,42 @@ export function createIncomingPaymentRoutes( async function getIncomingPayment( deps: ServiceDependencies, - ctx: ReadContext + ctx: ReadContextWithAuthenticatedStatus +) { + if (ctx.authenticated) { + await getIncomingPaymentPrivate(deps, ctx) + console.log('getIncomingPaymentPrivate') + } else { + await getIncomingPaymentPublic(deps, ctx) + console.log('getIncomingPaymentPublic: ', ctx.body) + console.log( + 'getIncomingPaymentPublic response: ', + JSON.stringify(ctx.response, null, 2) + ) + } +} + +async function getIncomingPaymentPublic( + deps: ServiceDependencies, + ctx: ReadContextWithAuthenticatedStatus +) { + try { + const incomingPayment = await deps.incomingPaymentService.get({ + id: ctx.params.id, + client: ctx.accessAction === AccessAction.Read ? ctx.client : undefined, + paymentPointerId: ctx.paymentPointer.id + }) + ctx.body = incomingPayment?.toPublicOpenPaymentsType() + } catch (err) { + const msg = 'Error trying to get incoming payment' + deps.logger.error({ err }, msg) + ctx.throw(500, msg) + } +} + +async function getIncomingPaymentPrivate( + deps: ServiceDependencies, + ctx: ReadContextWithAuthenticatedStatus ): Promise { let incomingPayment: IncomingPayment | undefined try { diff --git a/packages/backend/src/open_payments/payment_pointer/middleware.ts b/packages/backend/src/open_payments/payment_pointer/middleware.ts index 35332a9bad..ffd6434669 100644 --- a/packages/backend/src/open_payments/payment_pointer/middleware.ts +++ b/packages/backend/src/open_payments/payment_pointer/middleware.ts @@ -5,6 +5,9 @@ export function createPaymentPointerMiddleware() { ctx: AppContext, next: () => Promise ): Promise => { + console.log('createPaymentPointerMiddleware called', { + response: JSON.stringify(ctx.response, null, 2) + }) ctx.paymentPointerUrl = `https://${ctx.request.host}/${ctx.params.paymentPointerPath}` const config = await ctx.container.use('config') if (ctx.paymentPointerUrl !== config.paymentPointerUrl) { @@ -20,6 +23,9 @@ export function createPaymentPointerMiddleware() { } ctx.paymentPointer = paymentPointer } + console.log('createPaymentPointerMiddleware ended', { + response: JSON.stringify(ctx.response, null, 2) + }) await next() } } diff --git a/packages/backend/src/open_payments/payment_pointer/model.test.ts b/packages/backend/src/open_payments/payment_pointer/model.test.ts index 2fcae7bdea..e46ec21ca7 100644 --- a/packages/backend/src/open_payments/payment_pointer/model.test.ts +++ b/packages/backend/src/open_payments/payment_pointer/model.test.ts @@ -52,6 +52,9 @@ export const setup = ( ctx.grant = options.grant ctx.client = options.client ctx.accessAction = options.accessAction + // TODO: fix @ts-ignroe + // @ts-ignore + ctx.authenticated = true return ctx } From c4c735b6e481005b0330b7b9c1d854b2f6f38859 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 20 Sep 2023 18:19:30 +0300 Subject: [PATCH 04/13] fix(backend): token introspection middleware --- packages/backend/src/open_payments/auth/middleware.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/open_payments/auth/middleware.ts b/packages/backend/src/open_payments/auth/middleware.ts index 67e13d089f..61d8301656 100644 --- a/packages/backend/src/open_payments/auth/middleware.ts +++ b/packages/backend/src/open_payments/auth/middleware.ts @@ -55,7 +55,7 @@ export function createTokenIntrospectionMiddleware({ }) { return async ( ctx: PaymentPointerContext, - next: () => Promise + next: () => Promise ): Promise => { const config = await ctx.container.use('config') try { @@ -128,8 +128,13 @@ export function createTokenIntrospectionMiddleware({ } await next() } catch (err) { - // TODO: narrow this to not include any error? maybe: bypassError && err instanceof HttpError && [401,403].includes(err.status) - if (bypassError) await next() + if ( + bypassError && + err instanceof HttpError && + [401, 403].includes(err.status) + ) { + return await next() + } if (err instanceof HttpError && err.status === 401) { ctx.status = 401 From 7f32f069650b977e8881b9ff55eaf9727ac47315 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 20 Sep 2023 18:20:08 +0300 Subject: [PATCH 05/13] chore(backend): cleanup --- packages/backend/src/app.ts | 9 --------- .../backend/src/open_payments/payment/incoming/routes.ts | 6 ------ .../src/open_payments/payment_pointer/middleware.ts | 6 ------ 3 files changed, 21 deletions(-) diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 486b8d8444..f14179fc8a 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -492,15 +492,6 @@ export class App { createTokenIntrospectionMiddleware({ requestType: AccessType.IncomingPayment, requestAction: RequestAction.Read, - // authenticated path: - // - createTokenIntrospectionMiddleware succeeds - // - authenticatedStatusMiddleware sets ctx.authenticated to true - // - incomingPaymentRoutes.get calls private getIncomingPayment - - // non-authenticated path: - // - createTokenIntrospectionMiddleware fails, but error is bypassed - // - authenticatedStatusMiddleware sets ctx.authenticated to false - // - incomingPaymentRoutes.get calls public getIncomingPayment bypassError: true }), authenticatedStatusMiddleware, diff --git a/packages/backend/src/open_payments/payment/incoming/routes.ts b/packages/backend/src/open_payments/payment/incoming/routes.ts index b83d92f886..7703b4269c 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.ts @@ -68,14 +68,8 @@ async function getIncomingPayment( ) { if (ctx.authenticated) { await getIncomingPaymentPrivate(deps, ctx) - console.log('getIncomingPaymentPrivate') } else { await getIncomingPaymentPublic(deps, ctx) - console.log('getIncomingPaymentPublic: ', ctx.body) - console.log( - 'getIncomingPaymentPublic response: ', - JSON.stringify(ctx.response, null, 2) - ) } } diff --git a/packages/backend/src/open_payments/payment_pointer/middleware.ts b/packages/backend/src/open_payments/payment_pointer/middleware.ts index ffd6434669..35332a9bad 100644 --- a/packages/backend/src/open_payments/payment_pointer/middleware.ts +++ b/packages/backend/src/open_payments/payment_pointer/middleware.ts @@ -5,9 +5,6 @@ export function createPaymentPointerMiddleware() { ctx: AppContext, next: () => Promise ): Promise => { - console.log('createPaymentPointerMiddleware called', { - response: JSON.stringify(ctx.response, null, 2) - }) ctx.paymentPointerUrl = `https://${ctx.request.host}/${ctx.params.paymentPointerPath}` const config = await ctx.container.use('config') if (ctx.paymentPointerUrl !== config.paymentPointerUrl) { @@ -23,9 +20,6 @@ export function createPaymentPointerMiddleware() { } ctx.paymentPointer = paymentPointer } - console.log('createPaymentPointerMiddleware ended', { - response: JSON.stringify(ctx.response, null, 2) - }) await next() } } From ae7f71e86c60db203dc2315e1a6f969c4ce53d55 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 21 Sep 2023 17:01:20 +0300 Subject: [PATCH 06/13] chore(backend): cleanup --- .../src/open_payments/auth/middleware.ts | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/packages/backend/src/open_payments/auth/middleware.ts b/packages/backend/src/open_payments/auth/middleware.ts index 61d8301656..23eb77753e 100644 --- a/packages/backend/src/open_payments/auth/middleware.ts +++ b/packages/backend/src/open_payments/auth/middleware.ts @@ -42,8 +42,6 @@ function contextToRequestLike(ctx: HttpSigContext): RequestLike { } } -// TODO: unauthed requests are getting screwed up here. -// unauthed requests work without this middleware, but with it im seeing an error: InternalServerError: response must be null. export function createTokenIntrospectionMiddleware({ requestType, requestAction, @@ -216,53 +214,3 @@ export const httpsigMiddleware = async ( await throwIfSignatureInvalid(ctx) await next() } - -// export const httpsigMiddleware = async ( -// ctx: HttpSigContext, -// next: () => Promise -// ): Promise => { -// const keyId = getKeyId(ctx.request.headers['signature-input']) -// if (!keyId) { -// ctx.throw(401, 'Invalid signature input') -// } -// // TODO -// // cache client key(s) -// let jwks: JWKS | undefined -// try { -// const openPaymentsClient = await ctx.container.use('openPaymentsClient') -// jwks = await openPaymentsClient.paymentPointer.getKeys({ -// url: ctx.client -// }) -// } catch (error) { -// const logger = await ctx.container.use('logger') -// logger.debug( -// { -// error, -// client: ctx.client -// }, -// 'retrieving client key' -// ) -// } -// const key = jwks?.keys.find((key) => key.kid === keyId) -// if (!key) { -// ctx.throw(401, 'Invalid signature input') -// } -// try { -// if (!(await validateSignature(key, contextToRequestLike(ctx)))) { -// ctx.throw(401, 'Invalid signature') -// } -// } catch (err) { -// if (err instanceof Koa.HttpError) { -// throw err -// } -// const logger = await ctx.container.use('logger') -// logger.warn( -// { -// err -// }, -// 'httpsig error' -// ) -// ctx.throw(401, `Invalid signature`) -// } -// await next() -// } From 3a0b8fc9e9203270c3208426a09c89711b847e13 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Thu, 21 Sep 2023 17:01:51 +0300 Subject: [PATCH 07/13] test(backend): update incoming payment tests --- .../payment/incoming/routes.test.ts | 42 ++++++++++++++++++- .../open_payments/payment/incoming/routes.ts | 2 +- .../payment_pointer/model.test.ts | 12 +++--- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/packages/backend/src/open_payments/payment/incoming/routes.test.ts b/packages/backend/src/open_payments/payment/incoming/routes.test.ts index 8dbc322a9d..e8cf097fcb 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.test.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.test.ts @@ -16,7 +16,11 @@ import { } from '../../../app' import { truncateTables } from '../../../tests/tableManager' import { IncomingPayment } from './model' -import { IncomingPaymentRoutes, CreateBody } from './routes' +import { + IncomingPaymentRoutes, + CreateBody, + ReadContextWithAuthenticatedStatus +} from './routes' import { createAsset } from '../../../tests/asset' import { createIncomingPayment } from '../../../tests/incomingPayment' import { createPaymentPointer } from '../../../tests/paymentPointer' @@ -85,7 +89,8 @@ describe('Incoming Payment Routes', (): void => { incomingAmount, metadata }), - get: (ctx) => incomingPaymentRoutes.get(ctx as any), // TODO: fix any + get: (ctx) => + incomingPaymentRoutes.get(ctx as ReadContextWithAuthenticatedStatus), getBody: (incomingPayment, list) => { return { id: incomingPayment.getUrl(paymentPointer), @@ -289,4 +294,37 @@ describe('Incoming Payment Routes', (): void => { }) }) }) + describe('get unauthenticated incoming payment', (): void => { + test('Can get incoming payment with public fields', async (): Promise => { + const incomingPayment = await createIncomingPayment(deps, { + paymentPointerId: paymentPointer.id, + expiresAt, + incomingAmount, + metadata + }) + + const ctx = setup({ + reqOpts: { + headers: { Accept: 'application/json' }, + method: 'GET', + url: `/incoming-payments/${incomingPayment.id}` + }, + params: { + id: incomingPayment.id + }, + paymentPointer + }) + ctx.authenticated = false + + await expect(incomingPaymentRoutes.get(ctx)).resolves.toBeUndefined() + expect(ctx.response).toSatisfyApiSpec() + expect(ctx.body).toEqual({ + receivedAmount: { + value: '0', + assetCode: asset.code, + assetScale: asset.scale + } + }) + }) + }) }) diff --git a/packages/backend/src/open_payments/payment/incoming/routes.ts b/packages/backend/src/open_payments/payment/incoming/routes.ts index 7703b4269c..547dd4c1a2 100644 --- a/packages/backend/src/open_payments/payment/incoming/routes.ts +++ b/packages/backend/src/open_payments/payment/incoming/routes.ts @@ -35,7 +35,7 @@ interface ServiceDependencies { connectionService: ConnectionService } -type ReadContextWithAuthenticatedStatus = ReadContext & +export type ReadContextWithAuthenticatedStatus = ReadContext & AuthenticatedStatusContext export interface IncomingPaymentRoutes { diff --git a/packages/backend/src/open_payments/payment_pointer/model.test.ts b/packages/backend/src/open_payments/payment_pointer/model.test.ts index e46ec21ca7..7d7b0124f1 100644 --- a/packages/backend/src/open_payments/payment_pointer/model.test.ts +++ b/packages/backend/src/open_payments/payment_pointer/model.test.ts @@ -14,7 +14,8 @@ import { PaymentPointerContext, ReadContext, ListContext, - AppServices + AppServices, + AuthenticatedStatusContext } from '../../app' import { getPageTests } from '../../shared/baseModel.test' import { createContext } from '../../tests/context' @@ -25,6 +26,7 @@ import { createTestApp, TestContainer } from '../../tests/app' import { Config } from '../../config/app' import { IocContract } from '@adonisjs/fold' import assert from 'assert' +import { ReadContextWithAuthenticatedStatus } from '../payment/incoming/routes' export interface SetupOptions { reqOpts: httpMocks.RequestOptions @@ -35,7 +37,9 @@ export interface SetupOptions { accessAction?: AccessAction } -export const setup = ( +export const setup = < + T extends PaymentPointerContext & Partial +>( options: SetupOptions ): T => { const ctx = createContext( @@ -52,8 +56,6 @@ export const setup = ( ctx.grant = options.grant ctx.client = options.client ctx.accessAction = options.accessAction - // TODO: fix @ts-ignroe - // @ts-ignore ctx.authenticated = true return ctx } @@ -208,7 +210,7 @@ type RouteTestsOptions = Omit< 'testGet' | 'testList' > & { getPaymentPointer: () => Promise - get: (ctx: ReadContext) => Promise + get: (ctx: ReadContext | ReadContextWithAuthenticatedStatus) => Promise getBody: (model: M, list?: boolean) => Record list?: (ctx: ListContext) => Promise urlPath: string From 83189eba0063d082475a8c91c5f5014c38483562 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:52:23 +0300 Subject: [PATCH 08/13] test(backend): add tests for new option --- .../src/open_payments/auth/middleware.test.ts | 36 +++++++++++++++++++ .../src/open_payments/auth/middleware.ts | 6 +--- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/open_payments/auth/middleware.test.ts b/packages/backend/src/open_payments/auth/middleware.test.ts index e227aed7dd..39fffc8a10 100644 --- a/packages/backend/src/open_payments/auth/middleware.test.ts +++ b/packages/backend/src/open_payments/auth/middleware.test.ts @@ -75,6 +75,42 @@ describe('Auth Middleware', (): void => { await appContainer.shutdown() }) + describe('bypassError option', (): void => { + test('calls next for HTTP errors', async (): Promise => { + const middleware = createTokenIntrospectionMiddleware({ + requestType: type, + requestAction: action, + bypassError: true + }) + ctx.request.headers.authorization = '' + + const throwSpy = jest.spyOn(ctx, 'throw') + await expect(middleware(ctx, next)).resolves.toBeUndefined() + expect(throwSpy).toHaveBeenCalledWith(401, 'Unauthorized') + expect(next).toHaveBeenCalled() + }) + + test('throws error for unkonwn errors', async (): Promise => { + const middleware = createTokenIntrospectionMiddleware({ + requestType: type, + requestAction: action, + bypassError: true + }) + ctx.request.headers.authorization = '' + const error = new Error('Unknown') + ctx.throw = jest + .fn() + .mockImplementation( + (message: string, code?: number, properties?: {}) => { + throw error + } + ) as never + + await expect(middleware(ctx, next)).rejects.toBe(error) + expect(next).not.toHaveBeenCalled() + }) + }) + test.each` authorization | description ${undefined} | ${'missing'} diff --git a/packages/backend/src/open_payments/auth/middleware.ts b/packages/backend/src/open_payments/auth/middleware.ts index 23eb77753e..958aa05a32 100644 --- a/packages/backend/src/open_payments/auth/middleware.ts +++ b/packages/backend/src/open_payments/auth/middleware.ts @@ -126,11 +126,7 @@ export function createTokenIntrospectionMiddleware({ } await next() } catch (err) { - if ( - bypassError && - err instanceof HttpError && - [401, 403].includes(err.status) - ) { + if (bypassError && err instanceof HttpError) { return await next() } From be142f1716faba8713e9274adff0ed9780176b0e Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 22 Sep 2023 04:44:12 -0400 Subject: [PATCH 09/13] feat(backend): add unauthed incoming payment --- postman/collections/Interledger.json | 189 +++++++++++++++------------ 1 file changed, 104 insertions(+), 85 deletions(-) diff --git a/postman/collections/Interledger.json b/postman/collections/Interledger.json index afa18bb08d..d84fea0916 100644 --- a/postman/collections/Interledger.json +++ b/postman/collections/Interledger.json @@ -285,48 +285,6 @@ }, "response": [] }, - { - "name": "Create Or Update Peer By Url", - "event": [ - { - "listen": "test", - "script": { - "id": "69d9415d-bcec-4a61-9592-9141c8a9c7e7", - "exec": [ - "const body = pm.response.json();", - "", - "pm.collectionVariables.set(\"peerId\", body.data.createOrUpdatePeerByUrl.peer.id);" - ], - "type": "text/javascript" - } - } - ], - "id": "dc9a1002-453d-4037-bd38-deb18681d78a", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "graphql", - "graphql": { - "query": "mutation CreateOrUpdatePeerByUrl ($input: CreateOrUpdatePeerByUrlInput!) {\n createOrUpdatePeerByUrl (input: $input) {\n code\n message\n success\n peer {\n id\n name\n asset {\n id\n scale\n code\n withdrawalThreshold\n }\n }\n }\n}", - "variables": "{\n \"input\": {\n \"peerUrl\": \"http://happy-life-bank-backend:3005\",\n \"assetId\": \"{{assetId}}\",\n \"addedLiquidity\": 100000\n }\n}" - } - }, - "url": { - "raw": "{{RafikiGraphqlHost}}/graphql", - "host": [ - "{{RafikiGraphqlHost}}" - ], - "path": [ - "graphql" - ] - } - }, - "response": [] - }, { "name": "Update Peer", "id": "206fa427-cf5e-4aa8-9c59-5ddb1b021720", @@ -1594,6 +1552,63 @@ }, "response": [] }, + { + "name": "Get Incoming Payment (Unauthenticated)", + "event": [ + { + "listen": "test", + "script": { + "id": "836507d4-1c20-4b2c-ad6f-b374cb57d305", + "exec": [ + "pm.test(\"Status code is 200\", function () {", + " pm.response.to.have.status(200);", + "});", + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "prerequest", + "script": { + "id": "96baae4b-4a6a-4265-b1f5-29d0f16e11ad", + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "id": "e53e9769-1a49-4d1d-a1f8-a94e54e4a4fd", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [ + { + "key": "Authorization", + "value": "GNAP {{accessToken}}", + "disabled": true + }, + { + "key": "Host", + "value": "happy-life-bank-backend" + } + ], + "url": { + "raw": "{{pfryPaymentPointer}}/incoming-payments/{{incomingPaymentId}}", + "host": [ + "{{pfryPaymentPointer}}" + ], + "path": [ + "incoming-payments", + "{{incomingPaymentId}}" + ] + } + }, + "response": [] + }, { "name": "List Incoming Payments", "event": [ @@ -2251,6 +2266,7 @@ { "listen": "test", "script": { + "id": "5abe8b73-6d8a-4748-ba9e-20661769ca18", "exec": [ "const body = pm.response.json();", "pm.collectionVariables.set(\"accessToken\", body?.access_token?.value);", @@ -2264,6 +2280,7 @@ { "listen": "prerequest", "script": { + "id": "43a627a3-dd14-45c4-bdad-99441ba521e8", "exec": [ "eval(pm.collectionVariables.get('preRequestSignaturesGrantRequest'))", "" @@ -2306,6 +2323,7 @@ { "listen": "prerequest", "script": { + "id": "1234c6e0-6ca3-4fd5-b0d4-f116564a7c70", "exec": [ "pm.collectionVariables.set(\"tomorrow\", (new Date(new Date().setDate(new Date().getDate() + 1))).toISOString());", "", @@ -2317,6 +2335,7 @@ { "listen": "test", "script": { + "id": "03d72bce-96a3-43f0-920a-7bbeae7041fe", "exec": [ "pm.test(\"Status code is 201\", function () {", " pm.response.to.have.status(201);", @@ -3624,221 +3643,221 @@ ], "variable": [ { - "id": "7bca49f8-2255-4e1e-9e3e-a792bddf7d37", + "id": "266936d6-76e4-460d-a29a-411a00cf6143", "key": "OpenPaymentsHost", "value": "http://localhost:3000" }, { - "id": "cf0b2f1f-0bf8-4efb-90d0-1a729e60c278", + "id": "d674fcb8-0411-41ea-bb7b-6ba9cac95fde", "key": "PeerOpenPaymentsHost", "value": "http://localhost:4000" }, { - "id": "dc12553e-9186-4b46-b3c1-035e4ff0b4fc", + "id": "f5263af1-3cdd-42ac-a197-6313d7e60406", "key": "OpenPaymentsAuthHost", "value": "http://localhost:3006" }, { - "id": "4899179b-2e52-4615-8363-93c442a0dc6f", + "id": "f543063c-4b28-4531-ae48-3633499baed5", "key": "PeerOpenPaymentsAuthHost", "value": "http://localhost:4006" }, { - "id": "09f92422-6952-496e-8d7e-92994e2164b5", + "id": "372f33fb-a2d3-47cd-bfdc-6cf77edfc64d", "key": "RafikiGraphqlHost", "value": "http://localhost:3001" }, { - "id": "21106488-0732-4c92-b8eb-53fa7fdeb66e", + "id": "4135489f-3910-4714-af0b-6b822368a384", "key": "RafikiAuthGraphqlHost", "value": "http://localhost:3003", "type": "string" }, { - "id": "c044d9ae-0430-4359-9c67-d6aa51447ddb", + "id": "2d322304-7b78-4f2a-ae11-57e6cac2195c", "key": "PeerGraphqlHost", "value": "http://localhost:4001" }, { - "id": "290af3fa-0b10-462d-8547-be99f816db33", + "id": "5ecfa931-5bba-4732-a77a-33fd71220ab4", "key": "PeerAuthGraphqlHost", "value": "http://localhost:4003", "type": "string" }, { - "id": "ca873ee6-a434-4423-ac95-66ed1846f651", + "id": "5f1fd5cc-2558-4da2-b074-f497641daa4e", "key": "SignatureHost", "value": "http://localhost:3040" }, { - "id": "d32df9ea-eca8-47b9-869b-740f83e94659", + "id": "c265d85a-ef39-4bb7-a048-62f15e61e599", "key": "PeerSignatureHost", "value": "http://localhost:3041" }, { - "id": "054de5da-ddd7-4572-9e33-1d0d9d7994bd", + "id": "2b55a26c-9b78-48ab-b7e6-9949dd48a777", "key": "gfranklinPaymentPointer", "value": "http://localhost:3000/accounts/gfranklin" }, { - "id": "3499a0c4-6f52-4aa9-a925-86c01a2fb9b7", + "id": "ae1facfa-22bb-421d-bc77-7bcd864f6281", "key": "asmithPaymentPointer", "value": "http://localhost:4000/accounts/asmith", "type": "string" }, { - "id": "fd54617f-1502-46d9-b61b-9ea9cace8e64", + "id": "9ed12506-78af-467a-bdf7-f85d1f0922f5", "key": "gfranklinPaymentPointerId", "value": "6abd8671-72d0-41ee-8e0a-0b28eff522a5", "type": "string" }, { - "id": "c74fbc09-5f9d-451b-a34c-23c711e94bfb", + "id": "4afb465c-0bda-48ee-b16f-5ab0156f600c", "key": "pfryPaymentPointer", "value": "http://localhost:4000/accounts/pfry" }, { - "id": "3fd8f075-0845-41a9-829b-d10efe543c04", + "id": "ba69fc63-d815-43de-b842-f18a6eb68865", "key": "accessToken", "value": "example-access-token" }, { - "id": "e4f1730e-e722-46b9-a416-7669eaaad3c9", + "id": "dde7b140-9bc5-4b53-a371-535e3af0f3e3", "key": "paymentPointerId", "value": "bde8da93-9bff-4251-90ce-abb15e7d444c" }, { - "id": "0bcac358-db98-4839-a806-91f556a6b003", + "id": "a97df656-e6f7-40d4-9e55-0a219b26bba8", "key": "incomingPaymentId", "value": "dc7b71a5-d78e-450c-b4c5-7db299f2a133" }, { - "id": "5bbd0d1b-5db2-4d12-9deb-7181bef36f55", + "id": "81e722ff-9035-4a6f-9cba-fc2d2e5be49a", "key": "connectionId", "value": "23de6081-b340-4334-b335-a2328bbb3334" }, { - "id": "8f5b498d-7d5e-4417-8d32-0c49b4b3a832", + "id": "0ef0da05-6a9f-48e9-b6a5-62177b47c12d", "key": "outgoingPaymentId", "value": "empty" }, { - "id": "f3338ce8-95d7-44ef-ba64-9da305900635", + "id": "7252b12e-d186-42ba-9ada-fbb9567a406c", "key": "quoteId", "value": "http://backend/d982ab2f-4380-4d05-be3b-43d23a57d23f/quotes/8adaa7f9-fbd2-4cc2-89b5-6f354d68728e" }, { - "id": "81aa0faf-aaa1-466e-aae9-e46b993cf0f8", + "id": "34854223-b49a-4a4f-bdb0-9b1730ccacc3", "key": "tokenId", "value": "f6cf615c-70e8-4b04-bd86-0d6ac41a4c85" }, { - "id": "d7e2ec43-7ef9-4237-abf3-51e3619c4567", + "id": "fa59bbcd-1430-46be-b515-06bc4a987aea", "key": "tomorrow", "value": "" }, { - "id": "c5f8746d-882c-4643-8453-bb78955fc7fd", + "id": "78b3b0f8-483d-47fc-8cea-2b6890b51fb6", "key": "uniquePaymentPointer", "value": "" }, { - "id": "fc9f2abb-0ecb-4ec3-8d62-cecac50e691f", + "id": "77d3c2d4-366a-4894-8610-7b4b281043f3", "key": "quoteUrl", "value": "" }, { - "id": "70a748e2-5ade-4d7f-a0cc-b8b4b4aa647f", + "id": "3b6431fe-742f-4783-a282-d71e2884f765", "key": "incomingPaymentUrl", "value": "" }, { - "id": "76c0881b-296b-4ade-b7e4-727a842e6971", + "id": "87eef7e9-09bf-418d-80c9-d4ab6a5a7e15", "key": "continueToken", "value": "" }, { - "id": "125fab79-62cb-45d0-bf18-9a18255172bf", + "id": "49590127-133c-4506-8489-ab0d7c031143", "key": "continueId", "value": "" }, { - "id": "c7009ec1-a6ee-4955-9c98-4bb9bb6eb0aa", + "id": "827ed42b-36d7-49db-8605-91fa4b037270", "key": "paymentPointerUrl", "value": "" }, { - "id": "68591d65-91d4-47de-a3c6-c3449678287f", + "id": "abc16ecc-ac7c-4d4f-9950-96d916ccba8b", "key": "createPaymentPointerRequest", "value": "" }, { - "id": "04a56d1b-8f43-4cfd-8786-213077e53a83", + "id": "135eb1a7-23b9-438d-b880-c967b6063078", "key": "secondPaymentPointerId", "value": "" }, { - "id": "016d6f61-73dd-4886-8086-4929003f4216", + "id": "1a730c5c-0755-4cf6-96df-642bc1dbc030", "key": "assetId", "value": "" }, { - "id": "f33bd6de-de53-43d4-91e0-c196f721db11", + "id": "53fb7a93-e372-45c8-b759-f77fc526fc37", "key": "preRequestSignatures", "value": "const requestUrl = request.url\n .replace(/{{([A-Za-z]\\w+)}}/g, (_, key) => pm.collectionVariables.get(key))\n .replace(/localhost:([3,4])000/g, (_, key) =>\n key === '3' ? 'cloud-nine-wallet-backend' : 'happy-life-bank-backend'\n )\nconst requestBody =\n request.method === 'POST' && Object.keys(request.data).length !== 0\n ? request.data.replace(/{{([A-Za-z]\\w+)}}/g, (_, key) =>\n pm.collectionVariables.get(key)\n )\n : undefined\nconst requestHeaders = JSON.parse(\n JSON.stringify(request.headers).replace(/{{([A-Za-z]\\w+)}}/g, (_, key) =>\n pm.collectionVariables.get(key)\n )\n)\n// Request Signature Headers\npm.sendRequest(\n {\n url: pm.collectionVariables.get('signatureUrl'),\n method: 'POST',\n header: {\n 'content-type': 'application/json'\n },\n body: {\n mode: 'raw',\n raw: JSON.stringify({\n keyId: pm.collectionVariables.get('keyId'),\n request: {\n url: requestUrl,\n method: request.method,\n headers: requestHeaders,\n body: requestBody\n }\n })\n }\n },\n (_, res) => {\n const headers = res.json()\n for (let [key, value] of Object.entries(headers)) {\n pm.request.headers.add({ key, value })\n }\n }\n)\n" }, { - "id": "609fc40b-b729-4bf8-84f8-3901fa6f75f6", + "id": "7d0fb16c-dfde-4b08-b5af-81274df89253", "key": "preRequestSignaturesGrantRequest", "value": "const url = require('url')\n\nconst body = JSON.parse(request.data)\nconst client = url.parse(body.client)\nconst jwkUrl = `http://localhost:${\n client.host === 'cloud-nine-wallet-backend' ? '3' : '4'\n}000${client.path}/jwks.json`\npm.collectionVariables.set(\n 'signatureUrl',\n pm.collectionVariables.get(\n client.host === 'cloud-nine-wallet-backend'\n ? 'SignatureHost'\n : 'PeerSignatureHost'\n )\n)\n\nconst requestUrl = request.url.replace(/{{([A-Za-z]\\w+)}}/g, (_, key) =>\n pm.collectionVariables.get(key)\n)\nconst requestBody = request.data.replace(/{{([A-Za-z]\\w+)}}/g, (_, key) =>\n pm.collectionVariables.get(key)\n)\nconst requestHeaders = JSON.parse(\n JSON.stringify(request.headers).replace(/{{([A-Za-z]\\w+)}}/g, (_, key) =>\n pm.collectionVariables.get(key)\n )\n)\n\n// Request Client JWK\npm.sendRequest(\n {\n url: jwkUrl,\n method: 'GET',\n header: {\n Host: client.host\n }\n },\n (err, res) => {\n const keys = res.json()\n pm.collectionVariables.set('keyId', keys.keys[0].kid)\n\n // Request Signature Headers\n pm.sendRequest(\n {\n url: pm.collectionVariables.get('signatureUrl'),\n method: 'POST',\n header: {\n 'content-type': 'application/json'\n },\n body: {\n mode: 'raw',\n raw: JSON.stringify({\n keyId: pm.collectionVariables.get('keyId'),\n request: {\n url: requestUrl,\n method: request.method,\n headers: requestHeaders,\n body: requestBody\n }\n })\n }\n },\n (_, res) => {\n const headers = res.json()\n for (let [key, value] of Object.entries(headers)) {\n pm.request.headers.add({ key, value })\n }\n }\n )\n }\n)\n" }, { - "id": "fc1134c6-3530-42aa-ad7f-e5c2eaf139f9", + "id": "f1348533-0aa1-4988-aa05-70352ff3b0de", "key": "signatureUrl", "value": "" }, { - "id": "374278d5-b71e-40f0-80ae-64966999a2d5", + "id": "b6d04985-041c-40b8-a512-4b767c06a170", "key": "keyId", "value": "" }, { - "id": "f58a7498-3f2f-4c63-ac39-1ec69479498c", + "id": "447f3038-1593-44e6-a83b-d69142b0c18c", "key": "peerId", "value": "" }, { - "id": "3d8e60e7-ad84-4f17-b549-6bd918e42efc", + "id": "5469c453-5a1b-4340-b738-afcc1d522975", "key": "paymentPointerKeyId", "value": "" }, { - "id": "1f570601-8d5d-4919-9b6f-2006a223123c", + "id": "bba57e9b-334d-4d19-a51b-262ddc9d2a94", "key": "secureOpenPaymentsHost", "value": "" }, { - "id": "875c5100-9a58-4bdb-9c61-84ec262aa62b", + "id": "0b2ca6c2-6897-42c5-9705-c0954432a759", "key": "tokenManagementUrl", "value": "" }, { - "id": "42417819-bef4-436b-96dd-cb8f19e19aaf", + "id": "ebc99a02-3d0b-4dca-856d-f1a5850415a5", "key": "quoteDebitAmountValue", "value": "" }, { - "id": "18477869-6a28-4de0-8fb5-aebedad94ad4", + "id": "1174469c-5c59-4a72-b5fe-a828f0702561", "key": "quoteReceiveAmountValue", "value": "" }, { - "id": "1c265993-5da1-44a3-abde-d2427bbe5c7f", + "id": "5245c9eb-434c-4f89-b61a-5571bcea3880", "key": "idempotencyKey", "value": "" }, { - "id": "64018552-bfff-44d6-bc45-95ac05f848a7", + "id": "611442dd-dcd5-4071-9ca0-8e09666c349b", "key": "receiverId", "value": "" } From d5be86611c38a42db4b301b9cc9e3d4cf6f9ba9c Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 22 Sep 2023 11:52:30 +0300 Subject: [PATCH 10/13] fix: accidentally removed postman request --- postman/collections/Interledger.json | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/postman/collections/Interledger.json b/postman/collections/Interledger.json index d84fea0916..da7e94846a 100644 --- a/postman/collections/Interledger.json +++ b/postman/collections/Interledger.json @@ -285,6 +285,48 @@ }, "response": [] }, + { + "name": "Create Or Update Peer By Url", + "event": [ + { + "listen": "test", + "script": { + "id": "69d9415d-bcec-4a61-9592-9141c8a9c7e7", + "exec": [ + "const body = pm.response.json();", + "", + "pm.collectionVariables.set(\"peerId\", body.data.createOrUpdatePeerByUrl.peer.id);" + ], + "type": "text/javascript" + } + } + ], + "id": "dc9a1002-453d-4037-bd38-deb18681d78a", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "graphql", + "graphql": { + "query": "mutation CreateOrUpdatePeerByUrl ($input: CreateOrUpdatePeerByUrlInput!) {\n createOrUpdatePeerByUrl (input: $input) {\n code\n message\n success\n peer {\n id\n name\n asset {\n id\n scale\n code\n withdrawalThreshold\n }\n }\n }\n}", + "variables": "{\n \"input\": {\n \"peerUrl\": \"http://happy-life-bank-backend:3005\",\n \"assetId\": \"{{assetId}}\",\n \"addedLiquidity\": 100000\n }\n}" + } + }, + "url": { + "raw": "{{RafikiGraphqlHost}}/graphql", + "host": [ + "{{RafikiGraphqlHost}}" + ], + "path": [ + "graphql" + ] + } + }, + "response": [] + }, { "name": "Update Peer", "id": "206fa427-cf5e-4aa8-9c59-5ddb1b021720", From 0fc40c2b4d7aeeec6f8ae2b70e4f9ebba5fe4987 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 22 Sep 2023 12:03:55 +0300 Subject: [PATCH 11/13] refactor(backend): optional sig params --- openapi/resource-server.yaml | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/openapi/resource-server.yaml b/openapi/resource-server.yaml index a8491219b6..1e455d31d7 100644 --- a/openapi/resource-server.yaml +++ b/openapi/resource-server.yaml @@ -649,10 +649,10 @@ paths: $ref: '#/components/responses/403' '404': description: Incoming Payment Not Found + parameters: + - $ref: '#/components/parameters/optional-signature-input' + - $ref: '#/components/parameters/optional-signature' description: A client can fetch the latest state of an incoming payment to determine the amount received into the payment pointer. - # parameters: - # - $ref: '#/components/parameters/signature-input' - # - $ref: '#/components/parameters/signature' parameters: - $ref: '#/components/parameters/id' '/incoming-payments/{id}/complete': @@ -1040,7 +1040,6 @@ components: properties: receiveAmount: $ref: ./schemas.yaml#/components/schemas/amount - # description: The total amount that should be received by the receiver when the associated outgoing payment has been paid. unresolvedProperites: false outgoing-payment: @@ -1334,17 +1333,29 @@ components: name: Signature in: header schema: - type: string - example: 'Signature: sig1=:EWJgAONk3D6542Scj8g51rYeMHw96cH2XiCMxcyL511wyemGcw==:' + $ref: '#/components/parameters/optional-signature' description: 'The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK.' required: true signature-input: + name: Signature-Input + in: header + schema: + $ref: '#/components/parameters/optional-signature-input' + description: 'The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member''s key is the label that uniquely identifies the message signature within the context of the HTTP message. The member''s value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization". When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details.' + required: true + optional-signature: + name: Signature + in: header + schema: + type: string + example: 'Signature: sig1=:EWJgAONk3D6542Scj8g51rYeMHw96cH2XiCMxcyL511wyemGcw==:' + description: 'The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK.' + optional-signature-input: name: Signature-Input in: header schema: type: string example: 'Signature-Input: sig1=("@method" "@target-uri" "content-digest" "content-length" "content-type");created=1618884473;keyid="gnap-rsa"' description: 'The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member''s key is the label that uniquely identifies the message signature within the context of the HTTP message. The member''s value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization". When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details.' - required: true security: - GNAP: [] From 7650f2fec4d3f474304a7008b0cb3f1884deeb45 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 22 Sep 2023 12:05:16 +0300 Subject: [PATCH 12/13] fix(backend): typo --- packages/backend/src/app.ts | 2 +- packages/backend/src/open_payments/auth/middleware.test.ts | 6 +++--- packages/backend/src/open_payments/auth/middleware.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/app.ts b/packages/backend/src/app.ts index 67abf20022..a950040379 100644 --- a/packages/backend/src/app.ts +++ b/packages/backend/src/app.ts @@ -130,7 +130,7 @@ export type HttpSigContext = AppContext & { client: string } -export type HttpSigWithAuthenticatedStausContext = HttpSigContext & +export type HttpSigWithAuthenticatedStatusContext = HttpSigContext & AuthenticatedStatusContext // Payment pointer subresources diff --git a/packages/backend/src/open_payments/auth/middleware.test.ts b/packages/backend/src/open_payments/auth/middleware.test.ts index 39fffc8a10..ba56ffa79b 100644 --- a/packages/backend/src/open_payments/auth/middleware.test.ts +++ b/packages/backend/src/open_payments/auth/middleware.test.ts @@ -20,7 +20,7 @@ import { initIocContainer } from '../../' import { AppServices, HttpSigContext, - HttpSigWithAuthenticatedStausContext, + HttpSigWithAuthenticatedStatusContext, PaymentPointerContext } from '../../app' import { createTestApp, TestContainer } from '../../tests/app' @@ -404,7 +404,7 @@ describe('authenticatedStatusMiddleware', (): void => { }) test('sets ctx.authenticated to false if http signature is invalid', async (): Promise => { - const ctx = createContext({ + const ctx = createContext({ headers: { 'signature-input': '' } }) @@ -426,7 +426,7 @@ describe('authenticatedStatusMiddleware', (): void => { Authorization: `GNAP ${token}` } } - const ctx = createContext({ + const ctx = createContext({ headers: { Accept: 'application/json', Authorization: `GNAP ${token}`, diff --git a/packages/backend/src/open_payments/auth/middleware.ts b/packages/backend/src/open_payments/auth/middleware.ts index 958aa05a32..5107db653d 100644 --- a/packages/backend/src/open_payments/auth/middleware.ts +++ b/packages/backend/src/open_payments/auth/middleware.ts @@ -7,7 +7,7 @@ import Koa, { HttpError } from 'koa' import { Limits, parseLimits } from '../payment/outgoing/limits' import { HttpSigContext, - HttpSigWithAuthenticatedStausContext, + HttpSigWithAuthenticatedStatusContext, PaymentPointerContext } from '../../app' import { AccessAction, AccessType, JWKS } from '@interledger/open-payments' @@ -142,7 +142,7 @@ export function createTokenIntrospectionMiddleware({ } export const authenticatedStatusMiddleware = async ( - ctx: HttpSigWithAuthenticatedStausContext, + ctx: HttpSigWithAuthenticatedStatusContext, next: () => Promise ): Promise => { ctx.authenticated = false From 4e3e5ba49154c8e94afd9d1a77d304aae3b6a3cc Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Fri, 22 Sep 2023 12:07:26 +0300 Subject: [PATCH 13/13] fix(backend): unused args --- .../backend/src/open_payments/auth/middleware.test.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/open_payments/auth/middleware.test.ts b/packages/backend/src/open_payments/auth/middleware.test.ts index ba56ffa79b..c2b4ff1e9e 100644 --- a/packages/backend/src/open_payments/auth/middleware.test.ts +++ b/packages/backend/src/open_payments/auth/middleware.test.ts @@ -98,13 +98,9 @@ describe('Auth Middleware', (): void => { }) ctx.request.headers.authorization = '' const error = new Error('Unknown') - ctx.throw = jest - .fn() - .mockImplementation( - (message: string, code?: number, properties?: {}) => { - throw error - } - ) as never + ctx.throw = jest.fn().mockImplementation(() => { + throw error + }) as never await expect(middleware(ctx, next)).rejects.toBe(error) expect(next).not.toHaveBeenCalled()