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:
Scott Wilson
2025-06-03 15:48:42 -07:00
committed by GitHub
2 changed files with 123 additions and 60 deletions

View File

@@ -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"

View File

@@ -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}