feat: handle idp and sp entity ids separately
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
+68
-36
@@ -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 => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
class="rounded-xl border border-n-weak bg-n-solid-1 w-full text-sm text-n-slate-12 divide-y divide-n-weak"
|
||||
>
|
||||
<div class="pl-4 pr-1 py-1 flex justify-between items-center">
|
||||
<div class="flex">
|
||||
<span class="text-n-slate-11 w-24">
|
||||
{{ t('SECURITY_SETTINGS.SAML.ACS_URL.LABEL') }}
|
||||
</span>
|
||||
{{ acsUrl }}
|
||||
</div>
|
||||
<NextButton
|
||||
type="button"
|
||||
ghost
|
||||
sm
|
||||
slate
|
||||
icon="i-lucide-copy"
|
||||
@click="handleCopy(acsUrl)"
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-sm font-medium text-n-slate-12">
|
||||
{{ t('SECURITY_SETTINGS.SAML.INFO_SECTION.TITLE') }}
|
||||
</h3>
|
||||
<i
|
||||
v-tooltip.top="t('SECURITY_SETTINGS.SAML.INFO_SECTION.TOOLTIP')"
|
||||
class="i-lucide-info text-n-slate-10 w-4 h-4 cursor-help"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="fingerprint"
|
||||
class="pl-4 pr-1 py-1 flex justify-between items-center"
|
||||
<section
|
||||
class="rounded-xl border border-n-weak bg-n-solid-1 w-full text-sm text-n-slate-12 divide-y divide-n-weak"
|
||||
>
|
||||
<div class="flex">
|
||||
<span class="text-n-slate-11 w-24">
|
||||
{{ t('SECURITY_SETTINGS.SAML.FINGERPRINT.LABEL') }}
|
||||
</span>
|
||||
{{ fingerprint }}
|
||||
<div
|
||||
v-for="item in visibleInfoItems"
|
||||
:key="item.key"
|
||||
class="pl-4 pr-1 py-1 flex justify-between items-center"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-n-slate-11 w-32 flex items-center gap-1">
|
||||
{{ item.label }}
|
||||
<i
|
||||
v-tooltip.top="item.tooltip"
|
||||
class="i-lucide-info text-n-slate-9 w-3 h-3 cursor-help"
|
||||
/>
|
||||
</span>
|
||||
<span class="flex-1">{{ item.value }}</span>
|
||||
</div>
|
||||
<NextButton
|
||||
type="button"
|
||||
ghost
|
||||
sm
|
||||
slate
|
||||
icon="i-lucide-copy"
|
||||
@click="handleCopy(item.value)"
|
||||
/>
|
||||
</div>
|
||||
<NextButton
|
||||
type="button"
|
||||
ghost
|
||||
sm
|
||||
slate
|
||||
icon="i-lucide-copy"
|
||||
@click="handleCopy(fingerprint)"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
+17
-8
@@ -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(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<SamlInfoSection class="mb-5" :fingerprint="fingerprint" />
|
||||
<SamlInfoSection
|
||||
class="mb-5"
|
||||
:fingerprint="fingerprint"
|
||||
:sp-entity-id="spEntityId"
|
||||
/>
|
||||
<SamlAttributeMap class="mb-5" />
|
||||
|
||||
<form class="grid gap-5" @submit.prevent="handleSubmit">
|
||||
@@ -163,13 +170,15 @@ onMounted(() => {
|
||||
</WithLabel>
|
||||
|
||||
<WithLabel
|
||||
:label="t('SECURITY_SETTINGS.SAML.SP_ENTITY_ID.LABEL')"
|
||||
:help-message="t('SECURITY_SETTINGS.SAML.SP_ENTITY_ID.HELP')"
|
||||
:label="t('SECURITY_SETTINGS.SAML.IDP_ENTITY_ID.LABEL')"
|
||||
:help-message="t('SECURITY_SETTINGS.SAML.IDP_ENTITY_ID.HELP')"
|
||||
required
|
||||
>
|
||||
<TextInput
|
||||
v-model="spEntityId"
|
||||
v-model="idpEntityId"
|
||||
class="w-full"
|
||||
:placeholder="t('SECURITY_SETTINGS.SAML.SP_ENTITY_ID.PLACEHOLDER')"
|
||||
:placeholder="t('SECURITY_SETTINGS.SAML.IDP_ENTITY_ID.PLACEHOLDER')"
|
||||
required
|
||||
/>
|
||||
</WithLabel>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user