mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #3716 from Infisical/adjustable-max-view-limit-secret-sharing
Improvement(secret-sharing): Allow free number entry for max views in secret sharing
This commit is contained in:
@@ -71,7 +71,8 @@ const formSchema = z
|
||||
maxLifetimeUnit: z.enum(["m", "h", "d"], {
|
||||
invalid_type_error: "Please select a valid time unit"
|
||||
}),
|
||||
maxViewLimit: z.string()
|
||||
maxViewLimit: z.string(),
|
||||
shouldLimitView: z.boolean()
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
const { maxLifetimeValue, maxLifetimeUnit } = data;
|
||||
@@ -112,8 +113,8 @@ const formSchema = z
|
||||
type TForm = z.infer<typeof formSchema>;
|
||||
|
||||
const viewLimitOptions = [
|
||||
{ label: "1", value: 1 },
|
||||
{ label: "Unlimited", value: -1 }
|
||||
{ label: "Unlimited", value: false },
|
||||
{ label: "Limited", value: true }
|
||||
];
|
||||
|
||||
export const OrgSecretShareLimitSection = () => {
|
||||
@@ -125,13 +126,15 @@ export const OrgSecretShareLimitSection = () => {
|
||||
return {
|
||||
maxLifetimeValue: initialLifetime.maxLifetimeValue,
|
||||
maxLifetimeUnit: initialLifetime.maxLifetimeUnit,
|
||||
maxViewLimit: currentOrg?.maxSharedSecretViewLimit?.toString() || "-1"
|
||||
maxViewLimit: currentOrg?.maxSharedSecretViewLimit?.toString() || "1",
|
||||
shouldLimitView: Boolean(currentOrg?.maxSharedSecretViewLimit)
|
||||
};
|
||||
};
|
||||
|
||||
const {
|
||||
control,
|
||||
formState: { isSubmitting, isDirty },
|
||||
watch,
|
||||
handleSubmit,
|
||||
reset
|
||||
} = useForm<TForm>({
|
||||
@@ -139,6 +142,8 @@ export const OrgSecretShareLimitSection = () => {
|
||||
defaultValues: getDefaultFormValues()
|
||||
});
|
||||
|
||||
const shouldLimitView = watch("shouldLimitView");
|
||||
|
||||
useEffect(() => {
|
||||
if (currentOrg) {
|
||||
reset(getDefaultFormValues());
|
||||
@@ -154,8 +159,7 @@ export const OrgSecretShareLimitSection = () => {
|
||||
|
||||
await mutateAsync({
|
||||
orgId: currentOrg.id,
|
||||
maxSharedSecretViewLimit:
|
||||
formData.maxViewLimit === "-1" ? null : Number(formData.maxViewLimit),
|
||||
maxSharedSecretViewLimit: formData.shouldLimitView ? Number(formData.maxViewLimit) : null,
|
||||
maxSharedSecretLifetime: maxSharedSecretLifetimeSeconds
|
||||
});
|
||||
|
||||
@@ -249,26 +253,32 @@ export const OrgSecretShareLimitSection = () => {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex max-w-sm">
|
||||
<div className="flex max-w-sm items-end gap-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="maxViewLimit"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
name="shouldLimitView"
|
||||
render={({ field: { onChange, value, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Max Views"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
className="w-full"
|
||||
className="w-48"
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
defaultValue={value.toString()}
|
||||
value={value.toString()}
|
||||
onValueChange={(e) => onChange(e === "true")}
|
||||
className="w-full"
|
||||
position="popper"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
isDisabled={!isAllowed}
|
||||
{...field}
|
||||
>
|
||||
{viewLimitOptions.map(({ label, value: viewLimitValue }) => (
|
||||
<SelectItem value={String(viewLimitValue || "")} key={label}>
|
||||
<SelectItem
|
||||
value={viewLimitValue.toString()}
|
||||
key={viewLimitValue.toString()}
|
||||
>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -276,6 +286,29 @@ export const OrgSecretShareLimitSection = () => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{shouldLimitView && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="maxViewLimit"
|
||||
render={({ field: { onChange, value, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
className="w-48"
|
||||
>
|
||||
<Input
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
{...field}
|
||||
min={1}
|
||||
max={1000}
|
||||
type="number"
|
||||
isDisabled={!isAllowed}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
|
||||
@@ -35,8 +35,8 @@ const expiresInOptions = [
|
||||
];
|
||||
|
||||
const viewLimitOptions = [
|
||||
{ label: "1", value: 1 },
|
||||
{ label: "Unlimited", value: -1 }
|
||||
{ label: "Unlimited", value: false },
|
||||
{ label: "Limited", value: true }
|
||||
];
|
||||
|
||||
const schema = z.object({
|
||||
@@ -45,6 +45,7 @@ const schema = z.object({
|
||||
secret: z.string().min(1),
|
||||
expiresIn: z.string(),
|
||||
viewLimit: z.string(),
|
||||
shouldLimitView: z.boolean(),
|
||||
accessType: z.nativeEnum(SecretSharingAccessType).optional(),
|
||||
emails: z
|
||||
.string()
|
||||
@@ -96,25 +97,25 @@ export const ShareSecretForm = ({
|
||||
? expiresInOptions.filter((v) => v.value / 1000 <= maxSharedSecretLifetime)
|
||||
: expiresInOptions;
|
||||
|
||||
const filteredViewLimitOptions = maxSharedSecretViewLimit
|
||||
? viewLimitOptions.filter((v) => v.value > 0 && v.value <= maxSharedSecretViewLimit)
|
||||
: viewLimitOptions;
|
||||
|
||||
const {
|
||||
control,
|
||||
reset,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting }
|
||||
formState: { isSubmitting },
|
||||
watch
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
secret: value || "",
|
||||
viewLimit: filteredViewLimitOptions[filteredViewLimitOptions.length - 1].value.toString(),
|
||||
viewLimit: maxSharedSecretViewLimit?.toString() ?? "1",
|
||||
shouldLimitView: Boolean(maxSharedSecretViewLimit),
|
||||
expiresIn:
|
||||
filteredExpiresInOptions[Math.min(filteredExpiresInOptions.length - 1, 2)].value.toString()
|
||||
}
|
||||
});
|
||||
|
||||
const isLimitingView = watch("shouldLimitView");
|
||||
|
||||
const onFormSubmit = async ({
|
||||
name,
|
||||
password,
|
||||
@@ -122,7 +123,8 @@ export const ShareSecretForm = ({
|
||||
expiresIn,
|
||||
viewLimit,
|
||||
accessType,
|
||||
emails
|
||||
emails,
|
||||
shouldLimitView
|
||||
}: FormData) => {
|
||||
try {
|
||||
const expiresAt = new Date(new Date().getTime() + Number(expiresIn));
|
||||
@@ -134,7 +136,7 @@ export const ShareSecretForm = ({
|
||||
password,
|
||||
secretValue: secret,
|
||||
expiresAt,
|
||||
expiresAfterViews: viewLimit === "-1" ? undefined : Number(viewLimit),
|
||||
expiresAfterViews: shouldLimitView ? Number(viewLimit) : undefined,
|
||||
accessType,
|
||||
emails: processedEmails
|
||||
});
|
||||
@@ -320,44 +322,72 @@ export const ShareSecretForm = ({
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="viewLimit"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Max Views"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
helperText={
|
||||
viewLimitOptions.length !== filteredViewLimitOptions.length ? (
|
||||
<span className="text-yellow-500">
|
||||
Limited to{" "}
|
||||
{filteredViewLimitOptions[filteredViewLimitOptions.length - 1].label} by
|
||||
organization
|
||||
</span>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{viewLimitOptions.map(({ label, value: viewLimitValue }) => (
|
||||
<SelectItem
|
||||
value={String(viewLimitValue || "")}
|
||||
key={label}
|
||||
isDisabled={!filteredViewLimitOptions.some((v) => v.label === label)}
|
||||
<div className="flex w-full items-end gap-2">
|
||||
{maxSharedSecretViewLimit === null && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="shouldLimitView"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Max Views"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
className="flex-1"
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value.toString()}
|
||||
onValueChange={(e) => onChange(e === "true")}
|
||||
className="w-full"
|
||||
position="popper"
|
||||
{...field}
|
||||
value={field.value.toString()}
|
||||
dropdownContainerClassName="max-w-none"
|
||||
>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{viewLimitOptions.map(({ label, value: viewLimitValue }) => (
|
||||
<SelectItem
|
||||
value={viewLimitValue.toString()}
|
||||
key={viewLimitValue.toString()}
|
||||
>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
{isLimitingView && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="viewLimit"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label={maxSharedSecretViewLimit ? "Max Views" : undefined}
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
className="flex-1"
|
||||
helperText={
|
||||
maxSharedSecretViewLimit ? (
|
||||
<span className="text-yellow-500">
|
||||
Limited to {maxSharedSecretViewLimit} view
|
||||
{maxSharedSecretViewLimit === 1 ? "" : "s"} by organization
|
||||
</span>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<Input
|
||||
onChange={onChange}
|
||||
{...field}
|
||||
min={1}
|
||||
max={maxSharedSecretViewLimit ?? 1000}
|
||||
type="number"
|
||||
className="h-[37px]"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{!isPublic && (
|
||||
<Controller
|
||||
control={control}
|
||||
|
||||
Reference in New Issue
Block a user