diff --git a/packages/auth/src/access/utils.test.ts b/packages/auth/src/access/utils.test.ts index e4a22dcd24..351a535cf1 100644 --- a/packages/auth/src/access/utils.test.ts +++ b/packages/auth/src/access/utils.test.ts @@ -155,6 +155,66 @@ describe('Access utilities', (): void => { ).toBe(true) }) + test('Can compare an access item on a grant and an access item from a request with different action ordering', async (): Promise => { + const grantAccessItemSuperAction = await Access.query(trx).insertAndFetch({ + grantId: grant.id, + type: AccessType.OutgoingPayment, + actions: [AccessAction.Create, AccessAction.ReadAll, AccessAction.List], + identifier, + limits: { + receiver, + debitAmount: { + value: '400', + assetCode: 'USD', + assetScale: 2 + } + } + }) + + const requestAccessItem: AccessItem = { + type: 'outgoing-payment', + actions: ['read', 'list', 'create'], + identifier, + limits: { + receiver, + debitAmount: { + value: '400', + assetCode: 'USD', + assetScale: 2 + } + } + } + + expect( + compareRequestAndGrantAccessItems( + requestAccessItem, + toOpenPaymentsAccess(grantAccessItemSuperAction) + ) + ).toBe(true) + }) + + test('Can compare an access item on a grant without an identifier with a request with an identifier', async (): Promise => { + const grantAccessItemSuperAction = await Access.query(trx).insertAndFetch({ + grantId: grant.id, + type: AccessType.IncomingPayment, + actions: [AccessAction.ReadAll], + identifier: undefined + }) + + const requestAccessItem: AccessItem = { + type: 'incoming-payment', + actions: [AccessAction.ReadAll], + identifier + } + + expect( + compareRequestAndGrantAccessItems( + requestAccessItem, + toOpenPaymentsAccess(grantAccessItemSuperAction) + ) + ).toBe(true) + }) + test('access comparison fails if grant action items are insufficient', async (): Promise => { const identifier = `https://example.com/${v4()}` const receiver = @@ -206,4 +266,46 @@ describe('Access utilities', (): void => { ) ).toBe(false) }) + + test('access comparison fails if identifier mismatch', async (): Promise => { + const grantAccessItemSuperAction = await Access.query(trx).insertAndFetch({ + grantId: grant.id, + type: AccessType.IncomingPayment, + actions: [AccessAction.ReadAll], + identifier + }) + + const requestAccessItem: AccessItem = { + type: 'incoming-payment', + actions: [AccessAction.ReadAll], + identifier: `https://example.com/${v4()}` + } + + expect( + compareRequestAndGrantAccessItems( + requestAccessItem, + toOpenPaymentsAccess(grantAccessItemSuperAction) + ) + ).toBe(false) + }) + + test('access comparison fails if type mismatch', async (): Promise => { + const grantAccessItemSuperAction = await Access.query(trx).insertAndFetch({ + grantId: grant.id, + type: AccessType.Quote, + actions: [AccessAction.Read] + }) + + const requestAccessItem: AccessItem = { + type: 'incoming-payment', + actions: [AccessAction.Read] + } + + expect( + compareRequestAndGrantAccessItems( + requestAccessItem, + toOpenPaymentsAccess(grantAccessItemSuperAction) + ) + ).toBe(false) + }) }) diff --git a/packages/auth/src/access/utils.ts b/packages/auth/src/access/utils.ts index 22be1adc00..d0d3de5099 100644 --- a/packages/auth/src/access/utils.ts +++ b/packages/auth/src/access/utils.ts @@ -42,13 +42,22 @@ export function compareRequestAndGrantAccessItems( return false } - // Validate remaining keys + if (restOfRequestAccessItem.type !== restOfGrantAccessItem.type) { + return false + } + + // Validate identifier, if present on the grant + const grantAccessIdentifier = ( + restOfGrantAccessItem as OutgoingPaymentOrIncomingPaymentAccess + ).identifier + + const requestAccessIdentifier = ( + restOfRequestAccessItem as OutgoingPaymentOrIncomingPaymentAccess + ).identifier + if ( - restOfRequestAccessItem.type !== restOfGrantAccessItem.type || - (restOfRequestAccessItem as OutgoingPaymentOrIncomingPaymentAccess) - .identifier !== - (restOfGrantAccessItem as OutgoingPaymentOrIncomingPaymentAccess) - .identifier + grantAccessIdentifier && + requestAccessIdentifier !== grantAccessIdentifier ) { return false } diff --git a/packages/backend/src/open_payments/auth/middleware.test.ts b/packages/backend/src/open_payments/auth/middleware.test.ts index 5b8f38c246..96f590fc23 100644 --- a/packages/backend/src/open_payments/auth/middleware.test.ts +++ b/packages/backend/src/open_payments/auth/middleware.test.ts @@ -46,7 +46,7 @@ type IntrospectionCallObject = { access: { type: AccessType actions: AccessAction[] - identifier?: string + identifier: string }[] } @@ -193,7 +193,8 @@ describe('Auth Middleware', (): void => { access: [ { type: type, - actions: [action] + actions: [action], + identifier: ctx.walletAddressUrl } ] }) @@ -217,7 +218,8 @@ describe('Auth Middleware', (): void => { access: [ { type: type, - actions: [action] + actions: [action], + identifier: ctx.walletAddressUrl } ] }) @@ -286,15 +288,12 @@ describe('Auth Middleware', (): void => { access: [ { type, - actions: [action] + actions: [action], + identifier: ctx.walletAddressUrl } ] } - if (type === AccessType.OutgoingPayment) { - expectedCallObject.access[0].identifier = ctx.walletAddressUrl - } - expect(introspectSpy).toHaveBeenCalledWith(expectedCallObject) expect(next).not.toHaveBeenCalled() } @@ -313,7 +312,8 @@ describe('Auth Middleware', (): void => { access: [ { type, - actions: [action] + actions: [action], + identifier: ctx.walletAddressUrl } ] }) @@ -349,7 +349,8 @@ describe('Auth Middleware', (): void => { access: [ { type, - actions: [subAction] + actions: [subAction], + identifier: ctx.walletAddressUrl } ] }) @@ -376,7 +377,8 @@ describe('Auth Middleware', (): void => { access: [ { type, - actions: [superAction] + actions: [superAction], + identifier: ctx.walletAddressUrl } ] }) @@ -436,13 +438,11 @@ describe('Auth Middleware', (): void => { access: [ { type, - actions: [action] + actions: [action], + identifier: ctx.walletAddressUrl } ] } - if (type === AccessType.OutgoingPayment) { - expectedCallObject.access[0].identifier = ctx.walletAddressUrl - } expect(introspectSpy).toHaveBeenCalledWith(expectedCallObject) expect(next).toHaveBeenCalled() @@ -473,7 +473,8 @@ describe('Auth Middleware', (): void => { access: [ { type, - actions: [action] + actions: [action], + identifier: ctx.walletAddressUrl } ] }) diff --git a/packages/backend/src/open_payments/auth/middleware.ts b/packages/backend/src/open_payments/auth/middleware.ts index eee7319a29..27ed023a14 100644 --- a/packages/backend/src/open_payments/auth/middleware.ts +++ b/packages/backend/src/open_payments/auth/middleware.ts @@ -36,7 +36,7 @@ export interface Grant { export interface Access { type: string actions: AccessAction[] - identifier?: string + identifier: string } function contextToRequestLike(ctx: HttpSigContext): RequestLike { @@ -95,13 +95,11 @@ export function createTokenIntrospectionMiddleware({ tokenInfo = await tokenIntrospectionClient.introspect({ access_token: token, access: [ - requestType === AccessType.OutgoingPayment - ? toOpenPaymentsAccess( - requestType, - requestAction, - ctx.walletAddressUrl - ) - : toOpenPaymentsAccess(requestType, requestAction) + toOpenPaymentsAccess( + requestType, + requestAction, + ctx.walletAddressUrl + ) ] }) } catch (err) {