diff --git a/app/javascript/dashboard/i18n/locale/en/settings.json b/app/javascript/dashboard/i18n/locale/en/settings.json index 1cc0c7236..891dd14e7 100644 --- a/app/javascript/dashboard/i18n/locale/en/settings.json +++ b/app/javascript/dashboard/i18n/locale/en/settings.json @@ -391,7 +391,8 @@ "TITLE": "SAML SSO", "NOTE": "Configure SAML single sign-on for your account. Users will authenticate through your identity provider instead of using email/password.", "ACS_URL": { - "LABEL": "ACS URL" + "LABEL": "ACS URL", + "TOOLTIP": "Assertion Consumer Service URL - Configure this URL in your IdP as the destination for SAML responses" }, "SSO_URL": { "LABEL": "SSO URL", @@ -404,13 +405,19 @@ "PLACEHOLDER": "-----BEGIN CERTIFICATE-----\nMIIC..." }, "FINGERPRINT": { - "LABEL": "Fingerprint" + "LABEL": "Fingerprint", + "TOOLTIP": "SHA-1 fingerprint of the certificate - Use this to verify the certificate in your IdP configuration" }, "COPY_SUCCESS": "Copied to clipboard", "SP_ENTITY_ID": { - "LABEL": "Service Provider Entity ID", - "HELP": "Unique identifier for this application as a service provider. Leave blank to use default.", - "PLACEHOLDER": "my-unique-sp" + "LABEL": "SP Entity ID", + "HELP": "Unique identifier for this application as a service provider (auto-generated).", + "TOOLTIP": "Unique identifier for Chatwoot as the Service Provider - Configure this in your IdP settings" + }, + "IDP_ENTITY_ID": { + "LABEL": "Identity Provider Entity ID", + "HELP": "Unique identifier for your identity provider (usually found in IdP configuration)", + "PLACEHOLDER": "https://your-idp.com/saml" }, "UPDATE_BUTTON": "Update SAML Settings", "API": { @@ -420,7 +427,7 @@ "DISABLED": "SAML settings disabled successfully" }, "VALIDATION": { - "REQUIRED_FIELDS": "SSO URL and Certificate are required fields" + "REQUIRED_FIELDS": "SSO URL, Identity Provider Entity ID, and Certificate are required fields" }, "ENTERPRISE_PAYWALL": { "AVAILABLE_ON": "The SAML SSO feature is only available in the Enterprise plans.", @@ -437,6 +444,10 @@ "ATTRIBUTE_MAPPING": { "TITLE": "SAML Attribute Setup", "DESCRIPTION": "The following attribute mappings must be configured in your identity provider" + }, + "INFO_SECTION": { + "TITLE": "Service Provider Information", + "TOOLTIP": "Copy these values and configure them in your Identity Provider to establish the SAML connection" } } }, diff --git a/app/javascript/dashboard/routes/dashboard/settings/security/components/SamlInfoSection.vue b/app/javascript/dashboard/routes/dashboard/settings/security/components/SamlInfoSection.vue index 3e37af86b..ea8546fe3 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/security/components/SamlInfoSection.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/security/components/SamlInfoSection.vue @@ -6,11 +6,15 @@ import { useAlert } from 'dashboard/composables'; import { useAccount } from 'dashboard/composables/useAccount'; import NextButton from 'next/button/Button.vue'; -defineProps({ +const props = defineProps({ fingerprint: { type: String, default: '', }, + spEntityId: { + type: String, + default: '', + }, }); const { t } = useI18n(); @@ -21,6 +25,34 @@ const acsUrl = computed(() => { return `${currentHost}/omniauth/saml/callback?account_id=${accountId.value}`; }); +const allInfoItems = computed(() => [ + { + key: 'ACS_URL', + label: t('SECURITY_SETTINGS.SAML.ACS_URL.LABEL'), + value: acsUrl.value, + tooltip: t('SECURITY_SETTINGS.SAML.ACS_URL.TOOLTIP'), + show: true, + }, + { + key: 'SP_ENTITY_ID', + label: t('SECURITY_SETTINGS.SAML.SP_ENTITY_ID.LABEL'), + value: props.spEntityId, + tooltip: t('SECURITY_SETTINGS.SAML.SP_ENTITY_ID.TOOLTIP'), + show: !!props.spEntityId, + }, + { + key: 'FINGERPRINT', + label: t('SECURITY_SETTINGS.SAML.FINGERPRINT.LABEL'), + value: props.fingerprint, + tooltip: t('SECURITY_SETTINGS.SAML.FINGERPRINT.TOOLTIP'), + show: !!props.fingerprint, + }, +]); + +const visibleInfoItems = computed(() => + allInfoItems.value.filter(item => item.show) +); + const handleCopy = async text => { await copyTextToClipboard(text); useAlert(t('SECURITY_SETTINGS.SAML.COPY_SUCCESS')); @@ -28,43 +60,43 @@ const handleCopy = async text => { diff --git a/app/javascript/dashboard/routes/dashboard/settings/security/components/SamlSettings.vue b/app/javascript/dashboard/routes/dashboard/settings/security/components/SamlSettings.vue index cbc5b9354..edfacf266 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/security/components/SamlSettings.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/security/components/SamlSettings.vue @@ -22,6 +22,7 @@ const ssoUrl = ref(''); const certificate = ref(''); const fingerprint = ref(''); const spEntityId = ref(''); +const idpEntityId = ref(''); const roleMappings = ref({}); const isEnabled = ref(false); const isSubmitting = ref(false); @@ -42,6 +43,7 @@ const loadSamlSettings = async () => { ssoUrl.value = settings.sso_url; certificate.value = settings.certificate || ''; spEntityId.value = settings.sp_entity_id || ''; + idpEntityId.value = settings.idp_entity_id || ''; roleMappings.value = settings.role_mappings || {}; fingerprint.value = settings.fingerprint || ''; isEnabled.value = ssoUrl.value !== ''; @@ -72,7 +74,7 @@ const saveSamlSettings = async settings => { // Update local state with response data including fingerprint and id if (response?.data) { id.value = response.data.id; - fingerprint.value = response.data.certificate_fingerprint || ''; + fingerprint.value = response.data.fingerprint || ''; } useAlert(t('SECURITY_SETTINGS.SAML.API.SUCCESS')); @@ -90,7 +92,7 @@ const saveSamlSettings = async settings => { }; const handleSubmit = async () => { - if (!ssoUrl.value || !certificate.value) { + if (!ssoUrl.value || !certificate.value || !idpEntityId.value) { useAlert(t('SECURITY_SETTINGS.SAML.VALIDATION.REQUIRED_FIELDS')); return; } @@ -98,7 +100,7 @@ const handleSubmit = async () => { const settings = { sso_url: ssoUrl.value, certificate: certificate.value, - sp_entity_id: spEntityId.value, + idp_entity_id: idpEntityId.value, role_mappings: roleMappings.value, }; @@ -110,6 +112,7 @@ const handleDisable = async () => { ssoUrl.value = ''; certificate.value = ''; spEntityId.value = ''; + idpEntityId.value = ''; fingerprint.value = ''; roleMappings.value = {}; @@ -144,7 +147,11 @@ onMounted(() => { - +
@@ -163,13 +170,15 @@ onMounted(() => {