Access approval request

This commit is contained in:
Daniel Hougaard
2024-08-20 19:50:42 +04:00
committed by Daniel Hougaard
parent c64334462f
commit d3c63b5699
2 changed files with 126 additions and 33 deletions
@@ -1,7 +1,7 @@
import { Knex } from "knex"; import { Knex } from "knex";
import { TDbClient } from "@app/db"; import { TDbClient } from "@app/db";
import { AccessApprovalRequestsSchema, TableName, TAccessApprovalRequests } from "@app/db/schemas"; import { AccessApprovalRequestsSchema, TableName, TAccessApprovalRequests, TUsers } from "@app/db/schemas";
import { DatabaseError } from "@app/lib/errors"; import { DatabaseError } from "@app/lib/errors";
import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex"; import { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
@@ -40,6 +40,12 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.AccessApprovalPolicyApprover}.policyId` `${TableName.AccessApprovalPolicyApprover}.policyId`
) )
.join<TUsers>(
db(TableName.Users).as("requestedByUser"),
`${TableName.AccessApprovalRequest}.requestedByUserId`,
`requestedByUser.id`
)
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`) .leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.select(selectAllTableCols(TableName.AccessApprovalRequest)) .select(selectAllTableCols(TableName.AccessApprovalRequest))
@@ -52,7 +58,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId") db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId")
) )
.select(db.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover)) .select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
.select( .select(
db.ref("projectId").withSchema(TableName.Environment), db.ref("projectId").withSchema(TableName.Environment),
@@ -61,15 +67,20 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
) )
.select( .select(
db.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId"), db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"),
db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus") db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")
) )
// TODO: ADD SUPPORT FOR GROUPS!!!!
.select( .select(
db db.ref("email").withSchema("requestedByUser").as("requestedByUserEmail"),
.ref("projectMembershipId") db.ref("username").withSchema("requestedByUser").as("requestedByUserUsername"),
.withSchema(TableName.ProjectUserAdditionalPrivilege) db.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
.as("privilegeMembershipId"), db.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
db.ref("userId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeUserId"),
db.ref("projectId").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeMembershipId"),
db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeIsTemporary"), db.ref("isTemporary").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeIsTemporary"),
db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"), db.ref("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"),
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"), db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"),
@@ -102,9 +113,18 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
enforcementLevel: doc.policyEnforcementLevel, enforcementLevel: doc.policyEnforcementLevel,
envId: doc.policyEnvId envId: doc.policyEnvId
}, },
requestedByUser: {
userId: doc.requestedByUserId,
email: doc.requestedByUserEmail,
firstName: doc.requestedByUserFirstName,
lastName: doc.requestedByUserLastName,
username: doc.requestedByUserUsername
},
privilege: doc.privilegeId privilege: doc.privilegeId
? { ? {
membershipId: doc.privilegeMembershipId, membershipId: doc.privilegeMembershipId,
userId: doc.privilegeUserId,
projectId: doc.projectId,
isTemporary: doc.privilegeIsTemporary, isTemporary: doc.privilegeIsTemporary,
temporaryMode: doc.privilegeTemporaryMode, temporaryMode: doc.privilegeTemporaryMode,
temporaryRange: doc.privilegeTemporaryRange, temporaryRange: doc.privilegeTemporaryRange,
@@ -118,11 +138,11 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
}), }),
childrenMapper: [ childrenMapper: [
{ {
key: "reviewerMemberId", key: "reviewerUserId",
label: "reviewers" as const, label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined) mapper: ({ reviewerUserId: userId, reviewerStatus: status }) => (userId ? { userId, status } : undefined)
}, },
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId } { key: "approverUserId", label: "approvers" as const, mapper: ({ approverUserId }) => approverUserId }
] ]
}); });
@@ -146,30 +166,65 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.AccessApprovalPolicy}.id` `${TableName.AccessApprovalPolicy}.id`
) )
.join<TUsers>(
db(TableName.Users).as("requestedByUser"),
`${TableName.AccessApprovalRequest}.requestedByUserId`,
`requestedByUser.id`
)
.join( .join(
TableName.AccessApprovalPolicyApprover, TableName.AccessApprovalPolicyApprover,
`${TableName.AccessApprovalPolicy}.id`, `${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyApprover}.policyId` `${TableName.AccessApprovalPolicyApprover}.policyId`
) )
.join<TUsers>(
db(TableName.Users).as("accessApprovalPolicyApproverUser"),
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
"accessApprovalPolicyApproverUser.id"
)
.leftJoin( .leftJoin(
TableName.AccessApprovalRequestReviewer, TableName.AccessApprovalRequestReviewer,
`${TableName.AccessApprovalRequest}.id`, `${TableName.AccessApprovalRequest}.id`,
`${TableName.AccessApprovalRequestReviewer}.requestId` `${TableName.AccessApprovalRequestReviewer}.requestId`
) )
.leftJoin<TUsers>(
db(TableName.Users).as("accessApprovalReviewerUser"),
`${TableName.AccessApprovalRequestReviewer}.reviewerUserId`,
`accessApprovalReviewerUser.id`
)
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`) .leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.select(selectAllTableCols(TableName.AccessApprovalRequest)) .select(selectAllTableCols(TableName.AccessApprovalRequest))
.select( .select(
tx.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId"), tx.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover),
tx.ref("email").withSchema("accessApprovalPolicyApproverUser").as("approverEmail"),
tx.ref("username").withSchema("accessApprovalPolicyApproverUser").as("approverUsername"),
tx.ref("firstName").withSchema("accessApprovalPolicyApproverUser").as("approverFirstName"),
tx.ref("lastName").withSchema("accessApprovalPolicyApproverUser").as("approverLastName"),
tx.ref("email").withSchema("requestedByUser").as("requestedByUserEmail"),
tx.ref("username").withSchema("requestedByUser").as("requestedByUserUsername"),
tx.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
tx.ref("lastName").withSchema("requestedByUser").as("requestedByUserLastName"),
tx.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer),
tx.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"), tx.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"),
tx.ref("email").withSchema("accessApprovalReviewerUser").as("reviewerEmail"),
tx.ref("username").withSchema("accessApprovalReviewerUser").as("reviewerUsername"),
tx.ref("firstName").withSchema("accessApprovalReviewerUser").as("reviewerFirstName"),
tx.ref("lastName").withSchema("accessApprovalReviewerUser").as("reviewerLastName"),
tx.ref("id").withSchema(TableName.AccessApprovalPolicy).as("policyId"), tx.ref("id").withSchema(TableName.AccessApprovalPolicy).as("policyId"),
tx.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"), tx.ref("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
tx.ref("projectId").withSchema(TableName.Environment), tx.ref("projectId").withSchema(TableName.Environment),
tx.ref("slug").withSchema(TableName.Environment).as("environment"), tx.ref("slug").withSchema(TableName.Environment).as("environment"),
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"), tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"), tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"), tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals")
tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover)
); );
const findById = async (id: string, tx?: Knex) => { const findById = async (id: string, tx?: Knex) => {
@@ -189,15 +244,45 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
approvals: el.policyApprovals, approvals: el.policyApprovals,
secretPath: el.policySecretPath, secretPath: el.policySecretPath,
enforcementLevel: el.policyEnforcementLevel enforcementLevel: el.policyEnforcementLevel
},
requestedByUser: {
userId: el.requestedByUserId,
email: el.requestedByUserEmail,
firstName: el.requestedByUserFirstName,
lastName: el.requestedByUserLastName,
username: el.requestedByUserUsername
} }
}), }),
childrenMapper: [ childrenMapper: [
{ {
key: "reviewerMemberId", key: "reviewerUserId",
label: "reviewers" as const, label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined) mapper: ({
reviewerUserId: userId,
reviewerStatus: status,
reviewerEmail: email,
reviewerLastName: lastName,
reviewerUsername: username,
reviewerFirstName: firstName
}) => (userId ? { userId, status, email, firstName, lastName, username } : undefined)
}, },
{ key: "approverId", label: "approvers" as const, mapper: ({ approverId }) => approverId } {
key: "approverUserId",
label: "approvers" as const,
mapper: ({
approverUserId,
approverEmail: email,
approverUsername: username,
approverLastName: lastName,
approverFirstName: firstName
}) => ({
userId: approverUserId,
email,
firstName,
lastName,
username
})
}
] ]
}); });
if (!formatedDoc?.[0]) return; if (!formatedDoc?.[0]) return;
@@ -235,7 +320,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
.where(`${TableName.Environment}.projectId`, projectId) .where(`${TableName.Environment}.projectId`, projectId)
.select(selectAllTableCols(TableName.AccessApprovalRequest)) .select(selectAllTableCols(TableName.AccessApprovalRequest))
.select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus")) .select(db.ref("status").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerStatus"))
.select(db.ref("member").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerMemberId")); .select(db.ref("reviewerUserId").withSchema(TableName.AccessApprovalRequestReviewer).as("reviewerUserId"));
const formattedRequests = sqlNestRelationships({ const formattedRequests = sqlNestRelationships({
data: accessRequests, data: accessRequests,
@@ -245,9 +330,10 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
}), }),
childrenMapper: [ childrenMapper: [
{ {
key: "reviewerMemberId", key: "reviewerUserId",
label: "reviewers" as const, label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined) mapper: ({ reviewerUserId: reviewer, reviewerStatus: status }) =>
reviewer ? { reviewer, status } : undefined
} }
] ]
}); });
@@ -52,7 +52,10 @@ type TSecretApprovalRequestServiceFactoryDep = {
>; >;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">; projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
smtpService: Pick<TSmtpService, "sendMail">; smtpService: Pick<TSmtpService, "sendMail">;
userDAL: Pick<TUserDALFactory, "findUserByProjectMembershipId" | "findUsersByProjectMembershipIds">; userDAL: Pick<
TUserDALFactory,
"findUserByProjectMembershipId" | "findUsersByProjectMembershipIds" | "find" | "findById"
>;
}; };
export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>; export type TAccessApprovalRequestServiceFactory = ReturnType<typeof accessApprovalRequestServiceFactory>;
@@ -94,7 +97,7 @@ export const accessApprovalRequestServiceFactory = ({
); );
if (!membership) throw new UnauthorizedError({ message: "You are not a member of this project" }); if (!membership) throw new UnauthorizedError({ message: "You are not a member of this project" });
const requestedByUser = await userDAL.findUserByProjectMembershipId(membership.id); const requestedByUser = await userDAL.findById(actorId);
if (!requestedByUser) throw new UnauthorizedError({ message: "User not found" }); if (!requestedByUser) throw new UnauthorizedError({ message: "User not found" });
await projectDAL.checkProjectUpgradeStatus(project.id); await projectDAL.checkProjectUpgradeStatus(project.id);
@@ -114,13 +117,15 @@ export const accessApprovalRequestServiceFactory = ({
policyId: policy.id policyId: policy.id
}); });
const approverUsers = await userDAL.findUsersByProjectMembershipIds( const approverUsers = await userDAL.find({
approvers.map((approver) => approver.approverId) $in: {
); id: approvers.map((approver) => approver.approverUserId)
}
});
const duplicateRequests = await accessApprovalRequestDAL.find({ const duplicateRequests = await accessApprovalRequestDAL.find({
policyId: policy.id, policyId: policy.id,
requestedBy: membership.id, requestedByUserId: actorId,
permissions: JSON.stringify(requestedPermissions), permissions: JSON.stringify(requestedPermissions),
isTemporary isTemporary
}); });
@@ -153,7 +158,7 @@ export const accessApprovalRequestServiceFactory = ({
const approvalRequest = await accessApprovalRequestDAL.create( const approvalRequest = await accessApprovalRequestDAL.create(
{ {
policyId: policy.id, policyId: policy.id,
requestedBy: membership.id, requestedByUserId: actorId,
temporaryRange: temporaryRange || null, temporaryRange: temporaryRange || null,
permissions: JSON.stringify(requestedPermissions), permissions: JSON.stringify(requestedPermissions),
isTemporary isTemporary
@@ -212,7 +217,7 @@ export const accessApprovalRequestServiceFactory = ({
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id)); let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
if (authorProjectMembershipId) { if (authorProjectMembershipId) {
requests = requests.filter((request) => request.requestedBy === authorProjectMembershipId); requests = requests.filter((request) => request.requestedByUserId === actorId);
} }
if (envSlug) { if (envSlug) {
@@ -246,8 +251,8 @@ export const accessApprovalRequestServiceFactory = ({
if ( if (
!hasRole(ProjectMembershipRole.Admin) && !hasRole(ProjectMembershipRole.Admin) &&
accessApprovalRequest.requestedBy !== membership.id && // The request wasn't made by the current user accessApprovalRequest.requestedByUserId !== actorId && // The request wasn't made by the current user
!policy.approvers.find((approverId) => approverId === membership.id) // The request isn't performed by an assigned approver !policy.approvers.find((approver) => approver.userId === actorId) // The request isn't performed by an assigned approver
) { ) {
throw new UnauthorizedError({ message: "You are not authorized to approve this request" }); throw new UnauthorizedError({ message: "You are not authorized to approve this request" });
} }
@@ -273,7 +278,7 @@ export const accessApprovalRequestServiceFactory = ({
const review = await accessApprovalRequestReviewerDAL.findOne( const review = await accessApprovalRequestReviewerDAL.findOne(
{ {
requestId: accessApprovalRequest.id, requestId: accessApprovalRequest.id,
member: membership.id reviewerUserId: actorId
}, },
tx tx
); );
@@ -282,7 +287,7 @@ export const accessApprovalRequestServiceFactory = ({
{ {
status, status,
requestId: accessApprovalRequest.id, requestId: accessApprovalRequest.id,
member: membership.id reviewerUserId: actorId
}, },
tx tx
); );
@@ -303,7 +308,8 @@ export const accessApprovalRequestServiceFactory = ({
// Permanent access // Permanent access
const privilege = await additionalPrivilegeDAL.create( const privilege = await additionalPrivilegeDAL.create(
{ {
projectMembershipId: accessApprovalRequest.requestedBy, userId: accessApprovalRequest.requestedByUserId,
projectId: accessApprovalRequest.projectId,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`, slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions) permissions: JSON.stringify(accessApprovalRequest.permissions)
}, },
@@ -317,7 +323,8 @@ export const accessApprovalRequestServiceFactory = ({
const privilege = await additionalPrivilegeDAL.create( const privilege = await additionalPrivilegeDAL.create(
{ {
projectMembershipId: accessApprovalRequest.requestedBy, userId: accessApprovalRequest.requestedByUserId,
projectId: accessApprovalRequest.projectId,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`, slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions), permissions: JSON.stringify(accessApprovalRequest.permissions),
isTemporary: true, isTemporary: true,