Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(backend): update wallet address middleware & error handling #2722

Merged
merged 13 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 40 additions & 29 deletions packages/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import { HttpTokenService } from './payment-method/ilp/peer-http-token/service'
import { AssetService, AssetOptions } from './asset/service'
import { AccountingService } from './accounting/service'
import { PeerService } from './payment-method/ilp/peer/service'
import { createWalletAddressMiddleware } from './open_payments/wallet_address/middleware'
import { WalletAddress } from './open_payments/wallet_address/model'
import { WalletAddressService } from './open_payments/wallet_address/service'
import {
createTokenIntrospectionMiddleware,
Expand Down Expand Up @@ -88,14 +86,22 @@ import { TelemetryService } from './telemetry/service'
import { ApolloArmor } from '@escape.tech/graphql-armor'
import { openPaymentsServerErrorMiddleware } from './open_payments/route-errors'
import { verifyApiSignature } from './shared/utils'
import { WalletAddress } from './open_payments/wallet_address/model'
import {
getWalletAddressUrlFromIncomingPayment,
getWalletAddressUrlFromOutgoingPayment,
getWalletAddressUrlFromQueryParams,
getWalletAddressUrlFromQuote,
getWalletAddressUrlFromRequestBody,
getWalletAddressForSubresource,
getWalletAddressUrlFromPath
} from './open_payments/wallet_address/middleware'

export interface AppContextData {
logger: Logger
container: AppContainer
// Set by @koa/router.
params: { [key: string]: string }
walletAddress?: WalletAddress
walletAddressUrl?: string
}

export interface ApolloContext {
Expand All @@ -111,18 +117,15 @@ export type AppRequest<ParamsT extends string = string> = Omit<
params: Record<ParamsT, string>
}

export interface WalletAddressContext extends AppContext {
walletAddress: WalletAddress
export interface WalletAddressUrlContext extends AppContext {
walletAddressUrl: string
grant?: Grant
client?: string
accessAction?: AccessAction
}

export type WalletAddressKeysContext = Omit<
WalletAddressContext,
'walletAddress'
> & {
walletAddress?: WalletAddress
export interface WalletAddressContext extends WalletAddressUrlContext {
walletAddress: WalletAddress
}

type HttpSigHeaders = Record<'signature' | 'signature-input', string>
Expand All @@ -141,7 +144,7 @@ export type HttpSigWithAuthenticatedStatusContext = HttpSigContext &
AuthenticatedStatusContext

// Wallet address subresources
interface GetCollectionQuery {
export interface GetCollectionQuery {
'wallet-address': string
}

Expand All @@ -162,7 +165,7 @@ type CollectionContext<BodyT = never, QueryT = ParsedUrlQuery> = Omit<
accessAction: NonNullable<WalletAddressContext['accessAction']>
}

type SignedCollectionContext<
export type SignedCollectionContext<
BodyT = never,
QueryT = ParsedUrlQuery
> = CollectionContext<BodyT, QueryT> & HttpSigContext
Expand Down Expand Up @@ -426,7 +429,6 @@ export class App {
// Create incoming payment
router.post<DefaultState, SignedCollectionContext<IncomingCreateBody>>(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now, each route follows as such:

  1. validate against the OpenAPI spec (since we need all of the request parameters, including the walletAddressUrl ones to be always valid)
  2. Get wallet address URL, from either request body, query params, or the resource itself
  3. do token introspection
  4. do http sig verification (if required)
  5. fetch (poll for) walletAddress via the walletAddressUrl from step 2, add it onto the ctx
  6. Call the actual route

'/incoming-payments',
createWalletAddressMiddleware(),
createValidatorMiddleware<
ContextType<SignedCollectionContext<IncomingCreateBody>>
>(
Expand All @@ -437,11 +439,13 @@ export class App {
},
validatorMiddlewareOptions
),
getWalletAddressUrlFromRequestBody,
createTokenIntrospectionMiddleware({
requestType: AccessType.IncomingPayment,
requestAction: RequestAction.Create
}),
httpsigMiddleware,
getWalletAddressForSubresource,
incomingPaymentRoutes.create
)

Expand All @@ -452,7 +456,6 @@ export class App {
SignedCollectionContext<never, GetCollectionQuery>
>(
'/incoming-payments',
createWalletAddressMiddleware(),
createValidatorMiddleware<
ContextType<SignedCollectionContext<never, GetCollectionQuery>>
>(
Expand All @@ -463,19 +466,20 @@ export class App {
},
validatorMiddlewareOptions
),
getWalletAddressUrlFromQueryParams,
createTokenIntrospectionMiddleware({
requestType: AccessType.IncomingPayment,
requestAction: RequestAction.List
}),
httpsigMiddleware,
getWalletAddressForSubresource,
incomingPaymentRoutes.list
)

// POST /outgoing-payment
// Create outgoing payment
router.post<DefaultState, SignedCollectionContext<OutgoingCreateBody>>(
'/outgoing-payments',
createWalletAddressMiddleware(),
createValidatorMiddleware<
ContextType<SignedCollectionContext<OutgoingCreateBody>>
>(
Expand All @@ -486,11 +490,13 @@ export class App {
},
validatorMiddlewareOptions
),
getWalletAddressUrlFromRequestBody,
createTokenIntrospectionMiddleware({
requestType: AccessType.OutgoingPayment,
requestAction: RequestAction.Create
}),
httpsigMiddleware,
getWalletAddressForSubresource,
outgoingPaymentRoutes.create
)

Expand All @@ -501,7 +507,6 @@ export class App {
SignedCollectionContext<never, GetCollectionQuery>
>(
'/outgoing-payments',
createWalletAddressMiddleware(),
createValidatorMiddleware<
ContextType<SignedCollectionContext<never, GetCollectionQuery>>
>(
Expand All @@ -512,19 +517,20 @@ export class App {
},
validatorMiddlewareOptions
),
getWalletAddressUrlFromQueryParams,
createTokenIntrospectionMiddleware({
requestType: AccessType.OutgoingPayment,
requestAction: RequestAction.List
}),
httpsigMiddleware,
getWalletAddressForSubresource,
outgoingPaymentRoutes.list
)

// POST /quotes
// Create quote
router.post<DefaultState, SignedCollectionContext<QuoteCreateBody>>(
'/quotes',
createWalletAddressMiddleware(),
createValidatorMiddleware<
ContextType<SignedCollectionContext<QuoteCreateBody>>
>(
Expand All @@ -535,19 +541,20 @@ export class App {
},
validatorMiddlewareOptions
),
getWalletAddressUrlFromRequestBody,
createTokenIntrospectionMiddleware({
requestType: AccessType.Quote,
requestAction: RequestAction.Create
}),
httpsigMiddleware,
getWalletAddressForSubresource,
quoteRoutes.create
)

// GET /incoming-payments/{id}
// Read incoming payment
router.get<DefaultState, SubresourceContextWithAuthenticatedStatus>(
'/incoming-payments/:id',
createWalletAddressMiddleware(),
createValidatorMiddleware<
ContextType<SubresourceContextWithAuthenticatedStatus>
>(
Expand All @@ -558,20 +565,21 @@ export class App {
},
validatorMiddlewareOptions
),
getWalletAddressUrlFromIncomingPayment,
createTokenIntrospectionMiddleware({
requestType: AccessType.IncomingPayment,
requestAction: RequestAction.Read,
bypassError: true
}),
authenticatedStatusMiddleware,
getWalletAddressForSubresource,
incomingPaymentRoutes.get
)

// POST /incoming-payments/{id}/complete
// Complete incoming payment
router.post<DefaultState, SignedSubresourceContext>(
'/incoming-payments/:id/complete',
createWalletAddressMiddleware(),
createValidatorMiddleware<ContextType<SignedSubresourceContext>>(
resourceServerSpec,
{
Expand All @@ -580,19 +588,20 @@ export class App {
},
validatorMiddlewareOptions
),
getWalletAddressUrlFromIncomingPayment,
createTokenIntrospectionMiddleware({
requestType: AccessType.IncomingPayment,
requestAction: RequestAction.Complete
}),
httpsigMiddleware,
getWalletAddressForSubresource,
incomingPaymentRoutes.complete
)

// GET /outgoing-payments/{id}
// Read outgoing payment
router.get<DefaultState, SignedSubresourceContext>(
'/outgoing-payments/:id',
createWalletAddressMiddleware(),
createValidatorMiddleware<ContextType<SignedSubresourceContext>>(
resourceServerSpec,
{
Expand All @@ -601,19 +610,20 @@ export class App {
},
validatorMiddlewareOptions
),
getWalletAddressUrlFromOutgoingPayment,
createTokenIntrospectionMiddleware({
requestType: AccessType.OutgoingPayment,
requestAction: RequestAction.Read
}),
httpsigMiddleware,
getWalletAddressForSubresource,
outgoingPaymentRoutes.get
)

// GET /quotes/{id}
// Read quote
router.get<DefaultState, SignedSubresourceContext>(
'/quotes/:id',
createWalletAddressMiddleware(),
createValidatorMiddleware<ContextType<SignedSubresourceContext>>(
resourceServerSpec,
{
Expand All @@ -622,43 +632,44 @@ export class App {
},
validatorMiddlewareOptions
),
getWalletAddressUrlFromQuote,
createTokenIntrospectionMiddleware({
requestType: AccessType.Quote,
requestAction: RequestAction.Read
}),
httpsigMiddleware,
getWalletAddressForSubresource,
quoteRoutes.get
)

router.get(
WALLET_ADDRESS_PATH + '/jwks.json',
createWalletAddressMiddleware(),
createValidatorMiddleware<WalletAddressKeysContext>(
createValidatorMiddleware(
walletAddressServerSpec,
{
path: '/jwks.json',
method: HttpMethod.GET
},
validatorMiddlewareOptions
),
async (ctx: WalletAddressKeysContext): Promise<void> =>
await walletAddressKeyRoutes.getKeysByWalletAddressId(ctx)
getWalletAddressUrlFromPath,
walletAddressKeyRoutes.get
)

// Add the wallet address query route last.
// Otherwise it will be matched instead of other Open Payments endpoints.
router.get(
WALLET_ADDRESS_PATH,
createWalletAddressMiddleware(),
createSpspMiddleware(this.config.spspEnabled),
createValidatorMiddleware<WalletAddressContext>(
createValidatorMiddleware(
walletAddressServerSpec,
{
path: '/',
method: HttpMethod.GET
},
validatorMiddlewareOptions
),
getWalletAddressUrlFromPath,
createSpspMiddleware(this.config.spspEnabled),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do not call getWalletAddressForSubresource here and in the jwks.json route since we have different behaviours in these routes for handling deps.config.walletAddressUrl === ctx.walletAddressUrl

walletAddressRoutes.get
)

Expand Down
5 changes: 2 additions & 3 deletions packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,9 @@ export function initIocContainer(
})
})
container.singleton('walletAddressRoutes', async (deps) => {
const config = await deps.use('config')
return createWalletAddressRoutes({
authServer: config.authServerGrantUrl,
resourceServer: config.openPaymentsUrl
config: await deps.use('config'),
walletAddressService: await deps.use('walletAddressService')
})
})
container.singleton('walletAddressKeyRoutes', async (deps) => {
Expand Down
8 changes: 4 additions & 4 deletions packages/backend/src/open_payments/auth/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
AppServices,
HttpSigContext,
HttpSigWithAuthenticatedStatusContext,
WalletAddressContext
WalletAddressUrlContext
} from '../../app'
import { createTestApp, TestContainer } from '../../tests/app'
import { createContext } from '../../tests/context'
Expand All @@ -34,7 +34,7 @@ import assert from 'assert'
const nock = (global as unknown as { nock: typeof import('nock') }).nock

type AppMiddleware = (
ctx: WalletAddressContext,
ctx: WalletAddressUrlContext,
next: () => Promise<void>
) => Promise<void>

Expand All @@ -45,7 +45,7 @@ describe('Auth Middleware', (): void => {
let deps: IocContract<AppServices>
let appContainer: TestContainer
let middleware: AppMiddleware
let ctx: WalletAddressContext
let ctx: WalletAddressUrlContext
let tokenIntrospectionClient: Client

const type = AccessType.IncomingPayment
Expand Down Expand Up @@ -210,7 +210,7 @@ describe('Auth Middleware', (): void => {

beforeEach(async (): Promise<void> => {
if (identifierOption === IdentifierOption.Matching) {
identifier = ctx.walletAddress.url
identifier = ctx.walletAddressUrl
} else if (identifierOption === IdentifierOption.Conflicting) {
identifier = faker.internet.url({ appendSlash: false })
}
Expand Down
6 changes: 3 additions & 3 deletions packages/backend/src/open_payments/auth/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Limits, parseLimits } from '../payment/outgoing/limits'
import {
HttpSigContext,
HttpSigWithAuthenticatedStatusContext,
WalletAddressContext
WalletAddressUrlContext
} from '../../app'
import {
AccessAction,
Expand Down Expand Up @@ -62,7 +62,7 @@ export function createTokenIntrospectionMiddleware({
bypassError?: boolean
}) {
return async (
ctx: WalletAddressContext,
ctx: WalletAddressUrlContext,
next: () => Promise<void>
): Promise<void> => {
const config = await ctx.container.use('config')
Expand Down Expand Up @@ -95,7 +95,7 @@ export function createTokenIntrospectionMiddleware({
const access = tokenInfo.access.find((access: Access) => {
if (
access.type !== requestType ||
(access.identifier && access.identifier !== ctx.walletAddress.url)
(access.identifier && access.identifier !== ctx.walletAddressUrl)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at this point, in the token introspection middleware, we only have ctx.walletAddressUrl in the context

) {
return false
}
Expand Down
Loading