Access approval policy

This commit is contained in:
Daniel Hougaard
2024-08-20 19:50:42 +04:00
committed by Daniel Hougaard
parent c497e19b99
commit c64334462f
3 changed files with 65 additions and 60 deletions
@@ -1,9 +1,9 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { TableName, TAccessApprovalPolicies } from "@app/db/schemas"; import { AccessApprovalPoliciesSchema, TableName, TAccessApprovalPolicies } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { buildFindFilter, mergeOneToManyRelation, ormify, selectAllTableCols, TFindFilter } from "@app/lib/knex"; import { buildFindFilter, ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPolicyDALFactory>; export type TAccessApprovalPolicyDALFactory = ReturnType<typeof accessApprovalPolicyDALFactory>;
@@ -15,12 +15,12 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
// eslint-disable-next-line // eslint-disable-next-line
.where(buildFindFilter(filter)) .where(buildFindFilter(filter))
.join(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`) .join(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.join( .leftJoin(
TableName.AccessApprovalPolicyApprover, TableName.AccessApprovalPolicyApprover,
`${TableName.AccessApprovalPolicy}.id`, `${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyApprover}.policyId` `${TableName.AccessApprovalPolicyApprover}.policyId`
) )
.select(tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover)) .select(tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(tx.ref("name").withSchema(TableName.Environment).as("envName")) .select(tx.ref("name").withSchema(TableName.Environment).as("envName"))
.select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug")) .select(tx.ref("slug").withSchema(TableName.Environment).as("envSlug"))
.select(tx.ref("id").withSchema(TableName.Environment).as("envId")) .select(tx.ref("id").withSchema(TableName.Environment).as("envId"))
@@ -35,18 +35,30 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
const doc = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), { const doc = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), {
[`${TableName.AccessApprovalPolicy}.id` as "id"]: id [`${TableName.AccessApprovalPolicy}.id` as "id"]: id
}); });
const formatedDoc = mergeOneToManyRelation( const formattedDoc = sqlNestRelationships({
doc, data: doc,
"id", key: "id",
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({ parentMapper: (data) => ({
...el, environment: {
envId, id: data.envId,
environment: { id: envId, name, slug } name: data.envName,
slug: data.envSlug
},
projectId: data.projectId,
...AccessApprovalPoliciesSchema.parse(data)
}), }),
({ approverId }) => approverId, childrenMapper: [
"approvers" {
); key: "approverUserId",
return formatedDoc?.[0]; label: "userApprovers" as const,
mapper: ({ approverUserId }) => ({
userId: approverUserId
})
}
]
});
return formattedDoc?.[0];
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "FindById" }); throw new DatabaseError({ error, name: "FindById" });
} }
@@ -55,18 +67,32 @@ export const accessApprovalPolicyDALFactory = (db: TDbClient) => {
const find = async (filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>, tx?: Knex) => { const find = async (filter: TFindFilter<TAccessApprovalPolicies & { projectId: string }>, tx?: Knex) => {
try { try {
const docs = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), filter); const docs = await accessApprovalPolicyFindQuery(tx || db.replicaNode(), filter);
const formatedDoc = mergeOneToManyRelation(
docs, const formattedDocs = sqlNestRelationships({
"id", data: docs,
({ approverId, envId, envName: name, envSlug: slug, ...el }) => ({ key: "id",
...el, parentMapper: (data) => ({
envId, environment: {
environment: { id: envId, name, slug } id: data.envId,
name: data.envName,
slug: data.envSlug
},
projectId: data.projectId,
...AccessApprovalPoliciesSchema.parse(data)
// secretPath: data.secretPath || undefined,
}), }),
({ approverId }) => approverId, childrenMapper: [
"approvers" {
); key: "approverUserId",
return formatedDoc.map((policy) => ({ ...policy, secretPath: policy.secretPath || undefined })); label: "userApprovers" as const,
mapper: ({ approverUserId }) => ({
userId: approverUserId
})
}
]
});
return formattedDocs;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "Find" }); throw new DatabaseError({ error, name: "Find" });
} }
@@ -34,8 +34,7 @@ export const accessApprovalPolicyServiceFactory = ({
accessApprovalPolicyApproverDAL, accessApprovalPolicyApproverDAL,
permissionService, permissionService,
projectEnvDAL, projectEnvDAL,
projectDAL, projectDAL
projectMembershipDAL
}: TSecretApprovalPolicyServiceFactoryDep) => { }: TSecretApprovalPolicyServiceFactoryDep) => {
const createAccessApprovalPolicy = async ({ const createAccessApprovalPolicy = async ({
name, name,
@@ -45,7 +44,7 @@ export const accessApprovalPolicyServiceFactory = ({
secretPath, secretPath,
actorAuthMethod, actorAuthMethod,
approvals, approvals,
approvers, approverUserIds,
projectSlug, projectSlug,
environment, environment,
enforcementLevel enforcementLevel
@@ -53,7 +52,7 @@ export const accessApprovalPolicyServiceFactory = ({
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
if (!project) throw new BadRequestError({ message: "Project not found" }); if (!project) throw new BadRequestError({ message: "Project not found" });
if (approvals > approvers.length) if (approvals > approverUserIds.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" }); throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
const { permission } = await permissionService.getProjectPermission( const { permission } = await permissionService.getProjectPermission(
@@ -70,15 +69,6 @@ export const accessApprovalPolicyServiceFactory = ({
const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id }); const env = await projectEnvDAL.findOne({ slug: environment, projectId: project.id });
if (!env) throw new BadRequestError({ message: "Environment not found" }); if (!env) throw new BadRequestError({ message: "Environment not found" });
const secretApprovers = await projectMembershipDAL.find({
projectId: project.id,
$in: { id: approvers }
});
if (secretApprovers.length !== approvers.length) {
throw new BadRequestError({ message: "Approver not found in project" });
}
await verifyApprovers({ await verifyApprovers({
projectId: project.id, projectId: project.id,
orgId: actorOrgId, orgId: actorOrgId,
@@ -86,7 +76,7 @@ export const accessApprovalPolicyServiceFactory = ({
secretPath, secretPath,
actorAuthMethod, actorAuthMethod,
permissionService, permissionService,
userIds: secretApprovers.map((approver) => approver.userId) userIds: approverUserIds
}); });
const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => { const accessApproval = await accessApprovalPolicyDAL.transaction(async (tx) => {
@@ -101,8 +91,8 @@ export const accessApprovalPolicyServiceFactory = ({
tx tx
); );
await accessApprovalPolicyApproverDAL.insertMany( await accessApprovalPolicyApproverDAL.insertMany(
secretApprovers.map(({ id }) => ({ approverUserIds.map((userId) => ({
approverId: id, approverUserId: userId,
policyId: doc.id policyId: doc.id
})), })),
tx tx
@@ -138,7 +128,7 @@ export const accessApprovalPolicyServiceFactory = ({
const updateAccessApprovalPolicy = async ({ const updateAccessApprovalPolicy = async ({
policyId, policyId,
approvers, approverUserIds,
secretPath, secretPath,
name, name,
actorId, actorId,
@@ -171,16 +161,7 @@ export const accessApprovalPolicyServiceFactory = ({
}, },
tx tx
); );
if (approvers) { if (approverUserIds) {
// Find the workspace project memberships of the users passed in the approvers array
const secretApprovers = await projectMembershipDAL.find(
{
projectId: accessApprovalPolicy.projectId,
$in: { id: approvers }
},
{ tx }
);
await verifyApprovers({ await verifyApprovers({
projectId: accessApprovalPolicy.projectId, projectId: accessApprovalPolicy.projectId,
orgId: actorOrgId, orgId: actorOrgId,
@@ -188,15 +169,13 @@ export const accessApprovalPolicyServiceFactory = ({
secretPath: doc.secretPath!, secretPath: doc.secretPath!,
actorAuthMethod, actorAuthMethod,
permissionService, permissionService,
userIds: secretApprovers.map((approver) => approver.userId) userIds: approverUserIds
}); });
if (secretApprovers.length !== approvers.length)
throw new BadRequestError({ message: "Approvals cannot be greater than approvers" });
await accessApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx); await accessApprovalPolicyApproverDAL.delete({ policyId: doc.id }, tx);
await accessApprovalPolicyApproverDAL.insertMany( await accessApprovalPolicyApproverDAL.insertMany(
secretApprovers.map(({ id }) => ({ approverUserIds.map((userId) => ({
approverId: id, approverUserId: userId,
policyId: doc.id policyId: doc.id
})), })),
tx tx
@@ -17,7 +17,7 @@ export type TCreateAccessApprovalPolicy = {
approvals: number; approvals: number;
secretPath: string; secretPath: string;
environment: string; environment: string;
approvers: string[]; approverUserIds: string[];
projectSlug: string; projectSlug: string;
name: string; name: string;
enforcementLevel: EnforcementLevel; enforcementLevel: EnforcementLevel;
@@ -26,7 +26,7 @@ export type TCreateAccessApprovalPolicy = {
export type TUpdateAccessApprovalPolicy = { export type TUpdateAccessApprovalPolicy = {
policyId: string; policyId: string;
approvals?: number; approvals?: number;
approvers?: string[]; approverUserIds?: string[];
secretPath?: string; secretPath?: string;
name?: string; name?: string;
enforcementLevel?: EnforcementLevel; enforcementLevel?: EnforcementLevel;