/* eslint-disable no-nested-ternary */ /* eslint-disable react/jsx-no-useless-fragment */ import { useCallback, useMemo, useState } from "react"; import { faArrowUpRightFromSquare, faBan, faBookOpen, faCheck, faCheckCircle, faChevronDown, faClipboardCheck, faLock, faMagnifyingGlass, faPlus, faSearch, faStopwatch, faUser, IconDefinition } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { format, formatDistance } from "date-fns"; import { twMerge } from "tailwind-merge"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, EmptyState, Input, Pagination, Tooltip } from "@app/components/v2"; import { Badge } from "@app/components/v2/Badge"; import { ProjectPermissionMemberActions, ProjectPermissionSub, useProject, useProjectPermission, useSubscription, useUser } from "@app/context"; import { getUserTablePreference, PreferenceKey, setUserTablePreference } from "@app/helpers/userTablePreferences"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useGetWorkspaceUsers } from "@app/hooks/api"; import { accessApprovalKeys, useGetAccessApprovalPolicies, useGetAccessApprovalRequests, useGetAccessRequestsCount } from "@app/hooks/api/accessApproval/queries"; import { TAccessApprovalRequest } from "@app/hooks/api/accessApproval/types"; import { EnforcementLevel } from "@app/hooks/api/policies/enums"; import { queryClient } from "@app/hooks/api/reactQuery"; import { ApprovalStatus, TWorkspaceUser } from "@app/hooks/api/types"; import { RequestAccessModal } from "./components/RequestAccessModal"; import { ReviewAccessRequestModal } from "./components/ReviewAccessModal"; const generateRequestText = (request: TAccessApprovalRequest) => { const { isTemporary } = request; return (
Requested {isTemporary ? "temporary" : "permanent"} access to{" "} {request.policy.secretPath} {" "} in{" "} {request.environmentName}
); }; export const AccessApprovalRequest = ({ projectSlug, projectId }: { projectSlug: string; projectId: string; }) => { const [selectedRequest, setSelectedRequest] = useState< | (TAccessApprovalRequest & { user: { firstName?: string | null; lastName?: string | null; email?: string | null } | null; isRequestedByCurrentUser: boolean; isSelfApproveAllowed: boolean; isApprover: boolean; isDisabled?: boolean; }) | null >(null); const { handlePopUpOpen, popUp, handlePopUpClose } = usePopUp([ "requestAccess", "reviewRequest", "upgradePlan" ] as const); const { permission } = useProjectPermission(); const { user } = useUser(); const { subscription } = useSubscription(); const { currentProject } = useProject(); const { data: members } = useGetWorkspaceUsers(projectId, true); const membersGroupById = members?.reduce>( (prev, curr) => ({ ...prev, [curr.user.id]: curr }), {} ); const [statusFilter, setStatusFilter] = useState<"open" | "close">("open"); const [requestedByFilter, setRequestedByFilter] = useState(undefined); const [envFilter, setEnvFilter] = useState(undefined); const { data: requestCount } = useGetAccessRequestsCount({ projectSlug }); const { data: policies, isPending: policiesLoading } = useGetAccessApprovalPolicies({ projectSlug }); const { data: requests, refetch: refetchRequests, isPending: areRequestsPending } = useGetAccessApprovalRequests({ projectSlug, authorUserId: requestedByFilter, envSlug: envFilter }); const { search, setSearch, setPage, page, perPage, setPerPage, offset } = usePagination("", { initPerPage: getUserTablePreference("accessRequestsTable", PreferenceKey.PerPage, 20) }); const handlePerPageChange = (newPerPage: number) => { setPerPage(newPerPage); setUserTablePreference("accessRequestsTable", PreferenceKey.PerPage, newPerPage); }; const filteredRequests = useMemo(() => { let accessRequests: typeof requests; if (statusFilter === "open") accessRequests = requests?.filter( (request) => !request.policy.deletedAt && !request.isApproved && !request.reviewers.some((reviewer) => reviewer.status === ApprovalStatus.REJECTED) ); if (statusFilter === "close") accessRequests = requests?.filter( (request) => request.policy.deletedAt || request.isApproved || request.reviewers.some((reviewer) => reviewer.status === ApprovalStatus.REJECTED) ); return ( accessRequests?.filter((request) => { const { environmentName, requestedByUser } = request; const searchValue = search.trim().toLowerCase(); return ( environmentName?.toLowerCase().includes(searchValue) || `${requestedByUser?.email ?? ""} ${requestedByUser?.firstName ?? ""} ${requestedByUser?.lastName ?? ""}` .toLowerCase() .includes(searchValue) ); }) ?? [] ); }, [requests, statusFilter, requestedByFilter, envFilter, search]); useResetPageHelper({ totalCount: filteredRequests.length, offset, setPage }); const generateRequestDetails = useCallback( (request: TAccessApprovalRequest) => { const isReviewedByUser = request.reviewers.findIndex(({ userId }) => userId === user.id) !== -1; const isRejectedByAnyone = request.reviewers.some( ({ status }) => status === ApprovalStatus.REJECTED ); const isApprover = request.policy.approvers.findIndex((el) => el.userId === user.id || "") !== -1; const isAccepted = request.isApproved; const isSoftEnforcement = request.policy.enforcementLevel === EnforcementLevel.Soft; const isRequestedByCurrentUser = request.requestedByUserId === user.id; const isSelfApproveAllowed = request.policy.allowedSelfApprovals; const userReviewStatus = request.reviewers.find(({ userId }) => userId === user.id)?.status; const canBypass = !request.policy.bypassers.length || request.policy.bypassers.includes(user.id); let displayData: { label: string; type: "primary" | "danger" | "success"; tooltipContent?: string; icon: IconDefinition | null; } = { label: "", type: "primary", icon: null }; const isExpired = request.privilege && request.isApproved && new Date() > new Date(request.privilege.temporaryAccessEndTime || ("" as string)); if (isExpired) displayData = { label: "Access Expired", type: "danger", icon: faStopwatch, tooltipContent: request.privilege?.temporaryAccessEndTime ? `Expired ${format(request.privilege.temporaryAccessEndTime, "M/d/yyyy h:mm aa")}` : undefined }; else if (isAccepted) displayData = { label: "Access Granted", type: "success", icon: faCheck, tooltipContent: `Granted ${format(request.updatedAt, "M/d/yyyy h:mm aa")}` }; else if (isRejectedByAnyone) displayData = { label: "Rejected", type: "danger", icon: faBan, tooltipContent: `Rejected ${format(request.updatedAt, "M/d/yyyy h:mm aa")}` }; else if (userReviewStatus === ApprovalStatus.APPROVED) { displayData = { label: "Pending Additional Reviews", type: "primary", icon: faClipboardCheck }; } else if (!isReviewedByUser) displayData = { label: "Review Required", type: "primary", icon: faClipboardCheck }; return { displayData, isReviewedByUser, isRejectedByAnyone, isApprover, userReviewStatus, isAccepted, isSoftEnforcement, canBypass, isRequestedByCurrentUser, isSelfApproveAllowed }; }, [user] ); const handleSelectRequest = useCallback( (request: TAccessApprovalRequest) => { const details = generateRequestDetails(request); if (membersGroupById?.[request.requestedByUserId].user || details.isRequestedByCurrentUser) { setSelectedRequest({ ...request, user: details.isRequestedByCurrentUser || !membersGroupById?.[request.requestedByUserId].user ? user : membersGroupById?.[request.requestedByUserId].user, isRequestedByCurrentUser: details.isRequestedByCurrentUser, isSelfApproveAllowed: details.isSelfApproveAllowed, isApprover: details.isApprover }); } handlePopUpOpen("reviewRequest"); }, [generateRequestDetails, membersGroupById, user, setSelectedRequest, handlePopUpOpen] ); const isFiltered = Boolean(search || envFilter || requestedByFilter); return ( <>

Access Requests

Docs

Request and review access to secrets in sensitive environments and folders

setSearch(e.target.value)} leftIcon={} placeholder="Search approval requests by requesting user or environment..." className="flex-1" containerClassName="mb-4" />
setStatusFilter("open")} onKeyDown={(evt) => { if (evt.key === "Enter") setStatusFilter("open"); }} className={twMerge( "font-medium", statusFilter === "close" && "text-gray-500 duration-100 hover:text-gray-400" )} > {!!requestCount && requestCount?.pendingCount} Pending
setStatusFilter("close")} onKeyDown={(evt) => { if (evt.key === "Enter") setStatusFilter("close"); }} > {!!requestCount && requestCount.finalizedCount} Closed
Select an Environment {currentProject?.environments.map(({ slug, name }) => ( setEnvFilter((state) => (state === slug ? undefined : slug))} key={`request-filter-${slug}`} icon={envFilter === slug && } iconPos="right" > {name} ))} {!!permission.can(ProjectPermissionMemberActions.Read, ProjectPermissionSub.Member) && ( Select Requesting User {members?.map(({ user: membershipUser, id }) => ( setRequestedByFilter((state) => state === membershipUser.id ? undefined : membershipUser.id ) } key={`request-filter-member-${id}`} icon={ requestedByFilter === membershipUser.id && ( ) } iconPos="right" > {membershipUser.username} ))} )}
{filteredRequests?.length === 0 && !isFiltered && (
)} {Boolean(!filteredRequests?.length && isFiltered && !areRequestsPending) && (
)} {!!filteredRequests?.length && filteredRequests?.slice(offset, perPage * page).map((request) => { const details = generateRequestDetails(request); return (
handleSelectRequest(request)} onKeyDown={(evt) => { if (evt.key === "Enter") { handleSelectRequest(request); } }} >
{generateRequestText(request)}
{membersGroupById?.[request.requestedByUserId]?.user && ( <> Requested {formatDistance(new Date(request.createdAt), new Date())}{" "} ago by{" "} {membersGroupById?.[request.requestedByUserId]?.user?.firstName}{" "} {membersGroupById?.[request.requestedByUserId]?.user?.lastName} ( {membersGroupById?.[request.requestedByUserId]?.user?.email}){" "} )}
{request.requestedByUserId === user.id && (
Requested By You
)}
{details.displayData.icon && ( )} {details.displayData.label}
); })} {Boolean(filteredRequests.length) && ( )}
{!!policies && ( { queryClient.invalidateQueries({ queryKey: accessApprovalKeys.getAccessApprovalRequests( projectSlug, envFilter, requestedByFilter ) }); handlePopUpClose("requestAccess"); }} /> )} {!!selectedRequest && ( { handlePopUpClose("reviewRequest"); setSelectedRequest(null); refetchRequests(); }} onUpdate={(request) => { // scott: this isn't ideal but our current use of state makes this complicated... // we shouldn't be using state like this... handleSelectRequest({ ...selectedRequest, isTemporary: request.isTemporary, temporaryRange: request.temporaryRange, reviewers: [] }); }} canBypass={generateRequestDetails(selectedRequest).canBypass} /> )} handlePopUpClose("upgradePlan")} /> ); };