From 3c7fb472d71645f4f4546b28d1de4e127dc38dd4 Mon Sep 17 00:00:00 2001 From: Pierre Troger Date: Tue, 27 Aug 2024 18:40:50 +0200 Subject: [PATCH] setuped for criterion unit testing --- .../e2e/criterion/checkApprovals.spec.ts | 74 + .../e2e/criterion/checkRateLimit.spec.ts | 110 ++ .../e2e/criterion/checkSpendingLimit.spec.ts | 310 ++++ .../approvals-and-spending-limit.spec.ts | 287 ++++ .../e2e/{ => scenarii}/user-journeys.spec.ts | 20 +- .../src/lib/__test__/e2e/scenarios.spec.ts | 1244 ----------------- .../armory-sdk/src/lib/__test__/util/setup.ts | 14 +- .../src/resource/entity/default.json | 99 ++ .../admin-approval-required.json | 18 + .../checkPrincipalRole/admin-permit-all.json | 11 + .../members-2-transfer-per-day.json | 28 + .../member-can-transfer-1-eth-fixed.json | 22 + .../member-can-transfer-1-eth-rolling.json | 22 + ...-groupMember-can-transfer-1-eth-fixed.json | 22 + packages/armory-sdk/tsconfig.json | 3 +- 15 files changed, 1027 insertions(+), 1257 deletions(-) create mode 100644 packages/armory-sdk/src/lib/__test__/e2e/criterion/checkApprovals.spec.ts create mode 100644 packages/armory-sdk/src/lib/__test__/e2e/criterion/checkRateLimit.spec.ts create mode 100644 packages/armory-sdk/src/lib/__test__/e2e/criterion/checkSpendingLimit.spec.ts create mode 100644 packages/armory-sdk/src/lib/__test__/e2e/scenarii/approvals-and-spending-limit.spec.ts rename packages/armory-sdk/src/lib/__test__/e2e/{ => scenarii}/user-journeys.spec.ts (96%) delete mode 100644 packages/armory-sdk/src/lib/__test__/e2e/scenarios.spec.ts create mode 100644 packages/armory-sdk/src/resource/entity/default.json create mode 100644 packages/armory-sdk/src/resource/policy/checkApprovals/admin-approval-required.json create mode 100644 packages/armory-sdk/src/resource/policy/checkPrincipalRole/admin-permit-all.json create mode 100644 packages/armory-sdk/src/resource/policy/checkRateLimit/members-2-transfer-per-day.json create mode 100644 packages/armory-sdk/src/resource/policy/checkSpendingLimit/member-can-transfer-1-eth-fixed.json create mode 100644 packages/armory-sdk/src/resource/policy/checkSpendingLimit/member-can-transfer-1-eth-rolling.json create mode 100644 packages/armory-sdk/src/resource/policy/checkSpendingLimit/treasury-groupMember-can-transfer-1-eth-fixed.json diff --git a/packages/armory-sdk/src/lib/__test__/e2e/criterion/checkApprovals.spec.ts b/packages/armory-sdk/src/lib/__test__/e2e/criterion/checkApprovals.spec.ts new file mode 100644 index 000000000..08fe98d70 --- /dev/null +++ b/packages/armory-sdk/src/lib/__test__/e2e/criterion/checkApprovals.spec.ts @@ -0,0 +1,74 @@ +import { Action, Decision, entitiesSchema, FIXTURE, Request } from '@narval/policy-engine-shared' +import { v4 } from 'uuid' +import defaultEntities from '../../../../resource/entity/default.json' +import adminApproval from '../../../../resource/policy/checkApprovals/admin-approval-required.json' +import adminPermitAll from '../../../../resource/policy/checkPrincipalRole/admin-permit-all.json' +import { + buildAuthClient, + buildPolicy, + createClient, + genNonce, + getAuthAdminApiKey, + getAuthHost, + saveDataStore +} from '../../util/setup' + +const systemManagerHexPk = FIXTURE.UNSAFE_PRIVATE_KEY.Root +const bobPrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Bob +const alicePrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Alice + +describe('user entity approval', () => { + const request: Request = { + action: Action.SIGN_TRANSACTION, + nonce: 'test-nonce-1', + transactionRequest: { + from: '0x0301e2724a40E934Cce3345928b88956901aA127', + to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', + value: '0xde0b6b3a7640000', + chainId: 1 + }, + resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127' + } + // Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db. + const clientId = v4() + + beforeAll(async () => { + const entities = entitiesSchema.parse(defaultEntities) + + await createClient(systemManagerHexPk, { + clientId, + authHost: getAuthHost(), + authAdminApiKey: getAuthAdminApiKey() + }) + + const policies = buildPolicy([adminPermitAll, adminApproval]) + + await saveDataStore(systemManagerHexPk, { + clientId, + host: getAuthHost(), + entities, + policies + }) + }) + + it('get an accessToken after approval from an admin', async () => { + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + + const { decision, authId } = await authClient.authorize(genNonce(request)) + expect(decision).toBe(Decision.CONFIRM) + + const { authClient: adminClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + + await adminClient.approve(authId) + + const accessToken = await authClient.getAccessToken(authId) + + expect(accessToken).toMatchObject({ value: expect.any(String) }) + }) +}) diff --git a/packages/armory-sdk/src/lib/__test__/e2e/criterion/checkRateLimit.spec.ts b/packages/armory-sdk/src/lib/__test__/e2e/criterion/checkRateLimit.spec.ts new file mode 100644 index 000000000..3bf9c7a77 --- /dev/null +++ b/packages/armory-sdk/src/lib/__test__/e2e/criterion/checkRateLimit.spec.ts @@ -0,0 +1,110 @@ +import { Action, entitiesSchema, FIXTURE, Request } from '@narval/policy-engine-shared' +import { v4 } from 'uuid' +import defaultEntities from '../../../../resource/entity/default.json' +import adminPermitAll from '../../../../resource/policy/checkPrincipalRole/admin-permit-all.json' +import memberTwoTransferPerDay from '../../../../resource/policy/checkRateLimit/members-2-transfer-per-day.json' +import { + buildAuthClient, + buildPolicy, + createClient, + genNonce, + getAuthAdminApiKey, + getAuthHost, + saveDataStore +} from '../../util/setup' + +const systemManagerHexPk = FIXTURE.UNSAFE_PRIVATE_KEY.Root +const bobPrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Bob +const alicePrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Alice + +describe('rate limiting by principal', () => { + const request: Request = { + action: Action.SIGN_TRANSACTION, + nonce: 'test-nonce-1', + transactionRequest: { + from: '0x0301e2724a40E934Cce3345928b88956901aA127', + to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', + value: '0xde0b6b3a7640000', + chainId: 1 + }, + resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127' + } + // Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db. + const clientId = v4() + + beforeAll(async () => { + const entities = entitiesSchema.parse(defaultEntities) + + await createClient(systemManagerHexPk, { + clientId, + authHost: getAuthHost(), + authAdminApiKey: getAuthAdminApiKey() + }) + + const policies = buildPolicy([adminPermitAll, memberTwoTransferPerDay]) + + await saveDataStore(systemManagerHexPk, { + clientId, + host: getAuthHost(), + entities, + policies + }) + }) + + it('alice-admin does a transfer that is not counted against the rate limit', async () => { + const { authClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits member bob to do a first transfer', async () => { + // First transfer + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits member bob to do a second transfer', async () => { + // Second transfer + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('forbids member bob to do a third transfer', async () => { + expect.assertions(1) + // Third transfer + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + + try { + await authClient.requestAccessToken(genNonce(request)) + } catch (error: any) { + expect(error.message).toEqual('Unauthorized') + } + }) + + it('permits admin alice to do a transfer', async () => { + const { authClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) +}) diff --git a/packages/armory-sdk/src/lib/__test__/e2e/criterion/checkSpendingLimit.spec.ts b/packages/armory-sdk/src/lib/__test__/e2e/criterion/checkSpendingLimit.spec.ts new file mode 100644 index 000000000..57afab136 --- /dev/null +++ b/packages/armory-sdk/src/lib/__test__/e2e/criterion/checkSpendingLimit.spec.ts @@ -0,0 +1,310 @@ +import { Action, entitiesSchema, FIXTURE, Request } from '@narval/policy-engine-shared' +import { v4 } from 'uuid' +import defaultEntities from '../../../../resource/entity/default.json' +import adminPermitAll from '../../../../resource/policy/checkPrincipalRole/admin-permit-all.json' +import memberCanTransferOneEthFixed from '../../../../resource/policy/checkSpendingLimit/member-can-transfer-1-eth-fixed.json' +import memberCanTransferOneEthRolling from '../../../../resource/policy/checkSpendingLimit/member-can-transfer-1-eth-rolling.json' +import treasuryMemberCanTransferOneEth from '../../../../resource/policy/checkSpendingLimit/treasury-groupMember-can-transfer-1-eth-fixed.json' +import { + buildAuthClient, + buildPolicy, + createClient, + genNonce, + getAuthAdminApiKey, + getAuthHost, + saveDataStore +} from '../../util/setup' + +const systemManagerHexPk = FIXTURE.UNSAFE_PRIVATE_KEY.Root +const bobPrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Bob +const alicePrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Alice +const carolPrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Carol +describe('by groupId', () => { + const request: Request = { + action: Action.SIGN_TRANSACTION, + nonce: 'test-nonce-2', + transactionRequest: { + from: '0x0301e2724a40E934Cce3345928b88956901aA127', + to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', + value: '0x58D15E176280000', // 0.4 ETH + chainId: 1 + }, + resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127' + } + + // Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db. + const clientId = v4() + + beforeAll(async () => { + const entities = entitiesSchema.parse(defaultEntities) + + const policies = buildPolicy([adminPermitAll, treasuryMemberCanTransferOneEth]) + + await createClient(systemManagerHexPk, { + clientId, + authHost: getAuthHost(), + authAdminApiKey: getAuthAdminApiKey() + }) + await saveDataStore(systemManagerHexPk, { + clientId, + host: getAuthHost(), + entities, + policies + }) + }) + + it('alice-admin does a transfer that is not counted against the rate limit', async () => { + const { authClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits treasury-group member bob to do a transfer', async () => { + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits treasury-group member carol to do a transfer', async () => { + const { authClient } = await buildAuthClient(carolPrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('forbids member bob to exceed the limit', async () => { + expect.assertions(1) + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + + try { + await authClient.requestAccessToken(genNonce(request)) + } catch (error: any) { + expect(error.message).toEqual('Unauthorized') + } + }) + + it('forbids member carol to exceed the limit', async () => { + expect.assertions(1) + const { authClient } = await buildAuthClient(carolPrivateKey, { + host: getAuthHost(), + clientId + }) + + try { + await authClient.requestAccessToken(genNonce(request)) + } catch (error: any) { + expect(error.message).toEqual('Unauthorized') + } + }) + + it('permits admin alice to do a transfer', async () => { + const { authClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) +}) + +describe('rolling window', () => { + const request: Request = { + action: Action.SIGN_TRANSACTION, + nonce: 'test-nonce-3', + transactionRequest: { + from: '0x0301e2724a40E934Cce3345928b88956901aA127', + to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', + value: '0x429D069189E0000', // 0.3 ETH + chainId: 1 + }, + resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127' + } + + // Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db. + const clientId = v4() + + beforeAll(async () => { + const entities = entitiesSchema.parse(defaultEntities) + const policies = buildPolicy([adminPermitAll, memberCanTransferOneEthRolling]) + + await createClient(systemManagerHexPk, { + clientId, + authHost: getAuthHost(), + authAdminApiKey: getAuthAdminApiKey() + }) + await saveDataStore(systemManagerHexPk, { + clientId, + host: getAuthHost(), + entities, + policies + }) + }) + + it('alice-admin does a transfer that is not counted against the rate limit', async () => { + const { authClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits member bob to do a transfer', async () => { + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits member bob to do a second transfer', async () => { + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits member bob to do a third transfer', async () => { + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('forbids member bob to exceed the limit', async () => { + expect.assertions(1) + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + + try { + await authClient.requestAccessToken(genNonce(request)) + } catch (error: any) { + expect(error.message).toEqual('Unauthorized') + } + }) + + it('permits admin alice to do a transfer', async () => { + const { authClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) +}) + +describe('fixed window', () => { + const request: Request = { + action: Action.SIGN_TRANSACTION, + nonce: 'test-nonce-3', + transactionRequest: { + from: '0x0301e2724a40E934Cce3345928b88956901aA127', + to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', + value: '0x429D069189E0000', // 0.3 ETH + chainId: 1 + }, + resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127' + } + + // Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db. + const clientId = v4() + + beforeAll(async () => { + const entities = entitiesSchema.parse(defaultEntities) + const policies = buildPolicy([adminPermitAll, memberCanTransferOneEthFixed]) + + await createClient(systemManagerHexPk, { + clientId, + authHost: getAuthHost(), + authAdminApiKey: getAuthAdminApiKey() + }) + await saveDataStore(systemManagerHexPk, { + clientId, + host: getAuthHost(), + entities, + policies + }) + }) + it('alice-admin does a transfer that is not counted against the rate limit', async () => { + const { authClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits member bob to do a transfer', async () => { + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits member bob to do a second transfer', async () => { + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits member bob to do a third transfer', async () => { + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('forbids member bob to exceed the limit', async () => { + expect.assertions(1) + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + + try { + await authClient.requestAccessToken(genNonce(request)) + } catch (error: any) { + expect(error.message).toEqual('Unauthorized') + } + }) + + it('permits admin alice to do a transfer', async () => { + const { authClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) +}) diff --git a/packages/armory-sdk/src/lib/__test__/e2e/scenarii/approvals-and-spending-limit.spec.ts b/packages/armory-sdk/src/lib/__test__/e2e/scenarii/approvals-and-spending-limit.spec.ts new file mode 100644 index 000000000..132be58e9 --- /dev/null +++ b/packages/armory-sdk/src/lib/__test__/e2e/scenarii/approvals-and-spending-limit.spec.ts @@ -0,0 +1,287 @@ +/* eslint-disable jest/consistent-test-it */ +import { + Action, + Decision, + entitiesSchema, + EntityType, + FIXTURE, + Policy, + Request, + ValueOperators +} from '@narval/policy-engine-shared' +import { v4 } from 'uuid' +import defaultEntities from '../../../../resource/entity/default.json' +import { buildAuthClient, createClient, saveDataStore } from '../../util/setup' + +const TEST_TIMEOUT_MS = 30_000 + +jest.setTimeout(TEST_TIMEOUT_MS) + +export const advanceTime = (hours: number): void => { + jest.useFakeTimers() + jest.setSystemTime(Date.now() + hours * 60 * 60 * 1000) +} + +const systemManagerHexPk = FIXTURE.UNSAFE_PRIVATE_KEY.Root + +const getAuthHost = () => 'http://localhost:3005' +const getAuthAdminApiKey = () => 'armory-admin-api-key' +const bobPrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Bob +const alicePrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Alice +const carolPrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Carol + +const genNonce = (request: Request) => ({ ...request, nonce: `${request.nonce}-${v4()}` }) + +describe('End to end scenarios', () => { + describe('members can spend up to 1 eth per day, above an approval is required', () => { + const request: Request = { + action: Action.SIGN_TRANSACTION, + nonce: 'test-nonce-4', + transactionRequest: { + from: '0x0301e2724a40E934Cce3345928b88956901aA127', + to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', + value: '0x58D15E176280000', // 0.4 ETH + chainId: 1 + }, + resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127' + } + + // Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db. + const clientId = v4() + + beforeAll(async () => { + const entities = entitiesSchema.parse(defaultEntities) + + const policies: Policy[] = [ + { + id: '1-admin-can-do-anything', + description: 'admin can do any action', + when: [ + { + criterion: 'checkPrincipalRole', + args: ['admin'] + } + ], + then: 'permit' + }, + { + id: 'treasury-members-can-transfer-1-eth', + description: 'treasury group members can transfer 1 ETH', + when: [ + { + criterion: 'checkAction', + args: ['signTransaction'] + }, + { + criterion: 'checkIntentType', + args: ['transferNative'] + }, + { + criterion: 'checkIntentToken', + args: ['eip155:1/slip44:60'] + }, + { + criterion: 'checkPrincipalGroup', + args: ['treasury-group-id'] + }, + { + criterion: 'checkSpendingLimit', + args: { + limit: '1000000000000000000', + operator: 'lte' as ValueOperators, + timeWindow: { + type: 'rolling', + value: 86400 + }, + filters: { + userGroups: ['treasury-group-id'], + tokens: ['eip155:1/slip44:60'] + } + } + } + ], + then: 'permit' + }, + { + id: 'treasury-members-can-transfer-gt-1-eth-per-day-with-approval', + description: 'treasury group members transfers for more than 1 ETH per day requires an admin approval', + when: [ + { + criterion: 'checkAction', + args: ['signTransaction'] + }, + { + criterion: 'checkIntentType', + args: ['transferNative'] + }, + { + criterion: 'checkIntentToken', + args: ['eip155:1/slip44:60'] + }, + { + criterion: 'checkPrincipalGroup', + args: ['treasury-group-id'] + }, + { + criterion: 'checkSpendingLimit', + args: { + limit: '1000000000000000000', + operator: 'gt' as ValueOperators, + timeWindow: { + type: 'rolling', + value: 86400 + }, + filters: { + userGroups: ['treasury-group-id'], + tokens: ['eip155:1/slip44:60'] + } + } + }, + { + criterion: 'checkApprovals', + args: [ + { + approvalCount: 1, + countPrincipal: false, + approvalEntityType: 'Narval::UserRole' as EntityType, + entityIds: ['admin'] + } + ] + } + ], + then: 'permit' + } + ] + + await createClient(systemManagerHexPk, { + clientId, + authHost: getAuthHost(), + authAdminApiKey: getAuthAdminApiKey() + }) + await saveDataStore(systemManagerHexPk, { + clientId, + host: getAuthHost(), + entities, + policies + }) + }) + + it('alice-admin does a transfer that is not counted against the spending limit', async () => { + const { authClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits treasury-group member bob to do a transfer', async () => { + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits treasury-group member carol to do a transfer', async () => { + const { authClient } = await buildAuthClient(carolPrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('forbids member bob to exceed the limit', async () => { + expect.assertions(1) + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + + try { + await authClient.requestAccessToken(genNonce(request)) + } catch (error: any) { + expect(error.message).toEqual('Unauthorized') + } + }) + + it('forbids member carol to exceed the limit', async () => { + expect.assertions(1) + const { authClient } = await buildAuthClient(carolPrivateKey, { + host: getAuthHost(), + clientId + }) + + try { + await authClient.requestAccessToken(genNonce(request)) + } catch (error: any) { + expect(error.message).toEqual('Unauthorized') + } + }) + + it('permits admin alice to do a transfer', async () => { + const { authClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + + const response = await authClient.requestAccessToken(genNonce(request)) + expect(response).toMatchObject({ value: expect.any(String) }) + }) + + it('permits bob to exceed limit with alice-admin approval', async () => { + expect.assertions(2) + + const { authClient: adminClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + + const { authClient } = await buildAuthClient(bobPrivateKey, { + host: getAuthHost(), + clientId + }) + + const req = genNonce(request) + + const res = await authClient.authorize(req) + expect(res.decision).toEqual(Decision.CONFIRM) + + if (res.decision === Decision.CONFIRM) { + await adminClient.approve(res.authId) + + const accessToken = await authClient.getAccessToken(res.authId) + expect(accessToken).toMatchObject({ value: expect.any(String) }) + } + }) + + it('permits carol to exceed limit with alice-admin approval', async () => { + expect.assertions(2) + + const { authClient: adminClient } = await buildAuthClient(alicePrivateKey, { + host: getAuthHost(), + clientId + }) + + const { authClient } = await buildAuthClient(carolPrivateKey, { + host: getAuthHost(), + clientId + }) + + const res = await authClient.authorize(genNonce(request)) + expect(res.decision).toEqual(Decision.CONFIRM) + + if (res.decision === Decision.CONFIRM) { + await adminClient.approve(res.authId) + + const accessToken = await authClient.getAccessToken(res.authId) + expect(accessToken).toMatchObject({ value: expect.any(String) }) + } + }) + }) +}) diff --git a/packages/armory-sdk/src/lib/__test__/e2e/user-journeys.spec.ts b/packages/armory-sdk/src/lib/__test__/e2e/scenarii/user-journeys.spec.ts similarity index 96% rename from packages/armory-sdk/src/lib/__test__/e2e/user-journeys.spec.ts rename to packages/armory-sdk/src/lib/__test__/e2e/scenarii/user-journeys.spec.ts index ccbc80094..7737bd162 100644 --- a/packages/armory-sdk/src/lib/__test__/e2e/user-journeys.spec.ts +++ b/packages/armory-sdk/src/lib/__test__/e2e/scenarii/user-journeys.spec.ts @@ -26,16 +26,16 @@ import { import { format } from 'date-fns' import { v4 as uuid } from 'uuid' import { english, generateMnemonic, generatePrivateKey, privateKeyToAccount } from 'viem/accounts' -import { AuthAdminClient, AuthClient } from '../../auth/client' -import { EntityStoreClient, PolicyStoreClient } from '../../data-store/client' -import { DataStoreConfig } from '../../data-store/type' -import { createHttpDataStore, credential } from '../../data-store/util' -import { Permission } from '../../domain' -import { AuthorizationResponseDtoStatusEnum, CreateClientResponseDto } from '../../http/client/auth' -import { ClientDto, WalletDto } from '../../http/client/vault' -import { SignOptions, Signer } from '../../shared/type' -import { resourceId } from '../../utils' -import { VaultAdminClient, VaultClient } from '../../vault/client' +import { AuthAdminClient, AuthClient } from '../../../auth/client' +import { EntityStoreClient, PolicyStoreClient } from '../../../data-store/client' +import { DataStoreConfig } from '../../../data-store/type' +import { createHttpDataStore, credential } from '../../../data-store/util' +import { Permission } from '../../../domain' +import { AuthorizationResponseDtoStatusEnum, CreateClientResponseDto } from '../../../http/client/auth' +import { ClientDto, WalletDto } from '../../../http/client/vault' +import { SignOptions, Signer } from '../../../shared/type' +import { resourceId } from '../../../utils' +import { VaultAdminClient, VaultClient } from '../../../vault/client' const TEST_TIMEOUT_MS = 30_000 diff --git a/packages/armory-sdk/src/lib/__test__/e2e/scenarios.spec.ts b/packages/armory-sdk/src/lib/__test__/e2e/scenarios.spec.ts deleted file mode 100644 index f4f75f7e2..000000000 --- a/packages/armory-sdk/src/lib/__test__/e2e/scenarios.spec.ts +++ /dev/null @@ -1,1244 +0,0 @@ -/* eslint-disable jest/consistent-test-it */ -import { - Action, - Decision, - Entities, - EntityType, - FIXTURE, - Policy, - Request, - ValueOperators -} from '@narval/policy-engine-shared' -import { v4 } from 'uuid' -import { buildAuthClient, createClient, saveDataStore } from '../util/setup' - -const TEST_TIMEOUT_MS = 30_000 - -jest.setTimeout(TEST_TIMEOUT_MS) - -export const advanceTime = (hours: number): void => { - jest.useFakeTimers() - jest.setSystemTime(Date.now() + hours * 60 * 60 * 1000) -} - -const systemManagerHexPk = FIXTURE.UNSAFE_PRIVATE_KEY.Root - -const getAuthHost = () => 'http://localhost:3005' -const getAuthAdminApiKey = () => 'armory-admin-api-key' -const bobPrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Bob -const alicePrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Alice -const carolPrivateKey = FIXTURE.UNSAFE_PRIVATE_KEY.Carol - -const genNonce = (request: Request) => ({ ...request, nonce: `${request.nonce}-${v4()}` }) - -describe('End to end scenarios', () => { - describe('rate limiting by principal', () => { - const request: Request = { - action: Action.SIGN_TRANSACTION, - nonce: 'test-nonce-1', - transactionRequest: { - from: '0x0301e2724a40E934Cce3345928b88956901aA127', - to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', - value: '0xde0b6b3a7640000', - chainId: 1 - }, - resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127' - } - // Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db. - const clientId = v4() - - beforeAll(async () => { - const entities: Entities = { - addressBook: [ - { - id: 'eip155:1:0x9f38879167acCf7401351027EE3f9247A71cd0c5', - address: '0x9f38879167acCf7401351027EE3f9247A71cd0c5', - chainId: 1, - classification: 'internal' - }, - { - id: 'eip155:1:0x0f610AC9F0091f8F573c33f15155afE8aD747495', - address: '0x0f610AC9F0091f8F573c33f15155afE8aD747495', - chainId: 1, - classification: 'counterparty' - } - ], - credentials: [ - { - userId: 'test-alice-user-uid', - id: '0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94', - key: { - kty: 'EC', - alg: 'ES256K', - kid: '0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94', - crv: 'secp256k1', - x: 'zb-LwlHDtp5sV8E33k3H2TCm-LNTGIcFjODNWI4gHRY', - y: '6Pbt6dwxAeS7yHp7YV2GbXs_Px0tWrTfeTv9erjC7zs' - } - }, - { - userId: 'test-bob-user-uid', - id: '0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166', - key: { - kty: 'EC', - crv: 'secp256k1', - alg: 'ES256K', - kid: '0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166', - x: 'm5zj9v8I_UvB-15y7t7RmQXmyNmPuvAQPDdU71LRkUA', - y: 'Az5R7PGJbmKdPpK2-jmUh7xyuaOZlCIFNU4I83xy5lU' - } - } - ], - tokens: [], - userGroupMembers: [], - userGroups: [], - userAccounts: [], - users: [ - { - id: 'test-alice-user-uid', - role: 'admin' - }, - { - id: 'test-bob-user-uid', - role: 'member' - } - ], - accountGroupMembers: [], - accountGroups: [], - accounts: [ - { - id: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127', - address: '0x0301e2724a40e934cce3345928b88956901aa127', - accountType: 'eoa' - }, - { - id: 'eip155:eoa:0x76d1b7f9b3f69c435eef76a98a415332084a856f', - address: '0x76d1b7f9b3f69c435eef76a98a415332084a856f', - accountType: 'eoa' - } - ] - } - const policies: Policy[] = [ - { - id: '1-admin-can-do-anything', - description: 'admin can do any action', - when: [ - { - criterion: 'checkPrincipalRole', - args: ['admin'] - } - ], - then: 'permit' - }, - { - id: '1-allow-2-transfer-per-day', - description: 'Permit members to transfer native 2 times per day', - when: [ - { - criterion: 'checkRateLimit', - args: { limit: 2, timeWindow: { type: 'fixed', period: '1d' }, filters: { perPrincipal: true } } - }, - { - criterion: 'checkIntentType', - args: ['transferNative'] - }, - { - criterion: 'checkPrincipalRole', - args: ['member'] - } - ], - then: 'permit' - } - ] - - await createClient(systemManagerHexPk, { - clientId, - authHost: getAuthHost(), - authAdminApiKey: getAuthAdminApiKey() - }) - await saveDataStore(systemManagerHexPk, { - clientId, - host: getAuthHost(), - entities, - policies - }) - }) - it('alice-admin does a transfer that is not counted against the rate limit', async () => { - const { authClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits member bob to do a first transfer', async () => { - // First transfer - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits member bob to do a second transfer', async () => { - // Second transfer - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('forbids member bob to do a third transfer', async () => { - expect.assertions(1) - // Third transfer - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - - try { - await authClient.requestAccessToken(genNonce(request)) - } catch (error: any) { - expect(error.message).toEqual('Unauthorized') - } - }) - - it('permits admin alice to do a transfer', async () => { - const { authClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - }) - - describe('rate limiting by groupId', () => { - const request: Request = { - action: Action.SIGN_TRANSACTION, - nonce: 'test-nonce-2', - transactionRequest: { - from: '0x0301e2724a40E934Cce3345928b88956901aA127', - to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', - value: '0x58D15E176280000', // 0.4 ETH - chainId: 1 - }, - resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127' - } - - // Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db. - const clientId = v4() - - beforeAll(async () => { - const entities: Entities = { - addressBook: [ - { - id: 'eip155:1:0x9f38879167acCf7401351027EE3f9247A71cd0c5', - address: '0x9f38879167acCf7401351027EE3f9247A71cd0c5', - chainId: 1, - classification: 'internal' - }, - { - id: 'eip155:1:0x0f610AC9F0091f8F573c33f15155afE8aD747495', - address: '0x0f610AC9F0091f8F573c33f15155afE8aD747495', - chainId: 1, - classification: 'counterparty' - } - ], - credentials: [ - { - userId: 'test-alice-user-uid', - id: '0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94', - key: { - kty: 'EC', - alg: 'ES256K', - kid: '0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94', - crv: 'secp256k1', - x: 'zb-LwlHDtp5sV8E33k3H2TCm-LNTGIcFjODNWI4gHRY', - y: '6Pbt6dwxAeS7yHp7YV2GbXs_Px0tWrTfeTv9erjC7zs' - } - }, - { - userId: 'test-bob-user-uid', - id: '0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166', - key: { - kty: 'EC', - crv: 'secp256k1', - alg: 'ES256K', - kid: '0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166', - x: 'm5zj9v8I_UvB-15y7t7RmQXmyNmPuvAQPDdU71LRkUA', - y: 'Az5R7PGJbmKdPpK2-jmUh7xyuaOZlCIFNU4I83xy5lU' - } - }, - { - userId: 'test-carol-user-uid', - id: '0x8014093787673513011ee2be28bc685a1df716abbe0d4d76173b0dbdc33d0557', - key: { - kty: 'EC', - crv: 'secp256k1', - alg: 'ES256K', - kid: '0x8014093787673513011ee2be28bc685a1df716abbe0d4d76173b0dbdc33d0557', - x: 'OA4p5YjB0XBUzMuE6qhSwcSCLDu-yf9VekwcE320fWw', - y: 'nLK5Qr_VHqZD9rVCLMHToXvdE9KuTn6w--PJ9jcpdUU' - } - } - ], - tokens: [], - userGroupMembers: [ - { - groupId: 'treasury-group-id', - userId: 'test-bob-user-uid' - }, - { - groupId: 'treasury-group-id', - userId: 'test-carol-user-uid' - } - ], - userGroups: [ - { - id: 'treasury-group-id' - } - ], - userAccounts: [], - users: [ - { - id: 'test-alice-user-uid', - role: 'admin' - }, - { - id: 'test-bob-user-uid', - role: 'member' - }, - { - id: 'test-carol-user-uid', - role: 'member' - } - ], - accountGroupMembers: [], - accountGroups: [], - accounts: [ - { - id: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127', - address: '0x0301e2724a40e934cce3345928b88956901aa127', - accountType: 'eoa' - }, - { - id: 'eip155:eoa:0x76d1b7f9b3f69c435eef76a98a415332084a856f', - address: '0x76d1b7f9b3f69c435eef76a98a415332084a856f', - accountType: 'eoa' - } - ] - } - const policies: Policy[] = [ - { - id: '1-admin-can-do-anything', - description: 'admin can do any action', - when: [ - { - criterion: 'checkPrincipalRole', - args: ['admin'] - } - ], - then: 'permit' - }, - { - id: 'treasury-members-can-transfer-1-eth', - description: 'treasury group members can transfer 1 ETH', - when: [ - { - criterion: 'checkAction', - args: ['signTransaction'] - }, - { - criterion: 'checkIntentType', - args: ['transferNative'] - }, - { - criterion: 'checkIntentToken', - args: ['eip155:1/slip44:60'] - }, - { - criterion: 'checkSpendingLimit', - args: { - limit: '1000000000000000000', - operator: 'lte' as ValueOperators, - timeWindow: { - type: 'rolling', - value: 86400 - }, - filters: { - userGroups: ['treasury-group-id'], - tokens: ['eip155:1/slip44:60'] - } - } - } - ], - then: 'permit' - } - ] - - await createClient(systemManagerHexPk, { - clientId, - authHost: getAuthHost(), - authAdminApiKey: getAuthAdminApiKey() - }) - await saveDataStore(systemManagerHexPk, { - clientId, - host: getAuthHost(), - entities, - policies - }) - }) - - it('alice-admin does a transfer that is not counted against the rate limit', async () => { - const { authClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits treasury-group member bob to do a transfer', async () => { - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits treasury-group member carol to do a transfer', async () => { - const { authClient } = await buildAuthClient(carolPrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('forbids member bob to exceed the limit', async () => { - expect.assertions(1) - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - - try { - await authClient.requestAccessToken(genNonce(request)) - } catch (error: any) { - expect(error.message).toEqual('Unauthorized') - } - }) - - it('forbids member carol to exceed the limit', async () => { - expect.assertions(1) - const { authClient } = await buildAuthClient(carolPrivateKey, { - host: getAuthHost(), - clientId - }) - - try { - await authClient.requestAccessToken(genNonce(request)) - } catch (error: any) { - expect(error.message).toEqual('Unauthorized') - } - }) - - it('permits admin alice to do a transfer', async () => { - const { authClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - }) - - describe('spending limits', () => { - describe('rolling window', () => { - const request: Request = { - action: Action.SIGN_TRANSACTION, - nonce: 'test-nonce-3', - transactionRequest: { - from: '0x0301e2724a40E934Cce3345928b88956901aA127', - to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', - value: '0x429D069189E0000', // 0.3 ETH - chainId: 1 - }, - resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127' - } - - // Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db. - const clientId = v4() - - beforeAll(async () => { - const entities: Entities = { - addressBook: [ - { - id: 'eip155:1:0x9f38879167acCf7401351027EE3f9247A71cd0c5', - address: '0x9f38879167acCf7401351027EE3f9247A71cd0c5', - chainId: 1, - classification: 'internal' - }, - { - id: 'eip155:1:0x0f610AC9F0091f8F573c33f15155afE8aD747495', - address: '0x0f610AC9F0091f8F573c33f15155afE8aD747495', - chainId: 1, - classification: 'counterparty' - } - ], - credentials: [ - { - userId: 'test-alice-user-uid', - id: '0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94', - key: { - kty: 'EC', - alg: 'ES256K', - kid: '0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94', - crv: 'secp256k1', - x: 'zb-LwlHDtp5sV8E33k3H2TCm-LNTGIcFjODNWI4gHRY', - y: '6Pbt6dwxAeS7yHp7YV2GbXs_Px0tWrTfeTv9erjC7zs' - } - }, - { - userId: 'test-bob-user-uid', - id: '0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166', - key: { - kty: 'EC', - crv: 'secp256k1', - alg: 'ES256K', - kid: '0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166', - x: 'm5zj9v8I_UvB-15y7t7RmQXmyNmPuvAQPDdU71LRkUA', - y: 'Az5R7PGJbmKdPpK2-jmUh7xyuaOZlCIFNU4I83xy5lU' - } - } - ], - tokens: [], - userGroupMembers: [], - userGroups: [], - userAccounts: [], - users: [ - { - id: 'test-alice-user-uid', - role: 'admin' - }, - { - id: 'test-bob-user-uid', - role: 'member' - } - ], - accountGroupMembers: [], - accountGroups: [], - accounts: [ - { - id: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127', - address: '0x0301e2724a40e934cce3345928b88956901aa127', - accountType: 'eoa' - }, - { - id: 'eip155:eoa:0x76d1b7f9b3f69c435eef76a98a415332084a856f', - address: '0x76d1b7f9b3f69c435eef76a98a415332084a856f', - accountType: 'eoa' - } - ] - } - const policies: Policy[] = [ - { - id: '1-admin-can-do-anything', - description: 'admin can do any action', - when: [ - { - criterion: 'checkPrincipalRole', - args: ['admin'] - } - ], - then: 'permit' - }, - { - id: 'member-can-transfer-1-eth', - description: 'member can transfer 1 ETH', - when: [ - { - criterion: 'checkAction', - args: ['signTransaction'] - }, - { - criterion: 'checkPrincipalRole', - args: ['member'] - }, - { - criterion: 'checkIntentType', - args: ['transferNative'] - }, - { - criterion: 'checkIntentToken', - args: ['eip155:1/slip44:60'] - }, - { - criterion: 'checkSpendingLimit', - args: { - limit: '1000000000000000000', - operator: 'lte' as ValueOperators, - timeWindow: { - type: 'rolling', - value: 86400 - }, - filters: { - perPrincipal: true, - tokens: ['eip155:1/slip44:60'] - } - } - } - ], - then: 'permit' - } - ] - - await createClient(systemManagerHexPk, { - clientId, - authHost: getAuthHost(), - authAdminApiKey: getAuthAdminApiKey() - }) - await saveDataStore(systemManagerHexPk, { - clientId, - host: getAuthHost(), - entities, - policies - }) - }) - - it('alice-admin does a transfer that is not counted against the rate limit', async () => { - const { authClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits member bob to do a transfer', async () => { - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits member bob to do a second transfer', async () => { - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits member bob to do a third transfer', async () => { - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('forbids member bob to exceed the limit', async () => { - expect.assertions(1) - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - - try { - await authClient.requestAccessToken(genNonce(request)) - } catch (error: any) { - expect(error.message).toEqual('Unauthorized') - } - }) - - it('permits admin alice to do a transfer', async () => { - const { authClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - }) - describe('fixed window', () => { - const request: Request = { - action: Action.SIGN_TRANSACTION, - nonce: 'test-nonce-3', - transactionRequest: { - from: '0x0301e2724a40E934Cce3345928b88956901aA127', - to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', - value: '0x429D069189E0000', // 0.3 ETH - chainId: 1 - }, - resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127' - } - - // Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db. - const clientId = v4() - - beforeAll(async () => { - const entities: Entities = { - addressBook: [ - { - id: 'eip155:1:0x9f38879167acCf7401351027EE3f9247A71cd0c5', - address: '0x9f38879167acCf7401351027EE3f9247A71cd0c5', - chainId: 1, - classification: 'internal' - }, - { - id: 'eip155:1:0x0f610AC9F0091f8F573c33f15155afE8aD747495', - address: '0x0f610AC9F0091f8F573c33f15155afE8aD747495', - chainId: 1, - classification: 'counterparty' - } - ], - credentials: [ - { - userId: 'test-alice-user-uid', - id: '0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94', - key: { - kty: 'EC', - alg: 'ES256K', - kid: '0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94', - crv: 'secp256k1', - x: 'zb-LwlHDtp5sV8E33k3H2TCm-LNTGIcFjODNWI4gHRY', - y: '6Pbt6dwxAeS7yHp7YV2GbXs_Px0tWrTfeTv9erjC7zs' - } - }, - { - userId: 'test-bob-user-uid', - id: '0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166', - key: { - kty: 'EC', - crv: 'secp256k1', - alg: 'ES256K', - kid: '0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166', - x: 'm5zj9v8I_UvB-15y7t7RmQXmyNmPuvAQPDdU71LRkUA', - y: 'Az5R7PGJbmKdPpK2-jmUh7xyuaOZlCIFNU4I83xy5lU' - } - } - ], - tokens: [], - userGroupMembers: [], - userGroups: [], - userAccounts: [], - users: [ - { - id: 'test-alice-user-uid', - role: 'admin' - }, - { - id: 'test-bob-user-uid', - role: 'member' - } - ], - accountGroupMembers: [], - accountGroups: [], - accounts: [ - { - id: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127', - address: '0x0301e2724a40e934cce3345928b88956901aa127', - accountType: 'eoa' - }, - { - id: 'eip155:eoa:0x76d1b7f9b3f69c435eef76a98a415332084a856f', - address: '0x76d1b7f9b3f69c435eef76a98a415332084a856f', - accountType: 'eoa' - } - ] - } - const policies: Policy[] = [ - { - id: '1-admin-can-do-anything', - description: 'admin can do any action', - when: [ - { - criterion: 'checkPrincipalRole', - args: ['admin'] - } - ], - then: 'permit' - }, - { - id: 'member-can-transfer-1-eth', - description: 'member can transfer 1 ETH', - when: [ - { - criterion: 'checkAction', - args: ['signTransaction'] - }, - { - criterion: 'checkPrincipalRole', - args: ['member'] - }, - { - criterion: 'checkIntentType', - args: ['transferNative'] - }, - { - criterion: 'checkIntentToken', - args: ['eip155:1/slip44:60'] - }, - { - criterion: 'checkSpendingLimit', - args: { - limit: '1000000000000000000', - operator: 'lte' as ValueOperators, - timeWindow: { - type: 'fixed', - period: '1d' - }, - filters: { - perPrincipal: true, - tokens: ['eip155:1/slip44:60'] - } - } - } - ], - then: 'permit' - } - ] - - await createClient(systemManagerHexPk, { - clientId, - authHost: getAuthHost(), - authAdminApiKey: getAuthAdminApiKey() - }) - await saveDataStore(systemManagerHexPk, { - clientId, - host: getAuthHost(), - entities, - policies - }) - }) - it('alice-admin does a transfer that is not counted against the rate limit', async () => { - const { authClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits member bob to do a transfer', async () => { - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits member bob to do a second transfer', async () => { - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits member bob to do a third transfer', async () => { - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('forbids member bob to exceed the limit', async () => { - expect.assertions(1) - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - - try { - await authClient.requestAccessToken(genNonce(request)) - } catch (error: any) { - expect(error.message).toEqual('Unauthorized') - } - }) - - it('permits admin alice to do a transfer', async () => { - const { authClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - }) - }) - - describe('approvals by groupId and spending limit', () => { - const request: Request = { - action: Action.SIGN_TRANSACTION, - nonce: 'test-nonce-4', - transactionRequest: { - from: '0x0301e2724a40E934Cce3345928b88956901aA127', - to: '0x76d1b7f9b3F69C435eeF76a98A415332084A856F', - value: '0x58D15E176280000', // 0.4 ETH - chainId: 1 - }, - resourceId: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127' - } - - // Generate a new client ID for each test run, otherwise historical data with persist between tests if using a long-lived db. - const clientId = v4() - - beforeAll(async () => { - const entities: Entities = { - addressBook: [ - { - id: 'eip155:1:0x9f38879167acCf7401351027EE3f9247A71cd0c5', - address: '0x9f38879167acCf7401351027EE3f9247A71cd0c5', - chainId: 1, - classification: 'internal' - }, - { - id: 'eip155:1:0x0f610AC9F0091f8F573c33f15155afE8aD747495', - address: '0x0f610AC9F0091f8F573c33f15155afE8aD747495', - chainId: 1, - classification: 'counterparty' - } - ], - credentials: [ - { - userId: 'test-alice-user-uid', - id: '0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94', - key: { - kty: 'EC', - alg: 'ES256K', - kid: '0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94', - crv: 'secp256k1', - x: 'zb-LwlHDtp5sV8E33k3H2TCm-LNTGIcFjODNWI4gHRY', - y: '6Pbt6dwxAeS7yHp7YV2GbXs_Px0tWrTfeTv9erjC7zs' - } - }, - { - userId: 'test-bob-user-uid', - id: '0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166', - key: { - kty: 'EC', - crv: 'secp256k1', - alg: 'ES256K', - kid: '0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166', - x: 'm5zj9v8I_UvB-15y7t7RmQXmyNmPuvAQPDdU71LRkUA', - y: 'Az5R7PGJbmKdPpK2-jmUh7xyuaOZlCIFNU4I83xy5lU' - } - }, - { - userId: 'test-carol-user-uid', - id: '0x8014093787673513011ee2be28bc685a1df716abbe0d4d76173b0dbdc33d0557', - key: { - kty: 'EC', - crv: 'secp256k1', - alg: 'ES256K', - kid: '0x8014093787673513011ee2be28bc685a1df716abbe0d4d76173b0dbdc33d0557', - x: 'OA4p5YjB0XBUzMuE6qhSwcSCLDu-yf9VekwcE320fWw', - y: 'nLK5Qr_VHqZD9rVCLMHToXvdE9KuTn6w--PJ9jcpdUU' - } - } - ], - tokens: [], - userGroupMembers: [ - { - groupId: 'treasury-group-id', - userId: 'test-bob-user-uid' - }, - { - groupId: 'treasury-group-id', - userId: 'test-carol-user-uid' - } - ], - userGroups: [ - { - id: 'treasury-group-id' - } - ], - userAccounts: [], - users: [ - { - id: 'test-alice-user-uid', - role: 'admin' - }, - { - id: 'test-bob-user-uid', - role: 'member' - }, - { - id: 'test-carol-user-uid', - role: 'member' - } - ], - accountGroupMembers: [], - accountGroups: [], - accounts: [ - { - id: 'eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127', - address: '0x0301e2724a40e934cce3345928b88956901aa127', - accountType: 'eoa' - }, - { - id: 'eip155:eoa:0x76d1b7f9b3f69c435eef76a98a415332084a856f', - address: '0x76d1b7f9b3f69c435eef76a98a415332084a856f', - accountType: 'eoa' - } - ] - } - const policies: Policy[] = [ - { - id: '1-admin-can-do-anything', - description: 'admin can do any action', - when: [ - { - criterion: 'checkPrincipalRole', - args: ['admin'] - } - ], - then: 'permit' - }, - { - id: 'treasury-members-can-transfer-1-eth', - description: 'treasury group members can transfer 1 ETH', - when: [ - { - criterion: 'checkAction', - args: ['signTransaction'] - }, - { - criterion: 'checkIntentType', - args: ['transferNative'] - }, - { - criterion: 'checkIntentToken', - args: ['eip155:1/slip44:60'] - }, - { - criterion: 'checkPrincipalGroup', - args: ['treasury-group-id'] - }, - { - criterion: 'checkSpendingLimit', - args: { - limit: '1000000000000000000', - operator: 'lte' as ValueOperators, - timeWindow: { - type: 'rolling', - value: 86400 - }, - filters: { - userGroups: ['treasury-group-id'], - tokens: ['eip155:1/slip44:60'] - } - } - } - ], - then: 'permit' - }, - { - id: 'treasury-members-can-transfer-gt-1-eth-per-day-with-approval', - description: 'treasury group members transfers for more than 1 ETH per day requires an admin approval', - when: [ - { - criterion: 'checkAction', - args: ['signTransaction'] - }, - { - criterion: 'checkIntentType', - args: ['transferNative'] - }, - { - criterion: 'checkIntentToken', - args: ['eip155:1/slip44:60'] - }, - { - criterion: 'checkPrincipalGroup', - args: ['treasury-group-id'] - }, - { - criterion: 'checkSpendingLimit', - args: { - limit: '1000000000000000000', - operator: 'gt' as ValueOperators, - timeWindow: { - type: 'rolling', - value: 86400 - }, - filters: { - userGroups: ['treasury-group-id'], - tokens: ['eip155:1/slip44:60'] - } - } - }, - { - criterion: 'checkApprovals', - args: [ - { - approvalCount: 1, - countPrincipal: false, - approvalEntityType: 'Narval::UserRole' as EntityType, - entityIds: ['admin'] - } - ] - } - ], - then: 'permit' - } - ] - - await createClient(systemManagerHexPk, { - clientId, - authHost: getAuthHost(), - authAdminApiKey: getAuthAdminApiKey() - }) - await saveDataStore(systemManagerHexPk, { - clientId, - host: getAuthHost(), - entities, - policies - }) - }) - - it('alice-admin does a transfer that is not counted against the spending limit', async () => { - const { authClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits treasury-group member bob to do a transfer', async () => { - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits treasury-group member carol to do a transfer', async () => { - const { authClient } = await buildAuthClient(carolPrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('forbids member bob to exceed the limit', async () => { - expect.assertions(1) - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - - try { - await authClient.requestAccessToken(genNonce(request)) - } catch (error: any) { - expect(error.message).toEqual('Unauthorized') - } - }) - - it('forbids member carol to exceed the limit', async () => { - expect.assertions(1) - const { authClient } = await buildAuthClient(carolPrivateKey, { - host: getAuthHost(), - clientId - }) - - try { - await authClient.requestAccessToken(genNonce(request)) - } catch (error: any) { - expect(error.message).toEqual('Unauthorized') - } - }) - - it('permits admin alice to do a transfer', async () => { - const { authClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - - const response = await authClient.requestAccessToken(genNonce(request)) - expect(response).toMatchObject({ value: expect.any(String) }) - }) - - it('permits bob to exceed limit with alice-admin approval', async () => { - expect.assertions(2) - - const { authClient: adminClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - - const { authClient } = await buildAuthClient(bobPrivateKey, { - host: getAuthHost(), - clientId - }) - - const req = genNonce(request) - - const res = await authClient.authorize(req) - expect(res.decision).toEqual(Decision.CONFIRM) - - if (res.decision === Decision.CONFIRM) { - await adminClient.approve(res.authId) - - const accessToken = await authClient.getAccessToken(res.authId) - expect(accessToken).toMatchObject({ value: expect.any(String) }) - } - }) - - it('permits carol to exceed limit with alice-admin approval', async () => { - expect.assertions(2) - - const { authClient: adminClient } = await buildAuthClient(alicePrivateKey, { - host: getAuthHost(), - clientId - }) - - const { authClient } = await buildAuthClient(carolPrivateKey, { - host: getAuthHost(), - clientId - }) - - const res = await authClient.authorize(genNonce(request)) - expect(res.decision).toEqual(Decision.CONFIRM) - - if (res.decision === Decision.CONFIRM) { - await adminClient.approve(res.authId) - - const accessToken = await authClient.getAccessToken(res.authId) - expect(accessToken).toMatchObject({ value: expect.any(String) }) - } - }) - }) -}) diff --git a/packages/armory-sdk/src/lib/__test__/util/setup.ts b/packages/armory-sdk/src/lib/__test__/util/setup.ts index 6e42afcfa..6ad031b06 100644 --- a/packages/armory-sdk/src/lib/__test__/util/setup.ts +++ b/packages/armory-sdk/src/lib/__test__/util/setup.ts @@ -1,11 +1,16 @@ -import { Entities, Hex, Policy } from '@narval/policy-engine-shared' +import { Entities, Hex, Policy, policySchema, Request } from '@narval/policy-engine-shared' import { buildSignerForAlg, getPublicKey, privateKeyToJwk } from '@narval/signature' import { format } from 'date-fns' import { v4 } from 'uuid' import { AuthAdminClient, AuthClient, AuthConfig } from '../../auth' -import { DataStoreConfig, EntityStoreClient, PolicyStoreClient, createHttpDataStore } from '../../data-store' +import { createHttpDataStore, DataStoreConfig, EntityStoreClient, PolicyStoreClient } from '../../data-store' import { VaultAdminClient, VaultConfig } from '../../vault' +export const getAuthHost = () => 'http://localhost:3005' +export const getAuthAdminApiKey = () => 'armory-admin-api-key' + +export const genNonce = (request: Request) => ({ ...request, nonce: `${request.nonce}-${v4()}` }) + export const createClient = async ( SYSTEM_MANAGER_KEY: Hex, { @@ -170,3 +175,8 @@ export const buildAuthClient = async ( authClient } } + +export const buildPolicy = (objects: unknown[]): Policy[] => { + const policies = objects.map((obj) => policySchema.parse(obj)) + return policies +} diff --git a/packages/armory-sdk/src/resource/entity/default.json b/packages/armory-sdk/src/resource/entity/default.json new file mode 100644 index 000000000..03290c063 --- /dev/null +++ b/packages/armory-sdk/src/resource/entity/default.json @@ -0,0 +1,99 @@ +{ + "addressBook": [ + { + "id": "eip155:1:0x9f38879167acCf7401351027EE3f9247A71cd0c5", + "address": "0x9f38879167acCf7401351027EE3f9247A71cd0c5", + "chainId": 1, + "classification": "internal" + }, + { + "id": "eip155:1:0x0f610AC9F0091f8F573c33f15155afE8aD747495", + "address": "0x0f610AC9F0091f8F573c33f15155afE8aD747495", + "chainId": 1, + "classification": "counterparty" + } + ], + "credentials": [ + { + "userId": "test-alice-user-uid", + "id": "0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94", + "key": { + "kty": "EC", + "alg": "ES256K", + "kid": "0x4fca4ebdd44d54a470a273cb6c131303892cb754f0d374a860fab7936bb95d94", + "crv": "secp256k1", + "x": "zb-LwlHDtp5sV8E33k3H2TCm-LNTGIcFjODNWI4gHRY", + "y": "6Pbt6dwxAeS7yHp7YV2GbXs_Px0tWrTfeTv9erjC7zs" + } + }, + { + "userId": "test-bob-user-uid", + "id": "0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166", + "key": { + "kty": "EC", + "crv": "secp256k1", + "alg": "ES256K", + "kid": "0x7e431d5b570ba38e2e036387a596219ae9076e8a488a6149b491892b03582166", + "x": "m5zj9v8I_UvB-15y7t7RmQXmyNmPuvAQPDdU71LRkUA", + "y": "Az5R7PGJbmKdPpK2-jmUh7xyuaOZlCIFNU4I83xy5lU" + } + }, + { + "userId": "test-carol-user-uid", + "id": "0x8014093787673513011ee2be28bc685a1df716abbe0d4d76173b0dbdc33d0557", + "key": { + "kty": "EC", + "crv": "secp256k1", + "alg": "ES256K", + "kid": "0x8014093787673513011ee2be28bc685a1df716abbe0d4d76173b0dbdc33d0557", + "x": "OA4p5YjB0XBUzMuE6qhSwcSCLDu-yf9VekwcE320fWw", + "y": "nLK5Qr_VHqZD9rVCLMHToXvdE9KuTn6w--PJ9jcpdUU" + } + } + ], + "tokens": [], + "userGroupMembers": [ + { + "groupId": "treasury-group-id", + "userId": "test-bob-user-uid" + }, + { + "groupId": "treasury-group-id", + "userId": "test-carol-user-uid" + } + ], + "userGroups": [ + { + "id": "treasury-group-id" + } + ], + "userAccounts": [], + "users": [ + { + "id": "test-alice-user-uid", + "role": "admin" + }, + { + "id": "test-bob-user-uid", + "role": "member" + }, + { + "id": "test-carol-user-uid", + "role": "member" + } + ], + "accountGroupMembers": [], + "accountGroups": [], + "accounts": [ + { + "id": "eip155:eoa:0x0301e2724a40e934cce3345928b88956901aa127", + "address": "0x0301e2724a40e934cce3345928b88956901aa127", + "accountType": "eoa" + }, + { + "id": "eip155:eoa:0x76d1b7f9b3f69c435eef76a98a415332084a856f", + "address": "0x76d1b7f9b3f69c435eef76a98a415332084a856f", + "accountType": "eoa" + } + ] +} diff --git a/packages/armory-sdk/src/resource/policy/checkApprovals/admin-approval-required.json b/packages/armory-sdk/src/resource/policy/checkApprovals/admin-approval-required.json new file mode 100644 index 000000000..69869eacb --- /dev/null +++ b/packages/armory-sdk/src/resource/policy/checkApprovals/admin-approval-required.json @@ -0,0 +1,18 @@ +{ + "id": "an admin approval is required", + "description": "an admin approval is required", + "when": [ + { + "criterion": "checkApprovals", + "args": [ + { + "approvalCount": 1, + "countPrincipal": false, + "approvalEntityType": "Narval::UserRole", + "entityIds": ["admin"] + } + ] + } + ], + "then": "permit" +} diff --git a/packages/armory-sdk/src/resource/policy/checkPrincipalRole/admin-permit-all.json b/packages/armory-sdk/src/resource/policy/checkPrincipalRole/admin-permit-all.json new file mode 100644 index 000000000..3ba884b64 --- /dev/null +++ b/packages/armory-sdk/src/resource/policy/checkPrincipalRole/admin-permit-all.json @@ -0,0 +1,11 @@ +{ + "id": "1-admin-can-do-anything", + "description": "admin can do any action", + "when": [ + { + "criterion": "checkPrincipalRole", + "args": ["admin"] + } + ], + "then": "permit" +} diff --git a/packages/armory-sdk/src/resource/policy/checkRateLimit/members-2-transfer-per-day.json b/packages/armory-sdk/src/resource/policy/checkRateLimit/members-2-transfer-per-day.json new file mode 100644 index 000000000..cf94267c3 --- /dev/null +++ b/packages/armory-sdk/src/resource/policy/checkRateLimit/members-2-transfer-per-day.json @@ -0,0 +1,28 @@ +{ + "id": "1-allow-2-transfer-per-day", + "description": "Permit members to transfer native 2 times per day", + "when": [ + { + "criterion": "checkRateLimit", + "args": { + "limit": 2, + "timeWindow": { + "type": "fixed", + "period": "1d" + }, + "filters": { + "perPrincipal": true + } + } + }, + { + "criterion": "checkIntentType", + "args": ["transferNative"] + }, + { + "criterion": "checkPrincipalRole", + "args": ["member"] + } + ], + "then": "permit" +} diff --git a/packages/armory-sdk/src/resource/policy/checkSpendingLimit/member-can-transfer-1-eth-fixed.json b/packages/armory-sdk/src/resource/policy/checkSpendingLimit/member-can-transfer-1-eth-fixed.json new file mode 100644 index 000000000..d7a6e1e68 --- /dev/null +++ b/packages/armory-sdk/src/resource/policy/checkSpendingLimit/member-can-transfer-1-eth-fixed.json @@ -0,0 +1,22 @@ +{ + "id": "treasury-members-can-transfer-1-eth-fixed", + "description": "treasury group members can transfer 1 ETH per day", + "when": [ + { + "criterion": "checkSpendingLimit", + "args": { + "limit": "1000000000000000000", + "operator": "lte", + "timeWindow": { + "type": "fixed", + "period": "1d" + }, + "filters": { + "userGroups": ["treasury-group-id"], + "tokens": ["eip155:1/slip44:60"] + } + } + } + ], + "then": "permit" +} diff --git a/packages/armory-sdk/src/resource/policy/checkSpendingLimit/member-can-transfer-1-eth-rolling.json b/packages/armory-sdk/src/resource/policy/checkSpendingLimit/member-can-transfer-1-eth-rolling.json new file mode 100644 index 000000000..01448b55c --- /dev/null +++ b/packages/armory-sdk/src/resource/policy/checkSpendingLimit/member-can-transfer-1-eth-rolling.json @@ -0,0 +1,22 @@ +{ + "id": "treasury-members-can-transfer-1-eth", + "description": "treasury group members can transfer 1 ETH", + "when": [ + { + "criterion": "checkSpendingLimit", + "args": { + "limit": "1000000000000000000", + "operator": "lte", + "timeWindow": { + "type": "rolling", + "value": 86400 + }, + "filters": { + "userGroups": ["treasury-group-id"], + "tokens": ["eip155:1/slip44:60"] + } + } + } + ], + "then": "permit" +} diff --git a/packages/armory-sdk/src/resource/policy/checkSpendingLimit/treasury-groupMember-can-transfer-1-eth-fixed.json b/packages/armory-sdk/src/resource/policy/checkSpendingLimit/treasury-groupMember-can-transfer-1-eth-fixed.json new file mode 100644 index 000000000..ba94317d8 --- /dev/null +++ b/packages/armory-sdk/src/resource/policy/checkSpendingLimit/treasury-groupMember-can-transfer-1-eth-fixed.json @@ -0,0 +1,22 @@ +{ + "id": "treasury-members-can-transfer-1-eth", + "description": "treasury group members can transfer 1 ETH", + "when": [ + { + "criterion": "checkSpendingLimit", + "args": { + "limit": "1000000000000000000", + "operator": "lte", + "timeWindow": { + "type": "fixed", + "period": "1d" + }, + "filters": { + "userGroups": ["treasury-group-id"], + "tokens": ["eip155:1/slip44:60"] + } + } + } + ], + "then": "permit" +} diff --git a/packages/armory-sdk/tsconfig.json b/packages/armory-sdk/tsconfig.json index cb6e83589..efb889526 100644 --- a/packages/armory-sdk/tsconfig.json +++ b/packages/armory-sdk/tsconfig.json @@ -12,6 +12,7 @@ ], "compilerOptions": { "module": "commonjs", - "strict": true + "strict": true, + "resolveJsonModule": true } }