Feat: Request access

This commit is contained in:
Daniel Hougaard
2024-04-03 16:45:37 -07:00
parent 83bd3a0bf4
commit 7aee4fdfcd
3 changed files with 312 additions and 30 deletions

View File

@ -1,26 +1,34 @@
import { packRules } from "@casl/ability/extra";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { accessApprovalKeys } from "./queries";
import { TCreateAccessPolicyDTO, TDeleteSecretPolicyDTO, TUpdateAccessPolicyDTO } from "./types";
import {
TAccessApproval,
TCreateAccessPolicyDTO,
TCreateAccessRequestDTO,
TDeleteSecretPolicyDTO,
TUpdateAccessPolicyDTO
} from "./types";
export const useCreateAccessApprovalPolicy = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, TCreateAccessPolicyDTO>({
mutationFn: async ({ environment, workspaceId, approvals, approvers, name }) => {
mutationFn: async ({ environment, projectSlug, approvals, approvers, name, secretPath }) => {
const { data } = await apiRequest.post("/api/v1/access-approvals", {
environment,
workspaceId,
projectSlug,
approvals,
approvers,
secretPath,
name
});
return data;
},
onSuccess: (_, { workspaceId }) => {
queryClient.invalidateQueries(accessApprovalKeys.getAccessApprovalPolicies(workspaceId));
onSuccess: (_, { projectSlug }) => {
queryClient.invalidateQueries(accessApprovalKeys.getAccessApprovalPolicies(projectSlug));
}
});
};
@ -29,16 +37,17 @@ export const useUpdateAccessApprovalPolicy = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, TUpdateAccessPolicyDTO>({
mutationFn: async ({ id, approvers, approvals, name }) => {
mutationFn: async ({ id, approvers, approvals, name, secretPath }) => {
const { data } = await apiRequest.patch(`/api/v1/access-approvals/${id}`, {
approvals,
approvers,
secretPath,
name
});
return data;
},
onSuccess: (_, { workspaceId }) => {
queryClient.invalidateQueries(accessApprovalKeys.getAccessApprovalPolicies(workspaceId));
onSuccess: (_, { projectSlug }) => {
queryClient.invalidateQueries(accessApprovalKeys.getAccessApprovalPolicies(projectSlug));
}
});
};
@ -51,8 +60,66 @@ export const useDeleteAccessApprovalPolicy = () => {
const { data } = await apiRequest.delete(`/api/v1/access-approvals/${id}`);
return data;
},
onSuccess: (_, { workspaceId }) => {
queryClient.invalidateQueries(accessApprovalKeys.getAccessApprovalPolicies(workspaceId));
onSuccess: (_, { projectSlug }) => {
queryClient.invalidateQueries(accessApprovalKeys.getAccessApprovalPolicies(projectSlug));
}
});
};
export const useCreateAccessRequest = () => {
const queryClient = useQueryClient();
return useMutation<{}, {}, TCreateAccessRequestDTO>({
mutationFn: async ({ envSlug, projectSlug, secretPath, ...privilege }) => {
const { data } = await apiRequest.post<TAccessApproval>(
"/api/v1/access-approval-requests",
{
...privilege,
permissions: privilege.permissions ? packRules(privilege.permissions) : undefined
},
{
params: {
envSlug,
projectSlug,
secretPath
}
}
);
return data;
},
onSuccess: (_, { projectSlug }) => {
queryClient.invalidateQueries(accessApprovalKeys.getAccessApprovalRequests(projectSlug));
}
});
};
export const useReviewAccessRequest = () => {
const queryClient = useQueryClient();
return useMutation<
{},
{},
{
requestId: string;
status: "approved" | "rejected";
projectSlug: string;
envSlug?: string;
requestedBy?: string;
}
>({
mutationFn: async ({ requestId, status }) => {
const { data } = await apiRequest.post(
`/api/v1/access-approval-requests/${requestId}/review`,
{
status
}
);
return data;
},
onSuccess: (_, { projectSlug, envSlug, requestedBy }) => {
queryClient.invalidateQueries(
accessApprovalKeys.getAccessApprovalRequests(projectSlug, envSlug, requestedBy)
);
queryClient.invalidateQueries(accessApprovalKeys.getAccessApprovalRequestCount(projectSlug));
}
});
};

View File

@ -1,30 +1,125 @@
import { PackRule, unpackRules } from "@casl/ability/extra";
import { useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
import { TAccessApprovalPolicy, TGetSecretApprovalPoliciesDTO } from "./types";
import { TProjectPermission } from "../roles/types";
import {
TAccessApprovalPolicy,
TAccessApprovalRequest,
TAccessRequestCount,
TGetAccessApprovalRequestsDTO,
TGetAccessPolicyApprovalCountDTO
} from "./types";
export const accessApprovalKeys = {
getAccessApprovalPolicies: (workspaceId: string) =>
[{ workspaceId }, "access-approval-policies"] as const,
getAccessApprovalPolicyOfABoard: (workspaceId: string, environment: string) => [
{ workspaceId, environment },
"access-approval-policy"
]
getAccessApprovalPolicies: (projectSlug: string) =>
[{ projectSlug }, "access-approval-policies"] as const,
getAccessApprovalPolicyOfABoard: (workspaceId: string, environment: string) =>
[{ workspaceId, environment }, "access-approval-policy"] as const,
getAccessApprovalRequests: (projectSlug: string, envSlug?: string, requestedBy?: string) =>
[{ projectSlug, envSlug, requestedBy }, "access-approval-requests"] as const,
getAccessApprovalRequestCount: (projectSlug: string) =>
[{ projectSlug }, "access-approval-request-count"] as const
};
const fetchApprovalPolicies = async (workspaceId: string) => {
export const fetchPolicyApprovalCount = async ({
projectSlug,
envSlug
}: TGetAccessPolicyApprovalCountDTO) => {
const { data } = await apiRequest.get<{ policyCount: number }>(
"/api/v1/access-approvals/policy-count",
{
params: { projectSlug, envSlug }
}
);
return data.policyCount;
};
export const useGetAccessPolicyApprovalCount = ({
projectSlug,
envSlug,
options = {}
}: TGetAccessPolicyApprovalCountDTO & {
options?: UseQueryOptions<
number,
unknown,
number,
ReturnType<typeof accessApprovalKeys.getAccessApprovalPolicies>
>;
}) =>
useQuery({
queryFn: () => fetchPolicyApprovalCount({ projectSlug, envSlug }),
...options,
enabled: Boolean(projectSlug) && (options?.enabled ?? true)
});
const fetchApprovalPolicies = async ({ projectSlug }: TGetAccessApprovalRequestsDTO) => {
const { data } = await apiRequest.get<{ approvals: TAccessApprovalPolicy[] }>(
"/api/v1/access-approvals",
{ params: { workspaceId } }
{ params: { projectSlug } }
);
return data.approvals;
};
export const useGetAccessApprovalPolicies = ({
workspaceId,
const fetchApprovalRequests = async ({
projectSlug,
envSlug,
authorProjectMembershipId
}: TGetAccessApprovalRequestsDTO) => {
const { data } = await apiRequest.get<{ requests: TAccessApprovalRequest[] }>(
"/api/v1/access-approval-requests",
{ params: { projectSlug, envSlug, authorProjectMembershipId } }
);
return data.requests.map((request) => ({
...request,
privilege: request.privilege
? {
...request.privilege,
permissions: unpackRules(
request.privilege.permissions as unknown as PackRule<TProjectPermission>[]
)
}
: null,
permissions: unpackRules(request.permissions as unknown as PackRule<TProjectPermission>[])
}));
};
const fetchAccessRequestsCount = async (projectSlug: string) => {
const { data } = await apiRequest.get<TAccessRequestCount>(
"/api/v1/access-approval-requests/count",
{ params: { projectSlug } }
);
return data;
};
export const useGetAccessRequestsCount = ({
projectSlug,
options = {}
}: TGetSecretApprovalPoliciesDTO & {
}: TGetAccessApprovalRequestsDTO & {
options?: UseQueryOptions<
TAccessRequestCount,
unknown,
{ pendingCount: number; finalizedCount: number },
ReturnType<typeof accessApprovalKeys.getAccessApprovalRequestCount>
>;
}) =>
useQuery({
queryKey: accessApprovalKeys.getAccessApprovalRequestCount(projectSlug),
queryFn: () => fetchAccessRequestsCount(projectSlug),
...options,
enabled: Boolean(projectSlug) && (options?.enabled ?? true)
});
export const useGetAccessApprovalPolicies = ({
projectSlug,
envSlug,
authorProjectMembershipId,
options = {}
}: TGetAccessApprovalRequestsDTO & {
options?: UseQueryOptions<
TAccessApprovalPolicy[],
unknown,
@ -33,8 +128,32 @@ export const useGetAccessApprovalPolicies = ({
>;
}) =>
useQuery({
queryKey: accessApprovalKeys.getAccessApprovalPolicies(workspaceId),
queryFn: () => fetchApprovalPolicies(workspaceId),
queryKey: accessApprovalKeys.getAccessApprovalPolicies(projectSlug),
queryFn: () => fetchApprovalPolicies({ projectSlug, envSlug, authorProjectMembershipId }),
...options,
enabled: Boolean(workspaceId) && (options?.enabled ?? true)
enabled: Boolean(projectSlug) && (options?.enabled ?? true)
});
export const useGetAccessApprovalRequests = ({
projectSlug,
envSlug,
authorProjectMembershipId,
options = {}
}: TGetAccessApprovalRequestsDTO & {
options?: UseQueryOptions<
TAccessApprovalRequest[],
unknown,
TAccessApprovalRequest[],
ReturnType<typeof accessApprovalKeys.getAccessApprovalRequests>
>;
}) =>
useQuery({
queryKey: accessApprovalKeys.getAccessApprovalRequests(
projectSlug,
envSlug,
authorProjectMembershipId
),
queryFn: () => fetchApprovalRequests({ projectSlug, envSlug, authorProjectMembershipId }),
...options,
enabled: Boolean(projectSlug) && (options?.enabled ?? true)
});

View File

@ -1,9 +1,11 @@
import { TProjectPermission } from "../roles/types";
import { WorkspaceEnv } from "../workspace/types";
export type TAccessApprovalPolicy = {
id: string;
name: string;
approvals: number;
secretPath: string;
envId: string;
workspace: string;
environment: WorkspaceEnv;
@ -11,8 +13,99 @@ export type TAccessApprovalPolicy = {
approvers: string[];
};
export type TGetSecretApprovalPoliciesDTO = {
workspaceId: string;
export type TAccessApprovalRequest = {
id: string;
policyId: string;
privilegeId: string | null;
requestedBy: string;
createdAt: Date;
updatedAt: Date;
isTemporary: boolean;
temporaryRange: string | null | undefined;
permissions: TProjectPermission[] | null;
// Computed
environmentName: string;
isApproved: boolean;
privilege: {
membershipId: string;
isTemporary: boolean;
temporaryMode?: string | null;
temporaryRange?: string | null;
temporaryAccessStartTime?: Date | null;
temporaryAccessEndTime?: Date | null;
permissions: TProjectPermission[];
isApproved: boolean;
} | null;
policy: {
id: string;
name: string;
approvals: number;
approvers: string[];
secretPath?: string | null;
envId: string;
};
reviewers: {
member: string;
status: string;
}[];
};
export type TAccessApproval = {
id: string;
policyId: string;
privilegeId: string;
requestedBy: string;
};
export type TAccessRequestCount = {
pendingCount: number;
finalizedCount: number;
};
export type TProjectUserPrivilege = {
projectMembershipId: string;
slug: string;
id: string;
createdAt: Date;
updatedAt: Date;
permissions?: TProjectPermission[];
} & (
| {
isTemporary: true;
temporaryMode: string;
temporaryRange: string;
temporaryAccessStartTime: string;
temporaryAccessEndTime?: string;
}
| {
isTemporary: false;
temporaryMode?: null;
temporaryRange?: null;
temporaryAccessStartTime?: null;
temporaryAccessEndTime?: null;
}
);
export type TCreateAccessRequestDTO = {
envSlug: string;
projectSlug: string;
secretPath: string;
} & Omit<TProjectUserPrivilege, "id" | "createdAt" | "updatedAt" | "slug">;
export type TGetAccessApprovalRequestsDTO = {
projectSlug: string;
envSlug?: string;
authorProjectMembershipId?: string;
};
export type TGetAccessPolicyApprovalCountDTO = {
projectSlug: string;
envSlug: string;
};
export type TGetSecretApprovalPolicyOfBoardDTO = {
@ -22,24 +115,27 @@ export type TGetSecretApprovalPolicyOfBoardDTO = {
};
export type TCreateAccessPolicyDTO = {
workspaceId: string;
projectSlug: string;
name?: string;
environment: string;
approvers?: string[];
approvals?: number;
secretPath?: string;
};
export type TUpdateAccessPolicyDTO = {
id: string;
name?: string;
approvers?: string[];
secretPath?: string;
environment?: string;
approvals?: number;
// for invalidating list
workspaceId: string;
projectSlug: string;
};
export type TDeleteSecretPolicyDTO = {
id: string;
// for invalidating list
workspaceId: string;
projectSlug: string;
};