import { useCallback, useMemo, useState } from "react"; import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import ms from "ms"; import { twMerge } from "tailwind-merge"; import { createNotification } from "@app/components/notifications"; import { Button, Checkbox, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; import { Badge } from "@app/components/v2/Badge"; import { ProjectPermissionActions } from "@app/context"; import { useReviewAccessRequest } from "@app/hooks/api"; import { TAccessApprovalRequest } from "@app/hooks/api/accessApproval/types"; import { EnforcementLevel } from "@app/hooks/api/policies/enums"; export const ReviewAccessRequestModal = ({ isOpen, onOpenChange, request, projectSlug, selectedRequester, selectedEnvSlug, canBypass }: { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; request: TAccessApprovalRequest & { user: { firstName?: string; lastName?: string; email?: string } | null; isRequestedByCurrentUser: boolean; isSelfApproveAllowed: boolean; isApprover: boolean; }; projectSlug: string; selectedRequester: string | undefined; selectedEnvSlug: string | undefined; canBypass: boolean; }) => { const [isLoading, setIsLoading] = useState<"approved" | "rejected" | null>(null); const [bypassApproval, setBypassApproval] = useState(false); const [bypassReason, setBypassReason] = useState(""); const isSoftEnforcement = request.policy.enforcementLevel === EnforcementLevel.Soft; const accessDetails = { env: request.environmentName, // secret path will be inside $glob operator secretPath: request.policy.secretPath, read: request.permissions?.some(({ action }) => action.includes(ProjectPermissionActions.Read)), edit: request.permissions?.some(({ action }) => action.includes(ProjectPermissionActions.Edit)), create: request.permissions?.some(({ action }) => action.includes(ProjectPermissionActions.Create) ), delete: request.permissions?.some(({ action }) => action.includes(ProjectPermissionActions.Delete) ), temporaryAccess: { isTemporary: request.isTemporary, temporaryRange: request.temporaryRange } }; const requestedAccess = useMemo(() => { const access: string[] = []; if (accessDetails.read) access.push("Read"); if (accessDetails.edit) access.push("Edit"); if (accessDetails.create) access.push("Create"); if (accessDetails.delete) access.push("Delete"); return access.join(", "); }, [accessDetails]); const getAccessLabel = () => { if (!accessDetails.temporaryAccess.isTemporary || !accessDetails.temporaryAccess.temporaryRange) return "Permanent"; // convert the range to human readable format ms(ms(accessDetails.temporaryAccess.temporaryRange), { long: true }); return ( {`Valid for ${ms(ms(accessDetails.temporaryAccess.temporaryRange), { long: true })} after approval`} ); }; const reviewAccessRequest = useReviewAccessRequest(); const handleReview = useCallback( async (status: "approved" | "rejected") => { if (bypassApproval && bypassReason.length < 10) { createNotification({ title: "Failed to bypass approval", text: "Reason must be 10 characters or longer", type: "error" }); return; } setIsLoading(status); try { await reviewAccessRequest.mutateAsync({ requestId: request.id, status, projectSlug, envSlug: selectedEnvSlug, requestedBy: selectedRequester, bypassReason: bypassApproval ? bypassReason : undefined }); createNotification({ title: `Request ${status}`, text: `The request has been ${status}`, type: status === "approved" ? "success" : "info" }); } catch (error) { console.error(error); setIsLoading(null); return; } setIsLoading(null); onOpenChange(false); }, [ bypassApproval, bypassReason, reviewAccessRequest, request, selectedEnvSlug, selectedRequester, onOpenChange ] ); return (
{request.user && (request.user.firstName || request.user.lastName) && request.user.email ? ( {request.user?.firstName} {request.user?.lastName} ({request.user?.email}) ) : ( A user )}{" "} is requesting access to the following resource:
Requested path: {accessDetails.env + accessDetails.secretPath || ""}
Permissions: {requestedAccess}
Access Type: {getAccessLabel()}
{request.note && (
User Note: {request.note}
)}
{isSoftEnforcement && request.isRequestedByCurrentUser && !(request.isApprover && request.isSelfApproveAllowed) && canBypass && (
setBypassApproval(checked === true)} isChecked={bypassApproval} id="byPassApproval" checkIndicatorBg="text-white" className={twMerge( "mr-2", bypassApproval ? "border-red bg-red hover:bg-red-600" : "" )} > Approve without waiting for requirements to be met (bypass policy protection) {bypassApproval && ( setBypassReason(e.currentTarget.value)} placeholder="Enter reason for bypass (min 10 chars)" leftIcon={} /> )}
)}
); };