From 00d36bd947dbb05ee019075788ce91236a621003 Mon Sep 17 00:00:00 2001 From: samuel Date: Mon, 13 May 2024 12:32:39 +0200 Subject: [PATCH] Fixes after CR --- README.md | 5 ++- .../__test__/unit/evaluation.util.spec.ts | 42 +++++++++++++++++++ .../src/shared/testing/evaluation.testing.ts | 27 +++++++++++- apps/vault/src/main.constant.ts | 1 - .../http/rest/controller/import.controller.ts | 3 +- .../src/lib/__test__/unit/verify.spec.ts | 25 ++--------- packages/signature/src/lib/types.ts | 15 ++++--- packages/signature/src/lib/verify.ts | 20 ++++----- 8 files changed, 95 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 1f4d5598f..3cd07d178 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,8 @@ npx nx g @nx/nest:application --tags type:application For more information about code generation, please refer to the [NX documentation](https://nx.dev/nx-api/nx). - # NPM Auth Variables + The `.npmrc` file is needed for private registry credentials. This file is NOT in git, but it's necessary for the build @@ -99,10 +99,13 @@ This file is NOT in git, but it's necessary for the build # Troubleshooting ## Docker + Common issues you might encounter w/ docker ### DB URL in env variable fails when using `docker run`, but works when running outside docker + If using `docker run --env-file .env ...`, the env file cannot include quotes around values. The quotes will be included in the value. ### Localhost postgres url cannot connect + Inside docker, localhost points to the container not your computer. Change `localhost` to `host.docker.internal` to reference your computer's localhost ip. diff --git a/apps/policy-engine/src/open-policy-agent/core/util/__test__/unit/evaluation.util.spec.ts b/apps/policy-engine/src/open-policy-agent/core/util/__test__/unit/evaluation.util.spec.ts index 81722955b..e8fe525d3 100644 --- a/apps/policy-engine/src/open-policy-agent/core/util/__test__/unit/evaluation.util.spec.ts +++ b/apps/policy-engine/src/open-policy-agent/core/util/__test__/unit/evaluation.util.spec.ts @@ -2,6 +2,7 @@ import { Action, EvaluationRequest, FIXTURE, + GrantPermissionAction, SignMessageAction, SignRawAction, SignTransactionAction, @@ -9,6 +10,7 @@ import { } from '@narval/policy-engine-shared' import { InputType, decode } from '@narval/transaction-request-intent' import { + generateGrantPermissionRequest, generateSignMessageRequest, generateSignRawRequest, generateSignTransactionRequest, @@ -201,6 +203,46 @@ describe('toInput', () => { expect(input.approvals).toEqual(approvals) }) }) + + describe(`when action is ${Action.GRANT_PERMISSION}`, () => { + let evaluation: EvaluationRequest + + beforeEach(async () => { + evaluation = await generateGrantPermissionRequest() + }) + + it('maps action', () => { + const input = toInput({ evaluation, principal, approvals }) + + expect(input.action).toEqual(Action.GRANT_PERMISSION) + }) + + it('maps principal', () => { + const input = toInput({ evaluation, principal, approvals }) + + expect(input.principal).toEqual(principal) + }) + + it('maps resource', () => { + const input = toInput({ evaluation, principal, approvals }) + const request = evaluation.request as GrantPermissionAction + + expect(input.resource).toEqual({ uid: request.resourceId }) + }) + + it('maps approvals', () => { + const input = toInput({ evaluation, principal, approvals }) + + expect(input.approvals).toEqual(approvals) + }) + + it('maps permissions', async () => { + const input = toInput({ evaluation, principal, approvals }) + const request = evaluation.request as GrantPermissionAction + + expect(input.permissions).toEqual(request.permissions) + }) + }) }) describe('toData', () => { diff --git a/apps/policy-engine/src/shared/testing/evaluation.testing.ts b/apps/policy-engine/src/shared/testing/evaluation.testing.ts index 718964d3d..7fcb3dbfb 100644 --- a/apps/policy-engine/src/shared/testing/evaluation.testing.ts +++ b/apps/policy-engine/src/shared/testing/evaluation.testing.ts @@ -1,4 +1,12 @@ -import { Action, EvaluationRequest, FIXTURE, Request, TransactionRequest } from '@narval/policy-engine-shared' +import { + Action, + EvaluationRequest, + FIXTURE, + Permission, + Request, + Resource, + TransactionRequest +} from '@narval/policy-engine-shared' import { Alg, Payload, hash, privateKeyToJwk, signJwt } from '@narval/signature' import { randomBytes } from 'crypto' import { UNSAFE_PRIVATE_KEY } from 'packages/policy-engine-shared/src/lib/dev.fixture' @@ -155,3 +163,20 @@ export const generateSignTypedDataRequest = async (): Promise approvals: [bobSignature, carolSignature] } } + +export const generateGrantPermissionRequest = async (): Promise => { + const request: Request = { + action: Action.GRANT_PERMISSION, + nonce: uuid(), + resourceId: Resource.VAULT, + permissions: [Permission.WALLET_CREATE, Permission.WALLET_READ, Permission.WALLET_IMPORT] + } + + const { aliceSignature, bobSignature, carolSignature } = await sign(request) + + return { + authentication: aliceSignature, + request, + approvals: [bobSignature, carolSignature] + } +} diff --git a/apps/vault/src/main.constant.ts b/apps/vault/src/main.constant.ts index 94d035402..18cdcd608 100644 --- a/apps/vault/src/main.constant.ts +++ b/apps/vault/src/main.constant.ts @@ -2,7 +2,6 @@ import { RawAesWrappingSuiteIdentifier } from '@aws-crypto/client-node' export const REQUEST_HEADER_API_KEY = 'x-api-key' export const REQUEST_HEADER_CLIENT_ID = 'x-client-id' -export const REQUEST_ADMIN_TOKEN = 'x-admin-token' export const ENCRYPTION_KEY_NAMESPACE = 'armory.vault' export const ENCRYPTION_KEY_NAME = 'storage-encryption' diff --git a/apps/vault/src/vault/http/rest/controller/import.controller.ts b/apps/vault/src/vault/http/rest/controller/import.controller.ts index 1719ec60c..c4c438c1e 100644 --- a/apps/vault/src/vault/http/rest/controller/import.controller.ts +++ b/apps/vault/src/vault/http/rest/controller/import.controller.ts @@ -1,3 +1,4 @@ +import { Permission } from '@narval/policy-engine-shared' import { Body, Controller, HttpStatus, Post, UseGuards } from '@nestjs/common' import { ClientId } from '../../../../shared/decorator/client-id.decorator' import { Permissions } from '../../../../shared/decorator/permissions.decorator' @@ -9,7 +10,7 @@ import { ImportPrivateKeyDto } from '../dto/import-private-key-dto' import { ImportPrivateKeyResponseDto } from '../dto/import-private-key-response-dto' @Controller('/import') -@Permissions(['wallet:import']) +@Permissions([Permission.WALLET_IMPORT]) @UseGuards(AuthorizationGuard) export class ImportController { constructor(private importService: ImportService) {} diff --git a/packages/signature/src/lib/__test__/unit/verify.spec.ts b/packages/signature/src/lib/__test__/unit/verify.spec.ts index 538e304b3..9ab5d12dd 100644 --- a/packages/signature/src/lib/__test__/unit/verify.spec.ts +++ b/packages/signature/src/lib/__test__/unit/verify.spec.ts @@ -798,7 +798,7 @@ describe('checkDataHash', () => { }) }) -describe('checkPermissions', () => { +describe('checkAccess', () => { it('returns true when the access is valid', () => { const payload: Payload = { access: [ @@ -819,33 +819,16 @@ describe('checkPermissions', () => { ] }) ).toEqual(true) - }) - - it('throws JwtError when the access is invalid', () => { - const payload: Payload = { - access: [ - { - resource: 'vault', - permissions: ['wallet:create', 'wallet:read'] - } - ] - } - expect(() => - checkAccess(payload, { - access: [] - }) - ).toThrow(JwtError) - - expect(() => + expect( checkAccess(payload, { access: [ { resource: 'vault', - permissions: ['wallet:create', 'foo:bar'] + permissions: ['wallet:read'] } ] }) - ).toThrow(JwtError) + ).toEqual(true) }) }) diff --git a/packages/signature/src/lib/types.ts b/packages/signature/src/lib/types.ts index dba3f77b7..24a87a23b 100644 --- a/packages/signature/src/lib/types.ts +++ b/packages/signature/src/lib/types.ts @@ -197,6 +197,12 @@ export const JwsdHeader = z.object({ }) export type JwsdHeader = z.infer +export const PayloadAccessSchema = z.object({ + resource: z.string(), + permissions: z.array(z.string()) +}) +export type PayloadAccessSchema = z.infer + /** * Defines the payload of JWT. * @@ -226,14 +232,7 @@ export const Payload = z.intersection( cnf: publicKeySchema.optional(), requestHash: z.string().optional(), data: z.string().optional(), - access: z - .array( - z.object({ - resource: z.string(), - permissions: z.array(z.string()) - }) - ) - .optional() + access: z.array(PayloadAccessSchema).optional() }) ) export type Payload = z.infer diff --git a/packages/signature/src/lib/verify.ts b/packages/signature/src/lib/verify.ts index 621c95161..110bf794d 100644 --- a/packages/signature/src/lib/verify.ts +++ b/packages/signature/src/lib/verify.ts @@ -130,16 +130,16 @@ export const checkDataHash = (payload: Payload, opts: JwtVerifyOptions): boolean export const checkAccess = (payload: Payload, opts: JwtVerifyOptions): boolean => { if (opts.access) { - if ( - !opts.access.length || - !payload.access || - !payload.access.length || - !opts.access.every((oa) => { - const payloadAccess = payload.access?.find((pa) => pa.resource === oa.resource) - return payloadAccess && oa.permissions.every((p) => payloadAccess.permissions.includes(p)) - }) - ) { - throw new JwtError({ message: 'Invalid permissions', context: { payload } }) + for (const access of opts.access) { + const payloadAccess = payload.access?.find( + ({ resource }) => resource.toLowerCase() === access.resource.toLowerCase() + ) + const missingPermissions = access.permissions.filter( + (permission) => !payloadAccess?.permissions.includes(permission) + ) + if (missingPermissions.length) { + throw new JwtError({ message: 'Invalid permissions', context: { payload, missingPermissions } }) + } } } return true