diff --git a/apps/policy-engine/src/app/__test__/e2e/admin.spec.ts b/apps/policy-engine/src/app/__test__/e2e/admin.spec.ts deleted file mode 100644 index 8828a3182..000000000 --- a/apps/policy-engine/src/app/__test__/e2e/admin.spec.ts +++ /dev/null @@ -1,222 +0,0 @@ -import { Action, EntityType, FIXTURE, Signature, UserRole, ValueOperators } from '@narval/policy-engine-shared' -import { Alg } from '@narval/signature' -import { Intents } from '@narval/transaction-request-intent' -import { HttpStatus, INestApplication } from '@nestjs/common' -import { ConfigModule } from '@nestjs/config' -import { Test, TestingModule } from '@nestjs/testing' -import { readFileSync, unlinkSync } from 'fs' -import { mock } from 'jest-mock-extended' -import request from 'supertest' -import { AppModule } from '../../../app/app.module' -import { EncryptionService } from '../../../encryption/core/encryption.service' -import { load } from '../../../policy-engine.config' -import { PersistenceModule } from '../../../shared/module/persistence/persistence.module' -import { TestPrismaService } from '../../../shared/module/persistence/service/test-prisma.service' -import { Organization } from '../../../shared/type/entities.types' -import { Criterion, Then, TimeWindow } from '../../../shared/type/policy.type' -import { EntityRepository } from '../../persistence/repository/entity.repository' - -const REQUEST_HEADER_ORG_ID = 'x-org-id' -describe('Admin Endpoints', () => { - let app: INestApplication - let module: TestingModule - let testPrismaService: TestPrismaService - - // TODO: Real sigs; these will NOT match the test data. - const authentication: Signature = { - alg: Alg.ES256K, - pubKey: '0xd75D626a116D4a1959fE3bB938B2e7c116A05890', - sig: '0xe24d097cea880a40f8be2cf42f497b9fbda5f9e4a31b596827e051d78dce75c032fa7e5ee3046f7c6f116e5b98cb8d268fa9b9d222ff44719e2ec2a0d9159d0d1c' - } - - const approvals: Signature[] = [ - { - alg: Alg.ES256K, - pubKey: '0x501D5c2Ce1EF208aadf9131a98BAa593258CfA06', - sig: '0x48510e3b74799b8e8f4e01aba0d196e18f66d86a62ae91abf5b89be9391c15661c7d29ee4654a300ed6db977da512475ed5a39f70f677e23d1b2f53c1554d0dd1b' - }, - { - alg: Alg.ES256K, - pubKey: '0xab88c8785D0C00082dE75D801Fcb1d5066a6311e', - sig: '0xcc645f43d8df80c4deeb2e60a8c0c15d58586d2c29ea7c85208cea81d1c47cbd787b1c8473dde70c3a7d49f573e491223107933257b2b99ecc4806b7cc16848d1c' - } - ] - - const org: Organization = { - uid: 'ac1374c2-fd62-4b6e-bd49-a4afcdcb91cc' - } - - beforeAll(async () => { - const entityRepositoryMock = mock() - entityRepositoryMock.fetch.mockResolvedValue(FIXTURE.ENTITIES) - const encryptionMock = mock() - - module = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ - load: [load], - isGlobal: true - }), - AppModule, - PersistenceModule - ] - }) - .overrideProvider(EntityRepository) - .useValue(entityRepositoryMock) - .overrideProvider(EncryptionService) - .useValue(encryptionMock) - .compile() - - testPrismaService = module.get(TestPrismaService) - app = module.createNestApplication() - - await app.init() - }) - - afterAll(async () => { - await testPrismaService.truncateAll() - await module.close() - await app.close() - }) - - afterEach(async () => { - await testPrismaService.truncateAll() - }) - - describe('POST /policies', () => { - it('sets the organization policies', async () => { - const payload = { - authentication, - approvals, - request: { - action: Action.SET_POLICY_RULES, - nonce: 'random-nonce-111', - data: [ - { - then: Then.PERMIT, - name: 'examplePermitPolicy', - when: [ - { - criterion: Criterion.CHECK_RESOURCE_INTEGRITY, - args: null - }, - { - criterion: Criterion.CHECK_NONCE_EXISTS, - args: null - }, - { - criterion: Criterion.CHECK_ACTION, - args: [Action.SIGN_TRANSACTION] - }, - { - criterion: Criterion.CHECK_PRINCIPAL_ID, - args: ['matt@narval.xyz'] - }, - { - criterion: Criterion.CHECK_WALLET_ID, - args: ['eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b'] - }, - { - criterion: Criterion.CHECK_INTENT_TYPE, - args: [Intents.TRANSFER_NATIVE] - }, - { - criterion: Criterion.CHECK_INTENT_TOKEN, - args: ['eip155:137/slip44:966'] - }, - { - criterion: Criterion.CHECK_INTENT_AMOUNT, - args: { - currency: '*', - operator: ValueOperators.LESS_THAN_OR_EQUAL, - value: '1000000000000000000' - } - }, - { - criterion: Criterion.CHECK_APPROVALS, - args: [ - { - approvalCount: 2, - countPrincipal: false, - approvalEntityType: EntityType.User, - entityIds: ['aa@narval.xyz', 'bb@narval.xyz'] - }, - { - approvalCount: 1, - countPrincipal: false, - approvalEntityType: EntityType.UserRole, - entityIds: [UserRole.ADMIN] - } - ] - } - ] - }, - { - then: Then.FORBID, - name: 'exampleForbidPolicy', - when: [ - { - criterion: Criterion.CHECK_RESOURCE_INTEGRITY, - args: null - }, - { - criterion: Criterion.CHECK_NONCE_EXISTS, - args: null - }, - { - criterion: Criterion.CHECK_ACTION, - args: [Action.SIGN_TRANSACTION] - }, - { - criterion: Criterion.CHECK_PRINCIPAL_ID, - args: ['matt@narval.xyz'] - }, - { - criterion: Criterion.CHECK_WALLET_ID, - args: ['eip155:eoa:0x90d03a8971a2faa19a9d7ffdcbca28fe826a289b'] - }, - { - criterion: Criterion.CHECK_INTENT_TYPE, - args: [Intents.TRANSFER_NATIVE] - }, - { - criterion: Criterion.CHECK_INTENT_TOKEN, - args: ['eip155:137/slip44:966'] - }, - { - criterion: Criterion.CHECK_SPENDING_LIMIT, - args: { - limit: '1000000000000000000', - operator: ValueOperators.GREATER_THAN, - timeWindow: { - type: TimeWindow.ROLLING, - value: 43200 - }, - filters: { - tokens: ['eip155:137/slip44:966'], - users: ['matt@narval.xyz'] - } - } - } - ] - } - ] - } - } - - const { status, body } = await request(app.getHttpServer()) - .post('/admin/policies') - .set(REQUEST_HEADER_ORG_ID, org.uid) - .send(payload) - - expect(body.policies).toMatchObject(payload.request.data) - expect(status).toEqual(HttpStatus.CREATED) - - const path = `./apps/policy-engine/src/opa/rego/generated/${body.fileId}.rego` - const rego = readFileSync(path, 'utf-8') - expect(rego).toBeDefined() - - unlinkSync(path) - }) - }) -}) diff --git a/apps/policy-engine/src/app/app.module.ts b/apps/policy-engine/src/app/app.module.ts index bf1cfbb21..5f6adf991 100644 --- a/apps/policy-engine/src/app/app.module.ts +++ b/apps/policy-engine/src/app/app.module.ts @@ -7,10 +7,8 @@ import { load } from '../policy-engine.config' import { KeyValueModule } from '../shared/module/key-value/key-value.module' import { AppController } from './app.controller' import { AppService } from './app.service' -import { AdminService } from './core/service/admin.service' import { EngineService } from './core/service/engine.service' import { TenantService } from './core/service/tenant.service' -import { AdminController } from './http/rest/controller/admin.controller' import { TenantController } from './http/rest/controller/tenant.controller' import { OpaService } from './opa/opa.service' import { EngineRepository } from './persistence/repository/engine.repository' @@ -27,10 +25,9 @@ import { TenantRepository } from './persistence/repository/tenant.repository' EncryptionModule, KeyValueModule ], - controllers: [AppController, AdminController, TenantController], + controllers: [AppController, TenantController], providers: [ AppService, - AdminService, OpaService, EngineRepository, EngineService, diff --git a/apps/policy-engine/src/app/core/service/__test__/integration/data-store.service.spec.ts b/apps/policy-engine/src/app/core/service/__test__/integration/data-store.service.spec.ts index 85e72f87a..d012a285f 100644 --- a/apps/policy-engine/src/app/core/service/__test__/integration/data-store.service.spec.ts +++ b/apps/policy-engine/src/app/core/service/__test__/integration/data-store.service.spec.ts @@ -1,4 +1,13 @@ -import { DataStoreConfiguration, EntityData, EntitySignature, FIXTURE } from '@narval/policy-engine-shared' +import { + Action, + Criterion, + EntityData, + EntitySignature, + FIXTURE, + PolicyData, + PolicySignature, + Then +} from '@narval/policy-engine-shared' import { HttpModule } from '@nestjs/axios' import { HttpStatus } from '@nestjs/common' import { Test } from '@nestjs/testing' @@ -14,15 +23,35 @@ describe(DataStoreService.name, () => { const remoteDataStoreUrl = 'http://9.9.9.9:9000' - const entityDataStore: EntityData = { + const entityData: EntityData = { entity: { data: FIXTURE.ENTITIES } } - const entitySignatureStore: EntitySignature = { + const policyData: PolicyData = { + policy: { + data: [ + { + then: Then.PERMIT, + name: 'test-policy', + when: [ + { + criterion: Criterion.CHECK_ACTION, + args: [Action.SIGN_TRANSACTION] + } + ] + } + ] + } + } + + const signatureStore: EntitySignature & PolicySignature = { entity: { - signature: 'test-signature' + signature: 'test-entity-signature' + }, + policy: { + signature: 'test-policy-signature' } } @@ -37,20 +66,34 @@ describe(DataStoreService.name, () => { describe('fetch', () => { it('fetches data and signature from distinct stores', async () => { - nock(remoteDataStoreUrl).get('/').reply(HttpStatus.OK, entityDataStore) + nock(remoteDataStoreUrl).get('/entity').reply(HttpStatus.OK, entityData) + nock(remoteDataStoreUrl).get('/policy').reply(HttpStatus.OK, policyData) - await withTempJsonFile(JSON.stringify(entitySignatureStore), async (path) => { + await withTempJsonFile(JSON.stringify(signatureStore), async (path) => { const url = `file://${path}` - const config: DataStoreConfiguration = { - dataUrl: remoteDataStoreUrl, - signatureUrl: url, - keys: [] + const store = { + entity: { + dataUrl: `${remoteDataStoreUrl}/entity`, + signatureUrl: url, + keys: [] + }, + policy: { + dataUrl: `${remoteDataStoreUrl}/policy`, + signatureUrl: url, + keys: [] + } } - const { entity } = await service.fetch(config) + const { entity, policy } = await service.fetch(store) - expect(entity.data).toEqual(entityDataStore.entity.data) - expect(entity.signature).toEqual(entitySignatureStore.entity.signature) + expect(entity).toEqual({ + data: entityData.entity.data, + signature: signatureStore.entity.signature + }) + expect(policy).toEqual({ + data: policyData.policy.data, + signature: signatureStore.policy.signature + }) }) }) }) diff --git a/apps/policy-engine/src/app/core/service/admin.service.ts b/apps/policy-engine/src/app/core/service/admin.service.ts deleted file mode 100644 index 5eb76e359..000000000 --- a/apps/policy-engine/src/app/core/service/admin.service.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Injectable } from '@nestjs/common' -import { Policy, SetPolicyRulesRequest } from '../../../shared/type/policy.type' -import { OpaService } from '../../opa/opa.service' - -@Injectable() -export class AdminService { - constructor(private opaService: OpaService) {} - - async setPolicyRules(payload: SetPolicyRulesRequest): Promise<{ fileId: string; policies: Policy[] }> { - const { fileId, policies } = this.opaService.generateRegoPolicies(payload.request.data) - - return { fileId, policies } - } -} diff --git a/apps/policy-engine/src/app/core/service/data-store.service.ts b/apps/policy-engine/src/app/core/service/data-store.service.ts index dbb55eca3..5fc846d72 100644 --- a/apps/policy-engine/src/app/core/service/data-store.service.ts +++ b/apps/policy-engine/src/app/core/service/data-store.service.ts @@ -1,4 +1,12 @@ -import { DataStoreConfiguration, entityDataSchema, entitySignatureSchema } from '@narval/policy-engine-shared' +import { + DataStoreConfiguration, + Entities, + Policy, + entityDataSchema, + entitySignatureSchema, + policyDataSchema, + policySignatureSchema +} from '@narval/policy-engine-shared' import { HttpStatus, Injectable } from '@nestjs/common' import { ZodObject, z } from 'zod' import { DataStoreException } from '../exception/data-store.exception' @@ -8,16 +16,31 @@ import { DataStoreRepositoryFactory } from '../factory/data-store-repository.fac export class DataStoreService { constructor(private dataStoreRepositoryFactory: DataStoreRepositoryFactory) {} - async fetch(config: DataStoreConfiguration) { - const [entityData, entitySignature] = await Promise.all([ - this.fetchByUrl(config.dataUrl, entityDataSchema), - this.fetchByUrl(config.signatureUrl, entitySignatureSchema) + async fetch(store: { entity: DataStoreConfiguration; policy: DataStoreConfiguration }): Promise<{ + entity: { + data: Entities + signature: string + } + policy: { + data: Policy[] + signature: string + } + }> { + const [entityData, entitySignature, policyData, policySignature] = await Promise.all([ + this.fetchByUrl(store.entity.dataUrl, entityDataSchema), + this.fetchByUrl(store.entity.signatureUrl, entitySignatureSchema), + this.fetchByUrl(store.policy.dataUrl, policyDataSchema), + this.fetchByUrl(store.policy.signatureUrl, policySignatureSchema) ]) return { entity: { data: entityData.entity.data, signature: entitySignature.entity.signature + }, + policy: { + data: policyData.policy.data, + signature: policySignature.policy.signature } } } diff --git a/apps/policy-engine/src/app/http/rest/controller/admin.controller.ts b/apps/policy-engine/src/app/http/rest/controller/admin.controller.ts deleted file mode 100644 index 5cafae408..000000000 --- a/apps/policy-engine/src/app/http/rest/controller/admin.controller.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Body, Controller, Post } from '@nestjs/common' -import { SetPolicyRulesRequest } from '../../../../shared/type/policy.type' -import { AdminService } from '../../../core/service/admin.service' -import { SetPolicyRulesRequestDto } from '../dto/policy-rules/set-policy-rules-request.dto' -import { SetPolicyRulesResponseDto } from '../dto/policy-rules/set-policy-rules-response.dto' - -@Controller('/admin') -export class AdminController { - constructor(private readonly adminService: AdminService) {} - - @Post('/policies') - async setPolicyRules(@Body() body: SetPolicyRulesRequestDto) { - const payload: SetPolicyRulesRequest = body - - const policies = await this.adminService.setPolicyRules(payload) - - const response = new SetPolicyRulesResponseDto(policies) - return response - } -} diff --git a/apps/policy-engine/src/app/http/rest/dto/policy-rules/set-policy-rules-request.dto.ts b/apps/policy-engine/src/app/http/rest/dto/policy-rules/set-policy-rules-request.dto.ts deleted file mode 100644 index e968828b3..000000000 --- a/apps/policy-engine/src/app/http/rest/dto/policy-rules/set-policy-rules-request.dto.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Action, BaseActionDto, BaseActionRequestDto } from '@narval/policy-engine-shared' -import { ApiProperty } from '@nestjs/swagger' -import { Type } from 'class-transformer' -import { ArrayNotEmpty, IsDefined, Matches, ValidateNested } from 'class-validator' -import { Policy } from '../../../../../shared/type/policy.type' - -export class SetPolicyRulesDto extends BaseActionDto { - @Matches(Action.SET_POLICY_RULES) - @ApiProperty({ - enum: [Action.SET_POLICY_RULES], - default: Action.SET_POLICY_RULES - }) - action: typeof Action.SET_POLICY_RULES - - @ArrayNotEmpty() - @Type(() => Policy) - @ValidateNested({ each: true }) - @ApiProperty({ type: [Policy] }) - data: Policy[] -} - -export class SetPolicyRulesRequestDto extends BaseActionRequestDto { - @IsDefined() - @ValidateNested() - @Type(() => SetPolicyRulesDto) - @ApiProperty() - request: SetPolicyRulesDto -} diff --git a/apps/policy-engine/src/app/http/rest/dto/policy-rules/set-policy-rules-response.dto.ts b/apps/policy-engine/src/app/http/rest/dto/policy-rules/set-policy-rules-response.dto.ts deleted file mode 100644 index 23bb413bb..000000000 --- a/apps/policy-engine/src/app/http/rest/dto/policy-rules/set-policy-rules-response.dto.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger' -import { Type } from 'class-transformer' -import { ArrayNotEmpty, IsDefined, IsString, ValidateNested } from 'class-validator' -import { Policy } from '../../../../../shared/type/policy.type' - -export class SetPolicyRulesResponseDto { - @IsDefined() - @IsString() - fileId: string - - @ArrayNotEmpty() - @Type(() => Policy) - @ValidateNested({ each: true }) - @ApiProperty({ type: [Policy] }) - policies: Policy[] - - constructor(partial: Partial) { - Object.assign(this, partial) - } -} diff --git a/apps/policy-engine/src/app/opa/opa.service.ts b/apps/policy-engine/src/app/opa/opa.service.ts index 8ff902b2b..d39436cd2 100644 --- a/apps/policy-engine/src/app/opa/opa.service.ts +++ b/apps/policy-engine/src/app/opa/opa.service.ts @@ -1,4 +1,4 @@ -import { FIXTURE } from '@narval/policy-engine-shared' +import { FIXTURE, Policy } from '@narval/policy-engine-shared' import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common' import { loadPolicy } from '@open-policy-agent/opa-wasm' import { mkdirSync, readFileSync, writeFileSync } from 'fs' @@ -7,7 +7,6 @@ import { indexBy } from 'lodash/fp' import path from 'path' import { v4 as uuid } from 'uuid' import { RegoData, UserGroup, WalletGroup } from '../../shared/type/entities.types' -import { Policy } from '../../shared/type/policy.type' import { OpaResult, RegoInput } from '../../shared/type/rego' import { criterionToString, reasonToString } from '../../shared/utils/opa.utils' import { EntityRepository } from '../persistence/repository/entity.repository' diff --git a/apps/policy-engine/src/opa/script/translate-legacy-policy.script.ts b/apps/policy-engine/src/opa/script/translate-legacy-policy.script.ts index 6858cc3eb..f61b8d398 100644 --- a/apps/policy-engine/src/opa/script/translate-legacy-policy.script.ts +++ b/apps/policy-engine/src/opa/script/translate-legacy-policy.script.ts @@ -1,24 +1,15 @@ import { Action, - AssetType, - EntityType, - FiatCurrency, - UserRole, - ValueOperators, - toAccountId, - toAssetId -} from '@narval/policy-engine-shared' -import { Intents } from '@narval/transaction-request-intent' -import axios from 'axios' -import { Address, Hex } from 'viem' -import { ActionCriterion, ApprovalCondition, ApprovalsCriterion, + AssetType, Criterion, DestinationAddressCriterion, ERC1155TokenIdCriterion, ERC721TokenIdCriterion, + EntityType, + FiatCurrency, IntentAmountCriterion, IntentContractCriterion, IntentDomainCriterion, @@ -28,8 +19,15 @@ import { IntentTypeCriterion, Policy, PolicyCriterion, - Then -} from '../../shared/type/policy.type' + Then, + UserRole, + ValueOperators, + toAccountId, + toAssetId +} from '@narval/policy-engine-shared' +import { Intents } from '@narval/transaction-request-intent' +import axios from 'axios' +import { Address, Hex } from 'viem' type LegacyPolicy = { [key: string]: string | null } diff --git a/apps/policy-engine/src/opa/template/meta-permissions.data.ts b/apps/policy-engine/src/opa/template/meta-permissions.data.ts index 044585acf..257e4c65a 100644 --- a/apps/policy-engine/src/opa/template/meta-permissions.data.ts +++ b/apps/policy-engine/src/opa/template/meta-permissions.data.ts @@ -1,5 +1,4 @@ -import { Action, EntityType, UserRole } from '@narval/policy-engine-shared' -import { Criterion, Policy, Then } from '../../shared/type/policy.type' +import { Action, Criterion, EntityType, Policy, Then, UserRole } from '@narval/policy-engine-shared' const metaPermissions = [ Action.CREATE_ORGANIZATION, diff --git a/apps/policy-engine/src/opa/template/mockData.ts b/apps/policy-engine/src/opa/template/mockData.ts index d6bfc3350..852e744ad 100644 --- a/apps/policy-engine/src/opa/template/mockData.ts +++ b/apps/policy-engine/src/opa/template/mockData.ts @@ -1,6 +1,14 @@ -import { Action, EntityType, FIXTURE, UserRole, ValueOperators } from '@narval/policy-engine-shared' +import { + Action, + Criterion, + EntityType, + FIXTURE, + Policy, + Then, + UserRole, + ValueOperators +} from '@narval/policy-engine-shared' import { Intents } from '@narval/transaction-request-intent' -import { Criterion, Policy, Then } from '../../shared/type/policy.type' export const examplePermitPolicy: Policy = { then: Then.PERMIT, diff --git a/apps/policy-engine/src/shared/__test__/unit/opa.utils.spec.ts b/apps/policy-engine/src/shared/__test__/unit/opa.utils.spec.ts index ac58655d9..cc8846ced 100644 --- a/apps/policy-engine/src/shared/__test__/unit/opa.utils.spec.ts +++ b/apps/policy-engine/src/shared/__test__/unit/opa.utils.spec.ts @@ -1,13 +1,14 @@ -import { EntityType, ValueOperators } from '@narval/policy-engine-shared' import { ApprovalsCriterion, Criterion, ERC1155TransfersCriterion, + EntityType, IntentAmountCriterion, NonceRequiredCriterion, Then, + ValueOperators, WalletAddressCriterion -} from '../../type/policy.type' +} from '@narval/policy-engine-shared' import { criterionToString, reasonToString } from '../../utils/opa.utils' describe('criterionToString', () => { diff --git a/apps/policy-engine/src/shared/type/policy.type.ts b/apps/policy-engine/src/shared/type/policy.type.ts deleted file mode 100644 index 164f4955f..000000000 --- a/apps/policy-engine/src/shared/type/policy.type.ts +++ /dev/null @@ -1,745 +0,0 @@ -import { - AccountId, - AccountType, - Action, - AssetId, - BaseAction, - BaseAdminRequest, - EntityType, - FiatCurrency, - IdentityOperators, - IsAccountId, - IsAssetId, - IsHexString, - IsNotEmptyArrayEnum, - IsNotEmptyArrayString, - UserRole, - ValueOperators -} from '@narval/policy-engine-shared' -import { Alg } from '@narval/signature' -import { Intents } from '@narval/transaction-request-intent' -import { ApiExtraModels, ApiProperty, ApiPropertyOptional, getSchemaPath } from '@nestjs/swagger' -import { Transform, Type, plainToInstance } from 'class-transformer' -import { - ArrayNotEmpty, - IsBoolean, - IsDefined, - IsEnum, - IsIn, - IsNotEmpty, - IsNumber, - IsNumberString, - IsOptional, - IsString, - Matches, - ValidateNested -} from 'class-validator' -import { Address, Hex } from 'viem' - -export const Then = { - PERMIT: 'permit', - FORBID: 'forbid' -} as const - -export type Then = (typeof Then)[keyof typeof Then] - -export const Criterion = { - CHECK_ACTION: 'checkAction', - CHECK_RESOURCE_INTEGRITY: 'checkResourceIntegrity', - CHECK_PRINCIPAL_ID: 'checkPrincipalId', - CHECK_PRINCIPAL_ROLE: 'checkPrincipalRole', - CHECK_PRINCIPAL_GROUP: 'checkPrincipalGroup', - CHECK_WALLET_ID: 'checkWalletId', - CHECK_WALLET_ADDRESS: 'checkWalletAddress', - CHECK_WALLET_ACCOUNT_TYPE: 'checkWalletAccountType', - CHECK_WALLET_GROUP: 'checkWalletGroup', - CHECK_INTENT_TYPE: 'checkIntentType', - CHECK_DESTINATION_ID: 'checkDestinationId', - CHECK_DESTINATION_ADDRESS: 'checkDestinationAddress', - CHECK_DESTINATION_ACCOUNT_TYPE: 'checkDestinationAccountType', - CHECK_DESTINATION_CLASSIFICATION: 'checkDestinationClassification', - CHECK_INTENT_CONTRACT: 'checkIntentContract', - CHECK_INTENT_TOKEN: 'checkIntentToken', - CHECK_INTENT_SPENDER: 'checkIntentSpender', - CHECK_INTENT_CHAIN_ID: 'checkIntentChainId', - CHECK_INTENT_HEX_SIGNATURE: 'checkIntentHexSignature', - CHECK_INTENT_AMOUNT: 'checkIntentAmount', - CHECK_ERC721_TOKEN_ID: 'checkERC721TokenId', - CHECK_ERC1155_TOKEN_ID: 'checkERC1155TokenId', - CHECK_ERC1155_TRANSFERS: 'checkERC1155Transfers', - CHECK_INTENT_MESSAGE: 'checkIntentMessage', - CHECK_INTENT_PAYLOAD: 'checkIntentPayload', - CHECK_INTENT_ALGORITHM: 'checkIntentAlgorithm', - CHECK_INTENT_DOMAIN: 'checkIntentDomain', - CHECK_PERMIT_DEADLINE: 'checkPermitDeadline', - CHECK_CHAIN_ID: 'checkChainId', - CHECK_GAS_FEE_AMOUNT: 'checkGasFeeAmount', - CHECK_NONCE_EXISTS: 'checkNonceExists', - CHECK_NONCE_NOT_EXISTS: 'checkNonceNotExists', - CHECK_APPROVALS: 'checkApprovals', - CHECK_SPENDING_LIMIT: 'checkSpendingLimit' -} as const - -export type Criterion = (typeof Criterion)[keyof typeof Criterion] - -export const TimeWindow = { - ROLLING: 'rolling', - FIXED: 'fixed' -} as const - -export type TimeWindow = (typeof TimeWindow)[keyof typeof TimeWindow] - -export class AmountCondition { - @IsIn([...Object.values(FiatCurrency), '*']) - @ApiProperty({ enum: [...Object.values(FiatCurrency), '*'] }) - currency: FiatCurrency | '*' - - @IsEnum(ValueOperators) - @ApiProperty({ enum: ValueOperators }) - operator: ValueOperators - - @IsNotEmpty() - @IsNumberString() - @ApiProperty() - value: string -} - -export class ERC1155AmountCondition { - @IsAssetId() - @ApiProperty() - tokenId: AssetId - - @IsEnum(ValueOperators) - @ApiProperty({ enum: ValueOperators }) - operator: ValueOperators - - @IsNotEmpty() - @IsNumberString() - @ApiProperty() - value: string -} - -export class SignMessageCondition { - @IsIn([ValueOperators.EQUAL, IdentityOperators.CONTAINS]) - @ApiProperty({ enum: [ValueOperators.EQUAL, IdentityOperators.CONTAINS] }) - operator: ValueOperators.EQUAL | IdentityOperators.CONTAINS - - @IsNotEmpty() - @IsString() - @ApiProperty() - value: string -} - -export class SignTypedDataDomainCondition { - @IsOptional() - @IsNotEmptyArrayString() - @IsNumberString({}, { each: true }) - @ApiPropertyOptional() - version?: string[] - - @IsOptional() - @IsNotEmptyArrayString() - @IsNumberString({}, { each: true }) - @ApiPropertyOptional() - chainId?: string[] - - @IsOptional() - @IsNotEmptyArrayString() - @ApiPropertyOptional() - name?: string[] - - @IsOptional() - @IsNotEmptyArrayString() - @IsHexString({ each: true }) - @ApiPropertyOptional() - verifyingContract?: Address[] -} - -export class PermitDeadlineCondition { - @IsEnum(ValueOperators) - @ApiProperty({ enum: ValueOperators }) - operator: ValueOperators - - @IsNotEmpty() - @IsNumberString() - @ApiProperty() - value: string // timestamp in ms -} - -export class ApprovalCondition { - @IsDefined() - @IsNumber() - @ApiProperty() - approvalCount: number - - @IsDefined() - @IsBoolean() - @ApiProperty() - countPrincipal: boolean - - @IsDefined() - @IsEnum(EntityType) - @ApiProperty({ enum: EntityType }) - approvalEntityType: EntityType - - @IsNotEmptyArrayString() - @ApiProperty() - entityIds: string[] -} - -export class SpendingLimitTimeWindow { - @IsOptional() - @IsEnum(TimeWindow) - @ApiPropertyOptional({ enum: TimeWindow }) - type?: TimeWindow - - @IsNumber() - @IsOptional() - @ApiPropertyOptional() - value?: number // in seconds - - @IsNumber() - @IsOptional() - @ApiPropertyOptional() - startDate?: number // in seconds - - @IsNumber() - @IsOptional() - @ApiPropertyOptional() - endDate?: number // in seconds -} - -export class SpendingLimitFilters { - @IsNotEmptyArrayString() - @IsAssetId({ each: true }) - @IsOptional() - @ApiPropertyOptional() - tokens?: AssetId[] - - @IsNotEmptyArrayString() - @IsOptional() - @ApiPropertyOptional() - users?: string[] - - @IsNotEmptyArrayString() - @IsAccountId({ each: true }) - @IsOptional() - @ApiPropertyOptional() - resources?: AccountId[] - - @IsNotEmptyArrayString() - @IsNumberString({}, { each: true }) - @IsOptional() - @ApiPropertyOptional() - chains?: string[] - - @IsNotEmptyArrayString() - @IsOptional() - @ApiPropertyOptional() - userGroups?: string[] - - @IsNotEmptyArrayString() - @IsOptional() - @ApiPropertyOptional() - walletGroups?: string[] -} - -export class SpendingLimitCondition { - @IsNotEmpty() - @IsString() - @ApiProperty() - limit: string - - @IsEnum(ValueOperators) - @ApiProperty({ enum: ValueOperators }) - operator: ValueOperators - - @IsOptional() - @IsEnum(FiatCurrency) - @ApiPropertyOptional({ enum: FiatCurrency }) - currency?: FiatCurrency - - @ValidateNested() - @Type(() => SpendingLimitTimeWindow) - @IsOptional() - @ApiPropertyOptional() - timeWindow?: SpendingLimitTimeWindow - - @ValidateNested() - @Type(() => SpendingLimitFilters) - @IsOptional() - @ApiPropertyOptional() - filters?: SpendingLimitFilters -} - -class BaseCriterion { - criterion: Criterion -} - -export class ActionCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_ACTION) - criterion: typeof Criterion.CHECK_ACTION - - @IsNotEmptyArrayEnum(Action) - args: Action[] -} - -export class ResourceIntegrityCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_RESOURCE_INTEGRITY) - criterion: typeof Criterion.CHECK_RESOURCE_INTEGRITY - - args: null -} - -export class PrincipalIdCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_PRINCIPAL_ID) - criterion: typeof Criterion.CHECK_PRINCIPAL_ID - - @IsNotEmptyArrayString() - args: string[] -} - -export class PrincipalRoleCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_PRINCIPAL_ROLE) - criterion: typeof Criterion.CHECK_PRINCIPAL_ROLE - - @IsNotEmptyArrayEnum(UserRole) - args: UserRole[] -} - -export class PrincipalGroupCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_PRINCIPAL_GROUP) - criterion: typeof Criterion.CHECK_PRINCIPAL_GROUP - - @IsNotEmptyArrayString() - args: string[] -} - -export class WalletIdCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_WALLET_ID) - criterion: typeof Criterion.CHECK_WALLET_ID - - @IsNotEmptyArrayString() - args: string[] -} - -export class WalletAddressCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_WALLET_ADDRESS) - criterion: typeof Criterion.CHECK_WALLET_ADDRESS - - @IsNotEmptyArrayString() - args: string[] -} - -export class WalletAccountTypeCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_WALLET_ACCOUNT_TYPE) - criterion: typeof Criterion.CHECK_WALLET_ACCOUNT_TYPE - - @IsNotEmptyArrayEnum(AccountType) - args: AccountType[] -} - -export class ChainIdCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_CHAIN_ID) - criterion: typeof Criterion.CHECK_CHAIN_ID - - @IsNotEmptyArrayString() - @IsNumberString({}, { each: true }) - args: string[] -} - -export class WalletGroupCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_WALLET_GROUP) - criterion: typeof Criterion.CHECK_WALLET_GROUP - - @IsNotEmptyArrayString() - args: string[] -} - -export class IntentTypeCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_INTENT_TYPE) - criterion: typeof Criterion.CHECK_INTENT_TYPE - - @IsNotEmptyArrayEnum(Intents) - args: Intents[] -} - -export class DestinationIdCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_DESTINATION_ID) - criterion: typeof Criterion.CHECK_DESTINATION_ID - - @IsNotEmptyArrayString() - @IsAccountId({ each: true }) - args: AccountId[] -} - -export class DestinationAddressCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_DESTINATION_ADDRESS) - criterion: typeof Criterion.CHECK_DESTINATION_ADDRESS - - @IsNotEmptyArrayString() - args: string[] -} - -export class DestinationAccountTypeCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_DESTINATION_ACCOUNT_TYPE) - criterion: typeof Criterion.CHECK_DESTINATION_ACCOUNT_TYPE - - @IsNotEmptyArrayEnum(AccountType) - args: AccountType[] -} - -export class DestinationClassificationCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_DESTINATION_CLASSIFICATION) - criterion: typeof Criterion.CHECK_DESTINATION_CLASSIFICATION - - @IsNotEmptyArrayString() - args: string[] -} - -export class IntentContractCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_INTENT_CONTRACT) - criterion: typeof Criterion.CHECK_INTENT_CONTRACT - - @IsNotEmptyArrayString() - @IsAccountId({ each: true }) - args: AccountId[] -} - -export class IntentTokenCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_INTENT_TOKEN) - criterion: typeof Criterion.CHECK_INTENT_TOKEN - - @IsNotEmptyArrayString() - @IsAssetId({ each: true }) - args: AssetId[] -} - -export class IntentSpenderCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_INTENT_SPENDER) - criterion: typeof Criterion.CHECK_INTENT_SPENDER - - @IsNotEmptyArrayString() - @IsAccountId({ each: true }) - args: AccountId[] -} - -export class IntentChainIdCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_INTENT_CHAIN_ID) - criterion: typeof Criterion.CHECK_INTENT_CHAIN_ID - - @IsNotEmptyArrayString() - args: string[] -} - -export class IntentHexSignatureCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_INTENT_HEX_SIGNATURE) - criterion: typeof Criterion.CHECK_INTENT_HEX_SIGNATURE - - @IsNotEmptyArrayString() - @IsHexString({ each: true }) - args: Hex[] -} - -export class IntentAmountCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_INTENT_AMOUNT) - criterion: typeof Criterion.CHECK_INTENT_AMOUNT - - @ValidateNested() - @Type(() => AmountCondition) - args: AmountCondition -} - -export class ERC721TokenIdCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_ERC721_TOKEN_ID) - criterion: typeof Criterion.CHECK_ERC721_TOKEN_ID - - @IsNotEmptyArrayString() - @IsAssetId({ each: true }) - args: AssetId[] -} - -export class ERC1155TokenIdCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_ERC1155_TOKEN_ID) - criterion: typeof Criterion.CHECK_ERC1155_TOKEN_ID - - @IsNotEmptyArrayString() - @IsAssetId({ each: true }) - args: AssetId[] -} - -export class ERC1155TransfersCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_ERC1155_TRANSFERS) - criterion: typeof Criterion.CHECK_ERC1155_TRANSFERS - - @ValidateNested() - @Type(() => ERC1155AmountCondition) - args: ERC1155AmountCondition[] -} - -export class IntentMessageCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_INTENT_MESSAGE) - criterion: typeof Criterion.CHECK_INTENT_MESSAGE - - @ValidateNested() - @Type(() => SignMessageCondition) - args: SignMessageCondition -} - -export class IntentPayloadCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_INTENT_PAYLOAD) - criterion: typeof Criterion.CHECK_INTENT_PAYLOAD - - @IsNotEmptyArrayString() - args: string[] -} - -export class IntentAlgorithmCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_INTENT_ALGORITHM) - criterion: typeof Criterion.CHECK_INTENT_ALGORITHM - - @IsNotEmptyArrayEnum(Alg) - args: Alg[] -} - -export class IntentDomainCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_INTENT_DOMAIN) - criterion: typeof Criterion.CHECK_INTENT_DOMAIN - - @ValidateNested() - @Type(() => SignTypedDataDomainCondition) - args: SignTypedDataDomainCondition -} - -export class PermitDeadlineCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_PERMIT_DEADLINE) - criterion: typeof Criterion.CHECK_PERMIT_DEADLINE - - @ValidateNested() - @Type(() => PermitDeadlineCondition) - args: PermitDeadlineCondition -} - -export class GasFeeAmountCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_GAS_FEE_AMOUNT) - criterion: typeof Criterion.CHECK_GAS_FEE_AMOUNT - - @ValidateNested() - @Type(() => AmountCondition) - args: AmountCondition -} - -export class NonceRequiredCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_NONCE_EXISTS) - criterion: typeof Criterion.CHECK_NONCE_EXISTS - - args: null -} - -export class NonceNotRequiredCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_NONCE_NOT_EXISTS) - criterion: typeof Criterion.CHECK_NONCE_NOT_EXISTS - - args: null -} - -export class ApprovalsCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_APPROVALS) - criterion: typeof Criterion.CHECK_APPROVALS - - @ValidateNested() - @Type(() => ApprovalCondition) - args: ApprovalCondition[] -} - -export class SpendingLimitCriterion extends BaseCriterion { - @Matches(Criterion.CHECK_SPENDING_LIMIT) - criterion: typeof Criterion.CHECK_SPENDING_LIMIT - - @ValidateNested() - @Type(() => SpendingLimitCondition) - args: SpendingLimitCondition -} - -export type SetPolicyRulesAction = BaseAction & { - action: typeof Action.SET_POLICY_RULES - data: Policy[] -} - -export type SetPolicyRulesRequest = BaseAdminRequest & { - request: SetPolicyRulesAction -} - -const SUPPORTED_CRITERION = [ - ActionCriterion, - ResourceIntegrityCriterion, - PrincipalIdCriterion, - PrincipalRoleCriterion, - PrincipalGroupCriterion, - WalletIdCriterion, - WalletAddressCriterion, - WalletAccountTypeCriterion, - WalletGroupCriterion, - IntentTypeCriterion, - DestinationIdCriterion, - DestinationAddressCriterion, - DestinationAccountTypeCriterion, - DestinationClassificationCriterion, - IntentContractCriterion, - IntentTokenCriterion, - IntentSpenderCriterion, - IntentChainIdCriterion, - IntentHexSignatureCriterion, - IntentAmountCriterion, - ERC721TokenIdCriterion, - ERC1155TokenIdCriterion, - ERC1155TransfersCriterion, - IntentMessageCriterion, - IntentPayloadCriterion, - IntentAlgorithmCriterion, - IntentDomainCriterion, - PermitDeadlineCriterion, - ChainIdCriterion, - GasFeeAmountCriterion, - NonceRequiredCriterion, - NonceNotRequiredCriterion, - ApprovalsCriterion, - SpendingLimitCriterion -] as const - -export type PolicyCriterion = - | ActionCriterion - | ResourceIntegrityCriterion - | PrincipalIdCriterion - | PrincipalRoleCriterion - | PrincipalGroupCriterion - | WalletIdCriterion - | WalletAddressCriterion - | WalletAccountTypeCriterion - | WalletGroupCriterion - | IntentTypeCriterion - | DestinationIdCriterion - | DestinationAddressCriterion - | DestinationAccountTypeCriterion - | DestinationClassificationCriterion - | IntentContractCriterion - | IntentTokenCriterion - | IntentSpenderCriterion - | IntentChainIdCriterion - | IntentHexSignatureCriterion - | IntentAmountCriterion - | ERC721TokenIdCriterion - | ERC1155TokenIdCriterion - | ERC1155TransfersCriterion - | IntentMessageCriterion - | IntentPayloadCriterion - | IntentAlgorithmCriterion - | IntentDomainCriterion - | PermitDeadlineCriterion - | ChainIdCriterion - | GasFeeAmountCriterion - | NonceRequiredCriterion - | NonceNotRequiredCriterion - | ApprovalsCriterion - | SpendingLimitCriterion - -@ApiExtraModels(...SUPPORTED_CRITERION) -export class Policy { - @IsNotEmpty() - @IsString() - @ApiProperty() - name: string - - @ArrayNotEmpty() - @ValidateNested({ each: true }) - @Transform(({ value }) => { - return value.map((criterion: PolicyCriterion) => { - return instantiateCriterion(criterion) - }) - }) - @ApiProperty({ - oneOf: SUPPORTED_CRITERION.map((entity) => ({ - $ref: getSchemaPath(entity) - })) - }) - when: PolicyCriterion[] - - @IsEnum(Then) - @ApiProperty({ enum: Then }) - then: Then -} - -const instantiateCriterion = (criterion: PolicyCriterion) => { - switch (criterion.criterion) { - case Criterion.CHECK_ACTION: - return plainToInstance(ActionCriterion, criterion) - case Criterion.CHECK_RESOURCE_INTEGRITY: - return plainToInstance(ResourceIntegrityCriterion, criterion) - case Criterion.CHECK_PRINCIPAL_ID: - return plainToInstance(PrincipalIdCriterion, criterion) - case Criterion.CHECK_PRINCIPAL_ROLE: - return plainToInstance(PrincipalRoleCriterion, criterion) - case Criterion.CHECK_PRINCIPAL_GROUP: - return plainToInstance(PrincipalGroupCriterion, criterion) - case Criterion.CHECK_WALLET_ID: - return plainToInstance(WalletIdCriterion, criterion) - case Criterion.CHECK_WALLET_ADDRESS: - return plainToInstance(WalletAddressCriterion, criterion) - case Criterion.CHECK_WALLET_ACCOUNT_TYPE: - return plainToInstance(WalletAccountTypeCriterion, criterion) - case Criterion.CHECK_WALLET_GROUP: - return plainToInstance(WalletGroupCriterion, criterion) - case Criterion.CHECK_INTENT_TYPE: - return plainToInstance(IntentTypeCriterion, criterion) - case Criterion.CHECK_DESTINATION_ID: - return plainToInstance(DestinationIdCriterion, criterion) - case Criterion.CHECK_DESTINATION_ADDRESS: - return plainToInstance(DestinationAddressCriterion, criterion) - case Criterion.CHECK_DESTINATION_ACCOUNT_TYPE: - return plainToInstance(DestinationAccountTypeCriterion, criterion) - case Criterion.CHECK_DESTINATION_CLASSIFICATION: - return plainToInstance(DestinationClassificationCriterion, criterion) - case Criterion.CHECK_INTENT_CONTRACT: - return plainToInstance(IntentContractCriterion, criterion) - case Criterion.CHECK_INTENT_TOKEN: - return plainToInstance(IntentTokenCriterion, criterion) - case Criterion.CHECK_INTENT_SPENDER: - return plainToInstance(IntentSpenderCriterion, criterion) - case Criterion.CHECK_INTENT_CHAIN_ID: - return plainToInstance(IntentChainIdCriterion, criterion) - case Criterion.CHECK_INTENT_HEX_SIGNATURE: - return plainToInstance(IntentHexSignatureCriterion, criterion) - case Criterion.CHECK_INTENT_AMOUNT: - return plainToInstance(IntentAmountCriterion, criterion) - case Criterion.CHECK_ERC721_TOKEN_ID: - return plainToInstance(ERC721TokenIdCriterion, criterion) - case Criterion.CHECK_ERC1155_TOKEN_ID: - return plainToInstance(ERC1155TokenIdCriterion, criterion) - case Criterion.CHECK_ERC1155_TRANSFERS: - return plainToInstance(ERC1155TransfersCriterion, criterion) - case Criterion.CHECK_INTENT_MESSAGE: - return plainToInstance(IntentMessageCriterion, criterion) - case Criterion.CHECK_INTENT_PAYLOAD: - return plainToInstance(IntentPayloadCriterion, criterion) - case Criterion.CHECK_INTENT_ALGORITHM: - return plainToInstance(IntentAlgorithmCriterion, criterion) - case Criterion.CHECK_INTENT_DOMAIN: - return plainToInstance(IntentDomainCriterion, criterion) - case Criterion.CHECK_PERMIT_DEADLINE: - return plainToInstance(PermitDeadlineCriterion, criterion) - case Criterion.CHECK_CHAIN_ID: - return plainToInstance(ChainIdCriterion, criterion) - case Criterion.CHECK_GAS_FEE_AMOUNT: - return plainToInstance(GasFeeAmountCriterion, criterion) - case Criterion.CHECK_NONCE_EXISTS: - return plainToInstance(NonceRequiredCriterion, criterion) - case Criterion.CHECK_NONCE_NOT_EXISTS: - return plainToInstance(NonceNotRequiredCriterion, criterion) - case Criterion.CHECK_APPROVALS: - return plainToInstance(ApprovalsCriterion, criterion) - case Criterion.CHECK_SPENDING_LIMIT: - return plainToInstance(SpendingLimitCriterion, criterion) - default: - throw new Error('Unknown criterion: ' + criterion) - } -} diff --git a/apps/policy-engine/src/shared/utils/opa.utils.ts b/apps/policy-engine/src/shared/utils/opa.utils.ts index d5cced278..ebbed57d2 100644 --- a/apps/policy-engine/src/shared/utils/opa.utils.ts +++ b/apps/policy-engine/src/shared/utils/opa.utils.ts @@ -1,5 +1,5 @@ +import { Criterion, Policy, PolicyCriterion, Then } from '@narval/policy-engine-shared' import { isEmpty } from 'lodash' -import { Criterion, Policy, PolicyCriterion, Then } from '../type/policy.type' export const criterionToString = (item: PolicyCriterion) => { const criterion: Criterion = item.criterion diff --git a/packages/policy-engine-shared/src/index.ts b/packages/policy-engine-shared/src/index.ts index 6e9c30971..9f7030089 100644 --- a/packages/policy-engine-shared/src/index.ts +++ b/packages/policy-engine-shared/src/index.ts @@ -10,6 +10,7 @@ export * from './lib/type/action.type' export * from './lib/type/data-store.type' export * from './lib/type/domain.type' export * from './lib/type/entity.type' +export * from './lib/type/policy.type' export * as EntityUtil from './lib/util/entity.util' @@ -24,5 +25,6 @@ export * from './lib/schema/address.schema' export * from './lib/schema/data-store.schema' export * from './lib/schema/entity.schema' export * from './lib/schema/hex.schema' +export * from './lib/schema/policy.schema' export * as FIXTURE from './lib/dev.fixture' diff --git a/packages/policy-engine-shared/src/lib/schema/data-store.schema.ts b/packages/policy-engine-shared/src/lib/schema/data-store.schema.ts index 8a8a1f9ac..5fcb70f70 100644 --- a/packages/policy-engine-shared/src/lib/schema/data-store.schema.ts +++ b/packages/policy-engine-shared/src/lib/schema/data-store.schema.ts @@ -1,5 +1,6 @@ import { z } from 'zod' import { entitiesSchema } from './entity.schema' +import { policySchema } from './policy.schema' export const jsonWebKeySchema = z.object({ kty: z.enum(['EC', 'RSA']).describe('Key Type (e.g. RSA or EC'), @@ -22,26 +23,43 @@ export const dataStoreConfigurationSchema = z.object({ keys: z.array(jsonWebKeySchema) }) -export const entityDataSchema = z - .object({ - entity: z.object({ - data: entitiesSchema - }) +export const dataStoreSchema = z.object({ + entity: dataStoreConfigurationSchema, + policy: dataStoreConfigurationSchema +}) + +export const entityDataSchema = z.object({ + entity: z.object({ + data: entitiesSchema + }) +}) + +export const entitySignatureSchema = z.object({ + entity: z.object({ + signature: z.string() + }) +}) + +export const entityJsonWebKeysSchema = z.object({ + entity: z.object({ + keys: z.array(jsonWebKeySchema) }) - .describe('Entity data') +}) -export const entitySignatureSchema = z - .object({ - entity: z.object({ - signature: z.string() - }) +export const policyDataSchema = z.object({ + policy: z.object({ + data: z.array(policySchema) }) - .describe('Entity data signature') +}) -export const entityJsonWebKeySetSchema = z - .object({ - entity: z.object({ - keys: z.array(jsonWebKeySchema) - }) +export const policySignatureSchema = z.object({ + policy: z.object({ + signature: z.string() }) - .describe('Entity JWKS') +}) + +export const policyJsonWebKeysSchema = z.object({ + policy: z.object({ + keys: z.array(jsonWebKeySchema) + }) +}) diff --git a/packages/policy-engine-shared/src/lib/schema/policy.schema.ts b/packages/policy-engine-shared/src/lib/schema/policy.schema.ts new file mode 100644 index 000000000..3922bf355 --- /dev/null +++ b/packages/policy-engine-shared/src/lib/schema/policy.schema.ts @@ -0,0 +1,351 @@ +import { Alg } from '@narval/signature' +import { z } from 'zod' +import { Action } from '../type/action.type' +import { EntityType, FiatCurrency, IdentityOperators, ValueOperators } from '../type/domain.type' +import { AccountType, UserRole } from '../type/entity.type' +import { accountIdSchema, assetIdSchema } from '../util/caip.util' +import { addressSchema } from './address.schema' +import { hexSchema } from './hex.schema' + +// TODO: (@wcalderipe, 07/03/24): duplicate Intents enum from +// transaction-request-intent due to a circular dependency between the two +// packages. +const intentSchema = z.nativeEnum({ + TRANSFER_NATIVE: 'transferNative', + TRANSFER_ERC20: 'transferErc20', + TRANSFER_ERC721: 'transferErc721', + TRANSFER_ERC1155: 'transferErc1155', + APPROVE_TOKEN_ALLOWANCE: 'approveTokenAllowance', + PERMIT: 'permit', + PERMIT2: 'permit2', + CALL_CONTRACT: 'callContract', + RETRY_TRANSACTION: 'retryTransaction', + CANCEL_TRANSACTION: 'cancelTransaction', + DEPLOY_CONTRACT: 'deployContract', + DEPLOY_ERC_4337_WALLET: 'deployErc4337Wallet', + DEPLOY_SAFE_WALLET: 'deploySafeWallet', + SIGN_MESSAGE: 'signMessage', + SIGN_RAW: 'signRaw', + SIGN_TYPED_DATA: 'signTypedData', + USER_OPERATION: 'userOperation' +} as const) + +export const thenSchema = z.nativeEnum({ + PERMIT: 'permit', + FORBID: 'forbid' +} as const) + +export const criterionSchema = z.nativeEnum({ + CHECK_ACTION: 'checkAction', + CHECK_RESOURCE_INTEGRITY: 'checkResourceIntegrity', + CHECK_PRINCIPAL_ID: 'checkPrincipalId', + CHECK_PRINCIPAL_ROLE: 'checkPrincipalRole', + CHECK_PRINCIPAL_GROUP: 'checkPrincipalGroup', + CHECK_WALLET_ID: 'checkWalletId', + CHECK_WALLET_ADDRESS: 'checkWalletAddress', + CHECK_WALLET_ACCOUNT_TYPE: 'checkWalletAccountType', + CHECK_WALLET_GROUP: 'checkWalletGroup', + CHECK_INTENT_TYPE: 'checkIntentType', + CHECK_DESTINATION_ID: 'checkDestinationId', + CHECK_DESTINATION_ADDRESS: 'checkDestinationAddress', + CHECK_DESTINATION_ACCOUNT_TYPE: 'checkDestinationAccountType', + CHECK_DESTINATION_CLASSIFICATION: 'checkDestinationClassification', + CHECK_INTENT_CONTRACT: 'checkIntentContract', + CHECK_INTENT_TOKEN: 'checkIntentToken', + CHECK_INTENT_SPENDER: 'checkIntentSpender', + CHECK_INTENT_CHAIN_ID: 'checkIntentChainId', + CHECK_INTENT_HEX_SIGNATURE: 'checkIntentHexSignature', + CHECK_INTENT_AMOUNT: 'checkIntentAmount', + CHECK_ERC721_TOKEN_ID: 'checkERC721TokenId', + CHECK_ERC1155_TOKEN_ID: 'checkERC1155TokenId', + CHECK_ERC1155_TRANSFERS: 'checkERC1155Transfers', + CHECK_INTENT_MESSAGE: 'checkIntentMessage', + CHECK_INTENT_PAYLOAD: 'checkIntentPayload', + CHECK_INTENT_ALGORITHM: 'checkIntentAlgorithm', + CHECK_INTENT_DOMAIN: 'checkIntentDomain', + CHECK_PERMIT_DEADLINE: 'checkPermitDeadline', + CHECK_CHAIN_ID: 'checkChainId', + CHECK_GAS_FEE_AMOUNT: 'checkGasFeeAmount', + CHECK_NONCE_EXISTS: 'checkNonceExists', + CHECK_NONCE_NOT_EXISTS: 'checkNonceNotExists', + CHECK_APPROVALS: 'checkApprovals', + CHECK_SPENDING_LIMIT: 'checkSpendingLimit' +} as const) + +export const timeWindowSchema = z.nativeEnum({ + ROLLING: 'rolling', + FIXED: 'fixed' +} as const) + +export const amountConditionSchema = z.object({ + currency: z.union([z.nativeEnum(FiatCurrency), z.literal('*')]), + operator: z.nativeEnum(ValueOperators), + value: z.string() +}) + +export const erc1155AmountConditionSchema = z.object({ + tokenId: assetIdSchema, + operator: z.nativeEnum(ValueOperators), + value: z.string() +}) + +export const signMessageConditionSchema = z.object({ + operator: z.union([z.literal(ValueOperators.EQUAL), z.literal(IdentityOperators.CONTAINS)]), + value: z.string().min(1) +}) + +export const signTypedDataDomainConditionSchema = z.object({ + version: z.array(z.string()).min(1).optional(), + chainId: z.array(z.string()).min(1).optional(), + name: z.array(z.string()).min(1).optional(), + verifyingContract: z.array(addressSchema).min(1).optional() +}) + +export const permitDeadlineConditionSchema = z.object({ + operator: z.nativeEnum(ValueOperators), + value: z.string().min(1) +}) + +export const approvalConditionSchema = z.object({ + approvalCount: z.number().int(), + countPrincipal: z.boolean(), + approvalEntityType: z.nativeEnum(EntityType), + entityIds: z.array(z.string().min(1)) +}) + +export const spendingLimitTimeWindowSchema = z.object({ + type: timeWindowSchema.optional(), + value: z.number().optional(), + startDate: z.number().int().optional(), + endDate: z.number().int().optional() +}) + +export const spendingLimitFiltersSchema = z.object({ + tokens: z.array(assetIdSchema).min(1).optional(), + users: z.array(z.string().min(1)).min(1).optional(), + resources: z.array(accountIdSchema).min(1).optional(), + chains: z.array(z.string().min(1)).min(1).optional(), + userGroups: z.array(z.string().min(1)).min(1).optional(), + walletGroups: z.array(z.string().min(1)).min(1).optional() +}) + +export const spendingLimitConditionSchema = z.object({ + limit: z.string().min(1), + operator: z.nativeEnum(ValueOperators), + currency: z.nativeEnum(FiatCurrency).optional(), + timeWindow: spendingLimitTimeWindowSchema.optional(), + filters: spendingLimitFiltersSchema.optional() +}) + +export const actionCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_ACTION), + args: z.array(z.nativeEnum(Action)).min(1) +}) + +export const resourceIntegrityCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_RESOURCE_INTEGRITY), + args: z.null() +}) + +export const principalIdCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_PRINCIPAL_ID), + args: z.array(z.string().min(1)).min(1) +}) + +export const principalRoleCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_PRINCIPAL_ROLE), + args: z.array(z.nativeEnum(UserRole)).min(1) +}) + +export const principalGroupCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_PRINCIPAL_GROUP), + args: z.array(z.string().min(1)).min(1) +}) + +export const walletIdCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_WALLET_ID), + args: z.array(z.string().min(1)).min(1) +}) + +export const walletAddressCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_WALLET_ADDRESS), + args: z.array(z.string().min(1)).min(1) +}) + +export const walletAccountTypeCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_WALLET_ACCOUNT_TYPE), + args: z.array(z.nativeEnum(AccountType)).min(1) +}) + +export const chainIdCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_CHAIN_ID), + args: z.array(z.string().min(1)).min(1) +}) + +export const walletGroupCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_WALLET_GROUP), + args: z.array(z.string().min(1)).min(1) +}) + +export const intentTypeCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_INTENT_TYPE), + args: z.array(intentSchema).min(1) +}) + +export const destinationIdCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_DESTINATION_ID), + args: z.array(accountIdSchema).min(1) +}) + +export const destinationAddressCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_DESTINATION_ADDRESS), + args: z.array(z.string().min(1)).min(1) +}) + +export const destinationAccountTypeCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_DESTINATION_ACCOUNT_TYPE), + args: z.array(z.nativeEnum(AccountType)).min(1) +}) + +export const destinationClassificationCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_DESTINATION_CLASSIFICATION), + args: z.array(z.string().min(1)).min(1) +}) + +export const intentContractCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_INTENT_CONTRACT), + args: z.array(accountIdSchema).min(1) +}) + +export const intentTokenCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_INTENT_TOKEN), + args: z.array(assetIdSchema).min(1) +}) + +export const intentSpenderCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_INTENT_SPENDER), + args: z.array(accountIdSchema).min(1) +}) + +export const intentChainIdCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_INTENT_CHAIN_ID), + args: z.array(z.string().min(1)).min(1) +}) + +export const intentHexSignatureCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_INTENT_HEX_SIGNATURE), + args: z.array(hexSchema).min(1) +}) + +export const intentAmountCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_INTENT_AMOUNT), + args: amountConditionSchema +}) + +export const erc721TokenIdCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_ERC721_TOKEN_ID), + args: z.array(assetIdSchema).min(1) +}) + +export const erc1155TokenIdCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_ERC1155_TOKEN_ID), + args: z.array(assetIdSchema).min(1) +}) + +export const erc1155TransfersCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_ERC1155_TRANSFERS), + args: z.array(erc1155AmountConditionSchema).min(1) +}) + +export const intentMessageCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_INTENT_MESSAGE), + args: signMessageConditionSchema +}) + +export const intentPayloadCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_INTENT_PAYLOAD), + args: z.array(z.string().min(1)).min(1) +}) + +export const intentAlgorithmCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_INTENT_ALGORITHM), + args: z.array(z.nativeEnum(Alg)).min(1) +}) + +export const intentDomainCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_INTENT_DOMAIN), + args: signTypedDataDomainConditionSchema +}) + +export const permitDeadlineCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_PERMIT_DEADLINE), + args: permitDeadlineConditionSchema +}) + +export const gasFeeAmountCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_GAS_FEE_AMOUNT), + args: amountConditionSchema +}) + +export const nonceRequiredCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_NONCE_EXISTS), + args: z.null() +}) + +export const nonceNotRequiredCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_NONCE_NOT_EXISTS), + args: z.null() +}) + +export const approvalsCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_APPROVALS), + args: z.array(approvalConditionSchema).min(1) +}) + +export const spendingLimitCriterionSchema = z.object({ + criterion: z.literal(criterionSchema.enum.CHECK_SPENDING_LIMIT), + args: spendingLimitConditionSchema +}) + +export const policyCriterionSchema = z.discriminatedUnion('criterion', [ + actionCriterionSchema, + resourceIntegrityCriterionSchema, + principalIdCriterionSchema, + principalRoleCriterionSchema, + principalGroupCriterionSchema, + walletIdCriterionSchema, + walletAddressCriterionSchema, + walletAccountTypeCriterionSchema, + walletGroupCriterionSchema, + intentTypeCriterionSchema, + destinationIdCriterionSchema, + destinationAddressCriterionSchema, + destinationAccountTypeCriterionSchema, + destinationClassificationCriterionSchema, + intentContractCriterionSchema, + intentTokenCriterionSchema, + intentSpenderCriterionSchema, + intentChainIdCriterionSchema, + intentHexSignatureCriterionSchema, + intentAmountCriterionSchema, + erc721TokenIdCriterionSchema, + erc1155TokenIdCriterionSchema, + erc1155TransfersCriterionSchema, + intentMessageCriterionSchema, + intentPayloadCriterionSchema, + intentAlgorithmCriterionSchema, + intentDomainCriterionSchema, + permitDeadlineCriterionSchema, + chainIdCriterionSchema, + gasFeeAmountCriterionSchema, + nonceRequiredCriterionSchema, + nonceNotRequiredCriterionSchema, + approvalsCriterionSchema, + spendingLimitCriterionSchema +]) + +export const policySchema = z.object({ + name: z.string().min(1), + when: z.array(policyCriterionSchema).min(1), + then: thenSchema +}) diff --git a/packages/policy-engine-shared/src/lib/type/data-store.type.ts b/packages/policy-engine-shared/src/lib/type/data-store.type.ts index f25f23148..fb07af95a 100644 --- a/packages/policy-engine-shared/src/lib/type/data-store.type.ts +++ b/packages/policy-engine-shared/src/lib/type/data-store.type.ts @@ -3,9 +3,12 @@ import { dataStoreConfigurationSchema, dataStoreProtocolSchema, entityDataSchema, - entityJsonWebKeySetSchema, + entityJsonWebKeysSchema, entitySignatureSchema, - jsonWebKeySchema + jsonWebKeySchema, + policyDataSchema, + policyJsonWebKeysSchema, + policySignatureSchema } from '../schema/data-store.schema' export type JsonWebKey = z.infer @@ -16,7 +19,9 @@ export const DataStoreProtocol = dataStoreProtocolSchema.Enum export type DataStoreConfiguration = z.infer export type EntityData = z.infer - export type EntitySignature = z.infer +export type EntityJsonWebKeySet = z.infer -export type EntityJsonWebKeySet = z.infer +export type PolicyData = z.infer +export type PolicySignature = z.infer +export type PolicyJsonWebKeySet = z.infer diff --git a/packages/policy-engine-shared/src/lib/type/policy.type.ts b/packages/policy-engine-shared/src/lib/type/policy.type.ts new file mode 100644 index 000000000..307e9c3f0 --- /dev/null +++ b/packages/policy-engine-shared/src/lib/type/policy.type.ts @@ -0,0 +1,109 @@ +import { z } from 'zod' +import { + actionCriterionSchema, + amountConditionSchema, + approvalConditionSchema, + approvalsCriterionSchema, + chainIdCriterionSchema, + criterionSchema, + destinationAccountTypeCriterionSchema, + destinationAddressCriterionSchema, + destinationClassificationCriterionSchema, + destinationIdCriterionSchema, + erc1155AmountConditionSchema, + erc1155TokenIdCriterionSchema, + erc1155TransfersCriterionSchema, + erc721TokenIdCriterionSchema, + gasFeeAmountCriterionSchema, + intentAlgorithmCriterionSchema, + intentAmountCriterionSchema, + intentChainIdCriterionSchema, + intentContractCriterionSchema, + intentDomainCriterionSchema, + intentHexSignatureCriterionSchema, + intentMessageCriterionSchema, + intentPayloadCriterionSchema, + intentSpenderCriterionSchema, + intentTokenCriterionSchema, + intentTypeCriterionSchema, + nonceNotRequiredCriterionSchema, + nonceRequiredCriterionSchema, + permitDeadlineConditionSchema, + permitDeadlineCriterionSchema, + policyCriterionSchema, + policySchema, + principalGroupCriterionSchema, + principalIdCriterionSchema, + principalRoleCriterionSchema, + resourceIntegrityCriterionSchema, + signMessageConditionSchema, + signTypedDataDomainConditionSchema, + spendingLimitConditionSchema, + spendingLimitCriterionSchema, + spendingLimitFiltersSchema, + spendingLimitTimeWindowSchema, + thenSchema, + timeWindowSchema, + walletAccountTypeCriterionSchema, + walletAddressCriterionSchema, + walletGroupCriterionSchema, + walletIdCriterionSchema +} from '../schema/policy.schema' + +export const Then = thenSchema.enum +export type Then = z.infer + +export const Criterion = criterionSchema.enum +export type Criterion = z.infer + +export const TimeWindow = timeWindowSchema.enum +export type TimeWindow = z.infer + +export type AmountCondition = z.infer +export type ERC1155AmountCondition = z.infer +export type SignMessageCondition = z.infer +export type SignTypedDataDomainCondition = z.infer +export type PermitDeadlineCondition = z.infer +export type ApprovalCondition = z.infer +export type SpendingLimitTimeWindow = z.infer +export type SpendingLimitFilters = z.infer +export type SpendingLimitCondition = z.infer + +export type ActionCriterion = z.infer +export type ResourceIntegrityCriterion = z.infer +export type PrincipalIdCriterion = z.infer +export type PrincipalRoleCriterion = z.infer +export type PrincipalGroupCriterion = z.infer +export type WalletIdCriterion = z.infer +export type WalletAddressCriterion = z.infer +export type WalletAccountTypeCriterion = z.infer +export type ChainIdCriterion = z.infer +export type WalletGroupCriterion = z.infer +export type IntentTypeCriterion = z.infer +export type DestinationIdCriterion = z.infer +export type DestinationAddressCriterion = z.infer +export type DestinationAccountTypeCriterion = z.infer +export type DestinationClassificationCriterion = z.infer +export type IntentContractCriterion = z.infer +export type IntentTokenCriterion = z.infer +export type IntentSpenderCriterion = z.infer +export type IntentChainIdCriterion = z.infer +export type IntentHexSignatureCriterion = z.infer +export type IntentAmountCriterion = z.infer +export type ERC721TokenIdCriterion = z.infer +export type ERC1155TokenIdCriterion = z.infer +export type ERC1155TransfersCriterion = z.infer +export type IntentMessageCriterion = z.infer +export type IntentPayloadCriterion = z.infer +export type IntentAlgorithmCriterion = z.infer +export type IntentDomainCriterion = z.infer +export type PermitDeadlineCriterion = z.infer +export type GasFeeAmountCriterion = z.infer +export type NonceRequiredCriterion = z.infer +export type NonceNotRequiredCriterion = z.infer +export type ApprovalsCriterion = z.infer +export type SpendingLimitCriterion = z.infer + +export type PolicyCriterion = z.infer + +export type Policy = z.infer