diff --git a/web/messages/en/openid.json b/web/messages/en/openid.json index c7033c4df4..8c673f46df 100644 --- a/web/messages/en/openid.json +++ b/web/messages/en/openid.json @@ -62,6 +62,12 @@ "settings_openid_provider_delete_confirm_body": "Are you sure you want to delete this external identity provider? This action cannot be undone.", "settings_openid_provider_delete_success": "External identity provider deleted", "settings_openid_provider_delete_failed": "Failed to delete external identity provider", + "settings_openid_provider_validation_success_title": "External ID provider successfully added.", + "settings_openid_provider_validation_success_body": "The connection to the external identity provider has been successfully verified.", + "settings_openid_provider_validation_success_detail": "The connection to your external identity provider is now verified. Your users can now log in using this provider for a faster and more convenient authentication experience.", + "settings_openid_provider_validation_failure_title": "Connection test failed.", + "settings_openid_provider_validation_failure_body": "The provider was saved but the connection to the directory synchronization API could not be verified. Go back to fix the configuration, or finish to keep the provider without directory sync.", + "settings_openid_provider_validation_failure_error": "Error: {message}", "openid_consent_title": "{name} would like to", "openid_consent_scope_openid": "Use OpenID.", "openid_consent_scope_profile": "Know basic information from your profile.", diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/AddExternalOpenIdDirectoryStep.tsx b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/AddExternalOpenIdDirectoryStep.tsx index 9c1ad1c669..f30f33204c 100644 --- a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/AddExternalOpenIdDirectoryStep.tsx +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/AddExternalOpenIdDirectoryStep.tsx @@ -47,7 +47,7 @@ export const AddExternalOpenIdDirectoryStep = () => { const state = useAddExternalOpenIdStore.getState(); const providerState = state.providerState; const provider = state.provider; - const submitValues = { ...cloneDeep(providerState), value, kind: provider }; + const submitValues = { ...cloneDeep(providerState), ...value, kind: provider }; await mutateAsync(submitValues); }, [mutateAsync], diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdValidationStep/AddExternalOpenIdValidationStep.tsx b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdValidationStep/AddExternalOpenIdValidationStep.tsx index 63cca592ad..498ff8a6da 100644 --- a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdValidationStep/AddExternalOpenIdValidationStep.tsx +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdValidationStep/AddExternalOpenIdValidationStep.tsx @@ -37,30 +37,26 @@ export const AddExternalOpenIdValidationStep = () => { {result && ( <> - {'External ID provider successfully added.'} + {m.settings_openid_provider_validation_success_title()} - { - 'The connection to the external identity provider has been successfully verified.' - } + {m.settings_openid_provider_validation_success_body()} - {`The connection to your external identity provider is now verified. Your users can now log in using this provider for a faster and more convenient authentication experience.`} + {m.settings_openid_provider_validation_success_detail()} )} {!result && ( <> - {'External ID provider successfully added.'} + {m.settings_openid_provider_validation_failure_title()} - { - 'The connection to the external identity provider has been successfully verified.' - } + {m.settings_openid_provider_validation_failure_body()} {isPresent(message) && ( <> @@ -68,7 +64,7 @@ export const AddExternalOpenIdValidationStep = () => { )} diff --git a/web/src/pages/CERulePage/CERulePage.tsx b/web/src/pages/CERulePage/CERulePage.tsx index 84affcf988..a7d4703af0 100644 --- a/web/src/pages/CERulePage/CERulePage.tsx +++ b/web/src/pages/CERulePage/CERulePage.tsx @@ -828,6 +828,7 @@ const Content = ({ rule: initialRule, tab }: Props) => { )} @@ -856,6 +857,7 @@ const Content = ({ rule: initialRule, tab }: Props) => { )} diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/SettingsEditOpenIdProviderPage.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/SettingsEditOpenIdProviderPage.tsx index 8c5ebe48a8..ab69966e4d 100644 --- a/web/src/pages/settings/SettingsEditOpenIdProviderPage/SettingsEditOpenIdProviderPage.tsx +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/SettingsEditOpenIdProviderPage.tsx @@ -38,9 +38,6 @@ export const SettingsEditOpenIdProviderPage = () => { const { mutateAsync } = useMutation({ mutationFn: api.openIdProvider.editOpenIdProvider, - onSuccess: () => { - router.history.back(); - }, meta: { invalidate: [['settings'], ['info'], ['openid']], }, @@ -68,10 +65,28 @@ export const SettingsEditOpenIdProviderPage = () => { ...formData, directory_sync_group_match: joinCsv(formData.directory_sync_group_match), }; - await mutateAsync({ ...normalizedFormData, ...values }); + const submitValues = { ...normalizedFormData, ...values }; + await mutateAsync(submitValues); + if (values.directory_sync_enabled) { + try { + const { data: result } = await api.openIdProvider.testDirectorySync(); + if (!result.success) { + await mutateAsync({ ...submitValues, directory_sync_enabled: false }); + Snackbar.error( + result.message ?? m.settings_openid_provider_validation_failure_title(), + ); + return; + } + } catch (_) { + await mutateAsync({ ...submitValues, directory_sync_enabled: false }); + Snackbar.error(m.settings_openid_provider_validation_failure_title()); + return; + } + } + router.history.back(); } }, - [formData, mutateAsync], + [formData, mutateAsync, router], ); if (!formData) return null; diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditGoogleProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditGoogleProviderForm.tsx index 7c169c4b6d..09ad22fe1e 100644 --- a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditGoogleProviderForm.tsx +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditGoogleProviderForm.tsx @@ -7,6 +7,7 @@ import { EditPageControls } from '../../../../shared/components/EditPageControls import { EditPageFormSection } from '../../../../shared/components/EditPageFormSection/EditPageFormSection'; import { Fold } from '../../../../shared/defguard-ui/components/Fold/Fold'; import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { Snackbar } from '../../../../shared/defguard-ui/providers/snackbar/snackbar'; import { ThemeSpacing } from '../../../../shared/defguard-ui/types'; import { useAppForm } from '../../../../shared/form'; import { formChangeLogic } from '../../../../shared/formLogic'; @@ -38,6 +39,31 @@ const discriminatedSchema = z.discriminatedUnion('directory_sync_enabled', [ syncSchema, ]); +const validationSchema = syncSchema + .omit({ admin_email: true, google_service_account_file: true }) + .extend({ + admin_email: z.string().trim(), + google_service_account_file: z.file(m.form_error_required()).nullable(), + }) + .superRefine((val, ctx) => { + if (val.directory_sync_enabled) { + if (val.admin_email.trim().length === 0) { + ctx.addIssue({ + path: ['admin_email'], + code: 'custom', + message: m.form_error_required(), + }); + } + if (val.google_service_account_file === null) { + ctx.addIssue({ + path: ['google_service_account_file'], + code: 'custom', + message: m.form_error_required(), + }); + } + } + }); + type FormFields = z.infer; export const EditGoogleProviderForm = ({ @@ -72,17 +98,21 @@ export const EditGoogleProviderForm = ({ defaultValues, validationLogic: formChangeLogic, validators: { - onSubmit: syncSchema, - onChange: syncSchema, + onSubmit: validationSchema, + onChange: validationSchema, }, onSubmit: async ({ value }) => { if (value.directory_sync_enabled) { const inner = value as z.infer; const file = await parseGoogleKeyFile(inner.google_service_account_file as File); + if (!file) { + Snackbar.error(m.form_error_file_contents()); + return; + } await onSubmit({ ...omit(inner, ['google_service_account_file']), - google_service_account_email: file?.client_email, - google_service_account_key: file?.private_key, + google_service_account_email: file.client_email, + google_service_account_key: file.private_key, }); } else { await onSubmit(omit(value, ['google_service_account_file'])); diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditJumpCloudProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditJumpCloudProviderForm.tsx index 9889d9164e..c7bb77875e 100644 --- a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditJumpCloudProviderForm.tsx +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditJumpCloudProviderForm.tsx @@ -33,6 +33,19 @@ const discriminatedSchema = z.discriminatedUnion('directory_sync_enabled', [ syncSchema, ]); +const validationSchema = syncSchema + .omit({ jumpcloud_api_key: true }) + .extend({ jumpcloud_api_key: z.string() }) + .superRefine((val, ctx) => { + if (val.directory_sync_enabled && val.jumpcloud_api_key.trim().length === 0) { + ctx.addIssue({ + path: ['jumpcloud_api_key'], + code: 'custom', + message: m.form_error_required(), + }); + } + }); + type FormFields = z.infer; export const EditJumpCloudProviderForm = ({ @@ -60,8 +73,8 @@ export const EditJumpCloudProviderForm = ({ defaultValues, validationLogic: formChangeLogic, validators: { - onSubmit: syncSchema, - onChange: syncSchema, + onSubmit: validationSchema, + onChange: validationSchema, }, onSubmit: async ({ value }) => { await onSubmit(value); @@ -109,17 +122,6 @@ export const EditJumpCloudProviderForm = ({ )} - - {(field) => ( - - )} - - {(field) => ( )} + + + {(field) => ( + + )} + )} diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditMicrosoftProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditMicrosoftProviderForm.tsx index 3ca2c5239b..2ac8f449b9 100644 --- a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditMicrosoftProviderForm.tsx +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditMicrosoftProviderForm.tsx @@ -109,6 +109,7 @@ export const EditMicrosoftProviderForm = ({ /> )} + {(field) => (