mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 00:27:35 +00:00
feat: added support for configuring totp with secret key
This commit is contained in:
@@ -17,6 +17,17 @@ export enum LdapCredentialType {
|
|||||||
Static = "static"
|
Static = "static"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum TotpConfigType {
|
||||||
|
URL = "url",
|
||||||
|
MANUAL = "manual"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TotpAlgorithm {
|
||||||
|
SHA1 = "sha1",
|
||||||
|
SHA256 = "sha256",
|
||||||
|
SHA512 = "sha512"
|
||||||
|
}
|
||||||
|
|
||||||
export const DynamicSecretRedisDBSchema = z.object({
|
export const DynamicSecretRedisDBSchema = z.object({
|
||||||
host: z.string().trim().toLowerCase(),
|
host: z.string().trim().toLowerCase(),
|
||||||
port: z.number(),
|
port: z.number(),
|
||||||
@@ -221,9 +232,29 @@ export const LdapSchema = z.union([
|
|||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const DynamicSecretTotpSchema = z.object({
|
export const DynamicSecretTotpSchema = z.discriminatedUnion("configType", [
|
||||||
url: z.string().url().trim().min(1)
|
z.object({
|
||||||
});
|
configType: z.literal(TotpConfigType.URL),
|
||||||
|
url: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.refine((val) => {
|
||||||
|
const urlObj = new URL(val);
|
||||||
|
const secret = urlObj.searchParams.get("secret");
|
||||||
|
|
||||||
|
return Boolean(secret);
|
||||||
|
}, "OTP URL must contain secret field")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
configType: z.literal(TotpConfigType.MANUAL),
|
||||||
|
secret: z.string().min(1),
|
||||||
|
period: z.number().optional(),
|
||||||
|
algorithm: z.nativeEnum(TotpAlgorithm).optional(),
|
||||||
|
digits: z.number().optional()
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
export enum DynamicSecretProviders {
|
export enum DynamicSecretProviders {
|
||||||
SqlDatabase = "sql-database",
|
SqlDatabase = "sql-database",
|
||||||
|
|||||||
@@ -4,20 +4,12 @@ import { HashAlgorithms } from "otplib/core";
|
|||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
|
|
||||||
import { DynamicSecretTotpSchema, TDynamicProviderFns } from "./models";
|
import { DynamicSecretTotpSchema, TDynamicProviderFns, TotpConfigType } from "./models";
|
||||||
|
|
||||||
export const TotpProvider = (): TDynamicProviderFns => {
|
export const TotpProvider = (): TDynamicProviderFns => {
|
||||||
const validateProviderInputs = async (inputs: unknown) => {
|
const validateProviderInputs = async (inputs: unknown) => {
|
||||||
const providerInputs = await DynamicSecretTotpSchema.parseAsync(inputs);
|
const providerInputs = await DynamicSecretTotpSchema.parseAsync(inputs);
|
||||||
|
|
||||||
const urlObj = new URL(providerInputs.url);
|
|
||||||
const secret = urlObj.searchParams.get("secret");
|
|
||||||
if (!secret) {
|
|
||||||
throw new BadRequestError({
|
|
||||||
message: "TOTP secret is missing from URL"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return providerInputs;
|
return providerInputs;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,22 +23,46 @@ export const TotpProvider = (): TDynamicProviderFns => {
|
|||||||
const entityId = alphaNumericNanoId(32);
|
const entityId = alphaNumericNanoId(32);
|
||||||
const authenticatorInstance = authenticator.clone();
|
const authenticatorInstance = authenticator.clone();
|
||||||
|
|
||||||
const urlObj = new URL(providerInputs.url);
|
let secret: string;
|
||||||
const secret = urlObj.searchParams.get("secret") as string;
|
let period: number | null | undefined;
|
||||||
const periodFromUrl = urlObj.searchParams.get("period");
|
let digits: number | null | undefined;
|
||||||
const digitsFromUrl = urlObj.searchParams.get("digits");
|
let algorithm: HashAlgorithms | null | undefined;
|
||||||
const algorithm = urlObj.searchParams.get("algorithm");
|
|
||||||
|
|
||||||
if (digitsFromUrl) {
|
if (providerInputs.configType === TotpConfigType.URL) {
|
||||||
authenticatorInstance.options = { digits: +digitsFromUrl };
|
const urlObj = new URL(providerInputs.url);
|
||||||
|
secret = urlObj.searchParams.get("secret") as string;
|
||||||
|
const periodFromUrl = urlObj.searchParams.get("period");
|
||||||
|
const digitsFromUrl = urlObj.searchParams.get("digits");
|
||||||
|
const algorithmFromUrl = urlObj.searchParams.get("algorithm");
|
||||||
|
|
||||||
|
if (periodFromUrl) {
|
||||||
|
period = +periodFromUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (digitsFromUrl) {
|
||||||
|
digits = +digitsFromUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (algorithmFromUrl) {
|
||||||
|
algorithm = algorithmFromUrl.toLowerCase() as HashAlgorithms;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
secret = providerInputs.secret;
|
||||||
|
period = providerInputs.period;
|
||||||
|
digits = providerInputs.digits;
|
||||||
|
algorithm = providerInputs.algorithm as unknown as HashAlgorithms;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (digits) {
|
||||||
|
authenticatorInstance.options = { digits };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (algorithm) {
|
if (algorithm) {
|
||||||
authenticatorInstance.options = { algorithm: algorithm.toLowerCase() as HashAlgorithms };
|
authenticatorInstance.options = { algorithm };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (periodFromUrl) {
|
if (period) {
|
||||||
authenticatorInstance.options = { step: +periodFromUrl };
|
authenticatorInstance.options = { step: period };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { entityId, data: { TOTP: authenticatorInstance.generate(secret) } };
|
return { entityId, data: { TOTP: authenticatorInstance.generate(secret) } };
|
||||||
|
|||||||
@@ -22,23 +22,34 @@ The Infisical TOTP dynamic secret allows you to generate time-based one-time pas
|
|||||||

|

|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Provide the inputs for dynamic secret parameters">
|
<Step title="Provide the inputs for dynamic secret parameters">
|
||||||
<ParamField path="Secret Name" type="string" required>
|
<ParamField path="Secret Name" type="string" required>
|
||||||
Name by which you want the secret to be referenced
|
Name by which you want the secret to be referenced
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
<ParamField path="Configuration Type" type="string" required>
|
||||||
|
There are two supported configuration types - `url` and `manual`.
|
||||||
|
|
||||||
<ParamField path="Default TTL" type="string" required>
|
When `url` is selected, you can configure the TOTP generator using the OTP URL.
|
||||||
Default time-to-live for a generated secret (it is possible to modify this value after a secret is generated)
|
|
||||||
|
When `manual` is selected, you can configure the TOTP generator using the secret key along with other configurations like period, number of digits, and algorithm.
|
||||||
|
</ParamField>
|
||||||
|
<ParamField path="URL" type="string">
|
||||||
|
OTP URL in `otpauth://` format used to generate TOTP codes.
|
||||||
|
</ParamField>
|
||||||
|
<ParamField path="Secret Key" type="string">
|
||||||
|
Base32 encoded secret used to generate TOTP codes.
|
||||||
|
</ParamField>
|
||||||
|
<ParamField path="Period" type="number">
|
||||||
|
Time interval in seconds between generating new TOTP codes.
|
||||||
|
</ParamField>
|
||||||
|
<ParamField path="Digits" type="number" required>
|
||||||
|
Number of digits to generate in each TOTP code.
|
||||||
|
</ParamField>
|
||||||
|
<ParamField path="Algorithm" type="string" required>
|
||||||
|
Hash algorithm to use when generating TOTP codes. The supported algorithms are sha1, sha256, and sha512.
|
||||||
</ParamField>
|
</ParamField>
|
||||||
|
|
||||||
<ParamField path="Max TTL" type="string" required>
|

|
||||||
Maximum time-to-live for a generated secret
|

|
||||||
</ParamField>
|
|
||||||
|
|
||||||
<ParamField path="URL" type="string" required>
|
|
||||||
OTP url from the TOTP provider
|
|
||||||
</ParamField>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Click 'Submit'">
|
<Step title="Click 'Submit'">
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 418 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 408 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 424 KiB |
@@ -234,9 +234,18 @@ export type TDynamicSecretProvider =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: DynamicSecretProviders.Totp;
|
type: DynamicSecretProviders.Totp;
|
||||||
inputs: {
|
inputs:
|
||||||
url: string;
|
| {
|
||||||
};
|
configType: "url";
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
configType: "manual";
|
||||||
|
secret: string;
|
||||||
|
period?: number;
|
||||||
|
algorithm?: string;
|
||||||
|
digits?: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
export type TCreateDynamicSecretDTO = {
|
export type TCreateDynamicSecretDTO = {
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
|
|||||||
+173
-10
@@ -6,20 +6,52 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FormControl, Input } from "@app/components/v2";
|
import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2";
|
||||||
import { useCreateDynamicSecret } from "@app/hooks/api";
|
import { useCreateDynamicSecret } from "@app/hooks/api";
|
||||||
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
|
||||||
|
|
||||||
|
enum ConfigType {
|
||||||
|
URL = "url",
|
||||||
|
MANUAL = "manual"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TotpAlgorithm {
|
||||||
|
SHA1 = "sha1",
|
||||||
|
SHA256 = "sha256",
|
||||||
|
SHA512 = "sha512"
|
||||||
|
}
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
provider: z.object({
|
provider: z.discriminatedUnion("configType", [
|
||||||
url: z.string().url().trim().min(1)
|
z.object({
|
||||||
}),
|
configType: z.literal(ConfigType.URL),
|
||||||
|
url: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.refine((val) => {
|
||||||
|
const urlObj = new URL(val);
|
||||||
|
const secret = urlObj.searchParams.get("secret");
|
||||||
|
|
||||||
|
return Boolean(secret);
|
||||||
|
}, "OTP URL must contain secret field")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
configType: z.literal(ConfigType.MANUAL),
|
||||||
|
secret: z.string().min(1),
|
||||||
|
period: z.number().optional(),
|
||||||
|
algorithm: z.nativeEnum(TotpAlgorithm).optional(),
|
||||||
|
digits: z.number().optional()
|
||||||
|
})
|
||||||
|
]),
|
||||||
name: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
.min(1)
|
.min(1)
|
||||||
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
.refine((val) => val.toLowerCase() === val, "Must be lowercase")
|
||||||
});
|
});
|
||||||
|
|
||||||
type TForm = z.infer<typeof formSchema>;
|
type TForm = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -39,12 +71,20 @@ export const TotpInputForm = ({
|
|||||||
}: Props) => {
|
}: Props) => {
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
watch,
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
handleSubmit
|
handleSubmit
|
||||||
} = useForm<TForm>({
|
} = useForm<TForm>({
|
||||||
resolver: zodResolver(formSchema)
|
resolver: zodResolver(formSchema),
|
||||||
|
defaultValues: {
|
||||||
|
provider: {
|
||||||
|
configType: ConfigType.URL
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectedConfigType = watch("provider.configType");
|
||||||
|
|
||||||
const createDynamicSecret = useCreateDynamicSecret();
|
const createDynamicSecret = useCreateDynamicSecret();
|
||||||
|
|
||||||
const handleCreateDynamicSecret = async ({ name, provider }: TForm) => {
|
const handleCreateDynamicSecret = async ({ name, provider }: TForm) => {
|
||||||
@@ -113,19 +153,142 @@ export const TotpInputForm = ({
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="provider.url"
|
name="provider.configType"
|
||||||
defaultValue=""
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="OTP URL"
|
label="Configuration Type"
|
||||||
className="flex-grow"
|
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
|
className="w-full"
|
||||||
>
|
>
|
||||||
<Input {...field} />
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
className="w-full"
|
||||||
|
onValueChange={(val) => {
|
||||||
|
field.onChange(val);
|
||||||
|
}}
|
||||||
|
dropdownContainerClassName="max-w-full"
|
||||||
|
>
|
||||||
|
<SelectItem value={ConfigType.URL} key="config-type-url">
|
||||||
|
URL
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={ConfigType.MANUAL} key="config-type-manual">
|
||||||
|
Manual
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{selectedConfigType === ConfigType.URL && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.url"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="OTP URL"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="otpauth://" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedConfigType === ConfigType.MANUAL && (
|
||||||
|
<>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.secret"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Secret Key"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.period"
|
||||||
|
defaultValue={30}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Period"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.digits"
|
||||||
|
defaultValue={6}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Digits"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="provider.algorithm"
|
||||||
|
defaultValue={TotpAlgorithm.SHA1}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Algorithm"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
className="w-full"
|
||||||
|
dropdownContainerClassName="max-w-full"
|
||||||
|
onValueChange={(val) => {
|
||||||
|
field.onChange(val);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectItem value={TotpAlgorithm.SHA1} key="algorithm-sha-1">
|
||||||
|
SHA1
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={TotpAlgorithm.SHA256} key="algorithm-sha-256">
|
||||||
|
SHA256
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={TotpAlgorithm.SHA512} key="algorithm-sha-512">
|
||||||
|
SHA512
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+167
-13
@@ -6,16 +6,47 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FormControl, Input } from "@app/components/v2";
|
import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2";
|
||||||
import { useUpdateDynamicSecret } from "@app/hooks/api";
|
import { useUpdateDynamicSecret } from "@app/hooks/api";
|
||||||
import { TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
|
import { TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
|
||||||
|
|
||||||
|
enum ConfigType {
|
||||||
|
URL = "url",
|
||||||
|
MANUAL = "manual"
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TotpAlgorithm {
|
||||||
|
SHA1 = "sha1",
|
||||||
|
SHA256 = "sha256",
|
||||||
|
SHA512 = "sha512"
|
||||||
|
}
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
inputs: z
|
inputs: z
|
||||||
.object({
|
.discriminatedUnion("configType", [
|
||||||
url: z.string().url().trim().min(1)
|
z.object({
|
||||||
})
|
configType: z.literal(ConfigType.URL),
|
||||||
.partial(),
|
url: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.trim()
|
||||||
|
.min(1)
|
||||||
|
.refine((val) => {
|
||||||
|
const urlObj = new URL(val);
|
||||||
|
const secret = urlObj.searchParams.get("secret");
|
||||||
|
|
||||||
|
return Boolean(secret);
|
||||||
|
}, "OTP URL must contain secret field")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
configType: z.literal(ConfigType.MANUAL),
|
||||||
|
secret: z.string().min(1),
|
||||||
|
period: z.number().optional(),
|
||||||
|
algorithm: z.nativeEnum(TotpAlgorithm).optional(),
|
||||||
|
digits: z.number().optional()
|
||||||
|
})
|
||||||
|
])
|
||||||
|
.optional(),
|
||||||
newName: z
|
newName: z
|
||||||
.string()
|
.string()
|
||||||
.trim()
|
.trim()
|
||||||
@@ -42,17 +73,17 @@ export const EditDynamicSecretTotpForm = ({
|
|||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
|
watch,
|
||||||
handleSubmit
|
handleSubmit
|
||||||
} = useForm<TForm>({
|
} = useForm<TForm>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
values: {
|
values: {
|
||||||
newName: dynamicSecret.name,
|
newName: dynamicSecret.name,
|
||||||
inputs: {
|
inputs: dynamicSecret.inputs as TForm["inputs"]
|
||||||
...(dynamicSecret.inputs as TForm["inputs"])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectedConfigType = watch("inputs.configType");
|
||||||
const updateDynamicSecret = useUpdateDynamicSecret();
|
const updateDynamicSecret = useUpdateDynamicSecret();
|
||||||
|
|
||||||
const handleUpdateDynamicSecret = async ({ inputs, newName }: TForm) => {
|
const handleUpdateDynamicSecret = async ({ inputs, newName }: TForm) => {
|
||||||
@@ -126,19 +157,142 @@ export const EditDynamicSecretTotpForm = ({
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="inputs.url"
|
name="inputs.configType"
|
||||||
defaultValue=""
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="OTP URL"
|
label="Configuration Type"
|
||||||
className="flex-grow"
|
|
||||||
isError={Boolean(error?.message)}
|
isError={Boolean(error?.message)}
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
|
className="w-full"
|
||||||
>
|
>
|
||||||
<Input {...field} />
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
className="w-full"
|
||||||
|
onValueChange={(val) => {
|
||||||
|
field.onChange(val);
|
||||||
|
}}
|
||||||
|
dropdownContainerClassName="max-w-full"
|
||||||
|
>
|
||||||
|
<SelectItem value={ConfigType.URL} key="config-type-url">
|
||||||
|
URL
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={ConfigType.MANUAL} key="config-type-manual">
|
||||||
|
Manual
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{selectedConfigType === ConfigType.URL && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.url"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="OTP URL"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{selectedConfigType === ConfigType.MANUAL && (
|
||||||
|
<>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.secret"
|
||||||
|
defaultValue=""
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Secret Key"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.period"
|
||||||
|
defaultValue={30}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Period"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.digits"
|
||||||
|
defaultValue={6}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Digits"
|
||||||
|
className="flex-grow"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
onChange={(e) => field.onChange(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="inputs.algorithm"
|
||||||
|
defaultValue={TotpAlgorithm.SHA1}
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Algorithm"
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
errorText={error?.message}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
defaultValue={field.value}
|
||||||
|
{...field}
|
||||||
|
className="w-full"
|
||||||
|
dropdownContainerClassName="max-w-full"
|
||||||
|
onValueChange={(val) => {
|
||||||
|
field.onChange(val);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectItem value={TotpAlgorithm.SHA1} key="algorithm-sha-1">
|
||||||
|
SHA1
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={TotpAlgorithm.SHA256} key="algorithm-sha-256">
|
||||||
|
SHA256
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={TotpAlgorithm.SHA512} key="algorithm-sha-512">
|
||||||
|
SHA512
|
||||||
|
</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user