mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
UI polish: Add better time inputs and tooltips
This commit is contained in:
@@ -22,3 +22,89 @@ export const formatDateTime = ({
|
||||
}
|
||||
return format(date, dateFormat);
|
||||
};
|
||||
|
||||
// Helper function to convert duration to seconds
|
||||
export const durationToSeconds = (
|
||||
value: number,
|
||||
unit: "s" | "m" | "h" | "d" | "w" | "y"
|
||||
): number => {
|
||||
switch (unit) {
|
||||
case "s":
|
||||
return value;
|
||||
case "m":
|
||||
return value * 60;
|
||||
case "h":
|
||||
return value * 60 * 60;
|
||||
case "d":
|
||||
return value * 60 * 60 * 24;
|
||||
case "w":
|
||||
return value * 60 * 60 * 24 * 7;
|
||||
case "y":
|
||||
return value * 60 * 60 * 24 * 365;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to convert seconds to value and unit
|
||||
export const getObjectFromSeconds = (
|
||||
totalSeconds: number,
|
||||
activeUnits?: Array<"s" | "m" | "h" | "d" | "w" | "y">
|
||||
): { value: number; unit: "s" | "m" | "h" | "d" | "w" | "y" } => {
|
||||
const SECONDS_IN_MINUTE = 60;
|
||||
const SECONDS_IN_HOUR = SECONDS_IN_MINUTE * 60;
|
||||
const SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
|
||||
const SECONDS_IN_WEEK = SECONDS_IN_DAY * 7;
|
||||
const SECONDS_IN_YEAR = SECONDS_IN_DAY * 365;
|
||||
|
||||
const activeUnitsSet = activeUnits ? new Set(activeUnits) : null;
|
||||
|
||||
const isUnitActive = (unit: "s" | "m" | "h" | "d" | "w" | "y"): boolean => {
|
||||
return activeUnitsSet ? activeUnitsSet.has(unit) : true;
|
||||
};
|
||||
|
||||
if (
|
||||
isUnitActive("y") &&
|
||||
totalSeconds >= SECONDS_IN_YEAR &&
|
||||
totalSeconds % SECONDS_IN_YEAR === 0
|
||||
) {
|
||||
return { value: totalSeconds / SECONDS_IN_YEAR, unit: "y" };
|
||||
}
|
||||
|
||||
if (
|
||||
isUnitActive("w") &&
|
||||
totalSeconds >= SECONDS_IN_WEEK &&
|
||||
totalSeconds % SECONDS_IN_WEEK === 0
|
||||
) {
|
||||
return { value: totalSeconds / SECONDS_IN_WEEK, unit: "w" };
|
||||
}
|
||||
|
||||
if (isUnitActive("d") && totalSeconds >= SECONDS_IN_DAY && totalSeconds % SECONDS_IN_DAY === 0) {
|
||||
return { value: totalSeconds / SECONDS_IN_DAY, unit: "d" };
|
||||
}
|
||||
|
||||
if (
|
||||
isUnitActive("h") &&
|
||||
totalSeconds >= SECONDS_IN_HOUR &&
|
||||
totalSeconds % SECONDS_IN_HOUR === 0
|
||||
) {
|
||||
return { value: totalSeconds / SECONDS_IN_HOUR, unit: "h" };
|
||||
}
|
||||
|
||||
if (
|
||||
isUnitActive("m") &&
|
||||
totalSeconds >= SECONDS_IN_MINUTE &&
|
||||
totalSeconds % SECONDS_IN_MINUTE === 0
|
||||
) {
|
||||
return { value: totalSeconds / SECONDS_IN_MINUTE, unit: "m" };
|
||||
}
|
||||
|
||||
if (isUnitActive("s") && totalSeconds >= 1) {
|
||||
return { value: totalSeconds, unit: "s" };
|
||||
}
|
||||
|
||||
return {
|
||||
value: 0,
|
||||
unit: "s"
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem,
|
||||
Switch,
|
||||
Tab,
|
||||
TabList,
|
||||
@@ -27,6 +29,7 @@ import { IdentityTrustedIp } from "@app/hooks/api/identities/types";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { IdentityFormTab } from "./types";
|
||||
import { durationToSeconds, getObjectFromSeconds } from "@app/helpers/datetime";
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
@@ -69,20 +72,73 @@ const schema = z
|
||||
(value) => Number(value) <= 30 && Number(value) >= 1,
|
||||
"Lockout threshold must be between 1 and 30"
|
||||
),
|
||||
lockoutDuration: z
|
||||
.string()
|
||||
.refine(
|
||||
(value) => Number(value) <= 86400 && Number(value) >= 30,
|
||||
"Lockout duration must be between 30 seconds and 1 day"
|
||||
),
|
||||
lockoutCounterReset: z
|
||||
.string()
|
||||
.refine(
|
||||
(value) => Number(value) <= 3600 && Number(value) >= 5,
|
||||
"Lockout counter reset must be between 5 seconds and 1 hour"
|
||||
)
|
||||
lockoutDurationValue: z.string(),
|
||||
lockoutDurationUnit: z.enum(["s", "m", "h", "d"], {
|
||||
invalid_type_error: "Please select a valid time unit"
|
||||
}),
|
||||
lockoutCounterResetValue: z.string(),
|
||||
lockoutCounterResetUnit: z.enum(["s", "m", "h"], {
|
||||
invalid_type_error: "Please select a valid time unit"
|
||||
})
|
||||
})
|
||||
.required();
|
||||
.required()
|
||||
.superRefine((data, ctx) => {
|
||||
const {
|
||||
lockoutDurationValue,
|
||||
lockoutCounterResetValue,
|
||||
lockoutDurationUnit,
|
||||
lockoutCounterResetUnit,
|
||||
lockoutEnabled
|
||||
} = data;
|
||||
|
||||
if (!lockoutEnabled) return;
|
||||
|
||||
let isAnyParseError = false;
|
||||
|
||||
const parsedLockoutDuration = parseInt(lockoutDurationValue, 10);
|
||||
if (isNaN(parsedLockoutDuration)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Lockout duration must be a number",
|
||||
path: ["lockoutDurationValue"]
|
||||
});
|
||||
isAnyParseError = true;
|
||||
}
|
||||
|
||||
const parsedLockoutCounterReset = parseInt(lockoutCounterResetValue, 10);
|
||||
if (isNaN(parsedLockoutCounterReset)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Lockout counter reset must be a number",
|
||||
path: ["lockoutCounterResetValue"]
|
||||
});
|
||||
isAnyParseError = true;
|
||||
}
|
||||
|
||||
if (isAnyParseError) return;
|
||||
|
||||
const lockoutDurationInSeconds = durationToSeconds(parsedLockoutDuration, lockoutDurationUnit);
|
||||
const lockoutCounterResetInSeconds = durationToSeconds(
|
||||
parsedLockoutCounterReset,
|
||||
lockoutCounterResetUnit
|
||||
);
|
||||
|
||||
if (lockoutDurationInSeconds > 86400 || lockoutDurationInSeconds < 30) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Lockout duration must be between 30 seconds and 1 day",
|
||||
path: ["lockoutDurationValue"]
|
||||
});
|
||||
}
|
||||
|
||||
if (lockoutCounterResetInSeconds > 3600 || lockoutCounterResetInSeconds < 5) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "Lockout counter reset must be between 5 seconds and 1 hour",
|
||||
path: ["lockoutCounterResetValue"]
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type FormData = z.infer<typeof schema>;
|
||||
|
||||
@@ -130,8 +186,10 @@ export const IdentityUniversalAuthForm = ({
|
||||
accessTokenPeriod: "0",
|
||||
lockoutEnabled: true,
|
||||
lockoutThreshold: "3",
|
||||
lockoutDuration: "300",
|
||||
lockoutCounterReset: "30"
|
||||
lockoutDurationValue: "5",
|
||||
lockoutDurationUnit: "m",
|
||||
lockoutCounterResetValue: "30",
|
||||
lockoutCounterResetUnit: "s"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -139,8 +197,10 @@ export const IdentityUniversalAuthForm = ({
|
||||
|
||||
const lockoutEnabled = watch("lockoutEnabled");
|
||||
const lockoutThreshold = watch("lockoutThreshold");
|
||||
const lockoutDuration = watch("lockoutDuration");
|
||||
const lockoutCounterReset = watch("lockoutCounterReset");
|
||||
const lockoutDurationValue = watch("lockoutDurationValue");
|
||||
const lockoutDurationUnit = watch("lockoutDurationUnit");
|
||||
const lockoutCounterResetValue = watch("lockoutCounterResetValue");
|
||||
const lockoutCounterResetUnit = watch("lockoutCounterResetUnit");
|
||||
|
||||
const {
|
||||
fields: clientSecretTrustedIpsFields,
|
||||
@@ -155,6 +215,9 @@ export const IdentityUniversalAuthForm = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const lockoutDurationObj = getObjectFromSeconds(data.lockoutDuration);
|
||||
const lockoutCounterResetObj = getObjectFromSeconds(data.lockoutCounterReset);
|
||||
|
||||
reset({
|
||||
accessTokenTTL: String(data.accessTokenTTL),
|
||||
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
|
||||
@@ -176,8 +239,10 @@ export const IdentityUniversalAuthForm = ({
|
||||
),
|
||||
lockoutEnabled: data.lockoutEnabled,
|
||||
lockoutThreshold: String(data.lockoutThreshold),
|
||||
lockoutDuration: String(data.lockoutDuration),
|
||||
lockoutCounterReset: String(data.lockoutCounterReset)
|
||||
lockoutDurationValue: String(lockoutDurationObj.value),
|
||||
lockoutDurationUnit: lockoutDurationObj.unit as "s" | "m" | "h" | "d",
|
||||
lockoutCounterResetValue: String(lockoutCounterResetObj.value),
|
||||
lockoutCounterResetUnit: lockoutCounterResetObj.unit as "s" | "m" | "h"
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
@@ -189,8 +254,10 @@ export const IdentityUniversalAuthForm = ({
|
||||
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }],
|
||||
lockoutEnabled: true,
|
||||
lockoutThreshold: "3",
|
||||
lockoutDuration: "300",
|
||||
lockoutCounterReset: "30"
|
||||
lockoutDurationValue: "5",
|
||||
lockoutDurationUnit: "m",
|
||||
lockoutCounterResetValue: "30",
|
||||
lockoutCounterResetUnit: "s"
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
@@ -204,12 +271,20 @@ export const IdentityUniversalAuthForm = ({
|
||||
accessTokenPeriod,
|
||||
lockoutEnabled,
|
||||
lockoutThreshold,
|
||||
lockoutDuration,
|
||||
lockoutCounterReset
|
||||
lockoutDurationValue,
|
||||
lockoutDurationUnit,
|
||||
lockoutCounterResetValue,
|
||||
lockoutCounterResetUnit
|
||||
}: FormData) => {
|
||||
try {
|
||||
if (!identityId) return;
|
||||
|
||||
const lockoutDuration = durationToSeconds(Number(lockoutDurationValue), lockoutDurationUnit);
|
||||
const lockoutCounterReset = durationToSeconds(
|
||||
Number(lockoutCounterResetValue),
|
||||
lockoutCounterResetUnit
|
||||
);
|
||||
|
||||
if (data) {
|
||||
// update universal auth configuration
|
||||
await updateMutateAsync({
|
||||
@@ -223,8 +298,8 @@ export const IdentityUniversalAuthForm = ({
|
||||
accessTokenPeriod: Number(accessTokenPeriod),
|
||||
lockoutEnabled,
|
||||
lockoutThreshold: Number(lockoutThreshold),
|
||||
lockoutDuration: Number(lockoutDuration),
|
||||
lockoutCounterReset: Number(lockoutCounterReset)
|
||||
lockoutDuration,
|
||||
lockoutCounterReset
|
||||
});
|
||||
} else {
|
||||
// create new universal auth configuration
|
||||
@@ -272,8 +347,10 @@ export const IdentityUniversalAuthForm = ({
|
||||
: [
|
||||
"lockoutEnabled",
|
||||
"lockoutThreshold",
|
||||
"lockoutDuration",
|
||||
"lockoutCounterReset"
|
||||
"lockoutDurationValue",
|
||||
"lockoutDurationUnit",
|
||||
"lockoutCounterResetValue",
|
||||
"lockoutCounterResetUnit"
|
||||
].includes(Object.keys(fields)[0])
|
||||
? IdentityFormTab.Lockout
|
||||
: IdentityFormTab.Configuration
|
||||
@@ -362,7 +439,7 @@ export const IdentityUniversalAuthForm = ({
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
helperText={`The lockout feature will prevent login attempts for ${lockoutDuration || 300} seconds after ${lockoutThreshold || 3} consecutive login failures. If ${lockoutCounterReset || 30} seconds pass after the most recent failure, the lockout counter resets.`}
|
||||
helperText={`The lockout feature will prevent login attempts for ${lockoutDurationValue}${lockoutDurationUnit} after ${lockoutThreshold} consecutive login failures. If ${lockoutCounterResetValue}${lockoutCounterResetUnit} pass after the most recent failure, the lockout counter resets.`}
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
@@ -380,57 +457,157 @@ export const IdentityUniversalAuthForm = ({
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="lockoutThreshold"
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`}
|
||||
label="Lockout Threshold"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
tooltipText="The amount of times login must fail before locking the identity auth method"
|
||||
>
|
||||
<Input {...field} placeholder="3" isDisabled={!lockoutEnabled} />
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="lockoutDuration"
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`}
|
||||
label="Lockout Duration (seconds)"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
tooltipText="How long an identity auth method lockout lasts"
|
||||
>
|
||||
<Input {...field} placeholder="300" isDisabled={!lockoutEnabled} />
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="lockoutCounterReset"
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`}
|
||||
label="Lockout Counter Reset (seconds)"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
tooltipText="How long to wait from the most recent failed login until resetting the lockout counter"
|
||||
>
|
||||
<Input {...field} placeholder="30" isDisabled={!lockoutEnabled} />
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="lockoutThreshold"
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`}
|
||||
label="Lockout Threshold"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
tooltipText="The amount of times login must fail before locking the identity auth method"
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="Enter lockout threshold..."
|
||||
isDisabled={!lockoutEnabled}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-end gap-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="lockoutDurationValue"
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`}
|
||||
label="Lockout Duration"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
tooltipText="How long an identity auth method lockout lasts"
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="Enter lockout duration..."
|
||||
isDisabled={!lockoutEnabled}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="lockoutDurationUnit"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className={`mb-0 ${lockoutEnabled ? "" : "opacity-70"}`}
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Select
|
||||
isDisabled={!lockoutEnabled}
|
||||
value={field.value}
|
||||
className="min-w-32 pr-2"
|
||||
onValueChange={field.onChange}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem
|
||||
value="s"
|
||||
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="ml-3 font-medium">Seconds</div>
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
value="m"
|
||||
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="ml-3 font-medium">Minutes</div>
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
value="h"
|
||||
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="ml-3 font-medium">Hours</div>
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
value="d"
|
||||
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="ml-3 font-medium">Days</div>
|
||||
</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-end gap-2">
|
||||
<Controller
|
||||
control={control}
|
||||
name="lockoutCounterResetValue"
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl
|
||||
className={`mb-0 flex-grow ${lockoutEnabled ? "" : "opacity-70"}`}
|
||||
label="Lockout Counter Reset"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
tooltipText="How long to wait from the most recent failed login until resetting the lockout counter"
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="Enter lockout counter reset..."
|
||||
isDisabled={!lockoutEnabled}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="lockoutCounterResetUnit"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className={`mb-0 ${lockoutEnabled ? "" : "opacity-70"}`}
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Select
|
||||
isDisabled={!lockoutEnabled}
|
||||
value={field.value}
|
||||
className="min-w-32 pr-2"
|
||||
onValueChange={field.onChange}
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem
|
||||
value="s"
|
||||
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="ml-3 font-medium">Seconds</div>
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
value="m"
|
||||
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="ml-3 font-medium">Minutes</div>
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
value="h"
|
||||
className="relative py-2 pl-6 pr-8 text-sm hover:bg-mineshaft-700"
|
||||
>
|
||||
<div className="ml-3 font-medium">Hours</div>
|
||||
</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
|
||||
|
||||
@@ -120,13 +120,7 @@ export const ViewIdentityUniversalAuthContent = ({
|
||||
isDisabled={!isAllowed || !lockedOutState || isClearLockoutsPending}
|
||||
size="xs"
|
||||
onClick={clearLockouts}
|
||||
leftIcon={
|
||||
isClearLockoutsPending ? (
|
||||
<FontAwesomeIcon icon={faArrowsRotate} className="animate-spin" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faFire} />
|
||||
)
|
||||
}
|
||||
isLoading={isClearLockoutsPending}
|
||||
colorSchema="secondary"
|
||||
>
|
||||
Clear All Lockouts
|
||||
|
||||
@@ -8,63 +8,11 @@ import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
|
||||
import { useUpdateOrg } from "@app/hooks/api";
|
||||
import { durationToSeconds, getObjectFromSeconds } from "@app/helpers/datetime";
|
||||
|
||||
const MAX_SHARED_SECRET_LIFETIME_SECONDS = 30 * 24 * 60 * 60; // 30 days in seconds
|
||||
const MIN_SHARED_SECRET_LIFETIME_SECONDS = 5 * 60; // 5 minutes in seconds
|
||||
|
||||
// Helper function to convert duration to seconds
|
||||
const durationToSeconds = (value: number, unit: "m" | "h" | "d"): number => {
|
||||
switch (unit) {
|
||||
case "m":
|
||||
return value * 60;
|
||||
case "h":
|
||||
return value * 60 * 60;
|
||||
case "d":
|
||||
return value * 60 * 60 * 24;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to convert seconds to form lifetime value and unit
|
||||
const getFormLifetimeFromSeconds = (
|
||||
totalSeconds: number | null | undefined
|
||||
): { maxLifetimeValue: number; maxLifetimeUnit: "m" | "h" | "d" } => {
|
||||
const DEFAULT_LIFETIME_VALUE = 30;
|
||||
const DEFAULT_LIFETIME_UNIT = "d" as "m" | "h" | "d";
|
||||
|
||||
if (totalSeconds == null || totalSeconds <= 0) {
|
||||
return {
|
||||
maxLifetimeValue: DEFAULT_LIFETIME_VALUE,
|
||||
maxLifetimeUnit: DEFAULT_LIFETIME_UNIT
|
||||
};
|
||||
}
|
||||
|
||||
const secondsInDay = 24 * 60 * 60;
|
||||
const secondsInHour = 60 * 60;
|
||||
const secondsInMinute = 60;
|
||||
|
||||
if (totalSeconds % secondsInDay === 0) {
|
||||
const value = totalSeconds / secondsInDay;
|
||||
if (value >= 1) return { maxLifetimeValue: value, maxLifetimeUnit: "d" };
|
||||
}
|
||||
|
||||
if (totalSeconds % secondsInHour === 0) {
|
||||
const value = totalSeconds / secondsInHour;
|
||||
if (value >= 1) return { maxLifetimeValue: value, maxLifetimeUnit: "h" };
|
||||
}
|
||||
|
||||
if (totalSeconds % secondsInMinute === 0) {
|
||||
const value = totalSeconds / secondsInMinute;
|
||||
if (value >= 1) return { maxLifetimeValue: value, maxLifetimeUnit: "m" };
|
||||
}
|
||||
|
||||
return {
|
||||
maxLifetimeValue: DEFAULT_LIFETIME_VALUE,
|
||||
maxLifetimeUnit: DEFAULT_LIFETIME_UNIT
|
||||
};
|
||||
};
|
||||
|
||||
const formSchema = z
|
||||
.object({
|
||||
maxLifetimeValue: z.number().min(1, "Value must be at least 1"),
|
||||
@@ -79,32 +27,18 @@ const formSchema = z
|
||||
|
||||
const durationInSeconds = durationToSeconds(maxLifetimeValue, maxLifetimeUnit);
|
||||
|
||||
// Check max limit
|
||||
if (durationInSeconds > MAX_SHARED_SECRET_LIFETIME_SECONDS) {
|
||||
let message = "Duration exceeds maximum allowed limit";
|
||||
|
||||
if (maxLifetimeUnit === "m") {
|
||||
message = `Maximum allowed minutes is ${MAX_SHARED_SECRET_LIFETIME_SECONDS / 60} (30 days)`;
|
||||
} else if (maxLifetimeUnit === "h") {
|
||||
message = `Maximum allowed hours is ${MAX_SHARED_SECRET_LIFETIME_SECONDS / (60 * 60)} (30 days)`;
|
||||
} else if (maxLifetimeUnit === "d") {
|
||||
message = `Maximum allowed days is ${MAX_SHARED_SECRET_LIFETIME_SECONDS / (24 * 60 * 60)}`;
|
||||
}
|
||||
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message,
|
||||
message: "Duration exceeds a maximum of 30 days",
|
||||
path: ["maxLifetimeValue"]
|
||||
});
|
||||
}
|
||||
|
||||
// Check min limit
|
||||
if (durationInSeconds < MIN_SHARED_SECRET_LIFETIME_SECONDS) {
|
||||
const message = `Duration must be at least ${MIN_SHARED_SECRET_LIFETIME_SECONDS / 60} minutes`; // 5 minutes
|
||||
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message,
|
||||
message: "Duration must be at least 5 minutes",
|
||||
path: ["maxLifetimeValue"]
|
||||
});
|
||||
}
|
||||
@@ -122,10 +56,14 @@ export const OrgSecretShareLimitSection = () => {
|
||||
const { currentOrg } = useOrganization();
|
||||
|
||||
const getDefaultFormValues = () => {
|
||||
const initialLifetime = getFormLifetimeFromSeconds(currentOrg?.maxSharedSecretLifetime);
|
||||
const initialLifetime = getObjectFromSeconds(currentOrg?.maxSharedSecretLifetime, [
|
||||
"m",
|
||||
"h",
|
||||
"d"
|
||||
]);
|
||||
return {
|
||||
maxLifetimeValue: initialLifetime.maxLifetimeValue,
|
||||
maxLifetimeUnit: initialLifetime.maxLifetimeUnit,
|
||||
maxLifetimeValue: initialLifetime.value,
|
||||
maxLifetimeUnit: initialLifetime.unit as "m" | "h" | "d",
|
||||
maxViewLimit: currentOrg?.maxSharedSecretViewLimit?.toString() || "1",
|
||||
shouldLimitView: Boolean(currentOrg?.maxSharedSecretViewLimit)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user