Access approval request

This commit is contained in:
Daniel Hougaard
2024-07-24 19:13:06 +02:00
committed by Daniel Hougaard
parent c64334462f
commit d3c63b5699
2 changed files with 126 additions and 33 deletions

View File

@@ -1,7 +1,7 @@
import { Knex } from "knex";
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 { ormify, selectAllTableCols, sqlNestRelationships, TFindFilter } from "@app/lib/knex";
@@ -40,6 +40,12 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
`${TableName.AccessApprovalPolicyApprover}.policyId`
)
.join<TUsers>(
db(TableName.Users).as("requestedByUser"),
`${TableName.AccessApprovalRequest}.requestedByUserId`,
`requestedByUser.id`
)
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.select(selectAllTableCols(TableName.AccessApprovalRequest))
@@ -52,7 +58,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
db.ref("envId").withSchema(TableName.AccessApprovalPolicy).as("policyEnvId")
)
.select(db.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(db.ref("approverUserId").withSchema(TableName.AccessApprovalPolicyApprover))
.select(
db.ref("projectId").withSchema(TableName.Environment),
@@ -61,15 +67,20 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
)
.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")
)
// TODO: ADD SUPPORT FOR GROUPS!!!!
.select(
db
.ref("projectMembershipId")
.withSchema(TableName.ProjectUserAdditionalPrivilege)
.as("privilegeMembershipId"),
db.ref("email").withSchema("requestedByUser").as("requestedByUserEmail"),
db.ref("username").withSchema("requestedByUser").as("requestedByUserUsername"),
db.ref("firstName").withSchema("requestedByUser").as("requestedByUserFirstName"),
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("temporaryMode").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryMode"),
db.ref("temporaryRange").withSchema(TableName.ProjectUserAdditionalPrivilege).as("privilegeTemporaryRange"),
@@ -102,9 +113,18 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
enforcementLevel: doc.policyEnforcementLevel,
envId: doc.policyEnvId
},
requestedByUser: {
userId: doc.requestedByUserId,
email: doc.requestedByUserEmail,
firstName: doc.requestedByUserFirstName,
lastName: doc.requestedByUserLastName,
username: doc.requestedByUserUsername
},
privilege: doc.privilegeId
? {
membershipId: doc.privilegeMembershipId,
userId: doc.privilegeUserId,
projectId: doc.projectId,
isTemporary: doc.privilegeIsTemporary,
temporaryMode: doc.privilegeTemporaryMode,
temporaryRange: doc.privilegeTemporaryRange,
@@ -118,11 +138,11 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
}),
childrenMapper: [
{
key: "reviewerMemberId",
key: "reviewerUserId",
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`
)
.join<TUsers>(
db(TableName.Users).as("requestedByUser"),
`${TableName.AccessApprovalRequest}.requestedByUserId`,
`requestedByUser.id`
)
.join(
TableName.AccessApprovalPolicyApprover,
`${TableName.AccessApprovalPolicy}.id`,
`${TableName.AccessApprovalPolicyApprover}.policyId`
)
.join<TUsers>(
db(TableName.Users).as("accessApprovalPolicyApproverUser"),
`${TableName.AccessApprovalPolicyApprover}.approverUserId`,
"accessApprovalPolicyApproverUser.id"
)
.leftJoin(
TableName.AccessApprovalRequestReviewer,
`${TableName.AccessApprovalRequest}.id`,
`${TableName.AccessApprovalRequestReviewer}.requestId`
)
.leftJoin<TUsers>(
db(TableName.Users).as("accessApprovalReviewerUser"),
`${TableName.AccessApprovalRequestReviewer}.reviewerUserId`,
`accessApprovalReviewerUser.id`
)
.leftJoin(TableName.Environment, `${TableName.AccessApprovalPolicy}.envId`, `${TableName.Environment}.id`)
.select(selectAllTableCols(TableName.AccessApprovalRequest))
.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("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("name").withSchema(TableName.AccessApprovalPolicy).as("policyName"),
tx.ref("projectId").withSchema(TableName.Environment),
tx.ref("slug").withSchema(TableName.Environment).as("environment"),
tx.ref("secretPath").withSchema(TableName.AccessApprovalPolicy).as("policySecretPath"),
tx.ref("enforcementLevel").withSchema(TableName.AccessApprovalPolicy).as("policyEnforcementLevel"),
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals"),
tx.ref("approverId").withSchema(TableName.AccessApprovalPolicyApprover)
tx.ref("approvals").withSchema(TableName.AccessApprovalPolicy).as("policyApprovals")
);
const findById = async (id: string, tx?: Knex) => {
@@ -189,15 +244,45 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
approvals: el.policyApprovals,
secretPath: el.policySecretPath,
enforcementLevel: el.policyEnforcementLevel
},
requestedByUser: {
userId: el.requestedByUserId,
email: el.requestedByUserEmail,
firstName: el.requestedByUserFirstName,
lastName: el.requestedByUserLastName,
username: el.requestedByUserUsername
}
}),
childrenMapper: [
{
key: "reviewerMemberId",
key: "reviewerUserId",
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;
@@ -235,7 +320,7 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
.where(`${TableName.Environment}.projectId`, projectId)
.select(selectAllTableCols(TableName.AccessApprovalRequest))
.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({
data: accessRequests,
@@ -245,9 +330,10 @@ export const accessApprovalRequestDALFactory = (db: TDbClient) => {
}),
childrenMapper: [
{
key: "reviewerMemberId",
key: "reviewerUserId",
label: "reviewers" as const,
mapper: ({ reviewerMemberId: member, reviewerStatus: status }) => (member ? { member, status } : undefined)
mapper: ({ reviewerUserId: reviewer, reviewerStatus: status }) =>
reviewer ? { reviewer, status } : undefined
}
]
});

View File

@@ -52,7 +52,10 @@ type TSecretApprovalRequestServiceFactoryDep = {
>;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "findById">;
smtpService: Pick<TSmtpService, "sendMail">;
userDAL: Pick<TUserDALFactory, "findUserByProjectMembershipId" | "findUsersByProjectMembershipIds">;
userDAL: Pick<
TUserDALFactory,
"findUserByProjectMembershipId" | "findUsersByProjectMembershipIds" | "find" | "findById"
>;
};
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" });
const requestedByUser = await userDAL.findUserByProjectMembershipId(membership.id);
const requestedByUser = await userDAL.findById(actorId);
if (!requestedByUser) throw new UnauthorizedError({ message: "User not found" });
await projectDAL.checkProjectUpgradeStatus(project.id);
@@ -114,13 +117,15 @@ export const accessApprovalRequestServiceFactory = ({
policyId: policy.id
});
const approverUsers = await userDAL.findUsersByProjectMembershipIds(
approvers.map((approver) => approver.approverId)
);
const approverUsers = await userDAL.find({
$in: {
id: approvers.map((approver) => approver.approverUserId)
}
});
const duplicateRequests = await accessApprovalRequestDAL.find({
policyId: policy.id,
requestedBy: membership.id,
requestedByUserId: actorId,
permissions: JSON.stringify(requestedPermissions),
isTemporary
});
@@ -153,7 +158,7 @@ export const accessApprovalRequestServiceFactory = ({
const approvalRequest = await accessApprovalRequestDAL.create(
{
policyId: policy.id,
requestedBy: membership.id,
requestedByUserId: actorId,
temporaryRange: temporaryRange || null,
permissions: JSON.stringify(requestedPermissions),
isTemporary
@@ -212,7 +217,7 @@ export const accessApprovalRequestServiceFactory = ({
let requests = await accessApprovalRequestDAL.findRequestsWithPrivilegeByPolicyIds(policies.map((p) => p.id));
if (authorProjectMembershipId) {
requests = requests.filter((request) => request.requestedBy === authorProjectMembershipId);
requests = requests.filter((request) => request.requestedByUserId === actorId);
}
if (envSlug) {
@@ -246,8 +251,8 @@ export const accessApprovalRequestServiceFactory = ({
if (
!hasRole(ProjectMembershipRole.Admin) &&
accessApprovalRequest.requestedBy !== membership.id && // 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
accessApprovalRequest.requestedByUserId !== actorId && // The request wasn't made by the current user
!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" });
}
@@ -273,7 +278,7 @@ export const accessApprovalRequestServiceFactory = ({
const review = await accessApprovalRequestReviewerDAL.findOne(
{
requestId: accessApprovalRequest.id,
member: membership.id
reviewerUserId: actorId
},
tx
);
@@ -282,7 +287,7 @@ export const accessApprovalRequestServiceFactory = ({
{
status,
requestId: accessApprovalRequest.id,
member: membership.id
reviewerUserId: actorId
},
tx
);
@@ -303,7 +308,8 @@ export const accessApprovalRequestServiceFactory = ({
// Permanent access
const privilege = await additionalPrivilegeDAL.create(
{
projectMembershipId: accessApprovalRequest.requestedBy,
userId: accessApprovalRequest.requestedByUserId,
projectId: accessApprovalRequest.projectId,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions)
},
@@ -317,7 +323,8 @@ export const accessApprovalRequestServiceFactory = ({
const privilege = await additionalPrivilegeDAL.create(
{
projectMembershipId: accessApprovalRequest.requestedBy,
userId: accessApprovalRequest.requestedByUserId,
projectId: accessApprovalRequest.projectId,
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
permissions: JSON.stringify(accessApprovalRequest.permissions),
isTemporary: true,