From 636aee2ea98ae93058748d605e73c5406ad78cbe Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Tue, 22 Apr 2025 17:36:18 -0700 Subject: [PATCH] improvements: address feedback --- .../src/ee/services/license/license-fns.ts | 2 +- .../ldap-password-rotation-constants.ts | 2 +- .../ldap-password-rotation-schemas.ts | 2 +- .../secret-rotation-v2-maps.ts | 4 +- .../general/password-requirements-schema.ts | 3 ++ .../routes/v1/app-connection-routers/index.ts | 2 +- .../ldap-connection-router.ts | 2 +- .../app-connection/app-connection-enums.ts | 2 +- .../app-connection/app-connection-fns.ts | 4 +- .../app-connection/app-connection-maps.ts | 2 +- .../app-connection/app-connection-service.ts | 2 +- .../ldap/ldap-connection-fns.ts | 2 +- .../ldap/ldap-connection-schemas.ts | 21 ++++++---- .../ldap/ldap-connection-types.ts | 2 +- .../secret-rotation/ldap-password.mdx | 8 ++-- docs/integrations/app-connections/ldap.mdx | 2 +- .../LdapPasswordRotationParametersFields.tsx | 38 ++----------------- .../shared/password-requirements-schema.ts | 11 +++++- frontend/src/helpers/appConnections.ts | 2 +- frontend/src/helpers/secretRotationsV2.ts | 2 +- .../src/hooks/api/appConnections/enums.ts | 2 +- .../api/appConnections/types/app-options.ts | 4 +- .../hooks/api/appConnections/types/index.ts | 2 +- .../appConnections/types/ldap-connection.ts | 2 +- .../types/ldap-password-rotation.ts | 2 +- .../AppConnectionForm/AppConnectionForm.tsx | 4 +- .../LdapConnectionFields.tsx | 19 ++++++---- 27 files changed, 70 insertions(+), 80 deletions(-) diff --git a/backend/src/ee/services/license/license-fns.ts b/backend/src/ee/services/license/license-fns.ts index 3f4af174b..21d378802 100644 --- a/backend/src/ee/services/license/license-fns.ts +++ b/backend/src/ee/services/license/license-fns.ts @@ -39,7 +39,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({ trial_end: null, has_used_trial: true, secretApproval: false, - secretRotation: false, + secretRotation: true, caCrl: false, instanceUserManagement: false, externalKms: false, diff --git a/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-constants.ts b/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-constants.ts index b46165c71..061bf11ea 100644 --- a/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-constants.ts +++ b/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-constants.ts @@ -5,7 +5,7 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums export const LDAP_PASSWORD_ROTATION_LIST_OPTION: TSecretRotationV2ListItem = { name: "LDAP Password", type: SecretRotation.LdapPassword, - connection: AppConnection.Ldap, + connection: AppConnection.LDAP, template: { secretsMapping: { dn: "LDAP_DN", diff --git a/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-schemas.ts b/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-schemas.ts index 2fc832c14..9441a4cb9 100644 --- a/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-schemas.ts +++ b/backend/src/ee/services/secret-rotation-v2/ldap-password/ldap-password-rotation-schemas.ts @@ -59,7 +59,7 @@ export const UpdateLdapPasswordRotationSchema = BaseUpdateSecretRotationSchema(S export const LdapPasswordRotationListItemSchema = z.object({ name: z.literal("LDAP Password"), - connection: z.literal(AppConnection.Ldap), + connection: z.literal(AppConnection.LDAP), type: z.literal(SecretRotation.LdapPassword), template: LdapPasswordRotationTemplateSchema }); diff --git a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts index c1bdcce99..0b9fc29f0 100644 --- a/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts +++ b/backend/src/ee/services/secret-rotation-v2/secret-rotation-v2-maps.ts @@ -3,7 +3,7 @@ import { AppConnection } from "@app/services/app-connection/app-connection-enums export const SECRET_ROTATION_NAME_MAP: Record = { [SecretRotation.PostgresCredentials]: "PostgreSQL Credentials", - [SecretRotation.MsSqlCredentials]: "Microsoft SQL Sever Credentials", + [SecretRotation.MsSqlCredentials]: "Microsoft SQL Server Credentials", [SecretRotation.Auth0ClientSecret]: "Auth0 Client Secret", [SecretRotation.LdapPassword]: "LDAP Password" }; @@ -12,5 +12,5 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record { + return Object.values(data.required).some((count) => count > 0); + }, "At least one character type must be required") .refine((data) => { const total = Object.values(data.required).reduce((sum, count) => sum + count, 0); return total <= data.length; diff --git a/backend/src/server/routes/v1/app-connection-routers/index.ts b/backend/src/server/routes/v1/app-connection-routers/index.ts index 9c6b0065e..c253de237 100644 --- a/backend/src/server/routes/v1/app-connection-routers/index.ts +++ b/backend/src/server/routes/v1/app-connection-routers/index.ts @@ -34,5 +34,5 @@ export const APP_CONNECTION_REGISTER_ROUTER_MAP: Record { registerAppConnectionEndpoints({ - app: AppConnection.Ldap, + app: AppConnection.LDAP, server, sanitizedResponseSchema: SanitizedLdapConnectionSchema, createSchema: CreateLdapConnectionSchema, diff --git a/backend/src/services/app-connection/app-connection-enums.ts b/backend/src/services/app-connection/app-connection-enums.ts index b4404c394..5960da356 100644 --- a/backend/src/services/app-connection/app-connection-enums.ts +++ b/backend/src/services/app-connection/app-connection-enums.ts @@ -13,7 +13,7 @@ export enum AppConnection { Camunda = "camunda", Windmill = "windmill", Auth0 = "auth0", - Ldap = "ldap" + LDAP = "ldap" } export enum AWSRegion { diff --git a/backend/src/services/app-connection/app-connection-fns.ts b/backend/src/services/app-connection/app-connection-fns.ts index e444ea21a..ba0ba260e 100644 --- a/backend/src/services/app-connection/app-connection-fns.ts +++ b/backend/src/services/app-connection/app-connection-fns.ts @@ -138,7 +138,7 @@ export const validateAppConnectionCredentials = async ( [AppConnection.TerraformCloud]: validateTerraformCloudConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Auth0]: validateAuth0ConnectionCredentials as TAppConnectionCredentialsValidator, [AppConnection.Windmill]: validateWindmillConnectionCredentials as TAppConnectionCredentialsValidator, - [AppConnection.Ldap]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator + [AppConnection.LDAP]: validateLdapConnectionCredentials as TAppConnectionCredentialsValidator }; return VALIDATE_APP_CONNECTION_CREDENTIALS_MAP[appConnection.app](appConnection); @@ -220,5 +220,5 @@ export const TRANSITION_CONNECTION_CREDENTIALS_TO_PLATFORM: Record< [AppConnection.Vercel]: platformManagedCredentialsNotSupported, [AppConnection.Windmill]: platformManagedCredentialsNotSupported, [AppConnection.Auth0]: platformManagedCredentialsNotSupported, - [AppConnection.Ldap]: platformManagedCredentialsNotSupported // we could support this in the future + [AppConnection.LDAP]: platformManagedCredentialsNotSupported // we could support this in the future }; diff --git a/backend/src/services/app-connection/app-connection-maps.ts b/backend/src/services/app-connection/app-connection-maps.ts index 886aa4955..b98636dd1 100644 --- a/backend/src/services/app-connection/app-connection-maps.ts +++ b/backend/src/services/app-connection/app-connection-maps.ts @@ -15,5 +15,5 @@ export const APP_CONNECTION_NAME_MAP: Record = { [AppConnection.Camunda]: "Camunda", [AppConnection.Windmill]: "Windmill", [AppConnection.Auth0]: "Auth0", - [AppConnection.Ldap]: "LDAP" + [AppConnection.LDAP]: "LDAP" }; diff --git a/backend/src/services/app-connection/app-connection-service.ts b/backend/src/services/app-connection/app-connection-service.ts index 27152da52..f2a838972 100644 --- a/backend/src/services/app-connection/app-connection-service.ts +++ b/backend/src/services/app-connection/app-connection-service.ts @@ -76,7 +76,7 @@ const VALIDATE_APP_CONNECTION_CREDENTIALS_MAP: Record { return { name: "LDAP" as const, - app: AppConnection.Ldap as const, + app: AppConnection.LDAP as const, methods: Object.values(LdapConnectionMethod) as [LdapConnectionMethod.SimpleBind] }; }; diff --git a/backend/src/services/app-connection/ldap/ldap-connection-schemas.ts b/backend/src/services/app-connection/ldap/ldap-connection-schemas.ts index 6cd957c15..3943fa2ff 100644 --- a/backend/src/services/app-connection/ldap/ldap-connection-schemas.ts +++ b/backend/src/services/app-connection/ldap/ldap-connection-schemas.ts @@ -12,7 +12,12 @@ import { LdapConnectionMethod, LdapProvider } from "./ldap-connection-enums"; export const LdapConnectionSimpleBindCredentialsSchema = z.object({ provider: z.nativeEnum(LdapProvider).describe(AppConnections.CREDENTIALS.LDAP.provider), - url: z.string().trim().min(1, "URL required").describe(AppConnections.CREDENTIALS.LDAP.url), + url: z + .string() + .trim() + .min(1, "URL required") + .refine((value) => value.startsWith("ldap://") || value.startsWith("ldaps://")) + .describe(AppConnections.CREDENTIALS.LDAP.url), dn: z.string().trim().min(1, "Distinguished Name (DN) required").describe(AppConnections.CREDENTIALS.LDAP.dn), password: z.string().trim().min(1, "Password required").describe(AppConnections.CREDENTIALS.LDAP.password), sslRejectUnauthorized: z.boolean().optional().describe(AppConnections.CREDENTIALS.LDAP.sslRejectUnauthorized), @@ -25,7 +30,7 @@ export const LdapConnectionSimpleBindCredentialsSchema = z.object({ }); const BaseLdapConnectionSchema = BaseAppConnectionSchema.extend({ - app: z.literal(AppConnection.Ldap) + app: z.literal(AppConnection.LDAP) }); export const LdapConnectionSchema = z.intersection( @@ -54,28 +59,28 @@ export const SanitizedLdapConnectionSchema = z.discriminatedUnion("method", [ export const ValidateLdapConnectionCredentialsSchema = z.discriminatedUnion("method", [ z.object({ - method: z.literal(LdapConnectionMethod.SimpleBind).describe(AppConnections.CREATE(AppConnection.Ldap).method), + method: z.literal(LdapConnectionMethod.SimpleBind).describe(AppConnections.CREATE(AppConnection.LDAP).method), credentials: LdapConnectionSimpleBindCredentialsSchema.describe( - AppConnections.CREATE(AppConnection.Ldap).credentials + AppConnections.CREATE(AppConnection.LDAP).credentials ) }) ]); export const CreateLdapConnectionSchema = ValidateLdapConnectionCredentialsSchema.and( - GenericCreateAppConnectionFieldsSchema(AppConnection.Ldap) + GenericCreateAppConnectionFieldsSchema(AppConnection.LDAP) ); export const UpdateLdapConnectionSchema = z .object({ credentials: LdapConnectionSimpleBindCredentialsSchema.optional().describe( - AppConnections.UPDATE(AppConnection.Ldap).credentials + AppConnections.UPDATE(AppConnection.LDAP).credentials ) }) - .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.Ldap)); + .and(GenericUpdateAppConnectionFieldsSchema(AppConnection.LDAP)); export const LdapConnectionListItemSchema = z.object({ name: z.literal("LDAP"), - app: z.literal(AppConnection.Ldap), + app: z.literal(AppConnection.LDAP), // the below is preferable but currently breaks with our zod to json schema parser // methods: z.tuple([z.literal(AwsConnectionMethod.ServicePrincipal), z.literal(AwsConnectionMethod.AccessKey)]), methods: z.nativeEnum(LdapConnectionMethod).array() diff --git a/backend/src/services/app-connection/ldap/ldap-connection-types.ts b/backend/src/services/app-connection/ldap/ldap-connection-types.ts index 4d324a932..ee69b2542 100644 --- a/backend/src/services/app-connection/ldap/ldap-connection-types.ts +++ b/backend/src/services/app-connection/ldap/ldap-connection-types.ts @@ -12,7 +12,7 @@ import { export type TLdapConnection = z.infer; export type TLdapConnectionInput = z.infer & { - app: AppConnection.Ldap; + app: AppConnection.LDAP; }; export type TValidateLdapConnectionCredentialsSchema = typeof ValidateLdapConnectionCredentialsSchema; diff --git a/docs/documentation/platform/secret-rotation/ldap-password.mdx b/docs/documentation/platform/secret-rotation/ldap-password.mdx index 53802b8a9..103fe4656 100644 --- a/docs/documentation/platform/secret-rotation/ldap-password.mdx +++ b/docs/documentation/platform/secret-rotation/ldap-password.mdx @@ -114,8 +114,8 @@ description: "Learn how to automatically rotate LDAP passwords." { "secretRotation": { "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a", - "name": "my-auth0-rotation", - "description": "my client secret rotation", + "name": "my-ldap-rotation", + "description": "my ldap password rotation", "secretsMapping": { "dn": "LDAP_DN", "password": "LDAP_PASSWORD" @@ -133,8 +133,8 @@ description: "Learn how to automatically rotate LDAP passwords." "lastRotationJobId": "3c90c3cc-0d44-4b50-8888-8dd25736052a", "nextRotationAt": "2023-11-07T05:31:56Z", "connection": { - "app": "auth0", - "name": "my-auth0-connection", + "app": "ldap", + "name": "my-ldap-connection", "id": "3c90c3cc-0d44-4b50-8888-8dd25736052a" }, "environment": { diff --git a/docs/integrations/app-connections/ldap.mdx b/docs/integrations/app-connections/ldap.mdx index de96173cc..63c4bfed1 100644 --- a/docs/integrations/app-connections/ldap.mdx +++ b/docs/integrations/app-connections/ldap.mdx @@ -62,7 +62,7 @@ Depending on how you intend to use your LDAP connection, there may be additional "provider": "active-directory", "url": "ldaps://domain-or-ip:636", "dn": "CN=John,CN=Users,DC=example,DC=com", - "password": "my-strong-password", + "password": "", "sslRejectUnauthorized": true, "sslCertificate": "..." } diff --git a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/LdapPasswordRotationParametersFields.tsx b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/LdapPasswordRotationParametersFields.tsx index 135abff4e..9c9d8329f 100644 --- a/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/LdapPasswordRotationParametersFields.tsx +++ b/frontend/src/components/secret-rotations-v2/forms/SecretRotationV2ParametersFields/LdapPasswordRotationParametersFields.tsx @@ -1,10 +1,8 @@ import { Controller, useFormContext } from "react-hook-form"; -import { faCircleInfo } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { TSecretRotationV2Form } from "@app/components/secret-rotations-v2/forms/schemas"; import { DEFAULT_PASSWORD_REQUIREMENTS } from "@app/components/secret-rotations-v2/forms/schemas/shared"; -import { FormControl, Input, Tooltip } from "@app/components/v2"; +import { FormControl, Input } from "@app/components/v2"; import { SecretRotation } from "@app/hooks/api/secretRotationsV2"; export const LdapPasswordRotationParametersFields = () => { @@ -24,37 +22,7 @@ export const LdapPasswordRotationParametersFields = () => { isError={Boolean(error)} errorText={error?.message} label="Distinguished Name (DN)" - helperText={ - - Ensure that your connection has the{" "} - read_clients permission and the - application exists in the connection's audience. - - } - > -
- Don't see the application you're looking for?{" "} - -
-
- } > - {/* client.id === value) ?? null} - onChange={(option) => { - onChange((option as SingleValue)?.id ?? null); - }} - options={clients} - placeholder="Select an application..." - getOptionLabel={(option) => option.name} - getOptionValue={(option) => option.id} - /> */} { defaultValue={DEFAULT_PASSWORD_REQUIREMENTS.length} render={({ field, fieldState: { error } }) => ( { placeholder="-_.~!*" size="sm" {...field} - onChange={(e) => field.onChange(Number(e.target.value))} + onChange={(e) => field.onChange(e.target.value)} /> )} diff --git a/frontend/src/components/secret-rotations-v2/forms/schemas/shared/password-requirements-schema.ts b/frontend/src/components/secret-rotations-v2/forms/schemas/shared/password-requirements-schema.ts index 640ecdd88..05b673ab6 100644 --- a/frontend/src/components/secret-rotations-v2/forms/schemas/shared/password-requirements-schema.ts +++ b/frontend/src/components/secret-rotations-v2/forms/schemas/shared/password-requirements-schema.ts @@ -15,8 +15,17 @@ export const PasswordRequirementsSchema = z allowedSymbols: z .string() .optional() - .transform((value) => value ?? "-_.~!*") + .transform((value) => value || "-_.~!*") }) + .refine( + (data) => { + return Object.values(data.required).some((count) => count > 0); + }, + { + message: "At least one character type must be required", + path: ["required.digits"] + } + ) .refine( (data) => { const total = Object.values(data.required).reduce((sum, count) => sum + count, 0); diff --git a/frontend/src/helpers/appConnections.ts b/frontend/src/helpers/appConnections.ts index ef8329b97..64ae2a699 100644 --- a/frontend/src/helpers/appConnections.ts +++ b/frontend/src/helpers/appConnections.ts @@ -52,7 +52,7 @@ export const APP_CONNECTION_MAP: Record< [AppConnection.Camunda]: { name: "Camunda", image: "Camunda.png" }, [AppConnection.Windmill]: { name: "Windmill", image: "Windmill.png" }, [AppConnection.Auth0]: { name: "Auth0", image: "Auth0.png", size: 40 }, - [AppConnection.Ldap]: { name: "LDAP", image: "LDAP.png", size: 65 } + [AppConnection.LDAP]: { name: "LDAP", image: "LDAP.png", size: 65 } }; export const getAppConnectionMethodDetails = (method: TAppConnection["method"]) => { diff --git a/frontend/src/helpers/secretRotationsV2.ts b/frontend/src/helpers/secretRotationsV2.ts index 73b6e141c..92e5d5257 100644 --- a/frontend/src/helpers/secretRotationsV2.ts +++ b/frontend/src/helpers/secretRotationsV2.ts @@ -31,7 +31,7 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record { return ; case AppConnection.Auth0: return ; - case AppConnection.Ldap: + case AppConnection.LDAP: return ; default: throw new Error(`Unhandled App ${app}`); @@ -156,7 +156,7 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => { return ; case AppConnection.Auth0: return ; - case AppConnection.Ldap: + case AppConnection.LDAP: return ; default: throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`); diff --git a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/LdapConnectionFields.tsx b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/LdapConnectionFields.tsx index ccc433895..308ac6f33 100644 --- a/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/LdapConnectionFields.tsx +++ b/frontend/src/pages/organization/AppConnections/AppConnectionsPage/components/AppConnectionForm/LdapConnectionFields.tsx @@ -37,7 +37,7 @@ type Props = { }; const rootSchema = genericAppConnectionFieldsSchema.extend({ - app: z.literal(AppConnection.Ldap) + app: z.literal(AppConnection.LDAP) }); const formSchema = z.discriminatedUnion("method", [ @@ -45,7 +45,12 @@ const formSchema = z.discriminatedUnion("method", [ method: z.literal(LdapConnectionMethod.SimpleBind), credentials: z.object({ provider: z.nativeEnum(LdapConnectionProvider), - url: z.string().url().trim().min(1, "LDAP URL required"), + url: z + .string() + .regex(/^ldaps?:\/\//, 'Must start with "ldaps://" or "ldap://"') + .url() + .trim() + .min(1, "LDAP URL required"), dn: z.string().trim().min(1, "Distinguished Name (DN) required"), password: z.string().trim().min(1, "Password required"), sslRejectUnauthorized: z.boolean(), @@ -67,7 +72,7 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => { const form = useForm({ resolver: zodResolver(formSchema), defaultValues: appConnection ?? { - app: AppConnection.Ldap, + app: AppConnection.LDAP, method: LdapConnectionMethod.SimpleBind, credentials: { provider: LdapConnectionProvider.ActiveDirectory, @@ -88,7 +93,7 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => { } = form; const selectedProvider = watch("credentials.provider"); - const sslEnabled = watch("credentials.url").startsWith("ldaps://"); + const sslEnabled = watch("credentials.url")?.startsWith("ldaps://") ?? false; return ( @@ -106,7 +111,7 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => { render={({ field: { value, onChange }, fieldState: { error } }) => ( { @@ -263,7 +268,7 @@ export const LdapConnectionForm = ({ appConnection, onSubmit }: Props) => { control={control} render={({ field: { value, onChange }, fieldState: { error } }) => (