Skip to content

Commit

Permalink
Store authorization request approvals
Browse files Browse the repository at this point in the history
  • Loading branch information
wcalderipe committed Jan 18, 2024
1 parent 19dc77e commit 1c773d2
Show file tree
Hide file tree
Showing 12 changed files with 154 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,15 @@ describe('Policy Engine Cluster Facade', () => {
const authzRequest: AuthorizationRequest = {
id: '986ae19d-c30c-40c6-b873-1fb6c49011de',
orgId: org.id,
initiatorId: 'ac792884-be18-4361-9323-8f711c3f070e',
status: AuthorizationRequestStatus.PERMITTED,
action: SupportedAction.SIGN_MESSAGE,
request: signMessageRequest,
hash: hashRequest(signMessageRequest),
idempotencyKey: '8dcbb7ad-82a2-4eca-b2f0-b1415c1d4a17',
evaluations: [],
approvals: [],
createdAt: new Date(),
updatedAt: new Date(),
evaluations: []
updatedAt: new Date()
}

beforeEach(async () => {
Expand Down
26 changes: 17 additions & 9 deletions apps/orchestration/src/policy-engine/core/type/domain.type.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import { Action, TransactionRequest } from '@narval/authz-shared'
import { SetOptional } from 'type-fest'

export type Evaluation = {
id: string
decision: string
signature: string | null
createdAt: Date
}

/**
* AuthZ actions currently supported by the Orchestration.
*/
Expand All @@ -26,10 +19,24 @@ export enum AuthorizationRequestStatus {
FORBIDDEN = 'FORBIDDEN'
}

export type Approval = {
id: string
sig: string
alg: string
pubKey: string
createdAt: Date
}

export type Evaluation = {
id: string
decision: string
signature: string | null
createdAt: Date
}

export type SharedAuthorizationRequest = {
id: string
orgId: string
initiatorId: string
status: `${AuthorizationRequestStatus}`
/**
* The hash of the request in EIP-191 format.
Expand All @@ -40,9 +47,10 @@ export type SharedAuthorizationRequest = {
*/
hash: string
idempotencyKey?: string | null
approvals: Approval[]
evaluations: Evaluation[]
createdAt: Date
updatedAt: Date
evaluations: Evaluation[]
}

export type SignTransactionAuthorizationRequest = SharedAuthorizationRequest & {
Expand Down
1 change: 1 addition & 0 deletions apps/orchestration/src/policy-engine/http/rest/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const toCreateAuthorizationRequest = (
orgId,
initiatorId: '97389cac-20f0-4d02-a3a9-b27c564ffd18',
hash: dto.hash,
approvals: [],
evaluations: []
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ describe('decodeAuthorizationRequest', () => {
const sharedModel = {
id: '3356d68c-bc63-4b08-9253-289eec475d1d',
orgId: 'f6477ee7-7f5e-4e19-92f9-7864c7af5fd4',
initiatorId: 'alice',
status: AuthorizationRequestStatus.CREATED,
hash: 'test-request-hash',
idempotencyKey: null,
evaluationLog: [],
approvals: [],
createdAt: new Date(),
updatedAt: new Date()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { AuthorizationRequest, Evaluation } from '@app/orchestration/policy-engine/core/type/domain.type'
import { DecodeAuthorizationRequestException } from '@app/orchestration/policy-engine/persistence/exception/decode-authorization-request.exception'
import { ACTION_REQUEST } from '@app/orchestration/policy-engine/policy-engine.constant'
import { AuthorizationRequest as AuthorizationRequestModel, EvaluationLog } from '@prisma/client/orchestration'
import {
AuthorizationRequestApproval,
AuthorizationRequest as AuthorizationRequestModel,
EvaluationLog
} from '@prisma/client/orchestration'
import { omit } from 'lodash/fp'
import { ZodIssueCode, ZodSchema } from 'zod'

type Model = AuthorizationRequestModel & { evaluationLog?: EvaluationLog[] }
type Model = AuthorizationRequestModel & { evaluationLog?: EvaluationLog[]; approvals: AuthorizationRequestApproval[] }

const buildEvaluation = ({ id, decision, signature, createdAt }: EvaluationLog): Evaluation => ({
id,
Expand All @@ -16,13 +21,13 @@ const buildEvaluation = ({ id, decision, signature, createdAt }: EvaluationLog):
const buildSharedAttributes = (model: Model) => ({
id: model.id,
orgId: model.orgId,
initiatorId: model.initiatorId,
status: model.status,
hash: model.hash,
idempotencyKey: model.idempotencyKey,
approvals: (model.approvals || []).map(omit('requestId')),
evaluations: (model.evaluationLog || []).map(buildEvaluation),
createdAt: model.createdAt,
updatedAt: model.updatedAt,
evaluations: (model.evaluationLog || []).map(buildEvaluation)
updatedAt: model.updatedAt
})

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { load } from '@app/orchestration/orchestration.config'
import {
Approval,
Evaluation,
SignMessageAuthorizationRequest,
SignTransactionAuthorizationRequest,
Expand Down Expand Up @@ -29,14 +30,14 @@ describe(AuthorizationRequestRepository.name, () => {
const signMessageRequest: SignMessageAuthorizationRequest = {
id: '6c7e92fc-d2b0-4840-8e9b-485393ecdf89',
orgId: org.id,
initiatorId: '5c6df361-8ec7-4cfa-bff6-53ffa7c985ff',
status: AuthorizationRequestStatus.PROCESSING,
action: SupportedAction.SIGN_MESSAGE,
request: {
message: 'Test request'
},
hash: 'test-hash',
idempotencyKey: null,
approvals: [],
evaluations: [],
createdAt: new Date(),
updatedAt: new Date()
Expand Down Expand Up @@ -75,7 +76,7 @@ describe(AuthorizationRequestRepository.name, () => {
}
})

expect(request).toMatchObject(omit('evaluations', signMessageRequest))
expect(request).toMatchObject(omit(['evaluations', 'approvals'], signMessageRequest))
})

it('defaults status to CREATED', async () => {
Expand Down Expand Up @@ -118,6 +119,34 @@ describe(AuthorizationRequestRepository.name, () => {
])
})

it('creates approvals', async () => {
const approval: Approval = {
id: 'c534332f-6dd9-4cc8-b727-e1ad21176238',
alg: 'ES256K',
sig: 'test-signature',
pubKey: 'test-public-key',
createdAt: new Date()
}

await repository.create({
...signMessageRequest,
approvals: [approval]
})

const approvals = await testPrismaService.getClient().authorizationRequestApproval.findMany({
where: {
requestId: signMessageRequest.id
}
})

expect(approvals).toEqual([
{
...approval,
requestId: signMessageRequest.id
}
])
})

describe(`when action is ${SupportedAction.SIGN_TRANSACTION}`, () => {
const signTransactionRequest: SignTransactionAuthorizationRequest = {
...signMessageRequest,
Expand Down Expand Up @@ -161,7 +190,7 @@ describe(AuthorizationRequestRepository.name, () => {
expect(actual?.status).toEqual(AuthorizationRequestStatus.PERMITTED)
})

it('updates evaluations', async () => {
it('appends evaluations', async () => {
const authzRequestOne = await repository.update({
...signMessageRequest,
evaluations: [
Expand Down Expand Up @@ -192,5 +221,39 @@ describe(AuthorizationRequestRepository.name, () => {
expect(authzRequestTwo.evaluations.length).toEqual(2)
expect(actual?.evaluations.length).toEqual(2)
})

it('appends approvals', async () => {
const authzRequestOne = await repository.update({
...signMessageRequest,
approvals: [
{
id: 'c534332f-6dd9-4cc8-b727-e1ad21176238',
alg: 'ES256K',
sig: 'test-signature',
pubKey: 'test-public-key',
createdAt: new Date()
}
]
})

const authzRequestTwo = await repository.update({
...signMessageRequest,
approvals: [
{
id: '790e30b0-35d1-4e22-8be5-71e64afbee89',
alg: 'ES256K',
sig: 'test-signature',
pubKey: 'test-public-key',
createdAt: new Date()
}
]
})

const actual = await repository.findById(signMessageRequest.id)

expect(authzRequestOne.approvals.length).toEqual(1)
expect(authzRequestTwo.approvals.length).toEqual(2)
expect(actual?.approvals.length).toEqual(2)
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -12,49 +12,49 @@ import {
import { PrismaService } from '@app/orchestration/shared/module/persistence/service/prisma.service'
import { Injectable } from '@nestjs/common'
import { EvaluationLog } from '@prisma/client/orchestration'
import { omit } from 'lodash/fp'

const toEvaluationLogs = (
requestId: string,
orgId?: string,
evaluations?: Omit<Evaluation, 'requestId'>[]
): EvaluationLog[] =>
orgId && evaluations?.length
const toEvaluationLogs = (orgId?: string, evaluations?: Evaluation[]): Omit<EvaluationLog, 'requestId'>[] => {
return orgId && evaluations?.length
? evaluations.map((evaluation) => ({
...evaluation,
orgId,
requestId
orgId
}))
: []
}

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

async create(input: CreateAuthorizationRequest): Promise<AuthorizationRequest> {
const { id, action, request, orgId, initiatorId, hash, status, idempotencyKey, createdAt, updatedAt, evaluations } =
const { id, action, request, orgId, hash, status, idempotencyKey, createdAt, updatedAt, evaluations, approvals } =
createAuthorizationRequestSchema.parse(input)
const evaluationLogs = toEvaluationLogs(id, orgId, evaluations)
const evaluationLogs = toEvaluationLogs(orgId, evaluations)

const model = await this.prismaService.authorizationRequest.create({
data: {
status: status || AuthorizationRequestStatus.CREATED,
id,
orgId,
initiatorId,
action,
request,
hash,
idempotencyKey,
createdAt,
updatedAt,
approvals: {
createMany: {
data: approvals
}
},
evaluationLog: {
createMany: {
data: evaluationLogs.map(omit('requestId'))
data: evaluationLogs
}
}
},
include: {
approvals: true,
evaluationLog: true
}
})
Expand All @@ -72,23 +72,30 @@ export class AuthorizationRequestRepository {
* @returns {AuthorizationRequest}
*/
async update(
input: Partial<Pick<AuthorizationRequest, 'orgId' | 'status' | 'evaluations'>> & Pick<AuthorizationRequest, 'id'>
input: Partial<Pick<AuthorizationRequest, 'orgId' | 'status' | 'evaluations' | 'approvals'>> &
Pick<AuthorizationRequest, 'id'>
): Promise<AuthorizationRequest> {
const { id } = input
const { orgId, status, evaluations } = updateAuthorizationRequestSchema.parse(input)
const evaluationLogs = toEvaluationLogs(id, orgId, evaluations)
const { orgId, status, evaluations, approvals } = updateAuthorizationRequestSchema.parse(input)
const evaluationLogs = toEvaluationLogs(orgId, evaluations)

const model = await this.prismaService.authorizationRequest.update({
where: { id },
data: {
status,
approvals: {
createMany: {
data: approvals?.length ? approvals : []
}
},
evaluationLog: {
createMany: {
data: evaluationLogs.map(omit('requestId'))
data: evaluationLogs
}
}
},
include: {
approvals: true,
evaluationLog: true
}
})
Expand All @@ -100,6 +107,7 @@ export class AuthorizationRequestRepository {
const model = await this.prismaService.authorizationRequest.findUnique({
where: { id },
include: {
approvals: true,
evaluationLog: true
}
})
Expand All @@ -119,6 +127,7 @@ export class AuthorizationRequestRepository {
status: Array.isArray(statusOrStatuses) ? { in: statusOrStatuses } : statusOrStatuses
},
include: {
approvals: true,
evaluationLog: true
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import {
import { AuthorizationRequestStatus } from '@prisma/client/orchestration'
import { z } from 'zod'

const approvalSchema = z.object({
id: z.string().uuid(),
sig: z.string(),
alg: z.string(),
pubKey: z.string(),
createdAt: z.date()
})

const evaluationSchema = z.object({
id: z.string().uuid(),
decision: z.string(),
Expand All @@ -20,13 +28,13 @@ const evaluationSchema = z.object({
const sharedAuthorizationRequestSchema = z.object({
id: z.string().uuid(),
orgId: z.string().uuid(),
initiatorId: z.string().uuid(),
status: z.nativeEnum(AuthorizationRequestStatus),
hash: z.string(),
idempotencyKey: z.string().nullish(),
approvals: z.array(approvalSchema),
evaluations: z.array(evaluationSchema),
createdAt: z.date(),
updatedAt: z.date(),
evaluations: z.array(evaluationSchema)
updatedAt: z.date()
})

export const readAuthorizationRequestSchema = z.discriminatedUnion('action', [
Expand Down Expand Up @@ -66,6 +74,9 @@ export const updateAuthorizationRequestSchema = sharedAuthorizationRequestSchema
id: true,
orgId: true,
status: true,
evaluations: true
// The update operation creates evaluations and approvals in the
// authorization request.
evaluations: true,
approvals: true
})
.partial()
Loading

0 comments on commit 1c773d2

Please sign in to comment.