mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 21:27:31 +00:00
ui and backend improvements
This commit is contained in:
+87
-60
@@ -386,7 +386,21 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const reviewStatus = await accessApprovalRequestReviewerDAL.transaction(async (tx) => {
|
const reviewStatus = await accessApprovalRequestReviewerDAL.transaction(async (tx) => {
|
||||||
const review = await accessApprovalRequestReviewerDAL.findOne(
|
const isBreakGlassApprovalAttempt =
|
||||||
|
policy.enforcementLevel === EnforcementLevel.Soft &&
|
||||||
|
actorId === accessApprovalRequest.requestedByUserId &&
|
||||||
|
status === ApprovalStatus.APPROVED;
|
||||||
|
|
||||||
|
let reviewForThisActorProcessing: {
|
||||||
|
id: string;
|
||||||
|
requestId: string;
|
||||||
|
reviewerUserId: string;
|
||||||
|
status: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingReviewByActorInTx = await accessApprovalRequestReviewerDAL.findOne(
|
||||||
{
|
{
|
||||||
requestId: accessApprovalRequest.id,
|
requestId: accessApprovalRequest.id,
|
||||||
reviewerUserId: actorId
|
reviewerUserId: actorId
|
||||||
@@ -394,69 +408,82 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
if (review) {
|
// Check if review exists for actor
|
||||||
throw new BadRequestError({ message: "You have already reviewed this request" });
|
if (existingReviewByActorInTx) {
|
||||||
}
|
// Check if breakglass re-approval
|
||||||
|
if (isBreakGlassApprovalAttempt && existingReviewByActorInTx.status === ApprovalStatus.APPROVED) {
|
||||||
const newReview = await accessApprovalRequestReviewerDAL.create(
|
reviewForThisActorProcessing = existingReviewByActorInTx;
|
||||||
{
|
|
||||||
status,
|
|
||||||
requestId: accessApprovalRequest.id,
|
|
||||||
reviewerUserId: actorId
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
const allReviews = [...existingReviews, newReview];
|
|
||||||
const approvedReviews = allReviews.filter((r) => r.status === ApprovalStatus.APPROVED);
|
|
||||||
|
|
||||||
if (status === ApprovalStatus.APPROVED && approvedReviews.length >= policy.approvals) {
|
|
||||||
if (accessApprovalRequest.isTemporary && !accessApprovalRequest.temporaryRange) {
|
|
||||||
throw new BadRequestError({ message: "Temporary range is required for temporary access" });
|
|
||||||
}
|
|
||||||
|
|
||||||
let privilegeId: string | null = null;
|
|
||||||
|
|
||||||
if (!accessApprovalRequest.isTemporary && !accessApprovalRequest.temporaryRange) {
|
|
||||||
// Permanent access
|
|
||||||
const privilege = await additionalPrivilegeDAL.create(
|
|
||||||
{
|
|
||||||
userId: accessApprovalRequest.requestedByUserId,
|
|
||||||
projectId: accessApprovalRequest.projectId,
|
|
||||||
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
|
|
||||||
permissions: JSON.stringify(accessApprovalRequest.permissions)
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
privilegeId = privilege.id;
|
|
||||||
} else {
|
} else {
|
||||||
// Temporary access
|
throw new BadRequestError({ message: "You have already reviewed this request" });
|
||||||
const relativeTempAllocatedTimeInMs = ms(accessApprovalRequest.temporaryRange!);
|
|
||||||
const startTime = new Date();
|
|
||||||
|
|
||||||
const privilege = await additionalPrivilegeDAL.create(
|
|
||||||
{
|
|
||||||
userId: accessApprovalRequest.requestedByUserId,
|
|
||||||
projectId: accessApprovalRequest.projectId,
|
|
||||||
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
|
|
||||||
permissions: JSON.stringify(accessApprovalRequest.permissions),
|
|
||||||
isTemporary: true, // Explicitly set to true for the privilege
|
|
||||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
|
||||||
temporaryRange: accessApprovalRequest.temporaryRange!,
|
|
||||||
temporaryAccessStartTime: startTime,
|
|
||||||
temporaryAccessEndTime: new Date(startTime.getTime() + relativeTempAllocatedTimeInMs)
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
privilegeId = privilege.id;
|
|
||||||
}
|
}
|
||||||
await accessApprovalRequestDAL.updateById(accessApprovalRequest.id, { privilegeId }, tx);
|
} else {
|
||||||
|
reviewForThisActorProcessing = await accessApprovalRequestReviewerDAL.create(
|
||||||
|
{
|
||||||
|
status,
|
||||||
|
requestId: accessApprovalRequest.id,
|
||||||
|
reviewerUserId: actorId
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSoftEnforcement = policy.enforcementLevel === EnforcementLevel.Soft;
|
const otherReviews = existingReviews.filter((er) => er.reviewerUserId !== actorId);
|
||||||
const wasSelfRequestAndReview = actorId === accessApprovalRequest.requestedByUserId;
|
const allUniqueReviews = [...otherReviews, reviewForThisActorProcessing];
|
||||||
|
|
||||||
if (isSoftEnforcement && wasSelfRequestAndReview && status === ApprovalStatus.APPROVED) {
|
const approvedReviews = allUniqueReviews.filter((r) => r.status === ApprovalStatus.APPROVED);
|
||||||
|
const meetsStandardApprovalThreshold = approvedReviews.length >= policy.approvals;
|
||||||
|
|
||||||
|
if (
|
||||||
|
reviewForThisActorProcessing.status === ApprovalStatus.APPROVED &&
|
||||||
|
(meetsStandardApprovalThreshold || isBreakGlassApprovalAttempt)
|
||||||
|
) {
|
||||||
|
const currentRequestState = await accessApprovalRequestDAL.findById(accessApprovalRequest.id, tx);
|
||||||
|
let privilegeIdToSet = currentRequestState?.privilegeId || null;
|
||||||
|
|
||||||
|
if (!privilegeIdToSet) {
|
||||||
|
if (accessApprovalRequest.isTemporary && !accessApprovalRequest.temporaryRange) {
|
||||||
|
throw new BadRequestError({ message: "Temporary range is required for temporary access" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!accessApprovalRequest.isTemporary && !accessApprovalRequest.temporaryRange) {
|
||||||
|
// Permanent access
|
||||||
|
const privilege = await additionalPrivilegeDAL.create(
|
||||||
|
{
|
||||||
|
userId: accessApprovalRequest.requestedByUserId,
|
||||||
|
projectId: accessApprovalRequest.projectId,
|
||||||
|
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
|
||||||
|
permissions: JSON.stringify(accessApprovalRequest.permissions)
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
privilegeIdToSet = privilege.id;
|
||||||
|
} else {
|
||||||
|
// Temporary access
|
||||||
|
const relativeTempAllocatedTimeInMs = ms(accessApprovalRequest.temporaryRange!);
|
||||||
|
const startTime = new Date();
|
||||||
|
|
||||||
|
const privilege = await additionalPrivilegeDAL.create(
|
||||||
|
{
|
||||||
|
userId: accessApprovalRequest.requestedByUserId,
|
||||||
|
projectId: accessApprovalRequest.projectId,
|
||||||
|
slug: `requested-privilege-${slugify(alphaNumericNanoId(12))}`,
|
||||||
|
permissions: JSON.stringify(accessApprovalRequest.permissions),
|
||||||
|
isTemporary: true, // Explicitly set to true for the privilege
|
||||||
|
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
||||||
|
temporaryRange: accessApprovalRequest.temporaryRange!,
|
||||||
|
temporaryAccessStartTime: startTime,
|
||||||
|
temporaryAccessEndTime: new Date(startTime.getTime() + relativeTempAllocatedTimeInMs)
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
privilegeIdToSet = privilege.id;
|
||||||
|
}
|
||||||
|
await accessApprovalRequestDAL.updateById(accessApprovalRequest.id, { privilegeId: privilegeIdToSet }, tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send notification if this was a breakglass approval
|
||||||
|
if (isBreakGlassApprovalAttempt) {
|
||||||
const cfg = getConfig();
|
const cfg = getConfig();
|
||||||
const actingUser = await userDAL.findById(actorId, tx);
|
const actingUser = await userDAL.findById(actorId, tx);
|
||||||
|
|
||||||
@@ -491,7 +518,7 @@ export const accessApprovalRequestServiceFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return newReview;
|
return reviewForThisActorProcessing;
|
||||||
});
|
});
|
||||||
|
|
||||||
return reviewStatus;
|
return reviewStatus;
|
||||||
|
|||||||
+35
-20
@@ -86,6 +86,7 @@ export const AccessApprovalRequest = ({
|
|||||||
| (TAccessApprovalRequest & {
|
| (TAccessApprovalRequest & {
|
||||||
user: { firstName?: string; lastName?: string; email?: string } | null;
|
user: { firstName?: string; lastName?: string; email?: string } | null;
|
||||||
isRequestedByCurrentUser: boolean;
|
isRequestedByCurrentUser: boolean;
|
||||||
|
isSelfApproveAllowed: boolean;
|
||||||
isApprover: boolean;
|
isApprover: boolean;
|
||||||
})
|
})
|
||||||
| null
|
| null
|
||||||
@@ -351,18 +352,24 @@ export const AccessApprovalRequest = ({
|
|||||||
role="button"
|
role="button"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (
|
// Whether the request has already been approved / rejected / reviewed
|
||||||
|
const isInactive =
|
||||||
details.isAccepted ||
|
details.isAccepted ||
|
||||||
details.isReviewedByUser ||
|
details.isReviewedByUser ||
|
||||||
details.isRejectedByAnyone ||
|
details.isRejectedByAnyone;
|
||||||
(!details.isApprover &&
|
|
||||||
!(
|
// Whether the current user can bypass policy
|
||||||
details.isSoftEnforcement &&
|
const canBypass =
|
||||||
details.isRequestedByCurrentUser &&
|
details.isSoftEnforcement &&
|
||||||
canBypassApprovalPermission
|
details.isRequestedByCurrentUser &&
|
||||||
))
|
canBypassApprovalPermission;
|
||||||
)
|
|
||||||
return;
|
// Whether the current user can approve
|
||||||
|
const canApprove =
|
||||||
|
details.isApprover &&
|
||||||
|
(!details.isRequestedByCurrentUser || details.isSelfApproveAllowed);
|
||||||
|
|
||||||
|
if (isInactive || (!canApprove && !canBypass)) return;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
membersGroupById?.[request.requestedByUserId].user ||
|
membersGroupById?.[request.requestedByUserId].user ||
|
||||||
@@ -376,6 +383,7 @@ export const AccessApprovalRequest = ({
|
|||||||
? user
|
? user
|
||||||
: membersGroupById?.[request.requestedByUserId].user,
|
: membersGroupById?.[request.requestedByUserId].user,
|
||||||
isRequestedByCurrentUser: details.isRequestedByCurrentUser,
|
isRequestedByCurrentUser: details.isRequestedByCurrentUser,
|
||||||
|
isSelfApproveAllowed: details.isSelfApproveAllowed,
|
||||||
isApprover: details.isApprover
|
isApprover: details.isApprover
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -383,18 +391,24 @@ export const AccessApprovalRequest = ({
|
|||||||
handlePopUpOpen("reviewRequest");
|
handlePopUpOpen("reviewRequest");
|
||||||
}}
|
}}
|
||||||
onKeyDown={(evt) => {
|
onKeyDown={(evt) => {
|
||||||
if (
|
// Whether the request has already been approved / rejected / reviewed
|
||||||
|
const isInactive =
|
||||||
details.isAccepted ||
|
details.isAccepted ||
|
||||||
details.isReviewedByUser ||
|
details.isReviewedByUser ||
|
||||||
details.isRejectedByAnyone ||
|
details.isRejectedByAnyone;
|
||||||
(!details.isApprover &&
|
|
||||||
!(
|
// Whether the current user can bypass policy
|
||||||
details.isSoftEnforcement &&
|
const canBypass =
|
||||||
details.isRequestedByCurrentUser &&
|
details.isSoftEnforcement &&
|
||||||
canBypassApprovalPermission
|
details.isRequestedByCurrentUser &&
|
||||||
))
|
canBypassApprovalPermission;
|
||||||
)
|
|
||||||
return;
|
// Whether the current user can approve
|
||||||
|
const canApprove =
|
||||||
|
details.isApprover &&
|
||||||
|
(!details.isRequestedByCurrentUser || details.isSelfApproveAllowed);
|
||||||
|
|
||||||
|
if (isInactive || (!canApprove && !canBypass)) return;
|
||||||
|
|
||||||
if (evt.key === "Enter") {
|
if (evt.key === "Enter") {
|
||||||
if (
|
if (
|
||||||
@@ -409,6 +423,7 @@ export const AccessApprovalRequest = ({
|
|||||||
? user
|
? user
|
||||||
: membersGroupById?.[request.requestedByUserId].user,
|
: membersGroupById?.[request.requestedByUserId].user,
|
||||||
isRequestedByCurrentUser: details.isRequestedByCurrentUser,
|
isRequestedByCurrentUser: details.isRequestedByCurrentUser,
|
||||||
|
isSelfApproveAllowed: details.isSelfApproveAllowed,
|
||||||
isApprover: details.isApprover
|
isApprover: details.isApprover
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-8
@@ -26,6 +26,7 @@ export const ReviewAccessRequestModal = ({
|
|||||||
request: TAccessApprovalRequest & {
|
request: TAccessApprovalRequest & {
|
||||||
user: { firstName?: string; lastName?: string; email?: string } | null;
|
user: { firstName?: string; lastName?: string; email?: string } | null;
|
||||||
isRequestedByCurrentUser: boolean;
|
isRequestedByCurrentUser: boolean;
|
||||||
|
isSelfApproveAllowed: boolean;
|
||||||
isApprover: boolean;
|
isApprover: boolean;
|
||||||
};
|
};
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
@@ -155,7 +156,6 @@ export const ReviewAccessRequestModal = ({
|
|||||||
)}{" "}
|
)}{" "}
|
||||||
is requesting access to the following resource:
|
is requesting access to the following resource:
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div className="mb-2 mt-4 border-l border-blue-500 bg-blue-500/20 px-3 py-2 text-mineshaft-200">
|
<div className="mb-2 mt-4 border-l border-blue-500 bg-blue-500/20 px-3 py-2 text-mineshaft-200">
|
||||||
<div className="mb-1 lowercase">
|
<div className="mb-1 lowercase">
|
||||||
<span className="font-bold capitalize">Requested path: </span>
|
<span className="font-bold capitalize">Requested path: </span>
|
||||||
@@ -179,16 +179,16 @@ export const ReviewAccessRequestModal = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<Button
|
<Button
|
||||||
isLoading={isLoading === "approved"}
|
isLoading={isLoading === "approved"}
|
||||||
isDisabled={
|
isDisabled={
|
||||||
!!isLoading ||
|
!!isLoading ||
|
||||||
(!request.isApprover &&
|
(!(
|
||||||
!bypassApproval &&
|
request.isApprover &&
|
||||||
isSoftEnforcement &&
|
(!request.isRequestedByCurrentUser || request.isSelfApproveAllowed)
|
||||||
canBypassApprovalPermission)
|
) &&
|
||||||
|
!bypassApproval)
|
||||||
}
|
}
|
||||||
onClick={() => handleReview("approved")}
|
onClick={() => handleReview("approved")}
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
@@ -207,10 +207,9 @@ export const ReviewAccessRequestModal = ({
|
|||||||
Reject Request
|
Reject Request
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isSoftEnforcement &&
|
{isSoftEnforcement &&
|
||||||
request.isRequestedByCurrentUser &&
|
request.isRequestedByCurrentUser &&
|
||||||
!request.isApprover &&
|
!(request.isApprover && request.isSelfApproveAllowed) &&
|
||||||
canBypassApprovalPermission && (
|
canBypassApprovalPermission && (
|
||||||
<div className="mt-2 flex flex-col space-y-2">
|
<div className="mt-2 flex flex-col space-y-2">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
Reference in New Issue
Block a user