mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 17:28:18 +00:00
feat: improved slug with a default generator for ui and server
This commit is contained in:
@@ -1,8 +1,12 @@
|
|||||||
|
import { packRules } from "@casl/ability/extra";
|
||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { IdentityProjectAdditionalPrivilegeSchema } from "@app/db/schemas";
|
import { IdentityProjectAdditionalPrivilegeSchema } from "@app/db/schemas";
|
||||||
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
|
import { IdentityProjectAdditionalPrivilegeTemporaryMode } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-types";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { zpStr } from "@app/lib/zod";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -13,22 +17,42 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
schema: {
|
schema: {
|
||||||
body: z.union([
|
body: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
identityId: z.string(),
|
identityId: z.string().min(1),
|
||||||
projectId: z.string(),
|
projectId: z.string().min(1),
|
||||||
slug: z.string().max(60).trim(),
|
// disallow empty string
|
||||||
name: z.string().trim(),
|
slug: zpStr(
|
||||||
|
z
|
||||||
|
.string()
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.optional()
|
||||||
|
.default(`privilege-${slugify(alphaNumericNanoId(12))}`)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
),
|
||||||
|
name: z.string().trim().min(1),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array(),
|
permissions: z.any().array(),
|
||||||
|
isPackedPermission: z.boolean().optional().default(true),
|
||||||
isTemporary: z.literal(false).default(false)
|
isTemporary: z.literal(false).default(false)
|
||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
identityId: z.string(),
|
identityId: z.string(),
|
||||||
projectId: z.string(),
|
projectId: z.string(),
|
||||||
slug: z.string().max(60).trim(),
|
slug: z
|
||||||
|
.string()
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.default(`privilege-${slugify(alphaNumericNanoId(12))}`)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
}),
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array(),
|
permissions: z.any().array(),
|
||||||
isTemporary: z.literal(true),
|
isTemporary: z.literal(true),
|
||||||
|
isPackedPermission: z.boolean().optional().default(true),
|
||||||
temporaryMode: z.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode),
|
temporaryMode: z.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode),
|
||||||
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
||||||
temporaryAccessStartTime: z.string().datetime()
|
temporaryAccessStartTime: z.string().datetime()
|
||||||
@@ -47,7 +71,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: JSON.stringify(req.body.permissions)
|
permissions: JSON.stringify(
|
||||||
|
req.body.isPackedPermission ? req.body.permissions : packRules(req.body.permissions)
|
||||||
|
)
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
}
|
}
|
||||||
@@ -62,10 +88,18 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
}),
|
}),
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
slug: z.string().max(60).trim(),
|
// disallow empty string
|
||||||
|
slug: z
|
||||||
|
.string()
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
}),
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array(),
|
permissions: z.any().array(),
|
||||||
|
isPackedPermission: z.boolean().optional().default(true),
|
||||||
isTemporary: z.boolean(),
|
isTemporary: z.boolean(),
|
||||||
temporaryMode: z.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode),
|
temporaryMode: z.nativeEnum(IdentityProjectAdditionalPrivilegeTemporaryMode),
|
||||||
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
temporaryRange: z.string().refine((val) => ms(val) > 0, "Temporary range must be a positive number"),
|
||||||
@@ -85,7 +119,9 @@ export const registerIdentityProjectAdditionalPrivilegeRouter = async (server: F
|
|||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
...req.body,
|
...req.body,
|
||||||
permissions: req.body.permissions ? JSON.stringify(req.body.permissions) : undefined,
|
permissions: req.body.permissions
|
||||||
|
? JSON.stringify(req.body.isPackedPermission ? req.body.permissions : packRules(req.body.permissions))
|
||||||
|
: undefined,
|
||||||
privilegeId: req.params.privilegeId
|
privilegeId: req.params.privilegeId
|
||||||
});
|
});
|
||||||
return { privilege };
|
return { privilege };
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas";
|
import { ProjectUserAdditionalPrivilegeSchema } from "@app/db/schemas";
|
||||||
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
|
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/ee/services/project-user-additional-privilege/project-user-additional-privilege-types";
|
||||||
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
import { zpStr } from "@app/lib/zod";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -14,7 +17,18 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
body: z.union([
|
body: z.union([
|
||||||
z.object({
|
z.object({
|
||||||
projectMembershipId: z.string(),
|
projectMembershipId: z.string(),
|
||||||
slug: z.string().max(60).trim(),
|
// to disallow empty string
|
||||||
|
slug: zpStr(
|
||||||
|
z
|
||||||
|
.string()
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.optional()
|
||||||
|
.default(`privilege-${slugify(alphaNumericNanoId(12))}`)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
),
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array(),
|
permissions: z.any().array(),
|
||||||
@@ -22,7 +36,17 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
}),
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
projectMembershipId: z.string(),
|
projectMembershipId: z.string(),
|
||||||
slug: z.string().max(60).trim(),
|
slug: zpStr(
|
||||||
|
z
|
||||||
|
.string()
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.optional()
|
||||||
|
.default(`privilege-${slugify(alphaNumericNanoId(12))}`)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
})
|
||||||
|
),
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array(),
|
permissions: z.any().array(),
|
||||||
@@ -60,7 +84,13 @@ export const registerUserAdditionalPrivilegeRouter = async (server: FastifyZodPr
|
|||||||
}),
|
}),
|
||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
slug: z.string().max(60).trim(),
|
slug: z
|
||||||
|
.string()
|
||||||
|
.max(60)
|
||||||
|
.trim()
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Slug must be a valid slug"
|
||||||
|
}),
|
||||||
name: z.string().trim(),
|
name: z.string().trim(),
|
||||||
description: z.string().trim().optional(),
|
description: z.string().trim().optional(),
|
||||||
permissions: z.any().array(),
|
permissions: z.any().array(),
|
||||||
|
|||||||
+22
-12
@@ -19,7 +19,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import { Button, FormControl, Input } from "@app/components/v2";
|
import guidGenerator from "@app/components/utilities/randomId";
|
||||||
|
import { Button, FormControl, Input, Spinner } from "@app/components/v2";
|
||||||
import { ProjectPermissionSub } from "@app/context";
|
import { ProjectPermissionSub } from "@app/context";
|
||||||
import {
|
import {
|
||||||
useCreateIdentityProjectAdditionalPrivilege,
|
useCreateIdentityProjectAdditionalPrivilege,
|
||||||
@@ -134,17 +135,16 @@ export const AdditionalPrivilegeForm = ({
|
|||||||
isIdentity
|
isIdentity
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const { createNotification } = useNotificationContext();
|
const { createNotification } = useNotificationContext();
|
||||||
const isNewRole = !privilegeId;
|
const isEdit = Boolean(privilegeId);
|
||||||
|
|
||||||
const { data: projectUserPrivilegeDetails } = useGetProjectUserPrivilegeDetails(
|
const { data: projectUserPrivilegeDetails, isLoading: isProjectUserPrivilegeLoading } =
|
||||||
privilegeId && !isIdentity ? privilegeId : ""
|
useGetProjectUserPrivilegeDetails(privilegeId && !isIdentity ? privilegeId : "");
|
||||||
);
|
|
||||||
|
|
||||||
const { data: identityProjectPrivilegeDetails } = useGetIdentityProjectPrivilegeDetails(
|
const { data: identityProjectPrivilegeDetails, isLoading: isIdentityProjectPrivilegeLoading } =
|
||||||
isIdentity && privilegeId ? privilegeId : ""
|
useGetIdentityProjectPrivilegeDetails(isIdentity && privilegeId ? privilegeId : "");
|
||||||
);
|
|
||||||
|
|
||||||
const privileges = isIdentity ? identityProjectPrivilegeDetails : projectUserPrivilegeDetails;
|
const privileges = isIdentity ? identityProjectPrivilegeDetails : projectUserPrivilegeDetails;
|
||||||
|
const isLoading = isIdentity ? isIdentityProjectPrivilegeLoading : isProjectUserPrivilegeLoading;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@@ -155,6 +155,9 @@ export const AdditionalPrivilegeForm = ({
|
|||||||
control
|
control
|
||||||
} = useForm<TFormSchema>({
|
} = useForm<TFormSchema>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
slug: `privilege-${guidGenerator().slice(0, 4).toLowerCase()}`
|
||||||
|
},
|
||||||
values: privileges && {
|
values: privileges && {
|
||||||
...privileges,
|
...privileges,
|
||||||
description: privileges.description || "",
|
description: privileges.description || "",
|
||||||
@@ -194,7 +197,7 @@ export const AdditionalPrivilegeForm = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleFormSubmit = async (el: TFormSchema) => {
|
const handleFormSubmit = async (el: TFormSchema) => {
|
||||||
if (!isNewRole) {
|
if (isEdit) {
|
||||||
await handleRoleUpdate(el);
|
await handleRoleUpdate(el);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -223,12 +226,20 @@ export const AdditionalPrivilegeForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isEdit && isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center p-8">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
<form onSubmit={handleSubmit(handleFormSubmit)}>
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<h1 className="text-xl font-semibold text-mineshaft-100">
|
<h1 className="text-xl font-semibold text-mineshaft-100">
|
||||||
{isNewRole ? "New" : "Edit"} user additional privilege
|
{!isEdit ? "New" : "Edit"} user additional privilege
|
||||||
</h1>
|
</h1>
|
||||||
<Button
|
<Button
|
||||||
onClick={onGoBack}
|
onClick={onGoBack}
|
||||||
@@ -255,7 +266,6 @@ export const AdditionalPrivilegeForm = ({
|
|||||||
<FormControl
|
<FormControl
|
||||||
label="Slug"
|
label="Slug"
|
||||||
helperText="Slugs are used for API access"
|
helperText="Slugs are used for API access"
|
||||||
isRequired
|
|
||||||
isError={Boolean(errors?.slug)}
|
isError={Boolean(errors?.slug)}
|
||||||
errorText={errors?.slug?.message}
|
errorText={errors?.slug?.message}
|
||||||
>
|
>
|
||||||
@@ -306,7 +316,7 @@ export const AdditionalPrivilegeForm = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-12 flex items-center space-x-4">
|
<div className="mt-12 flex items-center space-x-4">
|
||||||
<Button type="submit" isDisabled={isSubmitting || !isDirty} isLoading={isSubmitting}>
|
<Button type="submit" isDisabled={isSubmitting || !isDirty} isLoading={isSubmitting}>
|
||||||
{isNewRole ? "Grant Privilege" : "Save Changes"}
|
{!isEdit ? "Grant Privilege" : "Save Changes"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={onGoBack} variant="outline_bg">
|
<Button onClick={onGoBack} variant="outline_bg">
|
||||||
Cancel
|
Cancel
|
||||||
|
|||||||
+2
-2
@@ -146,8 +146,8 @@ export const AdditionalPrivilegeSection = ({
|
|||||||
key={id}
|
key={id}
|
||||||
>
|
>
|
||||||
<div className="flex flex-grow flex-col">
|
<div className="flex flex-grow flex-col">
|
||||||
<div className="mb-1 flex items-center text-lg font-medium capitalize">
|
<div className="mb-1 flex items-center text-lg font-medium">
|
||||||
{privilegeName}
|
<span className="capitalize">{privilegeName}</span>
|
||||||
<Tag size="xs" className="ml-2">
|
<Tag size="xs" className="ml-2">
|
||||||
{slug}
|
{slug}
|
||||||
</Tag>
|
</Tag>
|
||||||
|
|||||||
+1
@@ -31,6 +31,7 @@ export const formSchema = z.object({
|
|||||||
slug: z
|
slug: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
.refine((val) => val !== "custom", { message: "Cannot use custom as its a keyword" }),
|
.refine((val) => val !== "custom", { message: "Cannot use custom as its a keyword" }),
|
||||||
permissions: z
|
permissions: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
Reference in New Issue
Block a user