diff --git a/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretForm.tsx b/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretForm.tsx
index 7c37ed7b2..d260b9f40 100644
--- a/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretForm.tsx
+++ b/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretForm.tsx
@@ -17,6 +17,7 @@ import { EditDynamicSecretRedisProviderForm } from "./EditDynamicSecretRedisProv
import { EditDynamicSecretSapHanaForm } from "./EditDynamicSecretSapHanaForm";
import { EditDynamicSecretSnowflakeForm } from "./EditDynamicSecretSnowflakeForm";
import { EditDynamicSecretSqlProviderForm } from "./EditDynamicSecretSqlProviderForm";
+import { EditDynamicSecretTotpForm } from "./EditDynamicSecretTotpForm";
type Props = {
onClose: () => void;
@@ -276,6 +277,23 @@ export const EditDynamicSecretForm = ({
/>
)}
+ {dynamicSecretDetails?.type === DynamicSecretProviders.Totp && (
+
+
+
+ )}
);
};
diff --git a/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretTotpForm.tsx b/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretTotpForm.tsx
new file mode 100644
index 000000000..b27cf2e57
--- /dev/null
+++ b/frontend/src/views/SecretMainPage/components/DynamicSecretListView/EditDynamicSecretForm/EditDynamicSecretTotpForm.tsx
@@ -0,0 +1,214 @@
+import { Controller, useForm } from "react-hook-form";
+import Link from "next/link";
+import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { zodResolver } from "@hookform/resolvers/zod";
+import ms from "ms";
+import { z } from "zod";
+
+import { TtlFormLabel } from "@app/components/features";
+import { createNotification } from "@app/components/notifications";
+import { Button, FormControl, Input } from "@app/components/v2";
+import { useUpdateDynamicSecret } from "@app/hooks/api";
+import { TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
+
+const formSchema = z.object({
+ inputs: z
+ .object({
+ url: z.string().trim().min(1)
+ })
+ .partial(),
+ defaultTTL: z.string().superRefine((val, ctx) => {
+ const valMs = ms(val);
+ if (valMs < 60 * 1000)
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
+ // a day
+ if (valMs > 24 * 60 * 60 * 1000)
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
+ }),
+ maxTTL: z
+ .string()
+ .optional()
+ .superRefine((val, ctx) => {
+ if (!val) return;
+ const valMs = ms(val);
+ if (valMs < 60 * 1000)
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be a greater than 1min" });
+ // a day
+ if (valMs > 24 * 60 * 60 * 1000)
+ ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
+ }),
+ newName: z
+ .string()
+ .trim()
+ .min(1)
+ .refine((val) => val.toLowerCase() === val, "Must be lowercase")
+});
+type TForm = z.infer
;
+
+type Props = {
+ onClose: () => void;
+ dynamicSecret: TDynamicSecret & { inputs: unknown };
+ secretPath: string;
+ projectSlug: string;
+ environment: string;
+};
+
+export const EditDynamicSecretTotpForm = ({
+ onClose,
+ dynamicSecret,
+ environment,
+ secretPath,
+ projectSlug
+}: Props) => {
+ const {
+ control,
+ formState: { isSubmitting },
+ handleSubmit
+ } = useForm({
+ resolver: zodResolver(formSchema),
+ values: {
+ defaultTTL: dynamicSecret.defaultTTL,
+ maxTTL: dynamicSecret.maxTTL,
+ newName: dynamicSecret.name,
+ inputs: {
+ ...(dynamicSecret.inputs as TForm["inputs"])
+ }
+ }
+ });
+
+ const updateDynamicSecret = useUpdateDynamicSecret();
+
+ const handleUpdateDynamicSecret = async ({ inputs, maxTTL, defaultTTL, newName }: TForm) => {
+ // wait till previous request is finished
+ if (updateDynamicSecret.isLoading) return;
+ try {
+ await updateDynamicSecret.mutateAsync({
+ name: dynamicSecret.name,
+ path: secretPath,
+ projectSlug,
+ environmentSlug: environment,
+ data: {
+ maxTTL: maxTTL || undefined,
+ defaultTTL,
+ inputs,
+ newName: newName === dynamicSecret.name ? undefined : newName
+ }
+ });
+ onClose();
+ createNotification({
+ type: "success",
+ text: "Successfully updated dynamic secret"
+ });
+ } catch (err) {
+ createNotification({
+ type: "error",
+ text: err instanceof Error ? err.message : "Failed to update dynamic secret"
+ });
+ }
+ };
+
+ return (
+
+ );
+};