From 4c5f3859d655e4412f3c6156d9a6e1a9f2c73dd8 Mon Sep 17 00:00:00 2001
From: carlosmonastyrski
Date: Fri, 14 Mar 2025 11:59:13 -0300
Subject: [PATCH 01/18] Add dynamic secrets modal form on secrets overview page
---
.../OverviewPage/OverviewPage.tsx | 54 ++++++++-
.../components/ActionBar/ActionBar.tsx | 2 +-
.../AwsElastiCacheInputForm.tsx | 80 ++++++++++----
.../AwsIamInputForm.tsx | 84 ++++++++++----
.../AzureEntraIdInputForm.tsx | 101 +++++++++++------
.../CassandraInputForm.tsx | 81 ++++++++++----
.../CreateDynamicSecretForm.tsx | 35 +++---
.../ElasticSearchInputForm.tsx | 81 ++++++++++----
.../CreateDynamicSecretForm/LdapInputForm.tsx | 90 +++++++++++----
.../MongoAtlasInputForm.tsx | 81 ++++++++++----
.../MongoDBInputForm.tsx | 104 +++++++++++++-----
.../RabbitMqInputForm.tsx | 90 +++++++++++----
.../RedisInputForm.tsx | 81 ++++++++++----
.../SapAseInputForm.tsx | 81 ++++++++++----
.../SapHanaInputForm.tsx | 81 ++++++++++----
.../SnowflakeInputForm.tsx | 81 ++++++++++----
.../SqlDatabaseInputForm.tsx | 81 ++++++++++----
.../CreateDynamicSecretForm/TotpInputForm.tsx | 87 +++++++++++----
18 files changed, 1002 insertions(+), 373 deletions(-)
diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
index 78a2380d0..a611eb030 100644
--- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
+++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
@@ -19,6 +19,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useRouter, useSearch } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge";
+import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
@@ -49,8 +50,10 @@ import {
import { ROUTE_PATHS } from "@app/const/routes";
import {
ProjectPermissionActions,
+ ProjectPermissionDynamicSecretActions,
ProjectPermissionSub,
useProjectPermission,
+ useSubscription,
useWorkspace
} from "@app/context";
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
@@ -71,6 +74,7 @@ import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
import { useDynamicSecretOverview, useFolderOverview, useSecretOverview } from "@app/hooks/utils";
+import { CreateDynamicSecretForm } from "../SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm";
import { FolderForm } from "../SecretDashboardPage/components/ActionBar/FolderForm";
import { CreateSecretForm } from "./components/CreateSecretForm";
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
@@ -131,6 +135,7 @@ export const OverviewPage = () => {
const [searchFilter, setSearchFilter] = useState("");
const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(searchFilter);
const secretPath = (routerSearch?.secretPath as string) || "/";
+ const { subscription } = useSubscription();
const [filter, setFilter] = useState(DEFAULT_FILTER_STATE);
const [filterHistory, setFilterHistory] = useState<
@@ -178,6 +183,17 @@ export const OverviewPage = () => {
}, []);
const userAvailableEnvs = currentWorkspace?.environments || [];
+ const userAvailableDynamicSecretEnvs = userAvailableEnvs.filter((env) =>
+ permission.can(
+ ProjectPermissionDynamicSecretActions.CreateRootCredential,
+ subject(ProjectPermissionSub.DynamicSecrets, {
+ environment: env.id,
+ secretPath,
+ secretName: "*",
+ secretTags: ["*"]
+ })
+ )
+ );
const [visibleEnvs, setVisibleEnvs] = useState(userAvailableEnvs);
@@ -249,7 +265,9 @@ export const OverviewPage = () => {
"addSecretsInAllEnvs",
"addFolder",
"misc",
- "updateFolder"
+ "updateFolder",
+ "addDynamicSecret",
+ "upgradePlan"
] as const);
const handleFolderCreate = async (folderName: string, description: string | null) => {
@@ -845,6 +863,22 @@ export const OverviewPage = () => {
+
}
+ onClick={() => {
+ if (subscription && subscription.dynamicSecret) {
+ handlePopUpOpen("addDynamicSecret");
+ return;
+ }
+ handlePopUpOpen("upgradePlan");
+ }}
+ isDisabled={userAvailableDynamicSecretEnvs.length === 0}
+ variant="outline_bg"
+ className="h-10 text-left"
+ isFullWidth
+ >
+ Add Dynamic Secret
+
{
/>
+ handlePopUpToggle("addDynamicSecret", isOpen)}
+ projectSlug={projectSlug}
+ environments={userAvailableDynamicSecretEnvs}
+ secretPath={secretPath}
+ />
+ {subscription && (
+ handlePopUpToggle("upgradePlan", isOpen)}
+ text={
+ subscription.slug === null
+ ? "You can perform this action under an Enterprise license"
+ : "You can perform this action if you switch to Infisical's Team plan"
+ }
+ />
+ )}
);
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
index d545ba748..28c58ec5f 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
@@ -655,7 +655,7 @@ export const ActionBar = ({
isOpen={popUp.addDynamicSecret.isOpen}
onToggle={(isOpen) => handlePopUpToggle("addDynamicSecret", isOpen)}
projectSlug={projectSlug}
- environment={environment}
+ environments={[{ slug: environment, name: "not-used", id: "not-used" }]}
secretPath={secretPath}
/>
24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -59,13 +62,13 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const AwsElastiCacheInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -87,32 +90,42 @@ export const AwsElastiCacheInputForm = ({
revocationStatement: `{
"UserId": "{{username}}"
}`
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.AwsElastiCache, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (env) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.AwsElastiCache, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: env.slug
+ });
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
return (
@@ -299,6 +312,29 @@ export const AwsElastiCacheInputForm = ({
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
index 1acb24e8f..f0590a4e1 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
@@ -5,9 +5,10 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, Input, TextArea } from "@app/components/v2";
+import { Button, FilterableSelect, FormControl, Input, TextArea } from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -40,7 +41,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -49,13 +51,13 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const AwsIamInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -64,31 +66,43 @@ export const AwsIamInputForm = ({
formState: { isSubmitting },
handleSubmit
} = useForm({
- resolver: zodResolver(formSchema)
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ environments: environments.length === 1 ? environments : []
+ }
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.AwsIam, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (environment) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.AwsIam, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
return (
@@ -286,6 +300,30 @@ export const AwsIamInputForm = ({
)}
/>
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
index 55cebe173..4ab20c064 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
@@ -12,7 +12,7 @@ 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 { Button, FilterableSelect, FormControl, Input } from "@app/components/v2";
import {
DropdownMenu,
DropdownMenuContent,
@@ -23,6 +23,7 @@ import { Tooltip } from "@app/components/v2/Tooltip";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { useGetDynamicSecretProviderData } from "@app/hooks/api/dynamicSecret/queries";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
selectedUsers: z.array(
@@ -60,7 +61,8 @@ const formSchema = z.object({
name: z
.string()
.min(1)
- .refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ .refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -69,13 +71,13 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const AzureEntraIdInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -85,7 +87,10 @@ export const AzureEntraIdInputForm = ({
watch,
handleSubmit
} = useForm({
- resolver: zodResolver(formSchema)
+ resolver: zodResolver(formSchema),
+ defaultValues: {
+ environments: environments.length === 1 ? environments : []
+ }
});
const tenantId = watch("provider.tenantId");
const applicationId = watch("provider.applicationId");
@@ -107,38 +112,42 @@ export const AzureEntraIdInputForm = ({
selectedUsers,
provider,
maxTTL,
- defaultTTL
+ defaultTTL,
+ environments: selectedEnvs
}: TForm) => {
// wait till previous request is finished
- if (createDynamicSecret.isPending) return;
- try {
- selectedUsers.map(async (user: { id: string; name: string; email: string }) => {
- await createDynamicSecret.mutateAsync({
- provider: {
- type: DynamicSecretProviders.AzureEntraId,
- inputs: {
- userId: user.id,
- tenantId: provider.tenantId,
- email: user.email,
- applicationId: provider.applicationId,
- clientSecret: provider.clientSecret
- }
- },
- maxTTL,
- name: `${name}-${user.name}`,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
+ const promises = selectedEnvs.map(async (environment) => {
+ if (createDynamicSecret.isPending) return;
+ try {
+ selectedUsers.map(async (user: { id: string; name: string; email: string }) => {
+ await createDynamicSecret.mutateAsync({
+ provider: {
+ type: DynamicSecretProviders.AzureEntraId,
+ inputs: {
+ userId: user.id,
+ tenantId: provider.tenantId,
+ email: user.email,
+ applicationId: provider.applicationId,
+ clientSecret: provider.clientSecret
+ }
+ },
+ maxTTL,
+ name: `${name}-${user.name}`,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
});
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
return (
@@ -373,6 +382,30 @@ export const AzureEntraIdInputForm = ({
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx
index c8389fb38..a49ab620e 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CreateDynamicSecretForm.tsx
@@ -17,6 +17,7 @@ import { AnimatePresence, motion } from "framer-motion";
import { Modal, ModalContent } from "@app/components/v2";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
import { AwsElastiCacheInputForm } from "./AwsElastiCacheInputForm";
import { AwsIamInputForm } from "./AwsIamInputForm";
@@ -38,7 +39,7 @@ type Props = {
isOpen?: boolean;
onToggle: (isOpen: boolean) => void;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
secretPath: string;
};
@@ -129,7 +130,7 @@ export const CreateDynamicSecretForm = ({
isOpen,
onToggle,
projectSlug,
- environment,
+ environments,
secretPath
}: Props) => {
const [wizardStep, setWizardStep] = useState(WizardSteps.SelectProvider);
@@ -197,7 +198,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -215,7 +216,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -233,7 +234,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -251,7 +252,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -269,7 +270,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -287,7 +288,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -305,7 +306,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -323,7 +324,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -341,7 +342,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -359,7 +360,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -377,7 +378,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -395,7 +396,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -413,7 +414,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -432,7 +433,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
@@ -450,7 +451,7 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset}
projectSlug={projectSlug}
secretPath={secretPath}
- environment={environment}
+ environments={environments}
/>
)}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
index eb0a7008f..828f98110 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
@@ -9,6 +9,7 @@ import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
import {
Button,
+ FilterableSelect,
FormControl,
FormLabel,
IconButton,
@@ -19,6 +20,7 @@ import {
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const authMethods = [
{
@@ -73,7 +75,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -82,13 +85,13 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const ElasticSearchInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -107,32 +110,42 @@ export const ElasticSearchInputForm = ({
},
roles: ["superuser"],
port: 443
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (environment) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
const selectedAuthType = watch("provider.auth.type");
@@ -406,6 +419,30 @@ export const ElasticSearchInputForm = ({
)}
/>
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
index ef74db5b8..361067d1b 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
@@ -7,9 +7,18 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2";
+import {
+ Button,
+ FilterableSelect,
+ FormControl,
+ Input,
+ Select,
+ SelectItem,
+ TextArea
+} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
enum CredentialType {
Dynamic = "dynamic",
@@ -69,7 +78,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -79,7 +89,7 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const LdapInputForm = ({
@@ -87,7 +97,7 @@ export const LdapInputForm = ({
onCancel,
secretPath,
projectSlug,
- environment
+ environments
}: Props) => {
const {
control,
@@ -107,7 +117,8 @@ export const LdapInputForm = ({
revocationLdif: "",
rollbackLdif: "",
credentialType: CredentialType.Dynamic
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
@@ -115,26 +126,35 @@ export const LdapInputForm = ({
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.Ldap, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (environment) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.Ldap, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
return (
@@ -369,6 +389,30 @@ export const LdapInputForm = ({
)}
/>
)}
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
index 4de9b8acc..a6725b652 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
@@ -13,6 +13,7 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
FormLabel,
IconButton,
@@ -22,6 +23,7 @@ import {
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -63,7 +65,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -72,7 +75,7 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
const ATLAS_SCOPE_TYPES = [
@@ -93,7 +96,7 @@ const ATLAS_SCOPE_TYPES = [
export const MongoAtlasInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -108,7 +111,8 @@ export const MongoAtlasInputForm = ({
defaultValues: {
provider: {
roles: [{ databaseName: "", roleName: "" }]
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
@@ -124,26 +128,35 @@ export const MongoAtlasInputForm = ({
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (environment) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
return (
@@ -438,6 +451,30 @@ export const MongoAtlasInputForm = ({
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
index b2418f629..0a28a6913 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
@@ -7,9 +7,18 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, FormLabel, IconButton, Input, SecretInput } from "@app/components/v2";
+import {
+ Button,
+ FilterableSelect,
+ FormControl,
+ FormLabel,
+ IconButton,
+ Input,
+ SecretInput
+} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -46,7 +55,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -55,13 +65,13 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const MongoDBDatabaseInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -76,7 +86,8 @@ export const MongoDBDatabaseInputForm = ({
defaultValues: {
provider: {
roles: [{ roleName: "readWrite" }]
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
@@ -87,33 +98,42 @@ export const MongoDBDatabaseInputForm = ({
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: {
- type: DynamicSecretProviders.MongoDB,
- inputs: {
- ...provider,
- port: provider?.port ? provider.port : undefined,
- roles: provider.roles.map((el) => el.roleName)
- }
- },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (environment) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: {
+ type: DynamicSecretProviders.MongoDB,
+ inputs: {
+ ...provider,
+ port: provider?.port ? provider.port : undefined,
+ roles: provider.roles.map((el) => el.roleName)
+ }
+ },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
return (
@@ -321,6 +341,30 @@ export const MongoDBDatabaseInputForm = ({
)}
/>
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
index 21f8b40de..0a6b84d41 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
@@ -7,9 +7,18 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, FormLabel, IconButton, Input, SecretInput } from "@app/components/v2";
+import {
+ Button,
+ FilterableSelect,
+ FormControl,
+ FormLabel,
+ IconButton,
+ Input,
+ SecretInput
+} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -50,7 +59,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -59,13 +69,13 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const RabbitMqInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -89,32 +99,42 @@ export const RabbitMqInputForm = ({
}
},
tags: []
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (environment) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
const selectedTags = watch("provider.tags");
@@ -405,6 +425,30 @@ export const RabbitMqInputForm = ({
)}
/>
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
index 9d9933b44..35f165056 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
@@ -11,6 +11,7 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
Input,
SecretInput,
@@ -18,6 +19,7 @@ import {
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -50,7 +52,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -59,13 +62,13 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const RedisInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -81,32 +84,42 @@ export const RedisInputForm = ({
port: 6379,
creationStatement: "ACL SETUSER {{username}} on >{{password}} ~* &* +@all",
revocationStatement: "ACL DELUSER {{username}}"
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.Redis, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (environment) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.Redis, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
return (
@@ -313,6 +326,30 @@ export const RedisInputForm = ({
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
index efe6e0269..de32565da 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
@@ -11,12 +11,14 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
Input,
TextArea
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -48,7 +50,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -57,13 +60,13 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const SapAseInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -82,32 +85,42 @@ sp_adduser '{{username}}', '{{username}}', null;
sp_role 'grant', 'mon_role', '{{username}}';`,
revocationStatement: `sp_dropuser '{{username}}';
sp_droplogin '{{username}}';`
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.SapAse, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (environment) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.SapAse, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
return (
@@ -291,6 +304,30 @@ sp_droplogin '{{username}}';`
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
index 1b0bd64b8..d31464665 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
@@ -11,6 +11,7 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
Input,
SecretInput,
@@ -18,6 +19,7 @@ import {
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -50,7 +52,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -59,13 +62,13 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const SapHanaInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -82,32 +85,42 @@ GRANT "MONITORING" TO {{username}};`,
revocationStatement: `REVOKE "MONITORING" FROM {{username}};
DROP USER {{username}};`,
renewStatement: "ALTER USER {{username}} VALID UNTIL '{{expiration}}';"
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.SapHana, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (environment) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.SapHana, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
return (
@@ -311,6 +324,30 @@ DROP USER {{username}};`,
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
index a5f8b25d9..c5077f2ad 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
@@ -13,12 +13,14 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
Input,
TextArea
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({
provider: z.object({
@@ -54,7 +56,8 @@ const formSchema = z.object({
.string()
.trim()
.min(1)
- .refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ .refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -63,13 +66,13 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const SnowflakeInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -85,32 +88,42 @@ export const SnowflakeInputForm = ({
"CREATE USER {{username}} PASSWORD = '{{password}}' DEFAULT_ROLE = public DEFAULT_SECONDARY_ROLES = ('ALL') MUST_CHANGE_PASSWORD = FALSE DAYS_TO_EXPIRY = {{expiration}};",
revocationStatement: "DROP USER {{username}};",
renewStatement: "ALTER USER {{username}} SET DAYS_TO_EXPIRY = {{expiration}};"
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.Snowflake, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch (err) {
- createNotification({
- type: "error",
- text: err instanceof Error ? err.message : "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (environment) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.Snowflake, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
+ onCompleted();
+ } catch (err) {
+ createNotification({
+ type: "error",
+ text: err instanceof Error ? err.message : "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
return (
@@ -314,6 +327,30 @@ export const SnowflakeInputForm = ({
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
index 4af92b210..416d9c35b 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
@@ -12,6 +12,7 @@ import {
AccordionItem,
AccordionTrigger,
Button,
+ FilterableSelect,
FormControl,
Input,
SecretInput,
@@ -22,6 +23,7 @@ import {
import { useWorkspace } from "@app/context";
import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders, SqlProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
const passwordRequirementsSchema = z
.object({
@@ -79,7 +81,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
- name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -88,7 +91,7 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
const getSqlStatements = (provider: SqlProviders) => {
@@ -145,7 +148,7 @@ const getDefaultPort = (provider: SqlProviders) => {
export const SqlDatabaseInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -172,7 +175,8 @@ export const SqlDatabaseInputForm = ({
},
allowedSymbols: "-_.~!*"
}
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
@@ -181,26 +185,35 @@ export const SqlDatabaseInputForm = ({
gatewaysQueryKeys.listProjectGateways({ projectId: currentWorkspace.id })
);
- const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ maxTTL,
+ provider,
+ defaultTTL,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch {
- createNotification({
- type: "error",
- text: "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (env) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: env.slug
+ });
+ onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
const handleDatabaseChange = (type: SqlProviders) => {
@@ -649,6 +662,30 @@ export const SqlDatabaseInputForm = ({
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
index 01d5398f0..4ff83a741 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
@@ -5,9 +5,17 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
-import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2";
+import {
+ Button,
+ FilterableSelect,
+ FormControl,
+ Input,
+ Select,
+ SelectItem
+} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
+import { WorkspaceEnv } from "@app/hooks/api/types";
enum ConfigType {
URL = "url",
@@ -52,7 +60,8 @@ const formSchema = z.object({
.string()
.trim()
.min(1)
- .refine((val) => val.toLowerCase() === val, "Must be lowercase")
+ .refine((val) => val.toLowerCase() === val, "Must be lowercase"),
+ environments: z.object({ name: z.string(), slug: z.string() }).array()
});
type TForm = z.infer;
@@ -62,13 +71,13 @@ type Props = {
onCancel: () => void;
secretPath: string;
projectSlug: string;
- environment: string;
+ environments: WorkspaceEnv[];
};
export const TotpInputForm = ({
onCompleted,
onCancel,
- environment,
+ environments,
secretPath,
projectSlug
}: Props) => {
@@ -82,7 +91,8 @@ export const TotpInputForm = ({
defaultValues: {
provider: {
configType: ConfigType.URL
- }
+ },
+ environments: environments.length === 1 ? environments : []
}
});
@@ -90,26 +100,33 @@ export const TotpInputForm = ({
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({ name, provider }: TForm) => {
+ const handleCreateDynamicSecret = async ({
+ name,
+ provider,
+ environments: selectedEnvs
+ }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.Totp, inputs: provider },
- maxTTL: "24h",
- name,
- path: secretPath,
- defaultTTL: "1m",
- projectSlug,
- environmentSlug: environment
- });
- onCompleted();
- } catch (err) {
- createNotification({
- type: "error",
- text: err instanceof Error ? err.message : "Failed to create dynamic secret"
- });
- }
+ const promises = selectedEnvs.map(async (environment) => {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.Totp, inputs: provider },
+ maxTTL: "24h",
+ name,
+ path: secretPath,
+ defaultTTL: "1m",
+ projectSlug,
+ environmentSlug: environment.slug
+ });
+ onCompleted();
+ } catch (err) {
+ createNotification({
+ type: "error",
+ text: err instanceof Error ? err.message : "Failed to create dynamic secret"
+ });
+ }
+ });
+ await Promise.all(promises);
};
return (
@@ -295,6 +312,30 @@ export const TotpInputForm = ({
>
)}
+ {environments.length > 1 && (
+ (
+
+ option.name}
+ getOptionValue={(option) => option.slug}
+ menuPlacement="top"
+ />
+
+ )}
+ />
+ )}
From e39d1a053081e98b5eaeede8abb40d517de9f5b9 Mon Sep 17 00:00:00 2001
From: carlosmonastyrski
Date: Fri, 14 Mar 2025 12:26:20 -0300
Subject: [PATCH 02/18] Fix call of onCompleted after all promises are resolved
---
.../secret-manager/OverviewPage/OverviewPage.tsx | 2 +-
.../components/ActionBar/ActionBar.tsx | 2 +-
.../AwsElastiCacheInputForm.tsx | 8 ++++++--
.../CreateDynamicSecretForm/AwsIamInputForm.tsx | 12 ++++++++----
.../AzureEntraIdInputForm.tsx | 14 +++++++++-----
.../CreateDynamicSecretForm/CassandraInputForm.tsx | 12 ++++++++----
.../ElasticSearchInputForm.tsx | 12 ++++++++----
.../CreateDynamicSecretForm/LdapInputForm.tsx | 12 ++++++++----
.../MongoAtlasInputForm.tsx | 12 ++++++++----
.../CreateDynamicSecretForm/MongoDBInputForm.tsx | 12 ++++++++----
.../CreateDynamicSecretForm/RabbitMqInputForm.tsx | 12 ++++++++----
.../CreateDynamicSecretForm/RedisInputForm.tsx | 12 ++++++++----
.../CreateDynamicSecretForm/SapAseInputForm.tsx | 12 ++++++++----
.../CreateDynamicSecretForm/SapHanaInputForm.tsx | 12 ++++++++----
.../CreateDynamicSecretForm/SnowflakeInputForm.tsx | 10 +++++++---
.../SqlDatabaseInputForm.tsx | 8 ++++++--
.../CreateDynamicSecretForm/TotpInputForm.tsx | 10 +++++++---
17 files changed, 117 insertions(+), 57 deletions(-)
diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
index a611eb030..12e0100b6 100644
--- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
+++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
@@ -866,7 +866,7 @@ export const OverviewPage = () => {
}
onClick={() => {
- if (subscription && subscription.dynamicSecret) {
+ if (subscription?.dynamicSecret) {
handlePopUpOpen("addDynamicSecret");
return;
}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
index 28c58ec5f..0cf131ba8 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
@@ -655,7 +655,7 @@ export const ActionBar = ({
isOpen={popUp.addDynamicSecret.isOpen}
onToggle={(isOpen) => handlePopUpToggle("addDynamicSecret", isOpen)}
projectSlug={projectSlug}
- environments={[{ slug: environment, name: "not-used", id: "not-used" }]}
+ environments={[{ slug: environment, name: environment, id: "not-used" }]}
secretPath={secretPath}
/>
{
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
+ let hasErrors = false;
const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
@@ -117,15 +118,18 @@ export const AwsElastiCacheInputForm = ({
projectSlug,
environmentSlug: env.slug
});
- onCompleted();
} catch {
+ hasErrors = true;
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
index f0590a4e1..7b9996075 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
@@ -83,7 +83,8 @@ export const AwsIamInputForm = ({
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.AwsIam, inputs: provider },
@@ -92,17 +93,20 @@ export const AwsIamInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch {
+ hasErrors = true;
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
index 4ab20c064..80ce5b361 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
@@ -116,8 +116,9 @@ export const AzureEntraIdInputForm = ({
environments: selectedEnvs
}: TForm) => {
// wait till previous request is finished
- const promises = selectedEnvs.map(async (environment) => {
- if (createDynamicSecret.isPending) return;
+ if (createDynamicSecret.isPending) return;
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
selectedUsers.map(async (user: { id: string; name: string; email: string }) => {
await createDynamicSecret.mutateAsync({
@@ -136,18 +137,21 @@ export const AzureEntraIdInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
});
- onCompleted();
} catch {
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx
index f86a4728b..96c7958ee 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx
@@ -106,7 +106,8 @@ export const CassandraInputForm = ({
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.Cassandra, inputs: provider },
@@ -115,17 +116,20 @@ export const CassandraInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch {
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
index 828f98110..fbdd7ec75 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
@@ -126,7 +126,8 @@ export const ElasticSearchInputForm = ({
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider },
@@ -135,17 +136,20 @@ export const ElasticSearchInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch {
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
const selectedAuthType = watch("provider.auth.type");
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
index 361067d1b..88a9a41b6 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
@@ -135,7 +135,8 @@ export const LdapInputForm = ({
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.Ldap, inputs: provider },
@@ -144,17 +145,20 @@ export const LdapInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch {
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
index a6725b652..e015f7f99 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
@@ -137,7 +137,8 @@ export const MongoAtlasInputForm = ({
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider },
@@ -146,17 +147,20 @@ export const MongoAtlasInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch {
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
index 0a28a6913..729c6934d 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
@@ -107,7 +107,8 @@ export const MongoDBDatabaseInputForm = ({
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: {
@@ -123,17 +124,20 @@ export const MongoDBDatabaseInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch {
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
index 0a6b84d41..7d8265662 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
@@ -115,7 +115,8 @@ export const RabbitMqInputForm = ({
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider },
@@ -124,17 +125,20 @@ export const RabbitMqInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch {
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
const selectedTags = watch("provider.tags");
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
index 35f165056..f19095cb1 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
@@ -100,7 +100,8 @@ export const RedisInputForm = ({
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.Redis, inputs: provider },
@@ -109,17 +110,20 @@ export const RedisInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch {
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
index de32565da..1fd2f63c0 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
@@ -101,7 +101,8 @@ sp_droplogin '{{username}}';`
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.SapAse, inputs: provider },
@@ -110,17 +111,20 @@ sp_droplogin '{{username}}';`
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch {
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
index d31464665..ab86c67a2 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
@@ -101,7 +101,8 @@ DROP USER {{username}};`,
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.SapHana, inputs: provider },
@@ -110,17 +111,20 @@ DROP USER {{username}};`,
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch {
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret in environment ${env.name}`
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
index c5077f2ad..7c12e23f6 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
@@ -104,7 +104,8 @@ export const SnowflakeInputForm = ({
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.Snowflake, inputs: provider },
@@ -113,17 +114,20 @@ export const SnowflakeInputForm = ({
path: secretPath,
defaultTTL,
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch (err) {
createNotification({
type: "error",
text: err instanceof Error ? err.message : "Failed to create dynamic secret"
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
index 416d9c35b..9632f4063 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
@@ -194,6 +194,7 @@ export const SqlDatabaseInputForm = ({
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
+ let hasErrors = false;
const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
@@ -205,15 +206,18 @@ export const SqlDatabaseInputForm = ({
projectSlug,
environmentSlug: env.slug
});
- onCompleted();
} catch {
createNotification({
type: "error",
- text: "Failed to create dynamic secret"
+ text: `Failed to create dynamic secret for environment ${env.name}`
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
const handleDatabaseChange = (type: SqlProviders) => {
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
index 4ff83a741..a774c6c78 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
@@ -107,7 +107,8 @@ export const TotpInputForm = ({
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- const promises = selectedEnvs.map(async (environment) => {
+ let hasErrors = false;
+ const promises = selectedEnvs.map(async (env) => {
try {
await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.Totp, inputs: provider },
@@ -116,17 +117,20 @@ export const TotpInputForm = ({
path: secretPath,
defaultTTL: "1m",
projectSlug,
- environmentSlug: environment.slug
+ environmentSlug: env.slug
});
- onCompleted();
} catch (err) {
createNotification({
type: "error",
text: err instanceof Error ? err.message : "Failed to create dynamic secret"
});
+ hasErrors = true;
}
});
await Promise.all(promises);
+ if (hasErrors) {
+ onCompleted();
+ }
};
return (
From 94d7d2b0293a9d4aa6b12ceacdea98c8ab352208 Mon Sep 17 00:00:00 2001
From: carlosmonastyrski
Date: Fri, 14 Mar 2025 12:44:49 -0300
Subject: [PATCH 03/18] Fix call of onCompleted after all promises are resolved
---
.../CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx | 2 +-
.../CreateDynamicSecretForm/ElasticSearchInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx | 2 +-
.../ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx | 2 +-
15 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx
index 008c24165..ae3ddac05 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx
@@ -127,7 +127,7 @@ export const AwsElastiCacheInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
index 7b9996075..1f2e5b0a8 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
@@ -104,7 +104,7 @@ export const AwsIamInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
index 80ce5b361..f681a75a9 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
@@ -149,7 +149,7 @@ export const AzureEntraIdInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx
index 96c7958ee..a770d67b2 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx
@@ -127,7 +127,7 @@ export const CassandraInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
index fbdd7ec75..0fddf332c 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
@@ -147,7 +147,7 @@ export const ElasticSearchInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
index 88a9a41b6..cfa9fd45f 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
@@ -156,7 +156,7 @@ export const LdapInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
index e015f7f99..53c716200 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
@@ -158,7 +158,7 @@ export const MongoAtlasInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
index 729c6934d..46597291a 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
@@ -135,7 +135,7 @@ export const MongoDBDatabaseInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
index 7d8265662..ffe2164e8 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
@@ -136,7 +136,7 @@ export const RabbitMqInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
index f19095cb1..82a5f2bdf 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
@@ -121,7 +121,7 @@ export const RedisInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
index 1fd2f63c0..126b95712 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
@@ -122,7 +122,7 @@ sp_droplogin '{{username}}';`
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
index ab86c67a2..d4d45ea75 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
@@ -122,7 +122,7 @@ DROP USER {{username}};`,
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
index 7c12e23f6..d7e55c183 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
@@ -125,7 +125,7 @@ export const SnowflakeInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
index 9632f4063..d80d92c4a 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
@@ -215,7 +215,7 @@ export const SqlDatabaseInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
index a774c6c78..4609d2604 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
@@ -128,7 +128,7 @@ export const TotpInputForm = ({
}
});
await Promise.all(promises);
- if (hasErrors) {
+ if (!hasErrors) {
onCompleted();
}
};
From 1da5a5f41788c765d0b83695dfaaf3c9450a3353 Mon Sep 17 00:00:00 2001
From: =
Date: Fri, 14 Mar 2025 22:09:08 +0530
Subject: [PATCH 04/18] feat: completed backend code for oidc permission inject
---
.../src/@types/fastify-request-context.d.ts | 7 ---
backend/src/@types/fastify.d.ts | 6 +++
...50314145202_identity-oidc-claim-mapping.ts | 21 +++++++++
backend/src/db/schemas/identity-oidc-auths.ts | 3 +-
backend/src/db/schemas/secret-sharing.ts | 4 +-
.../ee/services/audit-log/audit-log-types.ts | 2 +
.../services/permission/permission-service.ts | 20 +++++----
backend/src/lib/api-docs/constants.ts | 2 +
backend/src/lib/template/dot-access.ts | 34 +++++++++++++++
.../server/plugins/auth/inject-identity.ts | 5 +++
.../routes/v1/identity-oidc-auth-router.ts | 5 +++
.../identity-access-token-types.ts | 5 +++
.../identity-jwt-auth-service.ts | 4 +-
.../identity-oidc-auth-service.ts | 43 ++++++++++++++++---
.../identity-oidc-auth-types.ts | 2 +
backend/src/services/identity/identity-dal.ts | 2 +-
.../super-admin/super-admin-service.ts | 2 +-
17 files changed, 139 insertions(+), 28 deletions(-)
delete mode 100644 backend/src/@types/fastify-request-context.d.ts
create mode 100644 backend/src/db/migrations/20250314145202_identity-oidc-claim-mapping.ts
create mode 100644 backend/src/lib/template/dot-access.ts
diff --git a/backend/src/@types/fastify-request-context.d.ts b/backend/src/@types/fastify-request-context.d.ts
deleted file mode 100644
index fc8d94e07..000000000
--- a/backend/src/@types/fastify-request-context.d.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import "@fastify/request-context";
-
-declare module "@fastify/request-context" {
- interface RequestContextData {
- reqId: string;
- }
-}
diff --git a/backend/src/@types/fastify.d.ts b/backend/src/@types/fastify.d.ts
index 85a19b676..99c7c4184 100644
--- a/backend/src/@types/fastify.d.ts
+++ b/backend/src/@types/fastify.d.ts
@@ -100,6 +100,12 @@ import { TWorkflowIntegrationServiceFactory } from "@app/services/workflow-integ
declare module "@fastify/request-context" {
interface RequestContextData {
reqId: string;
+ identityAuthInfo?: {
+ identityId: string;
+ oidc?: {
+ claims: Record;
+ };
+ };
}
}
diff --git a/backend/src/db/migrations/20250314145202_identity-oidc-claim-mapping.ts b/backend/src/db/migrations/20250314145202_identity-oidc-claim-mapping.ts
new file mode 100644
index 000000000..482c5c4eb
--- /dev/null
+++ b/backend/src/db/migrations/20250314145202_identity-oidc-claim-mapping.ts
@@ -0,0 +1,21 @@
+import { Knex } from "knex";
+
+import { TableName } from "../schemas";
+
+export async function up(knex: Knex): Promise {
+ const hasMappingField = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "claimMetadataMapping");
+ if (!hasMappingField) {
+ await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => {
+ t.jsonb("claimMetadataMapping");
+ });
+ }
+}
+
+export async function down(knex: Knex): Promise {
+ const hasMappingField = await knex.schema.hasColumn(TableName.IdentityOidcAuth, "claimMetadataMapping");
+ if (hasMappingField) {
+ await knex.schema.alterTable(TableName.IdentityOidcAuth, (t) => {
+ t.dropColumn("claimMetadataMapping");
+ });
+ }
+}
diff --git a/backend/src/db/schemas/identity-oidc-auths.ts b/backend/src/db/schemas/identity-oidc-auths.ts
index ebde5e7dc..03bfcf40a 100644
--- a/backend/src/db/schemas/identity-oidc-auths.ts
+++ b/backend/src/db/schemas/identity-oidc-auths.ts
@@ -26,7 +26,8 @@ export const IdentityOidcAuthsSchema = z.object({
boundSubject: z.string().nullable().optional(),
createdAt: z.date(),
updatedAt: z.date(),
- encryptedCaCertificate: zodBuffer.nullable().optional()
+ encryptedCaCertificate: zodBuffer.nullable().optional(),
+ claimMetadataMapping: z.unknown().nullable().optional()
});
export type TIdentityOidcAuths = z.infer;
diff --git a/backend/src/db/schemas/secret-sharing.ts b/backend/src/db/schemas/secret-sharing.ts
index 3406b4d63..24ea26677 100644
--- a/backend/src/db/schemas/secret-sharing.ts
+++ b/backend/src/db/schemas/secret-sharing.ts
@@ -12,7 +12,6 @@ import { TImmutableDBKeys } from "./models";
export const SecretSharingSchema = z.object({
id: z.string().uuid(),
encryptedValue: z.string().nullable().optional(),
- type: z.string(),
iv: z.string().nullable().optional(),
tag: z.string().nullable().optional(),
hashedHex: z.string().nullable().optional(),
@@ -27,7 +26,8 @@ export const SecretSharingSchema = z.object({
lastViewedAt: z.date().nullable().optional(),
password: z.string().nullable().optional(),
encryptedSecret: zodBuffer.nullable().optional(),
- identifier: z.string().nullable().optional()
+ identifier: z.string().nullable().optional(),
+ type: z.string().default("share")
});
export type TSecretSharing = z.infer;
diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts
index a8f96bb73..9244e569e 100644
--- a/backend/src/ee/services/audit-log/audit-log-types.ts
+++ b/backend/src/ee/services/audit-log/audit-log-types.ts
@@ -978,6 +978,7 @@ interface AddIdentityOidcAuthEvent {
boundIssuer: string;
boundAudiences: string;
boundClaims: Record;
+ claimMetadataMapping: Record;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
@@ -1002,6 +1003,7 @@ interface UpdateIdentityOidcAuthEvent {
boundIssuer?: string;
boundAudiences?: string;
boundClaims?: Record;
+ claimMetadataMapping?: Record;
boundSubject?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts
index ddaf55d9d..6b36234ca 100644
--- a/backend/src/ee/services/permission/permission-service.ts
+++ b/backend/src/ee/services/permission/permission-service.ts
@@ -1,5 +1,6 @@
import { createMongoAbility, MongoAbility, RawRuleOf } from "@casl/ability";
import { PackRule, unpackRules } from "@casl/ability/extra";
+import { requestContext } from "@fastify/request-context";
import { MongoQuery } from "@ucast/mongo2js";
import handlebars from "handlebars";
@@ -317,14 +318,17 @@ export const permissionServiceFactory = ({
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
- const metadataKeyValuePair = escapeHandlebarsMissingMetadata(
- objectify(
- identityProjectPermission.metadata,
- (i) => i.key,
- (i) => i.value
- )
- );
-
+ const identityAuthInfo = requestContext.get("identityAuthInfo");
+ const unescapedMetadata = objectify(
+ identityProjectPermission.metadata,
+ (i) => i.key,
+ (i) => i.value
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ ) as Record;
+ if (identityAuthInfo?.identityId === identityId && identityAuthInfo) {
+ unescapedMetadata.auth = identityAuthInfo;
+ }
+ const metadataKeyValuePair = escapeHandlebarsMissingMetadata(unescapedMetadata);
const interpolateRules = templatedRules(
{
identity: {
diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts
index fe893d5ab..b1f55c4d3 100644
--- a/backend/src/lib/api-docs/constants.ts
+++ b/backend/src/lib/api-docs/constants.ts
@@ -329,6 +329,7 @@ export const OIDC_AUTH = {
boundIssuer: "The unique identifier of the identity provider issuing the JWT.",
boundAudiences: "The list of intended recipients.",
boundClaims: "The attributes that should be present in the JWT for it to be valid.",
+ claimMetadataMapping: "The attributes that should be present in the permission metadata from the JWT.",
boundSubject: "The expected principal that is the subject of the JWT.",
accessTokenTrustedIps: "The IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The lifetime for an access token in seconds.",
@@ -342,6 +343,7 @@ export const OIDC_AUTH = {
boundIssuer: "The new unique identifier of the identity provider issuing the JWT.",
boundAudiences: "The new list of intended recipients.",
boundClaims: "The new attributes that should be present in the JWT for it to be valid.",
+ claimMetadataMapping: "The new attributes that should be present in the permission metadata from the JWT.",
boundSubject: "The new expected principal that is the subject of the JWT.",
accessTokenTrustedIps: "The new IPs or CIDR ranges that access tokens can be used from.",
accessTokenTTL: "The new lifetime for an access token in seconds.",
diff --git a/backend/src/lib/template/dot-access.ts b/backend/src/lib/template/dot-access.ts
new file mode 100644
index 000000000..ec3208feb
--- /dev/null
+++ b/backend/src/lib/template/dot-access.ts
@@ -0,0 +1,34 @@
+/**
+ * Safely retrieves a value from a nested object using dot notation path
+ */
+export const getStringValueByDot = (
+ obj: Record | null | undefined,
+ path: string,
+ defaultValue?: string
+): string | undefined => {
+ // Handle null or undefined input
+ if (!obj) {
+ return defaultValue;
+ }
+
+ const parts = path.split(".");
+ let current: unknown = obj;
+
+ for (const part of parts) {
+ const isObject = typeof current === "object" && !Array.isArray(current) && current !== null;
+ if (!isObject) {
+ return defaultValue;
+ }
+ if (!Object.hasOwn(current as object, part)) {
+ // Check if the property exists as an own property
+ return defaultValue;
+ }
+ current = (current as Record)[part];
+ }
+
+ if (typeof current !== "string") {
+ return defaultValue;
+ }
+
+ return current;
+};
diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts
index 9d239a405..9005fbf96 100644
--- a/backend/src/server/plugins/auth/inject-identity.ts
+++ b/backend/src/server/plugins/auth/inject-identity.ts
@@ -1,3 +1,4 @@
+import { requestContext } from "@fastify/request-context";
import { FastifyRequest } from "fastify";
import fp from "fastify-plugin";
import jwt, { JwtPayload } from "jsonwebtoken";
@@ -137,6 +138,10 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
identityName: identity.name,
authMethod: null
};
+ requestContext.set("identityAuthInfo", {
+ identityId: identity.identityId,
+ oidc: token?.identityAuth?.oidc
+ });
break;
}
case AuthMode.SERVICE_TOKEN: {
diff --git a/backend/src/server/routes/v1/identity-oidc-auth-router.ts b/backend/src/server/routes/v1/identity-oidc-auth-router.ts
index 7ce0b05b7..a27c12daf 100644
--- a/backend/src/server/routes/v1/identity-oidc-auth-router.ts
+++ b/backend/src/server/routes/v1/identity-oidc-auth-router.ts
@@ -23,6 +23,7 @@ const IdentityOidcAuthResponseSchema = IdentityOidcAuthsSchema.pick({
boundIssuer: true,
boundAudiences: true,
boundClaims: true,
+ claimMetadataMapping: true,
boundSubject: true,
createdAt: true,
updatedAt: true
@@ -104,6 +105,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: z.string().min(1).describe(OIDC_AUTH.ATTACH.boundIssuer),
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.ATTACH.boundAudiences),
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.boundClaims),
+ claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.claimMetadataMapping),
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.ATTACH.boundSubject),
accessTokenTrustedIps: z
.object({
@@ -161,6 +163,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: identityOidcAuth.boundIssuer,
boundAudiences: identityOidcAuth.boundAudiences,
boundClaims: identityOidcAuth.boundClaims as Record,
+ claimMetadataMapping: identityOidcAuth.claimMetadataMapping as Record,
boundSubject: identityOidcAuth.boundSubject as string,
accessTokenTTL: identityOidcAuth.accessTokenTTL,
accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL,
@@ -200,6 +203,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: z.string().min(1).describe(OIDC_AUTH.UPDATE.boundIssuer),
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.UPDATE.boundAudiences),
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.boundClaims),
+ claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.claimMetadataMapping),
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.UPDATE.boundSubject),
accessTokenTrustedIps: z
.object({
@@ -258,6 +262,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: identityOidcAuth.boundIssuer,
boundAudiences: identityOidcAuth.boundAudiences,
boundClaims: identityOidcAuth.boundClaims as Record,
+ claimMetadataMapping: identityOidcAuth.claimMetadataMapping as Record,
boundSubject: identityOidcAuth.boundSubject as string,
accessTokenTTL: identityOidcAuth.accessTokenTTL,
accessTokenMaxTTL: identityOidcAuth.accessTokenMaxTTL,
diff --git a/backend/src/services/identity-access-token/identity-access-token-types.ts b/backend/src/services/identity-access-token/identity-access-token-types.ts
index 86967df76..c97d2f40a 100644
--- a/backend/src/services/identity-access-token/identity-access-token-types.ts
+++ b/backend/src/services/identity-access-token/identity-access-token-types.ts
@@ -7,4 +7,9 @@ export type TIdentityAccessTokenJwtPayload = {
clientSecretId: string;
identityAccessTokenId: string;
authTokenType: string;
+ identityAuth: {
+ oidc?: {
+ claims: Record;
+ };
+ };
};
diff --git a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts
index 555dc00a6..9844b337f 100644
--- a/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts
+++ b/backend/src/services/identity-jwt-auth/identity-jwt-auth-service.ts
@@ -11,6 +11,7 @@ import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
+import { getStringValueByDot } from "@app/lib/template/dot-access";
import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
@@ -177,8 +178,9 @@ export const identityJwtAuthServiceFactory = ({
if (identityJwtAuth.boundClaims) {
Object.keys(identityJwtAuth.boundClaims).forEach((claimKey) => {
const claimValue = (identityJwtAuth.boundClaims as Record)[claimKey];
+ const value = getStringValueByDot(tokenData, claimKey) || "";
- if (!tokenData[claimKey]) {
+ if (!value) {
throw new UnauthorizedError({
message: `Access denied: token has no ${claimKey} field`
});
diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts
index 3c947f504..cb641af4d 100644
--- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts
+++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-service.ts
@@ -12,6 +12,7 @@ import { validatePermissionBoundary } from "@app/lib/casl/boundary";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError, ForbiddenRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
import { extractIPDetails, isValidIpOrCidr } from "@app/lib/ip";
+import { getStringValueByDot } from "@app/lib/template/dot-access";
import { ActorType, AuthTokenType } from "../auth/auth-type";
import { TIdentityOrgDALFactory } from "../identity/identity-org-dal";
@@ -77,7 +78,7 @@ export const identityOidcAuthServiceFactory = ({
const { data: discoveryDoc } = await axios.get<{ jwks_uri: string }>(
`${identityOidcAuth.oidcDiscoveryUrl}/.well-known/openid-configuration`,
{
- httpsAgent: requestAgent
+ httpsAgent: identityOidcAuth.oidcDiscoveryUrl.includes("https") ? requestAgent : undefined
}
);
const jwksUri = discoveryDoc.jwks_uri;
@@ -91,7 +92,7 @@ export const identityOidcAuthServiceFactory = ({
const client = new JwksClient({
jwksUri,
- requestAgent
+ requestAgent: identityOidcAuth.oidcDiscoveryUrl.includes("https") ? requestAgent : undefined
});
const { kid } = decodedToken.header;
@@ -108,7 +109,6 @@ export const identityOidcAuthServiceFactory = ({
message: `Access denied: ${error.message}`
});
}
-
throw error;
}
@@ -135,10 +135,16 @@ export const identityOidcAuthServiceFactory = ({
if (identityOidcAuth.boundClaims) {
Object.keys(identityOidcAuth.boundClaims).forEach((claimKey) => {
const claimValue = (identityOidcAuth.boundClaims as Record)[claimKey];
+ const value = getStringValueByDot(tokenData, claimKey) || "";
+
+ if (!value) {
+ throw new UnauthorizedError({
+ message: `Access denied: token has no ${claimKey} field`
+ });
+ }
+
// handle both single and multi-valued claims
- if (
- !claimValue.split(", ").some((claimEntry) => doesFieldValueMatchOidcPolicy(tokenData[claimKey], claimEntry))
- ) {
+ if (!claimValue.split(", ").some((claimEntry) => doesFieldValueMatchOidcPolicy(value, claimEntry))) {
throw new UnauthorizedError({
message: "Access denied: OIDC claim not allowed."
});
@@ -146,6 +152,20 @@ export const identityOidcAuthServiceFactory = ({
});
}
+ const filteredClaims: Record = {};
+ if (identityOidcAuth.claimMetadataMapping) {
+ Object.keys(identityOidcAuth.claimMetadataMapping).forEach((permissionKey) => {
+ const claimKey = (identityOidcAuth.claimMetadataMapping as Record)[permissionKey];
+ const value = getStringValueByDot(tokenData, claimKey) || "";
+ if (!value) {
+ throw new UnauthorizedError({
+ message: `Access denied: token has no ${claimKey} field`
+ });
+ }
+ filteredClaims[permissionKey] = value;
+ });
+ }
+
const identityAccessToken = await identityOidcAuthDAL.transaction(async (tx) => {
const newToken = await identityAccessTokenDAL.create(
{
@@ -167,7 +187,12 @@ export const identityOidcAuthServiceFactory = ({
{
identityId: identityOidcAuth.identityId,
identityAccessTokenId: identityAccessToken.id,
- authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN
+ authTokenType: AuthTokenType.IDENTITY_ACCESS_TOKEN,
+ identityAuth: {
+ oidc: {
+ claims: filteredClaims
+ }
+ }
} as TIdentityAccessTokenJwtPayload,
appCfg.AUTH_SECRET,
// akhilmhdh: for non-expiry tokens you should not even set the value, including undefined. Even for undefined jsonwebtoken throws error
@@ -188,6 +213,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject,
accessTokenTTL,
accessTokenMaxTTL,
@@ -254,6 +280,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject,
accessTokenMaxTTL,
accessTokenTTL,
@@ -274,6 +301,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject,
accessTokenTTL,
accessTokenMaxTTL,
@@ -335,6 +363,7 @@ export const identityOidcAuthServiceFactory = ({
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject,
accessTokenMaxTTL,
accessTokenTTL,
diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts
index 761f68aa7..7f9f62296 100644
--- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts
+++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts
@@ -7,6 +7,7 @@ export type TAttachOidcAuthDTO = {
boundIssuer: string;
boundAudiences: string;
boundClaims: Record;
+ claimMetadataMapping: Record;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
@@ -21,6 +22,7 @@ export type TUpdateOidcAuthDTO = {
boundIssuer?: string;
boundAudiences?: string;
boundClaims?: Record;
+ claimMetadataMapping?: Record;
boundSubject?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
diff --git a/backend/src/services/identity/identity-dal.ts b/backend/src/services/identity/identity-dal.ts
index 8b7fccab3..c4d0b6307 100644
--- a/backend/src/services/identity/identity-dal.ts
+++ b/backend/src/services/identity/identity-dal.ts
@@ -1,7 +1,7 @@
import { TDbClient } from "@app/db";
import { TableName, TIdentities } from "@app/db/schemas";
-import { ormify, selectAllTableCols } from "@app/lib/knex";
import { DatabaseError } from "@app/lib/errors";
+import { ormify, selectAllTableCols } from "@app/lib/knex";
export type TIdentityDALFactory = ReturnType;
diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts
index bf10d67ab..1f343b75f 100644
--- a/backend/src/services/super-admin/super-admin-service.ts
+++ b/backend/src/services/super-admin/super-admin-service.ts
@@ -7,6 +7,7 @@ import { getConfig } from "@app/lib/config/env";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { getUserPrivateKey } from "@app/lib/crypto/srp";
import { BadRequestError, NotFoundError } from "@app/lib/errors";
+import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
import { TAuthLoginFactory } from "../auth/auth-login-service";
import { AuthMethod } from "../auth/auth-type";
@@ -20,7 +21,6 @@ import { TUserAliasDALFactory } from "../user-alias/user-alias-dal";
import { UserAliasType } from "../user-alias/user-alias-types";
import { TSuperAdminDALFactory } from "./super-admin-dal";
import { LoginMethod, TAdminGetIdentitiesDTO, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types";
-import { TIdentityDALFactory } from "@app/services/identity/identity-dal";
type TSuperAdminServiceFactoryDep = {
identityDAL: Pick;
From 0f31fa3128cd6d8a408bd0faf45def7e9fe8e9c9 Mon Sep 17 00:00:00 2001
From: =
Date: Fri, 14 Mar 2025 22:10:23 +0530
Subject: [PATCH 05/18] feat: updated form for oidc auth
---
frontend/src/hooks/api/admin/queries.ts | 2 +-
.../src/hooks/api/identities/mutations.tsx | 8 +-
frontend/src/hooks/api/identities/types.ts | 3 +
.../pages/admin/OverviewPage/OverviewPage.tsx | 2 +-
.../IdentitySection/IdentityOidcAuthForm.tsx | 104 ++++++++++++++++++
.../ViewIdentityOidcAuthContent.tsx | 20 ++++
.../components/SecretListView/SecretItem.tsx | 5 +-
7 files changed, 139 insertions(+), 5 deletions(-)
diff --git a/frontend/src/hooks/api/admin/queries.ts b/frontend/src/hooks/api/admin/queries.ts
index 496990abe..b24841dbd 100644
--- a/frontend/src/hooks/api/admin/queries.ts
+++ b/frontend/src/hooks/api/admin/queries.ts
@@ -1,6 +1,7 @@
import { useInfiniteQuery, useQuery, UseQueryOptions } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
+import { Identity } from "@app/hooks/api/identities/types";
import { User } from "../types";
import {
@@ -10,7 +11,6 @@ import {
TGetServerRootKmsEncryptionDetails,
TServerConfig
} from "./types";
-import { Identity } from "@app/hooks/api/identities/types";
export const adminStandaloneKeys = {
getUsers: "get-users",
diff --git a/frontend/src/hooks/api/identities/mutations.tsx b/frontend/src/hooks/api/identities/mutations.tsx
index 3e38d9067..d68595ad5 100644
--- a/frontend/src/hooks/api/identities/mutations.tsx
+++ b/frontend/src/hooks/api/identities/mutations.tsx
@@ -462,6 +462,7 @@ export const useUpdateIdentityOidcAuth = () => {
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject
}) => {
const {
@@ -478,7 +479,8 @@ export const useUpdateIdentityOidcAuth = () => {
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
- accessTokenTrustedIps
+ accessTokenTrustedIps,
+ claimMetadataMapping
}
);
@@ -504,6 +506,7 @@ export const useAddIdentityOidcAuth = () => {
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject,
accessTokenTTL,
accessTokenMaxTTL,
@@ -524,7 +527,8 @@ export const useAddIdentityOidcAuth = () => {
accessTokenTTL,
accessTokenMaxTTL,
accessTokenNumUsesLimit,
- accessTokenTrustedIps
+ accessTokenTrustedIps,
+ claimMetadataMapping
}
);
diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts
index 8d344e7f7..9920fcf0b 100644
--- a/frontend/src/hooks/api/identities/types.ts
+++ b/frontend/src/hooks/api/identities/types.ts
@@ -193,6 +193,7 @@ export type IdentityOidcAuth = {
boundIssuer: string;
boundAudiences: string;
boundClaims: Record;
+ claimMetadataMapping: Record;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
@@ -208,6 +209,7 @@ export type AddIdentityOidcAuthDTO = {
boundIssuer: string;
boundAudiences: string;
boundClaims: Record;
+ claimMetadataMapping: Record;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
@@ -225,6 +227,7 @@ export type UpdateIdentityOidcAuthDTO = {
boundIssuer?: string;
boundAudiences?: string;
boundClaims?: Record;
+ claimMetadataMapping?: Record;
boundSubject?: string;
accessTokenTTL?: number;
accessTokenMaxTTL?: number;
diff --git a/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx b/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx
index 776c5e20f..8d1e25bc5 100644
--- a/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx
+++ b/frontend/src/pages/admin/OverviewPage/OverviewPage.tsx
@@ -28,13 +28,13 @@ import {
useGetServerRootKmsEncryptionDetails,
useUpdateServerConfig
} from "@app/hooks/api";
+import { IdentityPanel } from "@app/pages/admin/OverviewPage/components/IdentityPanel";
import { AuthPanel } from "./components/AuthPanel";
import { EncryptionPanel } from "./components/EncryptionPanel";
import { IntegrationPanel } from "./components/IntegrationPanel";
import { RateLimitPanel } from "./components/RateLimitPanel";
import { UserPanel } from "./components/UserPanel";
-import { IdentityPanel } from "@app/pages/admin/OverviewPage/components/IdentityPanel";
enum TabSections {
Settings = "settings",
diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx
index b1b0739cf..094be734e 100644
--- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx
+++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx
@@ -52,6 +52,12 @@ const schema = z.object({
value: z.string()
})
),
+ claimMetadataMapping: z.array(
+ z.object({
+ key: z.string(),
+ value: z.string()
+ })
+ ),
boundSubject: z.string().optional().default("")
});
@@ -109,6 +115,15 @@ export const IdentityOidcAuthForm = ({
name: "boundClaims"
});
+ const {
+ fields: claimMetadataMappingFields,
+ append: appendClaimMetadataMappingField,
+ remove: removeClaimMetadataMappingField
+ } = useFieldArray({
+ control,
+ name: "claimMetadataMapping"
+ });
+
const {
fields: accessTokenTrustedIpsFields,
append: appendAccessTokenTrustedIp,
@@ -126,6 +141,10 @@ export const IdentityOidcAuthForm = ({
key,
value
})),
+ claimMetadataMapping: Object.entries(data.claimMetadataMapping).map(([key, value]) => ({
+ key,
+ value
+ })),
boundSubject: data.boundSubject,
accessTokenTTL: String(data.accessTokenTTL),
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
@@ -164,6 +183,7 @@ export const IdentityOidcAuthForm = ({
boundIssuer,
boundAudiences,
boundClaims,
+ claimMetadataMapping,
boundSubject
}: FormData) => {
try {
@@ -180,6 +200,9 @@ export const IdentityOidcAuthForm = ({
boundIssuer,
boundAudiences,
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
+ claimMetadataMapping: Object.fromEntries(
+ claimMetadataMapping.map((entry) => [entry.key, entry.value])
+ ),
boundSubject,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
@@ -194,6 +217,9 @@ export const IdentityOidcAuthForm = ({
boundIssuer,
boundAudiences,
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
+ claimMetadataMapping: Object.fromEntries(
+ claimMetadataMapping.map((entry) => [entry.key, entry.value])
+ ),
boundSubject,
organizationId: orgId,
accessTokenTTL: Number(accessTokenTTL),
@@ -449,6 +475,84 @@ export const IdentityOidcAuthForm = ({
Add Claims
+ {claimMetadataMappingFields.map(({ id }, index) => (
+
+ {
+ return (
+
+
+
+ ) : undefined
+ }
+ isError={Boolean(error)}
+ errorText={error?.message}
+ >
+ field.onChange(e)}
+ placeholder="property"
+ />
+
+ );
+ }}
+ />
+ {
+ return (
+
+ field.onChange(e)}
+ placeholder="key1.nested-key2"
+ />
+
+ );
+ }}
+ />
+ removeClaimMetadataMappingField(index)}
+ size="lg"
+ colorSchema="danger"
+ variant="plain"
+ ariaLabel="update"
+ className="p-3"
+ >
+
+
+
+ ))}
+
+
+
{accessTokenTrustedIpsFields.map(({ id }, index) => (
)}
+
+ {Object.keys(data.claimMetadataMapping).length && (
+
+ {JSON.stringify(data.claimMetadataMapping, null, 2)}
+
+ }
+ >
+
+
+
+ Reveal
+
+
+
+ )}
+
);
};
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx
index 629c23048..01d477097 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/SecretListView/SecretItem.tsx
@@ -306,7 +306,10 @@ export const SecretItem = memo(
)}
/>
)}
-
+
Date: Fri, 14 Mar 2025 22:10:48 +0530
Subject: [PATCH 06/18] feat: added a simple oidc server
---
sink/oidc-server/main.js | 87 ++
sink/oidc-server/package-lock.json | 1708 ++++++++++++++++++++++++++++
sink/oidc-server/package.json | 21 +
sink/oidc-server/test-infisical.js | 80 ++
4 files changed, 1896 insertions(+)
create mode 100644 sink/oidc-server/main.js
create mode 100644 sink/oidc-server/package-lock.json
create mode 100644 sink/oidc-server/package.json
create mode 100644 sink/oidc-server/test-infisical.js
diff --git a/sink/oidc-server/main.js b/sink/oidc-server/main.js
new file mode 100644
index 000000000..0d5f10922
--- /dev/null
+++ b/sink/oidc-server/main.js
@@ -0,0 +1,87 @@
+import Provider from "oidc-provider";
+import express from "express";
+
+const configuration = {
+ jwks: {
+ keys: [
+ {
+ kty: "RSA",
+ use: "sig",
+ alg: "RS256",
+ d: "EF2Kky61jzvMYQ_B6ImXzCsQ8uQzbFJrGnB2azlpr_CFStjjUVKP4EKrSCVEasD6SGNJV2QSiNJr7j05nvuGmHMKa__rbU8fqP4qbDahUgCgWOq-zS5tGK6Ifk4II_cZ_V1F-TnrvmcOKMWBiSV-p8i72KpXXucbHGNRwASVs7--M55wp_m1UsybI2jSQ4IgyvGzTnvMmQ_GsX-XoD8u0zGU_4eN3DGc8l6hdxxuSymH0fEeL1Aj0LoCj6teRGF37a2sBQdU6mkNNAuyyirkoDqGZCGJToQLqX4F1FafnzjeIgfdneRa-vuaV380Hhr2rorWnQyBqOO27M5O_VAkJbfRaWJVrXTJ69ZgkU4GPdeYdklVL0HkU6laziTNqNMeAjnt4m51sWokVyJpvdWcb_vJ4NSCsRo7kHOz7g-UvWTXa8UW0DTDliq_TJ3rN4Gv0vn9tBlFfaeuLPpK4VNmRRDRXY_fcuzlnQwYExL9a4V_vCyGmabdb7PrUFPBcjR5",
+ dp: "SX52TkZEc_eLIk5gYrKjAC643LJIw1RxMBWWewRSGLn_rbrH1he3hy7AGDUV6Uon7zkNh9R5GBVuxmlluBRAGbrhIXAAf8sWeyma3F6FIAt-MH_VkfW5K2p88PLOyVGljlv8-Z3wzdKYOlDP4yFU18LqGMqaRSDLDGhILkuZhjLYA40sfYJeJTi_HVP5UyWL4ohayqUWCT2W3DgeDDThYHmufOaqlrSLhUst6uez_cDz0BXAYIZvUuPVL_n1-_px",
+ dq: "K1KYU77I6yyPA2u32rc0exp_TCG59hhpWxrmXN8yTXWyq_xYBhCJA_nHdY8UV25Hmd7q0iX2i8y2cCAFNWA5UWiSiNg9-fKRLI2nz53IM4dGfssOLwUk66wzX8r_u3XiLZsO7XNNtQZdcZmF0YuNTtzEdiNDhaOyHiwwHgShL36WNmUn00mZR__G5Qk60VvI8vsbvJU9xRnWuEVS1wRgyD7v6Nl9nIxb8N7oibCdTJLmgnRXPWvArsW0cJ-NURfr",
+ e: "AQAB",
+ n: "2QwX-NBMkQYedGpbPvHL7Ca0isvfmLC7lSc8XSOCLmCUIf6Bk_pdCNx2kxsmT81IoA8CfvJLHQj5vWKoVDFMLfwo4IujvsC3m2IrEg6jERE-YHfC3W5jKZtmzQYpfx5vC2_XTmcyPigtyaNVsftGfycES3B_tvphNsFmQcJjVGOsJQXXqh_TDv6FMcH4m9pngyw6wfe3GgAKA0dRTSfD0h7wLdNCeuid53lLpkQypTNdZ6_PiCMu2gr_cH5M0MPZtBb2TW12_2zOabExK1lI5-HvdPtbMT4Qzs2nd2NkjcWmlbKRZzq6IzyWt7W2EnfZDsi61PHECtTb-EQN2icl8Wnsp-0Bw66yviAOj0gn3X5hRLx-TknT_PnWMou17l5GoAojKDezcTW0iLlrfs2ixFlY28u7WklUN8uYhHvwgON6fsdefG-3bPpiRLBPZ_tgXa4doALsCwfXu2oz0vYktk31A-UYv92uJsKSUbK0_8ODTN0rslCqCYN_1a_aVt2P",
+ p: "--L5BX8juLlGJk8hdPgEUmJjD7SsZuMrdq3cSibkkbaWUE5CQQ7vhLPr2dWCS1jUnY9WyoCx9QCZvhTHjORX50ykkOyBso9VJjWvYPjsrPpF7_Y6V0dKlblDmbbmRT9BW-MgjbwTivu3c2OpMXh2XLF-FOTq3t3Brs7SRnhTkD6GBDFf3X95J0PF7NELa9z2-kzPSDYz3k-9FepXnRPBM_ViDzlRw4eKUdylVuhzGbC2TRSmab9BRP0wipQKd-f5",
+ q: "3Jd5CRJpQV3xUi3FiHHAwcjfsRkfXMrxfaXt0PjX2xWzxscYiDcyCF6VhHTAGsiq5SOtCp3l5mg6A9PzdR53AzM2-706D82fMwiUZvsLOVTepXkgriP_xw7rDlkOeAvjB80sL2G9scFliTzzRZ8I8E79A8DxZihfB75AIN9ijklEihnwxfhp2EgO5MYEyQRcqU1TT8wD8ekLMzd-kJUWyTz3BogiVJH__BQoB6kaDyjvQoxBgwh0hi72t9H5XqPH",
+ qi: "cwK0jhzwbu8BaTmTQhwfGiqwNN3v9F4nUQ4dtnBYRI6zlki4cLb2Mf9-VhyEsUYhhdTm8R7RwO9m5Xct3gEfozdk35wuvkVwkZgL3Uho5asao0xi4aENeUk5DCkU-paO3yLSDhIs9YYuYIDjUX6QuMCPjomypuE3SRm-Dg1PGOxYvX3w_P-0kd5iBFrm4jwGTZViFOr8tl_dXgDRDWDgofOYOYcmUv2_0zt1aO3j5dhEpwdkyuDMLfVZNpJQyopJ",
+ kid: "f262a3214213d194c92991d6735b153b",
+ },
+ ],
+ },
+ features: {
+ clientCredentials: {
+ enabled: true,
+ },
+ introspection: {
+ enabled: true,
+ },
+ resourceIndicators: {
+ enabled: true,
+ getResourceServerInfo(ctx, resourceIndicator) {
+ if (resourceIndicator === "urn:api") {
+ return {
+ scope: "read",
+ audience: "urn:api",
+ accessTokenTTL: 1 * 60 * 60, // 1 hour
+ accessTokenFormat: "jwt",
+ };
+ }
+
+ throw new errors.InvalidTarget();
+ },
+ },
+ },
+ clients: [
+ {
+ client_id: "app",
+ client_secret: "a_secret",
+ grant_types: ["client_credentials"],
+ redirect_uris: [],
+ response_types: [],
+ },
+ {
+ client_id: "oidc_client",
+ client_secret: "a_different_secret",
+ grant_types: ["authorization_code"],
+ response_types: ["code"],
+ redirect_uris: ["http://localhost:3001/cb"],
+ },
+ ],
+ claims: {
+ profile: [
+ "birthdate",
+ "family_name",
+ "gender",
+ "given_name",
+ "locale",
+ "middle_name",
+ "name",
+ "nickname",
+ "picture",
+ "preferred_username",
+ "profile",
+ "updated_at",
+ "website",
+ "zoneinfo",
+ ],
+ email: ["email", "email_verified"],
+ },
+};
+
+const oidc = new Provider("http://localhost:3000", configuration);
+
+const app = express();
+app.use("/oidc", oidc.callback());
+app.listen(3000);
diff --git a/sink/oidc-server/package-lock.json b/sink/oidc-server/package-lock.json
new file mode 100644
index 000000000..2be29633b
--- /dev/null
+++ b/sink/oidc-server/package-lock.json
@@ -0,0 +1,1708 @@
+{
+ "name": "oidc-server",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "oidc-server",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^1.8.3",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "form-data": "^4.0.2",
+ "jose": "^6.0.10",
+ "oidc-provider": "^8.8.1"
+ }
+ },
+ "node_modules/@koa/cors": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz",
+ "integrity": "sha512-x/iUDjcS90W69PryLDIMgFyV21YLTnG9zOpPXS7Bkt2b8AsY3zZsIpOLBkYr9fBcF3HbkKaER5hOBZLfpLgYNw==",
+ "license": "MIT",
+ "dependencies": {
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/@koa/router": {
+ "version": "13.1.0",
+ "resolved": "https://registry.npmjs.org/@koa/router/-/router-13.1.0.tgz",
+ "integrity": "sha512-mNVu1nvkpSd8Q8gMebGbCkDWJ51ODetrFvLKYusej+V0ByD4btqHYnPIzTBLXnQMVUlm/oxVwqmWBY3zQfZilw==",
+ "license": "MIT",
+ "dependencies": {
+ "http-errors": "^2.0.0",
+ "koa-compose": "^4.1.0",
+ "path-to-regexp": "^6.3.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/@koa/router/node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "license": "MIT"
+ },
+ "node_modules/@sindresorhus/is": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz",
+ "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/@szmarczak/http-timer": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz",
+ "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==",
+ "license": "MIT",
+ "dependencies": {
+ "defer-to-connect": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=14.16"
+ }
+ },
+ "node_modules/@types/http-cache-semantics": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
+ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
+ "license": "MIT"
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz",
+ "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/cache-content-type": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz",
+ "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^2.1.18",
+ "ylru": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/cacheable-lookup": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz",
+ "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ }
+ },
+ "node_modules/cacheable-request": {
+ "version": "10.2.14",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz",
+ "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/http-cache-semantics": "^4.0.2",
+ "get-stream": "^6.0.1",
+ "http-cache-semantics": "^4.1.1",
+ "keyv": "^4.5.3",
+ "mimic-response": "^4.0.0",
+ "normalize-url": "^8.0.0",
+ "responselike": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "license": "MIT",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
+ },
+ "node_modules/cookies": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz",
+ "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "keygrip": "~1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decompress-response/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
+ "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==",
+ "license": "MIT"
+ },
+ "node_modules/defer-to-connect": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
+ "license": "MIT"
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.4.7",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
+ "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/eta": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/eta/-/eta-3.5.0.tgz",
+ "integrity": "sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/eta-dev/eta?sponsor=1"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.3.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.19.0",
+ "serve-static": "1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/form-data-encoder": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz",
+ "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.17"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/got": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz",
+ "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@sindresorhus/is": "^5.2.0",
+ "@szmarczak/http-timer": "^5.0.1",
+ "cacheable-lookup": "^7.0.0",
+ "cacheable-request": "^10.2.8",
+ "decompress-response": "^6.0.0",
+ "form-data-encoder": "^2.1.2",
+ "get-stream": "^6.0.1",
+ "http2-wrapper": "^2.1.10",
+ "lowercase-keys": "^3.0.0",
+ "p-cancelable": "^3.0.0",
+ "responselike": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-assert": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz",
+ "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==",
+ "license": "MIT",
+ "dependencies": {
+ "deep-equal": "~1.0.1",
+ "http-errors": "~1.8.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http-assert/node_modules/depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/http-assert/node_modules/http-errors": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/http-assert/node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
+ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http2-wrapper": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz",
+ "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ }
+ },
+ "node_modules/http2-wrapper/node_modules/quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
+ "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.0",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/jose": {
+ "version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.10.tgz",
+ "integrity": "sha512-skIAxZqcMkOrSwjJvplIPYrlXGpxTPnro2/QWTDCxAdWQrSTV5/KqspMWmi5WAx5+ULswASJiZ0a+1B/Lxt9cw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "license": "MIT"
+ },
+ "node_modules/keygrip": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
+ "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==",
+ "license": "MIT",
+ "dependencies": {
+ "tsscmp": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/koa": {
+ "version": "2.16.0",
+ "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.0.tgz",
+ "integrity": "sha512-Afhqq0Vq3W7C+/rW6IqHVBDLzqObwZ07JaUNUEF8yCQ6afiyFE3RAy+i7V0E46XOWlH7vPWn/x0vsZwNy6PWxw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^1.3.5",
+ "cache-content-type": "^1.0.0",
+ "content-disposition": "~0.5.2",
+ "content-type": "^1.0.4",
+ "cookies": "~0.9.0",
+ "debug": "^4.3.2",
+ "delegates": "^1.0.0",
+ "depd": "^2.0.0",
+ "destroy": "^1.0.4",
+ "encodeurl": "^1.0.2",
+ "escape-html": "^1.0.3",
+ "fresh": "~0.5.2",
+ "http-assert": "^1.3.0",
+ "http-errors": "^1.6.3",
+ "is-generator-function": "^1.0.7",
+ "koa-compose": "^4.1.0",
+ "koa-convert": "^2.0.0",
+ "on-finished": "^2.3.0",
+ "only": "~0.0.2",
+ "parseurl": "^1.3.2",
+ "statuses": "^1.5.0",
+ "type-is": "^1.6.16",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4"
+ }
+ },
+ "node_modules/koa-compose": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz",
+ "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==",
+ "license": "MIT"
+ },
+ "node_modules/koa-convert": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz",
+ "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==",
+ "license": "MIT",
+ "dependencies": {
+ "co": "^4.6.0",
+ "koa-compose": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/koa/node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/koa/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/koa/node_modules/http-errors": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/koa/node_modules/http-errors/node_modules/depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/koa/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/koa/node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/lowercase-keys": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz",
+ "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz",
+ "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "5.1.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.3.tgz",
+ "integrity": "sha512-zAbEOEr7u2CbxwoMRlz/pNSpRP0FdAU4pRaYunCdEezWohXFs+a0Xw7RfkKaezMsmSM1vttcLthJtwRnVtOfHQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ }
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/normalize-url": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz",
+ "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/oidc-provider": {
+ "version": "8.8.1",
+ "resolved": "https://registry.npmjs.org/oidc-provider/-/oidc-provider-8.8.1.tgz",
+ "integrity": "sha512-qVChpayTwojUREJxLkFofUSK8kiSRIdzPrVSsoGibqRHl/YO60ege94OZS8vh7zaK+zxcG/Gu8UMaYB5ulohCQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@koa/cors": "^5.0.0",
+ "@koa/router": "^13.1.0",
+ "debug": "^4.4.0",
+ "eta": "^3.5.0",
+ "got": "^13.0.0",
+ "jose": "^5.9.6",
+ "jsesc": "^3.1.0",
+ "koa": "^2.15.4",
+ "nanoid": "^5.0.9",
+ "object-hash": "^3.0.0",
+ "oidc-token-hash": "^5.0.3",
+ "quick-lru": "^7.0.0",
+ "raw-body": "^3.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/oidc-provider/node_modules/debug": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
+ "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/oidc-provider/node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/oidc-provider/node_modules/jose": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz",
+ "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/oidc-provider/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/oidc-provider/node_modules/raw-body": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz",
+ "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.6.3",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/oidc-token-hash": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz",
+ "integrity": "sha512-y0W+X7Ppo7oZX6eovsRkuzcSM40Bicg2JEJkDJ4irIt1wsYAP5MLSNv+QAogO8xivMffw/9OvV3um1pxXgt1uA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^10.13.0 || >=12.0.0"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/only": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz",
+ "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ=="
+ },
+ "node_modules/p-cancelable": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
+ "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/quick-lru": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-7.0.0.tgz",
+ "integrity": "sha512-MX8gB7cVYTrYcFfAnfLlhRd0+Toyl8yX8uBx1MrX7K0jegiz9TumwOK27ldXrgDlHRdVi+MqU9Ssw6dr4BNreg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/resolve-alpn": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
+ "license": "MIT"
+ },
+ "node_modules/responselike": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz",
+ "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==",
+ "license": "MIT",
+ "dependencies": {
+ "lowercase-keys": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tsscmp": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz",
+ "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6.x"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/ylru": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz",
+ "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ }
+ }
+}
diff --git a/sink/oidc-server/package.json b/sink/oidc-server/package.json
new file mode 100644
index 000000000..514629d46
--- /dev/null
+++ b/sink/oidc-server/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "oidc-server",
+ "version": "1.0.0",
+ "main": "index.js",
+ "type": "module",
+ "scripts": {
+ "start": "node main.js"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "description": "",
+ "dependencies": {
+ "axios": "^1.8.3",
+ "dotenv": "^16.4.7",
+ "express": "^4.21.2",
+ "form-data": "^4.0.2",
+ "jose": "^6.0.10",
+ "oidc-provider": "^8.8.1"
+ }
+}
diff --git a/sink/oidc-server/test-infisical.js b/sink/oidc-server/test-infisical.js
new file mode 100644
index 000000000..a2d8df022
--- /dev/null
+++ b/sink/oidc-server/test-infisical.js
@@ -0,0 +1,80 @@
+import axios from "axios";
+import { Buffer } from "buffer";
+import querystring from "querystring";
+
+// Configuration
+const config = {
+ issuer: "http://localhost:3000/oidc",
+ tokenEndpoint: "http://localhost:3000/oidc/token",
+ clientId: "app",
+ clientSecret: "a_secret",
+};
+
+// Client credentials flow for machine identity
+async function getMachineToken() {
+ try {
+ // Use application/x-www-form-urlencoded format as required by the OIDC spec
+ const data = querystring.stringify({
+ grant_type: "client_credentials",
+ scope: "read",
+ resource: "urn:api",
+ });
+
+ const authHeader =
+ "Basic " +
+ Buffer.from(`${config.clientId}:${config.clientSecret}`).toString(
+ "base64",
+ );
+
+ const response = await axios.post(config.tokenEndpoint, data, {
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ Authorization: authHeader,
+ },
+ });
+
+ console.log("Successfully obtained token:");
+ console.log("Access Token:", response.data.access_token);
+ console.log("Token Type:", response.data.token_type);
+ console.log("Expires In:", response.data.expires_in, "seconds");
+ console.log("Scope:", response.data.scope);
+
+ return response.data;
+ } catch (error) {
+ console.error("Error obtaining token:");
+ if (error.response && error.response.data) {
+ console.error(error.response.data);
+ } else {
+ console.error(error.message);
+ }
+ throw error;
+ }
+}
+
+// Test the machine identity authentication
+async function testMachineIdentity() {
+ try {
+ // Get token using client credentials
+ const token = await getMachineToken();
+
+ const loginData = querystring.stringify({
+ identityId: "5d81d5cc-602f-4af7-b242-ab7c1331b430",
+ jwt: token.access_token,
+ });
+
+ const response = await axios({
+ method: "post",
+ url: `http://localhost:8080/api/v1/auth/oidc-auth/login`,
+ headers: {
+ "Content-Type": "application/x-www-form-urlencoded",
+ },
+ data: loginData,
+ });
+ console.log(response.data);
+ } catch (error) {
+ console.error("Error in test:", error.message);
+ }
+}
+
+// Run the test
+testMachineIdentity();
From 6905029455550879c08b32ccbb18036d16e75d2a Mon Sep 17 00:00:00 2001
From: carlosmonastyrski
Date: Fri, 14 Mar 2025 14:59:19 -0300
Subject: [PATCH 07/18] Change overview dynamic secret creation modal to set
only one env
---
.../AwsElastiCacheInputForm.tsx | 50 ++++++-------
.../AwsIamInputForm.tsx | 51 ++++++--------
.../AzureEntraIdInputForm.tsx | 70 ++++++++-----------
.../CassandraInputForm.tsx | 51 ++++++--------
.../ElasticSearchInputForm.tsx | 50 ++++++-------
.../CreateDynamicSecretForm/LdapInputForm.tsx | 50 ++++++-------
.../MongoAtlasInputForm.tsx | 50 ++++++-------
.../MongoDBInputForm.tsx | 64 ++++++++---------
.../RabbitMqInputForm.tsx | 50 ++++++-------
.../RedisInputForm.tsx | 50 ++++++-------
.../SapAseInputForm.tsx | 50 ++++++-------
.../SapHanaInputForm.tsx | 50 ++++++-------
.../SnowflakeInputForm.tsx | 50 ++++++-------
.../SqlDatabaseInputForm.tsx | 51 ++++++--------
.../CreateDynamicSecretForm/TotpInputForm.tsx | 54 ++++++--------
15 files changed, 335 insertions(+), 456 deletions(-)
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx
index ae3ddac05..c326f9dbe 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsElastiCacheInputForm.tsx
@@ -53,7 +53,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -91,7 +91,7 @@ export const AwsElastiCacheInputForm = ({
"UserId": "{{username}}"
}`
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -102,33 +102,26 @@ export const AwsElastiCacheInputForm = ({
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.AwsElastiCache, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- hasErrors = true;
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.AwsElastiCache, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -319,19 +312,18 @@ export const AwsElastiCacheInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
/>
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
index 1f2e5b0a8..ad8cd1b0e 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AwsIamInputForm.tsx
@@ -42,7 +42,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -68,7 +68,7 @@ export const AwsIamInputForm = ({
} = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -79,33 +79,27 @@ export const AwsIamInputForm = ({
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.AwsIam, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- hasErrors = true;
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.AwsIam, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -307,19 +301,18 @@ export const AwsIamInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
index f681a75a9..4df6d7b5a 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/AzureEntraIdInputForm.tsx
@@ -62,7 +62,7 @@ const formSchema = z.object({
.string()
.min(1)
.refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -89,7 +89,7 @@ export const AzureEntraIdInputForm = ({
} = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
const tenantId = watch("provider.tenantId");
@@ -113,44 +113,37 @@ export const AzureEntraIdInputForm = ({
provider,
maxTTL,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- selectedUsers.map(async (user: { id: string; name: string; email: string }) => {
- await createDynamicSecret.mutateAsync({
- provider: {
- type: DynamicSecretProviders.AzureEntraId,
- inputs: {
- userId: user.id,
- tenantId: provider.tenantId,
- email: user.email,
- applicationId: provider.applicationId,
- clientSecret: provider.clientSecret
- }
- },
- maxTTL,
- name: `${name}-${user.name}`,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
+ try {
+ selectedUsers.map(async (user: { id: string; name: string; email: string }) => {
+ await createDynamicSecret.mutateAsync({
+ provider: {
+ type: DynamicSecretProviders.AzureEntraId,
+ inputs: {
+ userId: user.id,
+ tenantId: provider.tenantId,
+ email: user.email,
+ applicationId: provider.applicationId,
+ clientSecret: provider.clientSecret
+ }
+ },
+ maxTTL,
+ name: `${name}-${user.name}`,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
});
- } catch {
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -389,19 +382,18 @@ export const AzureEntraIdInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx
index a770d67b2..cd8486337 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/CassandraInputForm.tsx
@@ -55,7 +55,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -91,7 +91,7 @@ export const CassandraInputForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
provider: getSqlStatements(),
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -102,33 +102,27 @@ export const CassandraInputForm = ({
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.Cassandra, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.Cassandra, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -365,19 +359,18 @@ export const CassandraInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
index 0fddf332c..eea23a2e5 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
@@ -76,7 +76,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -111,7 +111,7 @@ export const ElasticSearchInputForm = ({
roles: ["superuser"],
port: 443
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -122,33 +122,26 @@ export const ElasticSearchInputForm = ({
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.ElasticSearch, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -426,19 +419,18 @@ export const ElasticSearchInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
index cfa9fd45f..ca0e1befb 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/LdapInputForm.tsx
@@ -79,7 +79,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -118,7 +118,7 @@ export const LdapInputForm = ({
rollbackLdif: "",
credentialType: CredentialType.Dynamic
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -131,33 +131,26 @@ export const LdapInputForm = ({
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.Ldap, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.Ldap, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -396,19 +389,18 @@ export const LdapInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
index 53c716200..524f086d4 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoAtlasInputForm.tsx
@@ -66,7 +66,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -112,7 +112,7 @@ export const MongoAtlasInputForm = ({
provider: {
roles: [{ databaseName: "", roleName: "" }]
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -133,33 +133,26 @@ export const MongoAtlasInputForm = ({
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.MongoAtlas, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -458,19 +451,18 @@ export const MongoAtlasInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
index 46597291a..87747c4db 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/MongoDBInputForm.tsx
@@ -56,7 +56,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -87,7 +87,7 @@ export const MongoDBDatabaseInputForm = ({
provider: {
roles: [{ roleName: "readWrite" }]
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -103,40 +103,33 @@ export const MongoDBDatabaseInputForm = ({
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: {
- type: DynamicSecretProviders.MongoDB,
- inputs: {
- ...provider,
- port: provider?.port ? provider.port : undefined,
- roles: provider.roles.map((el) => el.roleName)
- }
- },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: {
+ type: DynamicSecretProviders.MongoDB,
+ inputs: {
+ ...provider,
+ port: provider?.port ? provider.port : undefined,
+ roles: provider.roles.map((el) => el.roleName)
+ }
+ },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -348,19 +341,18 @@ export const MongoDBDatabaseInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
index ffe2164e8..fdecd23ab 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RabbitMqInputForm.tsx
@@ -60,7 +60,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -100,7 +100,7 @@ export const RabbitMqInputForm = ({
},
tags: []
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -111,33 +111,26 @@ export const RabbitMqInputForm = ({
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.RabbitMq, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -432,19 +425,18 @@ export const RabbitMqInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
index 82a5f2bdf..b0f7bd5d6 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/RedisInputForm.tsx
@@ -53,7 +53,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -85,7 +85,7 @@ export const RedisInputForm = ({
creationStatement: "ACL SETUSER {{username}} on >{{password}} ~* &* +@all",
revocationStatement: "ACL DELUSER {{username}}"
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -96,33 +96,26 @@ export const RedisInputForm = ({
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.Redis, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.Redis, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -333,19 +326,18 @@ export const RedisInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
index 126b95712..0d8aad9b8 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapAseInputForm.tsx
@@ -51,7 +51,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -86,7 +86,7 @@ sp_role 'grant', 'mon_role', '{{username}}';`,
revocationStatement: `sp_dropuser '{{username}}';
sp_droplogin '{{username}}';`
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -97,33 +97,26 @@ sp_droplogin '{{username}}';`
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.SapAse, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.SapAse, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -311,19 +304,18 @@ sp_droplogin '{{username}}';`
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
index d4d45ea75..565dc849f 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SapHanaInputForm.tsx
@@ -53,7 +53,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -86,7 +86,7 @@ GRANT "MONITORING" TO {{username}};`,
DROP USER {{username}};`,
renewStatement: "ALTER USER {{username}} VALID UNTIL '{{expiration}}';"
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -97,33 +97,26 @@ DROP USER {{username}};`,
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.SapHana, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret in environment ${env.name}`
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.SapHana, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -331,19 +324,18 @@ DROP USER {{username}};`,
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
index d7e55c183..e9442cbc3 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SnowflakeInputForm.tsx
@@ -57,7 +57,7 @@ const formSchema = z.object({
.trim()
.min(1)
.refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -89,7 +89,7 @@ export const SnowflakeInputForm = ({
revocationStatement: "DROP USER {{username}};",
renewStatement: "ALTER USER {{username}} SET DAYS_TO_EXPIRY = {{expiration}};"
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -100,33 +100,26 @@ export const SnowflakeInputForm = ({
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.Snowflake, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch (err) {
- createNotification({
- type: "error",
- text: err instanceof Error ? err.message : "Failed to create dynamic secret"
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.Snowflake, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch (err) {
+ createNotification({
+ type: "error",
+ text: err instanceof Error ? err.message : "Failed to create dynamic secret"
+ });
}
};
@@ -334,19 +327,18 @@ export const SnowflakeInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
index d80d92c4a..d5088f364 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/SqlDatabaseInputForm.tsx
@@ -82,7 +82,7 @@ const formSchema = z.object({
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -176,7 +176,7 @@ export const SqlDatabaseInputForm = ({
allowedSymbols: "-_.~!*"
}
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : undefined
}
});
@@ -190,33 +190,27 @@ export const SqlDatabaseInputForm = ({
maxTTL,
provider,
defaultTTL,
- environments: selectedEnvs
+ environment
}: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider },
- maxTTL,
- name,
- path: secretPath,
- defaultTTL,
- projectSlug,
- environmentSlug: env.slug
- });
- } catch {
- createNotification({
- type: "error",
- text: `Failed to create dynamic secret for environment ${env.name}`
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider },
+ maxTTL,
+ name,
+ path: secretPath,
+ defaultTTL,
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch {
+ createNotification({
+ type: "error",
+ text: "Failed to create dynamic secret"
+ });
}
};
@@ -669,19 +663,18 @@ export const SqlDatabaseInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
index 4609d2604..8207a117e 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/TotpInputForm.tsx
@@ -61,7 +61,7 @@ const formSchema = z.object({
.trim()
.min(1)
.refine((val) => val.toLowerCase() === val, "Must be lowercase"),
- environments: z.object({ name: z.string(), slug: z.string() }).array()
+ environment: z.object({ name: z.string(), slug: z.string() })
});
type TForm = z.infer;
@@ -92,7 +92,7 @@ export const TotpInputForm = ({
provider: {
configType: ConfigType.URL
},
- environments: environments.length === 1 ? environments : []
+ environment: environments.length === 1 ? environments[0] : environments[0]
}
});
@@ -100,36 +100,25 @@ export const TotpInputForm = ({
const createDynamicSecret = useCreateDynamicSecret();
- const handleCreateDynamicSecret = async ({
- name,
- provider,
- environments: selectedEnvs
- }: TForm) => {
+ const handleCreateDynamicSecret = async ({ name, provider, environment }: TForm) => {
// wait till previous request is finished
if (createDynamicSecret.isPending) return;
- let hasErrors = false;
- const promises = selectedEnvs.map(async (env) => {
- try {
- await createDynamicSecret.mutateAsync({
- provider: { type: DynamicSecretProviders.Totp, inputs: provider },
- maxTTL: "24h",
- name,
- path: secretPath,
- defaultTTL: "1m",
- projectSlug,
- environmentSlug: env.slug
- });
- } catch (err) {
- createNotification({
- type: "error",
- text: err instanceof Error ? err.message : "Failed to create dynamic secret"
- });
- hasErrors = true;
- }
- });
- await Promise.all(promises);
- if (!hasErrors) {
+ try {
+ await createDynamicSecret.mutateAsync({
+ provider: { type: DynamicSecretProviders.Totp, inputs: provider },
+ maxTTL: "24h",
+ name,
+ path: secretPath,
+ defaultTTL: "1m",
+ projectSlug,
+ environmentSlug: environment.slug
+ });
onCompleted();
+ } catch (err) {
+ createNotification({
+ type: "error",
+ text: err instanceof Error ? err.message : "Failed to create dynamic secret"
+ });
}
};
@@ -319,19 +308,18 @@ export const TotpInputForm = ({
{environments.length > 1 && (
(
option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
From fd1afc2cbe9e9fdc3427454cb73b514ff148b6fe Mon Sep 17 00:00:00 2001
From: Scott Wilson
Date: Fri, 14 Mar 2025 11:04:49 -0700
Subject: [PATCH 08/18] fix: handle disabled/destroyed values in gcp sync
---
backend/src/services/secret-sync/gcp/gcp-sync-fns.ts | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts b/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
index f0eb3ce88..5e6006d86 100644
--- a/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
+++ b/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
@@ -71,8 +71,13 @@ const getGcpSecrets = async (accessToken: string, secretSync: TGcpSyncWithCreden
res[key] = Buffer.from(secretLatest.payload.data, "base64").toString("utf-8");
} catch (error) {
- // when a secret in GCP has no versions, we treat it as if it's a blank value
- if (error instanceof AxiosError && error.response?.status === 404) {
+ // when a secret in GCP has no versions, or is disabled/destroyed, we treat it as if it's a blank value
+ if (
+ error instanceof AxiosError &&
+ (error.response?.status === 404 ||
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ (error.response?.status === 400 && error.response.data.error.status === "FAILED_PRECONDITION"))
+ ) {
res[key] = "";
} else {
throw new SecretSyncError({
From 48943b4d78cce286ad403913befe2c488d1b8892 Mon Sep 17 00:00:00 2001
From: Scott Wilson
Date: Fri, 14 Mar 2025 11:26:59 -0700
Subject: [PATCH 09/18] improvement: refine status check
---
backend/src/services/secret-sync/gcp/gcp-sync-fns.ts | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts b/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
index 5e6006d86..867b0b870 100644
--- a/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
+++ b/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
@@ -75,8 +75,11 @@ const getGcpSecrets = async (accessToken: string, secretSync: TGcpSyncWithCreden
if (
error instanceof AxiosError &&
(error.response?.status === 404 ||
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- (error.response?.status === 400 && error.response.data.error.status === "FAILED_PRECONDITION"))
+ (error.response?.status === 400 &&
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ error.response.data.error.status === "FAILED_PRECONDITION" &&
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
+ error.response.data.error.message.match(/(?:disabled|destroyed)/i)))
) {
res[key] = "";
} else {
From d4c69d8e5d3c0cb900d2377105b46723fc899770 Mon Sep 17 00:00:00 2001
From: Scott Wilson
Date: Fri, 14 Mar 2025 15:24:21 -0700
Subject: [PATCH 10/18] feature: disable secret deletion sync option
---
backend/src/lib/api-docs/constants.ts | 3 +-
.../aws-parameter-store-sync-fns.ts | 2 +
.../aws-secrets-manager-sync-fns.ts | 2 +
.../azure-app-configuration-sync-fns.ts | 2 +
.../azure-key-vault-sync-fns.ts | 2 +
.../databricks/databricks-sync-fns.ts | 2 +
.../services/secret-sync/gcp/gcp-sync-fns.ts | 3 ++
.../secret-sync/github/github-sync-fns.ts | 14 +++---
.../humanitec/humanitec-sync-fns.ts | 2 +
.../secret-sync/secret-sync-schemas.ts | 3 +-
.../secret-syncs/aws-parameter-store.mdx | 3 +-
.../secret-syncs/aws-secrets-manager.mdx | 2 +
.../secret-syncs/azure-app-configuration.mdx | 3 +-
.../secret-syncs/azure-key-vault.mdx | 3 +-
docs/integrations/secret-syncs/databricks.mdx | 1 +
.../secret-syncs/gcp-secret-manager.mdx | 1 +
docs/integrations/secret-syncs/github.mdx | 1 +
docs/integrations/secret-syncs/humanitec.mdx | 1 +
.../SecretSyncOptionsFields.tsx | 42 +++++++++++++++++-
.../SecretSyncReviewFields.tsx | 6 +++
.../forms/schemas/base-secret-sync-schema.ts | 3 +-
.../hooks/api/secretSyncs/types/root-sync.ts | 1 +
.../SecretSyncOptionsSection.tsx | 44 ++++++++++---------
23 files changed, 112 insertions(+), 34 deletions(-)
diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts
index 7a350938f..de7411cac 100644
--- a/backend/src/lib/api-docs/constants.ts
+++ b/backend/src/lib/api-docs/constants.ts
@@ -1725,7 +1725,8 @@ export const SecretSyncs = {
SYNC_OPTIONS: (destination: SecretSync) => {
const destinationName = SECRET_SYNC_NAME_MAP[destination];
return {
- initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`
+ initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
+ disableSecretDeletion: `Enable this flag to prevent removal of secrets when syncing from the ${destinationName} destination.`
};
},
ADDITIONAL_SYNC_OPTIONS: {
diff --git a/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts b/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts
index 9ca035774..7e77bd256 100644
--- a/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts
+++ b/backend/src/services/secret-sync/aws-parameter-store/aws-parameter-store-sync-fns.ts
@@ -382,6 +382,8 @@ export const AwsParameterStoreSyncFns = {
}
}
+ if (syncOptions.disableSecretDeletion) return;
+
const parametersToDelete: AWS.SSM.Parameter[] = [];
for (const entry of Object.entries(awsParameterStoreSecretsRecord)) {
diff --git a/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts b/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts
index d4f272475..7cea12d1b 100644
--- a/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts
+++ b/backend/src/services/secret-sync/aws-secrets-manager/aws-secrets-manager-sync-fns.ts
@@ -396,6 +396,8 @@ export const AwsSecretsManagerSyncFns = {
}
}
+ if (syncOptions.disableSecretDeletion) return;
+
for await (const secretKey of Object.keys(awsSecretsRecord)) {
if (!(secretKey in secretMap) || !secretMap[secretKey].value) {
try {
diff --git a/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts b/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts
index 8c0587599..64d82c125 100644
--- a/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts
+++ b/backend/src/services/secret-sync/azure-app-configuration/azure-app-configuration-sync-fns.ts
@@ -136,6 +136,8 @@ export const azureAppConfigurationSyncFactory = ({
}
}
+ if (secretSync.syncOptions.disableSecretDeletion) return;
+
for await (const key of Object.keys(azureAppConfigSecrets)) {
const azureSecret = azureAppConfigSecrets[key];
if (
diff --git a/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts b/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts
index 21fef297a..b50d78bb2 100644
--- a/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts
+++ b/backend/src/services/secret-sync/azure-key-vault/azure-key-vault-sync-fns.ts
@@ -189,6 +189,8 @@ export const azureKeyVaultSyncFactory = ({ kmsService, appConnectionDAL }: TAzur
});
}
+ if (secretSync.syncOptions.disableSecretDeletion) return;
+
for await (const deleteSecretKey of deleteSecrets.filter(
(secret) => !setSecrets.find((setSecret) => setSecret.key === secret)
)) {
diff --git a/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts b/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts
index 2d450b2b1..2ee7977a4 100644
--- a/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts
+++ b/backend/src/services/secret-sync/databricks/databricks-sync-fns.ts
@@ -112,6 +112,8 @@ export const databricksSyncFactory = ({ kmsService, appConnectionDAL }: TDatabri
accessToken
});
+ if (secretSync.syncOptions.disableSecretDeletion) return;
+
for await (const secret of databricksSecretKeys) {
if (!(secret.key in secretMap)) {
await deleteDatabricksSecrets({
diff --git a/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts b/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
index f0eb3ce88..27ad2cba6 100644
--- a/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
+++ b/backend/src/services/secret-sync/gcp/gcp-sync-fns.ts
@@ -147,6 +147,9 @@ export const GcpSyncFns = {
for await (const key of Object.keys(gcpSecrets)) {
try {
if (!(key in secretMap) || !secretMap[key].value) {
+ // eslint-disable-next-line no-continue
+ if (secretSync.syncOptions.disableSecretDeletion) continue;
+
// case: delete secret
await request.delete(
`${IntegrationUrls.GCP_SECRET_MANAGER_URL}/v1/projects/${destinationConfig.projectId}/secrets/${key}`,
diff --git a/backend/src/services/secret-sync/github/github-sync-fns.ts b/backend/src/services/secret-sync/github/github-sync-fns.ts
index a09a41163..1fe922de5 100644
--- a/backend/src/services/secret-sync/github/github-sync-fns.ts
+++ b/backend/src/services/secret-sync/github/github-sync-fns.ts
@@ -192,12 +192,6 @@ export const GithubSyncFns = {
const publicKey = await getPublicKey(client, secretSync);
- for await (const encryptedSecret of encryptedSecrets) {
- if (!(encryptedSecret.name in secretMap)) {
- await deleteSecret(client, secretSync, encryptedSecret);
- }
- }
-
await sodium.ready.then(async () => {
for await (const key of Object.keys(secretMap)) {
// convert secret & base64 key to Uint8Array.
@@ -224,6 +218,14 @@ export const GithubSyncFns = {
}
}
});
+
+ if (secretSync.syncOptions.disableSecretDeletion) return;
+
+ for await (const encryptedSecret of encryptedSecrets) {
+ if (!(encryptedSecret.name in secretMap)) {
+ await deleteSecret(client, secretSync, encryptedSecret);
+ }
+ }
},
getSecrets: async (secretSync: TGitHubSyncWithCredentials) => {
throw new Error(`${SECRET_SYNC_NAME_MAP[secretSync.destination]} does not support importing secrets.`);
diff --git a/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts b/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts
index a07d2d0b5..5fa0a3d63 100644
--- a/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts
+++ b/backend/src/services/secret-sync/humanitec/humanitec-sync-fns.ts
@@ -196,6 +196,8 @@ export const HumanitecSyncFns = {
}
}
+ if (secretSync.syncOptions.disableSecretDeletion) return;
+
for await (const humanitecSecret of humanitecSecrets) {
if (!secretMap[humanitecSecret.key]) {
await deleteSecret(secretSync, humanitecSecret);
diff --git a/backend/src/services/secret-sync/secret-sync-schemas.ts b/backend/src/services/secret-sync/secret-sync-schemas.ts
index 8ea3a4f0b..50ff3f307 100644
--- a/backend/src/services/secret-sync/secret-sync-schemas.ts
+++ b/backend/src/services/secret-sync/secret-sync-schemas.ts
@@ -23,7 +23,8 @@ const BaseSyncOptionsSchema = ({
initialSyncBehavior: (canImportSecrets
? z.nativeEnum(SecretSyncInitialSyncBehavior)
: z.literal(SecretSyncInitialSyncBehavior.OverwriteDestination)
- ).describe(SecretSyncs.SYNC_OPTIONS(destination).initialSyncBehavior)
+ ).describe(SecretSyncs.SYNC_OPTIONS(destination).initialSyncBehavior),
+ disableSecretDeletion: z.boolean().optional().describe(SecretSyncs.SYNC_OPTIONS(destination).disableSecretDeletion)
});
const schema = merge ? baseSchema.merge(merge) : baseSchema;
diff --git a/docs/integrations/secret-syncs/aws-parameter-store.mdx b/docs/integrations/secret-syncs/aws-parameter-store.mdx
index 165998841..831d0c752 100644
--- a/docs/integrations/secret-syncs/aws-parameter-store.mdx
+++ b/docs/integrations/secret-syncs/aws-parameter-store.mdx
@@ -43,8 +43,9 @@ description: "Learn how to configure an AWS Parameter Store Sync for Infisical."
- **KMS Key**: The AWS KMS key ID or alias to encrypt parameters with.
- **Tags**: Optional resource tags to add to parameters synced by Infisical.
- **Sync Secret Metadata as Resource Tags**: If enabled, metadata attached to secrets will be added as resource tags to parameters synced by Infisical.
- Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict.
+ Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Parameter Store Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/aws-secrets-manager.mdx b/docs/integrations/secret-syncs/aws-secrets-manager.mdx
index b8df0e8b3..a301ee86b 100644
--- a/docs/integrations/secret-syncs/aws-secrets-manager.mdx
+++ b/docs/integrations/secret-syncs/aws-secrets-manager.mdx
@@ -46,7 +46,9 @@ description: "Learn how to configure an AWS Secrets Manager Sync for Infisical."
- **KMS Key**: The AWS KMS key ID or alias to encrypt secrets with.
- **Tags**: Optional tags to add to secrets synced by Infisical.
- **Sync Secret Metadata as Tags**: If enabled, metadata attached to secrets will be added as tags to secrets synced by Infisical.
+ Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Secrets Manager Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/azure-app-configuration.mdx b/docs/integrations/secret-syncs/azure-app-configuration.mdx
index 3b443c672..35a577872 100644
--- a/docs/integrations/secret-syncs/azure-app-configuration.mdx
+++ b/docs/integrations/secret-syncs/azure-app-configuration.mdx
@@ -6,7 +6,7 @@ description: "Learn how to configure an Azure App Configuration Sync for Infisic
**Prerequisites:**
- Set up and add secrets to [Infisical Cloud](https://app.infisical.com)
- - Create a [Azure Connection](/integrations/app-connections/azure), configured for Azure App Configuration.
+ - Create an [Azure App Configuration Connection](/integrations/app-connections/azure-app-configuration)
The Azure App Configuration Secret Sync requires the following permissions to be set on the user / service principal
@@ -50,6 +50,7 @@ description: "Learn how to configure an Azure App Configuration Sync for Infisic
- **Import Secrets (Prioritize Azure App Configuration)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Azure App Configuration Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/azure-key-vault.mdx b/docs/integrations/secret-syncs/azure-key-vault.mdx
index 9b28f841f..5f55a73ae 100644
--- a/docs/integrations/secret-syncs/azure-key-vault.mdx
+++ b/docs/integrations/secret-syncs/azure-key-vault.mdx
@@ -6,7 +6,7 @@ description: "Learn how to configure a Azure Key Vault Sync for Infisical."
**Prerequisites:**
- Set up and add secrets to [Infisical Cloud](https://app.infisical.com)
- - Create a [Azure Connection](/integrations/app-connections/azure), configured for Azure Key Vault.
+ - Create an [Azure Key Vault Connection](/integrations/app-connections/azure-key-vault)
The Azure Key Vault Secret Sync requires the following secrets permissions to be set on the user / service principal
@@ -52,6 +52,7 @@ description: "Learn how to configure a Azure Key Vault Sync for Infisical."
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over Secrets Manager when keys conflict.
- **Import Secrets (Prioritize Azure Key Vault)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Secrets Manager over Infisical when keys conflict.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Azure Key Vault Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/databricks.mdx b/docs/integrations/secret-syncs/databricks.mdx
index 148542708..c9db5f88a 100644
--- a/docs/integrations/secret-syncs/databricks.mdx
+++ b/docs/integrations/secret-syncs/databricks.mdx
@@ -47,6 +47,7 @@ description: "Learn how to configure a Databricks Sync for Infisical."
Databricks does not support importing secrets.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Databricks Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/gcp-secret-manager.mdx b/docs/integrations/secret-syncs/gcp-secret-manager.mdx
index bcb3b87bd..72c932116 100644
--- a/docs/integrations/secret-syncs/gcp-secret-manager.mdx
+++ b/docs/integrations/secret-syncs/gcp-secret-manager.mdx
@@ -43,6 +43,7 @@ description: "Learn how to configure a GCP Secret Manager Sync for Infisical."
- **Import Secrets (Prioritize Infisical)**: Imports secrets from the destination endpoint before syncing, prioritizing values from Infisical over GCP Secret Manager when keys conflict.
- **Import Secrets (Prioritize GCP Secret Manager)**: Imports secrets from the destination endpoint before syncing, prioritizing values from GCP Secret Manager over Infisical when keys conflict.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your GCP Secret Manager Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/github.mdx b/docs/integrations/secret-syncs/github.mdx
index d7d7afd86..d55ec3d0b 100644
--- a/docs/integrations/secret-syncs/github.mdx
+++ b/docs/integrations/secret-syncs/github.mdx
@@ -63,6 +63,7 @@ description: "Learn how to configure a GitHub Sync for Infisical."
GitHub does not support importing secrets.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your GitHub Sync, then click **Next**.

diff --git a/docs/integrations/secret-syncs/humanitec.mdx b/docs/integrations/secret-syncs/humanitec.mdx
index 0edc2098e..e8cd7eafc 100644
--- a/docs/integrations/secret-syncs/humanitec.mdx
+++ b/docs/integrations/secret-syncs/humanitec.mdx
@@ -56,6 +56,7 @@ description: "Learn how to configure a Humanitec Sync for Infisical."
Humanitec does not support importing secrets.
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
+ - **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
6. Configure the **Details** of your Humanitec Sync, then click **Next**.

diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
index d3213caf7..802c9cc7c 100644
--- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
+++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
@@ -1,9 +1,9 @@
import { ReactNode } from "react";
import { Controller, useFormContext } from "react-hook-form";
-import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
+import { faQuestionCircle, faTriangleExclamation } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { FormControl, Select, SelectItem } from "@app/components/v2";
+import { FormControl, Select, SelectItem, Switch, Tooltip } from "@app/components/v2";
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP, SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { SecretSync, useSecretSyncOption } from "@app/hooks/api/secretSyncs";
@@ -116,6 +116,44 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
>
)}
{AdditionalSyncOptionsFieldsComponent}
+ {
+ return (
+
+
+
+ Disable Secret Deletion{" "}
+
+
+ When enabled, Infisical will not{" "}
+ remove secrets from the sync destination.
+
+
+ Enable this option if you intend to manage some secrets manually outside
+ of Infisical.
+
+ >
+ }
+ >
+
+
+
+
+
+ );
+ }}
+ />
{/* (
{
secretPath,
syncOptions: {
// appendSuffix, prependPrefix,
+ disableSecretDeletion,
initialSyncBehavior
},
destination,
@@ -111,6 +112,11 @@ export const SecretSyncReviewFields = () => {
{/* {prependPrefix}
{appendSuffix} */}
{AdditionalSyncOptionsFieldsComponent}
+ {disableSecretDeletion && (
+
+ Enabled
+
+ )}
diff --git a/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts b/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts
index b898fe91f..bf72321ce 100644
--- a/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts
+++ b/frontend/src/components/secret-syncs/forms/schemas/base-secret-sync-schema.ts
@@ -7,7 +7,8 @@ export const BaseSecretSyncSchema =
{
const baseSyncOptionsSchema = z.object({
- initialSyncBehavior: z.nativeEnum(SecretSyncInitialSyncBehavior)
+ initialSyncBehavior: z.nativeEnum(SecretSyncInitialSyncBehavior),
+ disableSecretDeletion: z.boolean().optional().default(false)
// scott: removed temporarily for evaluation of template formatting
// prependPrefix: z
// .string()
diff --git a/frontend/src/hooks/api/secretSyncs/types/root-sync.ts b/frontend/src/hooks/api/secretSyncs/types/root-sync.ts
index 49cf9c978..dfbbcd063 100644
--- a/frontend/src/hooks/api/secretSyncs/types/root-sync.ts
+++ b/frontend/src/hooks/api/secretSyncs/types/root-sync.ts
@@ -3,6 +3,7 @@ import { SecretSyncInitialSyncBehavior, SecretSyncStatus } from "@app/hooks/api/
export type RootSyncOptions = {
initialSyncBehavior: SecretSyncInitialSyncBehavior;
+ disableSecretDeletion?: boolean;
// prependPrefix?: string;
// appendSuffix?: string;
};
diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx
index be48428ea..139fcf916 100644
--- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx
+++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx
@@ -4,7 +4,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { ProjectPermissionCan } from "@app/components/permissions";
import { SecretSyncLabel } from "@app/components/secret-syncs";
-import { IconButton } from "@app/components/v2";
+import { Badge, IconButton } from "@app/components/v2";
import { ProjectPermissionSub } from "@app/context";
import { ProjectPermissionSecretSyncActions } from "@app/context/ProjectPermissionContext/types";
import { SECRET_SYNC_INITIAL_SYNC_BEHAVIOR_MAP } from "@app/helpers/secretSyncs";
@@ -24,7 +24,8 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
syncOptions: {
// appendSuffix,
// prependPrefix,
- initialSyncBehavior
+ initialSyncBehavior,
+ disableSecretDeletion
}
} = secretSync;
@@ -58,24 +59,22 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
Sync Options
- {AdditionalSyncOptionsComponent && (
-
- {(isAllowed) => (
-
-
-
- )}
-
- )}
+
+ {(isAllowed) => (
+
+
+
+ )}
+
@@ -85,6 +84,11 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
{/* {prependPrefix}
{appendSuffix} */}
{AdditionalSyncOptionsComponent}
+ {disableSecretDeletion && (
+
+ Enabled
+
+ )}
From b89212a0c9d459bd54087501e65678bb15b4da8d Mon Sep 17 00:00:00 2001
From: Scott Wilson
Date: Fri, 14 Mar 2025 15:27:47 -0700
Subject: [PATCH 11/18] improvement: improve property description
---
backend/src/lib/api-docs/constants.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts
index de7411cac..1a7ebf1ef 100644
--- a/backend/src/lib/api-docs/constants.ts
+++ b/backend/src/lib/api-docs/constants.ts
@@ -1726,7 +1726,7 @@ export const SecretSyncs = {
const destinationName = SECRET_SYNC_NAME_MAP[destination];
return {
initialSyncBehavior: `Specify how Infisical should resolve the initial sync to the ${destinationName} destination.`,
- disableSecretDeletion: `Enable this flag to prevent removal of secrets when syncing from the ${destinationName} destination.`
+ disableSecretDeletion: `Enable this flag to prevent removal of secrets from the ${destinationName} destination when syncing.`
};
},
ADDITIONAL_SYNC_OPTIONS: {
From cc7d0d752f306ba5f829ddfff9f8bf7728677e58 Mon Sep 17 00:00:00 2001
From: Scott Wilson
Date: Fri, 14 Mar 2025 15:30:31 -0700
Subject: [PATCH 12/18] improvement: improve tooltip description
---
.../forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
index 802c9cc7c..b14bce809 100644
--- a/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
+++ b/frontend/src/components/secret-syncs/forms/SecretSyncOptionsFields/SecretSyncOptionsFields.tsx
@@ -137,7 +137,7 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
<>
When enabled, Infisical will not{" "}
- remove secrets from the sync destination.
+ remove secrets from the destination during a sync.
Enable this option if you intend to manage some secrets manually outside
From e53439d586c24413182c57520ddbb1a11d556a08 Mon Sep 17 00:00:00 2001
From: carlosmonastyrski
Date: Fri, 14 Mar 2025 23:18:57 -0300
Subject: [PATCH 13/18] Improvements on dynamic secrets overview
---
.../OverviewPage/OverviewPage.tsx | 49 ++++++++++---------
.../components/ActionBar/ActionBar.tsx | 1 +
.../AwsElastiCacheInputForm.tsx | 8 +--
.../AwsIamInputForm.tsx | 8 +--
.../AzureEntraIdInputForm.tsx | 8 +--
.../CassandraInputForm.tsx | 8 +--
.../CreateDynamicSecretForm.tsx | 19 ++++++-
.../ElasticSearchInputForm.tsx | 8 +--
.../CreateDynamicSecretForm/LdapInputForm.tsx | 8 +--
.../MongoAtlasInputForm.tsx | 8 +--
.../MongoDBInputForm.tsx | 8 +--
.../RabbitMqInputForm.tsx | 8 +--
.../RedisInputForm.tsx | 8 +--
.../SapAseInputForm.tsx | 8 +--
.../SapHanaInputForm.tsx | 8 +--
.../SnowflakeInputForm.tsx | 8 +--
.../SqlDatabaseInputForm.tsx | 8 +--
.../CreateDynamicSecretForm/TotpInputForm.tsx | 8 +--
18 files changed, 121 insertions(+), 68 deletions(-)
diff --git a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
index 12e0100b6..34012bb2f 100644
--- a/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
+++ b/frontend/src/pages/secret-manager/OverviewPage/OverviewPage.tsx
@@ -187,10 +187,8 @@ export const OverviewPage = () => {
permission.can(
ProjectPermissionDynamicSecretActions.CreateRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, {
- environment: env.id,
- secretPath,
- secretName: "*",
- secretTags: ["*"]
+ environment: env.slug,
+ secretPath
})
)
);
@@ -863,42 +861,49 @@ export const OverviewPage = () => {
-
}
- onClick={() => {
- if (subscription?.dynamicSecret) {
- handlePopUpOpen("addDynamicSecret");
- return;
- }
- handlePopUpOpen("upgradePlan");
- }}
- isDisabled={userAvailableDynamicSecretEnvs.length === 0}
- variant="outline_bg"
- className="h-10 text-left"
- isFullWidth
- >
- Add Dynamic Secret
-
{(isAllowed) => (
}
+ leftIcon={}
onClick={() => {
handlePopUpOpen("addFolder");
handlePopUpClose("misc");
}}
isDisabled={!isAllowed}
variant="outline_bg"
- className="h-10"
+ className="h-10 text-left"
isFullWidth
>
Add Folder
)}
+
+ }
+ onClick={() => {
+ if (subscription?.dynamicSecret) {
+ handlePopUpOpen("addDynamicSecret");
+ handlePopUpClose("misc");
+ return;
+ }
+ handlePopUpOpen("upgradePlan");
+ }}
+ isDisabled={userAvailableDynamicSecretEnvs.length === 0}
+ variant="outline_bg"
+ className="h-10 text-left"
+ isFullWidth
+ >
+ Add Dynamic Secret
+
+
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
index 0cf131ba8..681ad99e3 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/ActionBar.tsx
@@ -657,6 +657,7 @@ export const ActionBar = ({
projectSlug={projectSlug}
environments={[{ slug: environment, name: environment, id: "not-used" }]}
secretPath={secretPath}
+ isSingleEnvironmentMode
/>
{
const {
control,
@@ -91,7 +93,7 @@ export const AwsElastiCacheInputForm = ({
"UserId": "{{username}}"
}`
},
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -309,7 +311,7 @@ export const AwsElastiCacheInputForm = ({
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const {
control,
@@ -68,7 +70,7 @@ export const AwsIamInputForm = ({
} = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -298,7 +300,7 @@ export const AwsIamInputForm = ({
)}
/>
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const {
control,
@@ -89,7 +91,7 @@ export const AzureEntraIdInputForm = ({
} = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
const tenantId = watch("provider.tenantId");
@@ -379,7 +381,7 @@ export const AzureEntraIdInputForm = ({
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
@@ -81,7 +82,8 @@ export const CassandraInputForm = ({
onCancel,
environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -91,7 +93,7 @@ export const CassandraInputForm = ({
resolver: zodResolver(formSchema),
defaultValues: {
provider: getSqlStatements(),
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -356,7 +358,7 @@ export const CassandraInputForm = ({
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const [wizardStep, setWizardStep] = useState(WizardSteps.SelectProvider);
const [selectedProvider, setSelectedProvider] = useState(null);
@@ -199,6 +201,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -217,6 +220,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -235,6 +239,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -253,6 +258,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -271,6 +277,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -289,6 +296,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -307,6 +315,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -325,6 +334,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -343,6 +353,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -361,6 +372,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -379,6 +391,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -397,6 +410,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -415,6 +429,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -434,6 +449,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
@@ -452,6 +468,7 @@ export const CreateDynamicSecretForm = ({
projectSlug={projectSlug}
secretPath={secretPath}
environments={environments}
+ isSingleEnvironmentMode={isSingleEnvironmentMode}
/>
)}
diff --git a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
index eea23a2e5..a7c84cb50 100644
--- a/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
+++ b/frontend/src/pages/secret-manager/SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm/ElasticSearchInputForm.tsx
@@ -86,6 +86,7 @@ type Props = {
secretPath: string;
projectSlug: string;
environments: WorkspaceEnv[];
+ isSingleEnvironmentMode?: boolean;
};
export const ElasticSearchInputForm = ({
@@ -93,7 +94,8 @@ export const ElasticSearchInputForm = ({
onCancel,
environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const {
control,
@@ -111,7 +113,7 @@ export const ElasticSearchInputForm = ({
roles: ["superuser"],
port: 443
},
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -416,7 +418,7 @@ export const ElasticSearchInputForm = ({
)}
/>
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const {
control,
@@ -118,7 +120,7 @@ export const LdapInputForm = ({
rollbackLdif: "",
credentialType: CredentialType.Dynamic
},
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -386,7 +388,7 @@ export const LdapInputForm = ({
)}
/>
)}
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const {
control,
@@ -112,7 +114,7 @@ export const MongoAtlasInputForm = ({
provider: {
roles: [{ databaseName: "", roleName: "" }]
},
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -448,7 +450,7 @@ export const MongoAtlasInputForm = ({
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const {
control,
@@ -87,7 +89,7 @@ export const MongoDBDatabaseInputForm = ({
provider: {
roles: [{ roleName: "readWrite" }]
},
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -338,7 +340,7 @@ export const MongoDBDatabaseInputForm = ({
)}
/>
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const {
control,
@@ -100,7 +102,7 @@ export const RabbitMqInputForm = ({
},
tags: []
},
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -422,7 +424,7 @@ export const RabbitMqInputForm = ({
)}
/>
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const {
control,
@@ -85,7 +87,7 @@ export const RedisInputForm = ({
creationStatement: "ACL SETUSER {{username}} on >{{password}} ~* &* +@all",
revocationStatement: "ACL DELUSER {{username}}"
},
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -323,7 +325,7 @@ export const RedisInputForm = ({
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const {
control,
@@ -86,7 +88,7 @@ sp_role 'grant', 'mon_role', '{{username}}';`,
revocationStatement: `sp_dropuser '{{username}}';
sp_droplogin '{{username}}';`
},
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -301,7 +303,7 @@ sp_droplogin '{{username}}';`
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const {
control,
@@ -86,7 +88,7 @@ GRANT "MONITORING" TO {{username}};`,
DROP USER {{username}};`,
renewStatement: "ALTER USER {{username}} VALID UNTIL '{{expiration}}';"
},
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -321,7 +323,7 @@ DROP USER {{username}};`,
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const {
control,
@@ -89,7 +91,7 @@ export const SnowflakeInputForm = ({
revocationStatement: "DROP USER {{username}};",
renewStatement: "ALTER USER {{username}} SET DAYS_TO_EXPIRY = {{expiration}};"
},
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -324,7 +326,7 @@ export const SnowflakeInputForm = ({
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
@@ -150,7 +151,8 @@ export const SqlDatabaseInputForm = ({
onCancel,
environments,
secretPath,
- projectSlug
+ projectSlug,
+ isSingleEnvironmentMode
}: Props) => {
const { currentWorkspace } = useWorkspace();
@@ -176,7 +178,7 @@ export const SqlDatabaseInputForm = ({
allowedSymbols: "-_.~!*"
}
},
- environment: environments.length === 1 ? environments[0] : undefined
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -660,7 +662,7 @@ export const SqlDatabaseInputForm = ({
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
{
const {
control,
@@ -92,7 +94,7 @@ export const TotpInputForm = ({
provider: {
configType: ConfigType.URL
},
- environment: environments.length === 1 ? environments[0] : environments[0]
+ environment: isSingleEnvironmentMode ? environments[0] : undefined
}
});
@@ -305,7 +307,7 @@ export const TotpInputForm = ({
>
)}
- {environments.length > 1 && (
+ {!isSingleEnvironmentMode && (
Date: Mon, 17 Mar 2025 21:28:50 +0530
Subject: [PATCH 14/18] fix: corrected table name check in migration
---
backend/src/db/migrations/20250212191958_create-gateway.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/src/db/migrations/20250212191958_create-gateway.ts b/backend/src/db/migrations/20250212191958_create-gateway.ts
index 7738da5cc..14c498ca9 100644
--- a/backend/src/db/migrations/20250212191958_create-gateway.ts
+++ b/backend/src/db/migrations/20250212191958_create-gateway.ts
@@ -85,7 +85,7 @@ export async function up(knex: Knex): Promise {
}
if (await knex.schema.hasTable(TableName.DynamicSecret)) {
- const doesGatewayColExist = await knex.schema.hasColumn(TableName.DynamicSecret, "gatewayId");
+ const doesGatewayColExist = await knex.schema.hasColumn(TableName.DynamicSecret, "projectGatewayId");
await knex.schema.alterTable(TableName.DynamicSecret, (t) => {
// not setting a foreign constraint so that cascade effects are not triggered
if (!doesGatewayColExist) {
From 18c32d872ce821abc3880e87f7311e32530a5955 Mon Sep 17 00:00:00 2001
From: Maidul Islam
Date: Mon, 17 Mar 2025 12:06:35 -0400
Subject: [PATCH 15/18] Revert "feat: confirm environment exists when running
`run` command"
---
.gitignore | 8 ++---
cli/packages/api/model.go | 14 +++-----
cli/packages/cmd/run.go | 69 +++---------------------------------
cli/packages/util/secrets.go | 1 +
cli/test/secrets_test.go | 1 +
flake.nix | 10 ------
6 files changed, 14 insertions(+), 89 deletions(-)
diff --git a/.gitignore b/.gitignore
index f21c85c41..f2a23324b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,3 @@
-.direnv/
-
# backend
node_modules
.env
@@ -28,6 +26,8 @@ node_modules
/.pnp
.pnp.js
+.env
+
# testing
coverage
reports
@@ -63,12 +63,10 @@ yarn-error.log*
# Editor specific
.vscode/*
-**/.idea/*
+.idea/*
frontend-build
-# cli
-.go/
*.tgz
cli/infisical-merge
cli/test/infisical-merge
diff --git a/cli/packages/api/model.go b/cli/packages/api/model.go
index 1eb936f24..72dbbc97b 100644
--- a/cli/packages/api/model.go
+++ b/cli/packages/api/model.go
@@ -2,12 +2,6 @@ package api
import "time"
-type Environment struct {
- Name string `json:"name"`
- Slug string `json:"slug"`
- ID string `json:"id"`
-}
-
// Stores info for login one
type LoginOneRequest struct {
Email string `json:"email"`
@@ -20,6 +14,7 @@ type LoginOneResponse struct {
}
// Stores info for login two
+
type LoginTwoRequest struct {
Email string `json:"email"`
ClientProof string `json:"clientProof"`
@@ -173,10 +168,9 @@ type Secret struct {
}
type Project struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Slug string `json:"slug"`
- Environments []Environment `json:"environments"`
+ ID string `json:"id"`
+ Name string `json:"name"`
+ Slug string `json:"slug"`
}
type RawSecret struct {
diff --git a/cli/packages/cmd/run.go b/cli/packages/cmd/run.go
index 388a4861d..7f11a3f95 100644
--- a/cli/packages/cmd/run.go
+++ b/cli/packages/cmd/run.go
@@ -15,9 +15,6 @@ import (
"syscall"
"time"
- "github.com/Infisical/infisical-merge/packages/api"
- "github.com/go-resty/resty/v2"
-
"github.com/Infisical/infisical-merge/packages/models"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/fatih/color"
@@ -62,11 +59,11 @@ var runCmd = &cobra.Command{
return nil
},
Run: func(cmd *cobra.Command, args []string) {
- environmentSlug, _ := cmd.Flags().GetString("env")
+ environmentName, _ := cmd.Flags().GetString("env")
if !cmd.Flags().Changed("env") {
environmentFromWorkspace := util.GetEnvFromWorkspaceFile()
if environmentFromWorkspace != "" {
- environmentSlug = environmentFromWorkspace
+ environmentName = environmentFromWorkspace
}
}
@@ -139,20 +136,8 @@ var runCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag")
}
- log.Debug().Msgf("Confirming selected environment is valid: %s", environmentSlug)
-
- hasEnvironment, err := confirmProjectHasEnvironment(environmentSlug, projectId, token)
- if err != nil {
- util.HandleError(err, "Could not confirm project has environment")
- }
- if !hasEnvironment {
- util.HandleError(fmt.Errorf("project does not have environment '%s'", environmentSlug))
- }
-
- log.Debug().Msgf("Project '%s' has environment '%s'", projectId, environmentSlug)
-
request := models.GetAllSecretsParameters{
- Environment: environmentSlug,
+ Environment: environmentName,
WorkspaceId: projectId,
TagSlugs: tagSlugs,
SecretsPath: secretsPath,
@@ -323,6 +308,7 @@ func waitForExitCommand(cmd *exec.Cmd) (int, error) {
}
func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInterval int, request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, token *models.TokenDetails) {
+
var cmd *exec.Cmd
var err error
var lastSecretsFetch time.Time
@@ -453,53 +439,8 @@ func executeCommandWithWatchMode(commandFlag string, args []string, watchModeInt
}
}
-func confirmProjectHasEnvironment(environmentSlug, projectId string, token *models.TokenDetails) (bool, error) {
- var accessToken string
-
- if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
- accessToken = token.Token
- } else {
- util.RequireLogin()
- util.RequireLocalWorkspaceFile()
-
- loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
- if err != nil {
- util.HandleError(err, "Unable to authenticate")
- }
-
- if loggedInUserDetails.LoginExpired {
- util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
- }
- accessToken = loggedInUserDetails.UserCredentials.JTWToken
- }
-
- if projectId == "" {
- workspaceFile, err := util.GetWorkSpaceFromFile()
- if err != nil {
- util.HandleError(err, "Unable to get local project details")
- }
-
- projectId = workspaceFile.WorkspaceId
- }
-
- httpClient := resty.New()
- httpClient.SetAuthToken(accessToken).
- SetHeader("Accept", "application/json")
-
- project, err := api.CallGetProjectById(httpClient, projectId)
- if err != nil {
- return false, err
- }
-
- for _, env := range project.Environments {
- if env.Slug == environmentSlug {
- return true, nil
- }
- }
- return false, nil
-}
-
func fetchAndFormatSecretsForShell(request models.GetAllSecretsParameters, projectConfigDir string, secretOverriding bool, token *models.TokenDetails) (models.InjectableEnvironmentResult, error) {
+
if token != nil && token.Type == util.SERVICE_TOKEN_IDENTIFIER {
request.InfisicalToken = token.Token
} else if token != nil && token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go
index 0e26e03b0..5a2a0ec24 100644
--- a/cli/packages/util/secrets.go
+++ b/cli/packages/util/secrets.go
@@ -232,6 +232,7 @@ func FilterSecretsByTag(plainTextSecrets []models.SingleEnvironmentVariable, tag
func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectConfigFilePath string) ([]models.SingleEnvironmentVariable, error) {
var secretsToReturn []models.SingleEnvironmentVariable
+ // var serviceTokenDetails api.GetServiceTokenDetailsResponse
var errorToReturn error
if params.InfisicalToken == "" && params.UniversalAuthAccessToken == "" {
diff --git a/cli/test/secrets_test.go b/cli/test/secrets_test.go
index 642d33277..f7f0f13ff 100644
--- a/cli/test/secrets_test.go
+++ b/cli/test/secrets_test.go
@@ -76,6 +76,7 @@ func TestUniversalAuth_SecretsGetWrongEnvironment(t *testing.T) {
if err != nil {
t.Fatalf("snapshot failed: %v", err)
}
+
}
func TestUserAuth_SecretsGetAll(t *testing.T) {
diff --git a/flake.nix b/flake.nix
index cc4cf8d65..094cfedc6 100644
--- a/flake.nix
+++ b/flake.nix
@@ -14,21 +14,11 @@
git
lazygit
- go
python312Full
nodejs_20
nodePackages.prettier
infisical
];
-
- env = {
- GOROOT = "${pkgs.go}/share/go";
- };
-
- shellHook = ''
- export GOPATH="$(pwd)/.go"
- mkdir -p "$GOPATH"
- '';
};
};
}
From c4da1ce32dc7a1007fa097a34635e0f1f80c3947 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 17 Mar 2025 23:38:24 +0530
Subject: [PATCH 16/18] feat: resolved PR feedbacks
---
.../server/plugins/auth/inject-identity.ts | 10 +++--
.../routes/v1/identity-oidc-auth-router.ts | 4 +-
.../identity-oidc-auth-types.ts | 2 +-
frontend/src/hooks/api/identities/types.ts | 4 +-
.../IdentitySection/IdentityOidcAuthForm.tsx | 38 ++++++++++---------
5 files changed, 32 insertions(+), 26 deletions(-)
diff --git a/backend/src/server/plugins/auth/inject-identity.ts b/backend/src/server/plugins/auth/inject-identity.ts
index 9005fbf96..5779b39f7 100644
--- a/backend/src/server/plugins/auth/inject-identity.ts
+++ b/backend/src/server/plugins/auth/inject-identity.ts
@@ -138,10 +138,12 @@ export const injectIdentity = fp(async (server: FastifyZodProvider) => {
identityName: identity.name,
authMethod: null
};
- requestContext.set("identityAuthInfo", {
- identityId: identity.identityId,
- oidc: token?.identityAuth?.oidc
- });
+ if (token?.identityAuth?.oidc) {
+ requestContext.set("identityAuthInfo", {
+ identityId: identity.identityId,
+ oidc: token?.identityAuth?.oidc
+ });
+ }
break;
}
case AuthMode.SERVICE_TOKEN: {
diff --git a/backend/src/server/routes/v1/identity-oidc-auth-router.ts b/backend/src/server/routes/v1/identity-oidc-auth-router.ts
index a27c12daf..f3098c851 100644
--- a/backend/src/server/routes/v1/identity-oidc-auth-router.ts
+++ b/backend/src/server/routes/v1/identity-oidc-auth-router.ts
@@ -105,7 +105,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: z.string().min(1).describe(OIDC_AUTH.ATTACH.boundIssuer),
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.ATTACH.boundAudiences),
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.boundClaims),
- claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.claimMetadataMapping),
+ claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.ATTACH.claimMetadataMapping).optional(),
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.ATTACH.boundSubject),
accessTokenTrustedIps: z
.object({
@@ -203,7 +203,7 @@ export const registerIdentityOidcAuthRouter = async (server: FastifyZodProvider)
boundIssuer: z.string().min(1).describe(OIDC_AUTH.UPDATE.boundIssuer),
boundAudiences: validateOidcAuthAudiencesField.describe(OIDC_AUTH.UPDATE.boundAudiences),
boundClaims: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.boundClaims),
- claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.claimMetadataMapping),
+ claimMetadataMapping: validateOidcBoundClaimsField.describe(OIDC_AUTH.UPDATE.claimMetadataMapping).optional(),
boundSubject: z.string().optional().default("").describe(OIDC_AUTH.UPDATE.boundSubject),
accessTokenTrustedIps: z
.object({
diff --git a/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts b/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts
index 7f9f62296..9727285e7 100644
--- a/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts
+++ b/backend/src/services/identity-oidc-auth/identity-oidc-auth-types.ts
@@ -7,7 +7,7 @@ export type TAttachOidcAuthDTO = {
boundIssuer: string;
boundAudiences: string;
boundClaims: Record;
- claimMetadataMapping: Record;
+ claimMetadataMapping?: Record;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
diff --git a/frontend/src/hooks/api/identities/types.ts b/frontend/src/hooks/api/identities/types.ts
index 9920fcf0b..86d35e086 100644
--- a/frontend/src/hooks/api/identities/types.ts
+++ b/frontend/src/hooks/api/identities/types.ts
@@ -193,7 +193,7 @@ export type IdentityOidcAuth = {
boundIssuer: string;
boundAudiences: string;
boundClaims: Record;
- claimMetadataMapping: Record;
+ claimMetadataMapping?: Record;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
@@ -209,7 +209,7 @@ export type AddIdentityOidcAuthDTO = {
boundIssuer: string;
boundAudiences: string;
boundClaims: Record;
- claimMetadataMapping: Record;
+ claimMetadataMapping?: Record;
boundSubject: string;
accessTokenTTL: number;
accessTokenMaxTTL: number;
diff --git a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx
index 094be734e..21440457a 100644
--- a/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx
+++ b/frontend/src/pages/organization/AccessManagementPage/components/OrgIdentityTab/components/IdentitySection/IdentityOidcAuthForm.tsx
@@ -52,12 +52,15 @@ const schema = z.object({
value: z.string()
})
),
- claimMetadataMapping: z.array(
- z.object({
- key: z.string(),
- value: z.string()
- })
- ),
+ claimMetadataMapping: z
+ .array(
+ z.object({
+ key: z.string(),
+ value: z.string()
+ })
+ )
+ .optional()
+ .nullable(),
boundSubject: z.string().optional().default("")
});
@@ -105,7 +108,6 @@ export const IdentityOidcAuthForm = ({
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
}
});
-
const {
fields: boundClaimsFields,
append: appendBoundClaimField,
@@ -141,10 +143,12 @@ export const IdentityOidcAuthForm = ({
key,
value
})),
- claimMetadataMapping: Object.entries(data.claimMetadataMapping).map(([key, value]) => ({
- key,
- value
- })),
+ claimMetadataMapping: data?.claimMetadataMapping
+ ? Object.entries(data.claimMetadataMapping).map(([key, value]) => ({
+ key,
+ value
+ }))
+ : undefined,
boundSubject: data.boundSubject,
accessTokenTTL: String(data.accessTokenTTL),
accessTokenMaxTTL: String(data.accessTokenMaxTTL),
@@ -200,9 +204,9 @@ export const IdentityOidcAuthForm = ({
boundIssuer,
boundAudiences,
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
- claimMetadataMapping: Object.fromEntries(
- claimMetadataMapping.map((entry) => [entry.key, entry.value])
- ),
+ claimMetadataMapping: claimMetadataMapping
+ ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value]))
+ : undefined,
boundSubject,
accessTokenTTL: Number(accessTokenTTL),
accessTokenMaxTTL: Number(accessTokenMaxTTL),
@@ -217,9 +221,9 @@ export const IdentityOidcAuthForm = ({
boundIssuer,
boundAudiences,
boundClaims: Object.fromEntries(boundClaims.map((entry) => [entry.key, entry.value])),
- claimMetadataMapping: Object.fromEntries(
- claimMetadataMapping.map((entry) => [entry.key, entry.value])
- ),
+ claimMetadataMapping: claimMetadataMapping
+ ? Object.fromEntries(claimMetadataMapping.map((entry) => [entry.key, entry.value]))
+ : undefined,
boundSubject,
organizationId: orgId,
accessTokenTTL: Number(accessTokenTTL),
From 9eb89bb46d93e49456213eed5406a8bce3e36b89 Mon Sep 17 00:00:00 2001
From: =
Date: Mon, 17 Mar 2025 23:52:11 +0530
Subject: [PATCH 17/18] fix: null causing ui error
---
.../ViewIdentityAuthModal/ViewIdentityOidcAuthContent.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityOidcAuthContent.tsx b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityOidcAuthContent.tsx
index 3eedf1411..116ecfd38 100644
--- a/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityOidcAuthContent.tsx
+++ b/frontend/src/pages/organization/IdentityDetailsByIDPage/components/ViewIdentityAuthModal/ViewIdentityOidcAuthContent.tsx
@@ -112,7 +112,7 @@ export const ViewIdentityOidcAuthContent = ({
)}
- {Object.keys(data.claimMetadataMapping).length && (
+ {data.claimMetadataMapping && Object.keys(data.claimMetadataMapping).length && (
Date: Mon, 17 Mar 2025 15:10:36 -0700
Subject: [PATCH 18/18] improvements: address feedback
---
docs/integrations/secret-syncs/aws-parameter-store.mdx | 4 +++-
docs/integrations/secret-syncs/aws-secrets-manager.mdx | 4 +++-
.../forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx | 4 ++--
.../SecretSyncOptionsSection/SecretSyncOptionsSection.tsx | 4 ++--
4 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/docs/integrations/secret-syncs/aws-parameter-store.mdx b/docs/integrations/secret-syncs/aws-parameter-store.mdx
index 831d0c752..fad37265a 100644
--- a/docs/integrations/secret-syncs/aws-parameter-store.mdx
+++ b/docs/integrations/secret-syncs/aws-parameter-store.mdx
@@ -43,7 +43,9 @@ description: "Learn how to configure an AWS Parameter Store Sync for Infisical."
- **KMS Key**: The AWS KMS key ID or alias to encrypt parameters with.
- **Tags**: Optional resource tags to add to parameters synced by Infisical.
- **Sync Secret Metadata as Resource Tags**: If enabled, metadata attached to secrets will be added as resource tags to parameters synced by Infisical.
- Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict.
+
+ Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict.
+
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
- **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
diff --git a/docs/integrations/secret-syncs/aws-secrets-manager.mdx b/docs/integrations/secret-syncs/aws-secrets-manager.mdx
index a301ee86b..8ed85be25 100644
--- a/docs/integrations/secret-syncs/aws-secrets-manager.mdx
+++ b/docs/integrations/secret-syncs/aws-secrets-manager.mdx
@@ -46,7 +46,9 @@ description: "Learn how to configure an AWS Secrets Manager Sync for Infisical."
- **KMS Key**: The AWS KMS key ID or alias to encrypt secrets with.
- **Tags**: Optional tags to add to secrets synced by Infisical.
- **Sync Secret Metadata as Tags**: If enabled, metadata attached to secrets will be added as tags to secrets synced by Infisical.
- Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict.
+
+ Manually configured tags from the **Tags** field will take precedence over secret metadata when tag keys conflict.
+
- **Auto-Sync Enabled**: If enabled, secrets will automatically be synced from the source location when changes occur. Disable to enforce manual syncing only.
- **Disable Secret Deletion**: If enabled, Infisical will not remove secrets from the sync destination. Enable this option if you intend to manage some secrets manually outside of Infisical.
diff --git a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx
index 6982d3b77..040944298 100644
--- a/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx
+++ b/frontend/src/components/secret-syncs/forms/SecretSyncReviewFields/SecretSyncReviewFields.tsx
@@ -113,8 +113,8 @@ export const SecretSyncReviewFields = () => {
{appendSuffix} */}
{AdditionalSyncOptionsFieldsComponent}
{disableSecretDeletion && (
-
- Enabled
+
+ Disabled
)}
diff --git a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx
index 139fcf916..351cb70e1 100644
--- a/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx
+++ b/frontend/src/pages/secret-manager/SecretSyncDetailsByIDPage/components/SecretSyncOptionsSection/SecretSyncOptionsSection.tsx
@@ -85,8 +85,8 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
{appendSuffix} */}
{AdditionalSyncOptionsComponent}
{disableSecretDeletion && (
-
- Enabled
+
+ Disabled
)}