Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add managed data store to armory app #233

Merged
merged 35 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
eeef6c9
setup new admin module
Apr 15, 2024
a73cd29
Merge remote-tracking branch 'origin/main' into feature/nar-1581-crea…
Apr 15, 2024
ecc5deb
Merge remote-tracking branch 'origin/main' into feature/nar-1581-crea…
Apr 15, 2024
93c7393
Merge remote-tracking branch 'origin/main' into feature/nar-1581-crea…
Apr 15, 2024
ad783ef
add prisma schema
Apr 15, 2024
775cf16
update schema
Apr 16, 2024
8f776ac
add policy model
Apr 16, 2024
2c6d7f3
add comment
Apr 16, 2024
8c4eef8
add signature model
Apr 16, 2024
59d23ac
wip
Apr 17, 2024
af421fe
fix
Apr 17, 2024
5af09d4
revert
Apr 17, 2024
04281ad
revert
Apr 17, 2024
e16051a
CR
Apr 17, 2024
62ef776
Merge remote-tracking branch 'origin/main' into bootstrap-admin-service
Apr 17, 2024
37f8e6d
remove resourceId from data store action
Apr 17, 2024
316be9e
Add headers to data store config (#234)
Apr 19, 2024
62366f2
Add sending evaluation request
Apr 19, 2024
13dd168
fix
Apr 19, 2024
ce0e6d7
fix
Apr 19, 2024
46a4de7
fix
Apr 19, 2024
08bf013
Update apps/armory/src/admin/core/service/entity-data-store.service.ts
Apr 19, 2024
8125622
Update apps/armory/src/orchestration/core/service/cluster.service.ts
Apr 19, 2024
739ff94
fixes after CR
Apr 19, 2024
4d2d4fd
fix
Apr 19, 2024
78a1f20
fix circular dependency
Apr 19, 2024
fc120d6
fixes after CR
May 3, 2024
5ae2e11
Merge remote-tracking branch 'origin/main' into bootstrap-admin-service
May 3, 2024
b417f77
table fixes
May 6, 2024
a2a0383
Add signature unit test
May 6, 2024
23dd945
fix
May 6, 2024
0c4888e
add parser when setting data store
May 6, 2024
5719e53
fix devtool
May 6, 2024
3c4eee9
Merge remote-tracking branch 'origin/main' into bootstrap-admin-service
May 6, 2024
892c5f7
last CR
May 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/armory/.env.default
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
NODE_ENV=development

CORS="http://localhost:4200"

PORT=3005

ARMORY_DATABASE_URL="postgresql://postgres:postgres@localhost:5432/armory?schema=public"
Expand Down
43 changes: 43 additions & 0 deletions apps/armory/src/admin/admin.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { HttpModule } from '@nestjs/axios'
import { ClassSerializerInterceptor, Module, ValidationPipe } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { APP_FILTER, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'
import { ZodValidationPipe } from 'nestjs-zod'
import { ApplicationExceptionFilter } from '../shared/filter/application-exception.filter'
import { ZodExceptionFilter } from '../shared/filter/zod-exception.filter'
import { PersistenceModule } from '../shared/module/persistence/persistence.module'
import { DataStoreService } from './core/service/data-store.service'
import { DataStoreController } from './http/controller/data-store.controller'
import { DataStoreRepository } from './persistence/repository/data-store.repository'

@Module({
imports: [ConfigModule.forRoot(), HttpModule, PersistenceModule],
controllers: [DataStoreController],
providers: [
DataStoreService,
DataStoreRepository,
{
provide: APP_FILTER,
useClass: ApplicationExceptionFilter
},
{
provide: APP_FILTER,
useClass: ZodExceptionFilter
},
{
provide: APP_INTERCEPTOR,
useClass: ClassSerializerInterceptor
},
{
// DEPRECATE: Use Zod generated DTOs to validate request and responses.
provide: APP_PIPE,
useClass: ValidationPipe
},
samteb marked this conversation as resolved.
Show resolved Hide resolved
{
provide: APP_PIPE,
useClass: ZodValidationPipe
}
],
exports: []
})
export class AdminModule {}
113 changes: 113 additions & 0 deletions apps/armory/src/admin/core/service/data-store.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Entities, EntityStore, Policy, PolicyStore } from '@narval/policy-engine-shared'
import {
Alg,
Payload,
SigningAlg,
buildSignerEip191,
hash,
privateKeyToJwk,
signJwt,
verifyJwt
} from '@narval/signature'
import { Injectable } from '@nestjs/common'
import { ACCOUNT, UNSAFE_PRIVATE_KEY } from 'packages/policy-engine-shared/src/lib/dev.fixture'
import { DataStoreRepository } from '../../persistence/repository/data-store.repository'

@Injectable()
export class DataStoreService {
constructor(private dataStoreRepository: DataStoreRepository) {}

async getEntities(orgId: string) {
const dataStore = await this.dataStoreRepository.getLatestDataStore(orgId)
return dataStore?.data.entity.data
}

async getPolicies(orgId: string) {
const dataStore = await this.dataStoreRepository.getLatestDataStore(orgId)
return dataStore?.data.policy.data
}

async setEntities(orgId: string, entities: Entities) {
const signature = await this.signDataPayload(entities)
const entity: EntityStore = {
data: entities,
signature
}
const dataStore = await this.dataStoreRepository.getLatestDataStore(orgId)

if (!dataStore) {
const policy: PolicyStore = {
data: [],
signature: ''
}

return this.dataStoreRepository.setDataStore({
orgId,
version: 1,
data: { entity, policy }
})
} else {
const { policy } = dataStore.data

return this.dataStoreRepository.setDataStore({
orgId,
version: dataStore.version + 1,
data: { entity, policy }
})
}
}

async setPolicies(orgId: string, policies: Policy[]) {
const signature = await this.signDataPayload(policies)
const policy: PolicyStore = {
data: policies,
signature
}
const dataStore = await this.dataStoreRepository.getLatestDataStore(orgId)

if (!dataStore) {
const entity: EntityStore = {
data: {} as Entities,
signature: ''
}

return this.dataStoreRepository.setDataStore({
orgId,
version: 1,
data: { entity, policy }
})
} else {
const { entity } = dataStore.data

return this.dataStoreRepository.setDataStore({
orgId,
version: dataStore.version + 1,
data: { entity, policy }
})
}
}

private async signDataPayload(data: Entities | Policy[]) {
const jwk = privateKeyToJwk(UNSAFE_PRIVATE_KEY.Root, Alg.ES256K)
samteb marked this conversation as resolved.
Show resolved Hide resolved

const payload: Payload = {
data: hash(data),
sub: ACCOUNT.Root.address,
iss: 'https://armory.narval.xyz',
iat: Math.floor(Date.now() / 1000)
}

const signature = await signJwt(
payload,
jwk,
{ alg: SigningAlg.EIP191 },
buildSignerEip191(UNSAFE_PRIVATE_KEY.Root)
)

const verify = await verifyJwt(signature, jwk)

console.log({ signature, verify })
samteb marked this conversation as resolved.
Show resolved Hide resolved

return signature
}
}
31 changes: 31 additions & 0 deletions apps/armory/src/admin/http/controller/data-store.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Entities, Policy } from '@narval/policy-engine-shared'
import { Body, Controller, Get, Post } from '@nestjs/common'
import { ApiTags } from '@nestjs/swagger'
import { OrgId } from '../../../shared/decorator/org-id.decorator'
import { DataStoreService } from '../../core/service/data-store.service'

@Controller('/data-store')
samteb marked this conversation as resolved.
Show resolved Hide resolved
@ApiTags('Data Store')
export class DataStoreController {
samteb marked this conversation as resolved.
Show resolved Hide resolved
constructor(private dataStoreService: DataStoreService) {}

@Get('/entities')
getEntities(@OrgId() orgId: string) {
return this.dataStoreService.getEntities(orgId)
}

@Get('/policies')
getPolicies(@OrgId() orgId: string) {
return this.dataStoreService.getPolicies(orgId)
}

@Post('/entities')
setEntities(@OrgId() orgId: string, @Body() body: Entities) {
return this.dataStoreService.setEntities(orgId, body)
}

@Post('/policies')
setPolicies(@OrgId() orgId: string, @Body() body: Policy[]) {
return this.dataStoreService.setPolicies(orgId, body)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { EntityStore, PolicyStore } from '@narval/policy-engine-shared'
import { Injectable } from '@nestjs/common'
import { PrismaService } from '../../../shared/module/persistence/service/prisma.service'

type DataStore = { entity: EntityStore; policy: PolicyStore }

@Injectable()
export class DataStoreRepository {
constructor(private prismaService: PrismaService) {}

setDataStore(data: { orgId: string; version: number; data: DataStore }) {
return this.prismaService.dataStore.create({ data })
}

async getLatestDataStore(orgId: string) {
const version = await this.getLatestVersion(orgId)

if (!version) return null

const dataStore = await this.prismaService.dataStore.findFirst({ where: { orgId, version } })

if (!dataStore) return null

return { ...dataStore, data: dataStore.data as DataStore }
}

private async getLatestVersion(orgId: string): Promise<number> {
const data = await this.prismaService.dataStore.aggregate({
where: {
orgId
},
_max: {
version: true
}
})

return data._max?.version || 0
}
}
2 changes: 2 additions & 0 deletions apps/armory/src/armory.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export enum Env {
const configSchema = z.object({
env: z.nativeEnum(Env),
port: z.coerce.number(),
cors: z.array(z.string()).optional(),
database: z.object({
url: z.string().startsWith('postgresql://')
}),
Expand All @@ -28,6 +29,7 @@ export const load = (): Config => {
const result = configSchema.safeParse({
env: process.env.NODE_ENV,
port: process.env.PORT,
cors: process.env.CORS ? process.env.CORS.split(',') : [],
database: {
url: process.env.ARMORY_DATABASE_URL
},
Expand Down
4 changes: 3 additions & 1 deletion apps/armory/src/armory.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ClassSerializerInterceptor, Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { APP_INTERCEPTOR } from '@nestjs/core'
import { AdminModule } from './admin/admin.module'
import { load } from './armory.config'
import { OrchestrationModule } from './orchestration/orchestration.module'
import { QueueModule } from './shared/module/queue/queue.module'
Expand All @@ -14,7 +15,8 @@ import { TransferTrackingModule } from './transfer-tracking/transfer-tracking.mo
}),
QueueModule.forRoot(),
OrchestrationModule,
TransferTrackingModule
TransferTrackingModule,
AdminModule
],
providers: [
{
Expand Down
3 changes: 2 additions & 1 deletion apps/armory/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { withSwagger } from '@narval/nestjs-shared'
import { withCors, withSwagger } from '@narval/nestjs-shared'
import { ClassSerializerInterceptor, INestApplication, Logger, ValidationPipe } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { NestFactory, Reflector } from '@nestjs/core'
Expand Down Expand Up @@ -76,6 +76,7 @@ async function bootstrap(): Promise<void> {
map(withGlobalPipes),
map(withGlobalInterceptors),
map(withGlobalFilters(configService)),
map(withCors(configService.get('cors'))),
switchMap((app) => app.listen(port))
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- CreateTable
CREATE TABLE "data_store" (
"id" VARCHAR(255) NOT NULL,
"org_id" TEXT NOT NULL,
"data" JSONB NOT NULL,
"version" INTEGER NOT NULL,

CONSTRAINT "data_store_pkey" PRIMARY KEY ("id")
);
samteb marked this conversation as resolved.
Show resolved Hide resolved
11 changes: 11 additions & 0 deletions apps/armory/src/shared/module/persistence/schema/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,14 @@ model Feed {

@@map("feed")
}

// Data Store

model DataStore {
id String @id @default(uuid()) @db.VarChar(255)
orgId String @map("org_id")
data Json
samteb marked this conversation as resolved.
Show resolved Hide resolved
version Int

@@map("data_store")
}
samteb marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion apps/devtool/src/app/_hooks/useAccountSignature.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
Curves,
JwsdHeader,
KeyTypes,
Payload,
PublicKey,
SigningAlg,
hash,
Expand Down Expand Up @@ -36,7 +37,7 @@ const useAccountSignature = () => {
return hexToBase64Url(signature)
}

const signAccountJwt = async (payload: any) => {
const signAccountJwt = async (payload: Payload) => {
if (!jwk) return ''

const signature = await signJwt(payload, jwk, { alg: SigningAlg.EIP191 }, signer)
Expand Down
Loading