feat: Control the allowed login methods via Super Admin (#12892)

- Control the allowed authentication methods for a chatwoot installation
via super admin configs. [SAML, Google Auth etc]
------
[Codex
Task](https://chatgpt.com/codex/tasks/task_e_6917d503b6e48326a261672c1de91462)

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
Sojan Jose
2025-11-17 21:55:12 -08:00
committed by GitHub
parent 5b56f64838
commit 77f492590e
14 changed files with 107 additions and 14 deletions

View File

@@ -78,10 +78,18 @@ class DashboardController < ActionController::Base
WHATSAPP_CONFIGURATION_ID: GlobalConfigService.load('WHATSAPP_CONFIGURATION_ID', ''),
IS_ENTERPRISE: ChatwootApp.enterprise?,
AZURE_APP_ID: GlobalConfigService.load('AZURE_APP_ID', ''),
GIT_SHA: GIT_HASH
GIT_SHA: GIT_HASH,
ALLOWED_LOGIN_METHODS: allowed_login_methods
}
end
def allowed_login_methods
methods = ['email']
methods << 'google_oauth' if GlobalConfigService.load('ENABLE_GOOGLE_OAUTH_LOGIN', 'true').to_s != 'false'
methods << 'saml' if ChatwootHub.pricing_plan != 'community' && GlobalConfigService.load('ENABLE_SAML_SSO_LOGIN', 'true').to_s != 'false'
methods
end
def set_application_pack
@application_pack = if request.path.include?('/auth') || request.path.include?('/login')
'v3app'

View File

@@ -15,14 +15,20 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
end
def create
errors = []
params['app_config'].each do |key, value|
next unless @allowed_configs.include?(key)
i = InstallationConfig.where(name: key).first_or_create(value: value, locked: false)
i.value = value
i.save!
errors.concat(i.errors.full_messages) unless i.save
end
if errors.any?
redirect_to super_admin_app_config_path(config: @config), alert: errors.join(', ')
else
redirect_to super_admin_settings_path, notice: "App Configs - #{@config.titleize} updated successfully"
end
redirect_to super_admin_settings_path, notice: "App Configs - #{@config.titleize} updated successfully"
end
private
@@ -42,7 +48,7 @@ class SuperAdmin::AppConfigsController < SuperAdmin::ApplicationController
'instagram' => %w[INSTAGRAM_APP_ID INSTAGRAM_APP_SECRET INSTAGRAM_VERIFY_TOKEN INSTAGRAM_API_VERSION ENABLE_INSTAGRAM_CHANNEL_HUMAN_AGENT],
'whatsapp_embedded' => %w[WHATSAPP_APP_ID WHATSAPP_APP_SECRET WHATSAPP_CONFIGURATION_ID WHATSAPP_API_VERSION],
'notion' => %w[NOTION_CLIENT_ID NOTION_CLIENT_SECRET],
'google' => %w[GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET GOOGLE_OAUTH_REDIRECT_URI]
'google' => %w[GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET GOOGLE_OAUTH_REDIRECT_URI ENABLE_GOOGLE_OAUTH_LOGIN]
}
@allowed_configs = mapping.fetch(

View File

@@ -9,6 +9,13 @@ captain:
icon: 'icon-captain'
config_key: 'captain'
enterprise: true
saml:
name: 'SAML SSO'
description: 'Configuration for controlling SAML Single Sign-On availability'
enabled: <%= ChatwootApp.enterprise? %>
icon: 'icon-lock-line'
config_key: 'saml'
enterprise: true
custom_branding:
name: 'Custom Branding'
description: 'Apply your own branding to this installation.'

View File

@@ -411,6 +411,7 @@
"TITLE": "Security",
"DESCRIPTION": "Manage your account security settings.",
"LINK_TEXT": "Learn more about SAML SSO",
"SAML_DISABLED_MESSAGE": "SAML SSO is currently disabled. Please contact your administrator to enable this feature.",
"SAML": {
"TITLE": "SAML SSO",
"NOTE": "Configure SAML single sign-on for your account. Users will authenticate through your identity provider instead of using email/password.",

View File

@@ -10,13 +10,23 @@ import { INSTALLATION_TYPES } from 'dashboard/constants/installationTypes';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
const { shouldShow, shouldShowPaywall } = usePolicy();
const shouldShowSaml = computed(() =>
shouldShow(
const allowedLoginMethods = computed(
() => window.chatwootConfig.allowedLoginMethods || ['email']
);
const isSamlSsoEnabled = computed(() =>
allowedLoginMethods.value.includes('saml')
);
const shouldShowSaml = computed(() => {
const hasPermission = shouldShow(
FEATURE_FLAGS.SAML,
['administrator'],
[INSTALLATION_TYPES.CLOUD, INSTALLATION_TYPES.ENTERPRISE]
)
);
);
return hasPermission && isSamlSsoEnabled.value;
});
const showPaywall = computed(() => shouldShowPaywall('saml'));
</script>
@@ -36,6 +46,9 @@ const showPaywall = computed(() => shouldShowPaywall('saml'));
<template #body>
<SamlPaywall v-if="showPaywall" />
<SamlSettings v-else-if="shouldShowSaml" />
<div v-else class="mt-6 text-sm text-slate-600">
{{ $t('SECURITY_SETTINGS.SAML_DISABLED_MESSAGE') }}
</div>
</template>
</SettingsLayout>
</template>

View File

@@ -99,8 +99,14 @@ export default {
}
return '';
},
allowedLoginMethods() {
return window.chatwootConfig.allowedLoginMethods || ['email'];
},
showGoogleOAuth() {
return Boolean(window.chatwootConfig.googleOAuthClientId);
return (
this.allowedLoginMethods.includes('google_oauth') &&
Boolean(window.chatwootConfig.googleOAuthClientId)
);
},
isFormValid() {
return !this.v$.$invalid && this.hasAValidCaptcha;
@@ -296,7 +302,7 @@ export default {
</form>
<div class="flex flex-col">
<SimpleDivider
v-if="showGoogleOAuth || showSamlLogin"
v-if="showGoogleOAuth"
:label="$t('COMMON.OR')"
bg="bg-n-background"
class="uppercase"

View File

@@ -2,7 +2,6 @@
// utils and composables
import { login } from '../../api/auth';
import { mapGetters } from 'vuex';
import { parseBoolean } from '@chatwoot/utils';
import { useAlert } from 'dashboard/composables';
import { required, email } from '@vuelidate/validators';
import { useVuelidate } from '@vuelidate/core';
@@ -85,14 +84,20 @@ export default {
},
computed: {
...mapGetters({ globalConfig: 'globalConfig/get' }),
allowedLoginMethods() {
return window.chatwootConfig.allowedLoginMethods || ['email'];
},
showGoogleOAuth() {
return Boolean(window.chatwootConfig.googleOAuthClientId);
return (
this.allowedLoginMethods.includes('google_oauth') &&
Boolean(window.chatwootConfig.googleOAuthClientId)
);
},
showSignupLink() {
return parseBoolean(window.chatwootConfig.signupEnabled);
return window.chatwootConfig.signupEnabled === 'true';
},
showSamlLogin() {
return this.globalConfig.isEnterprise;
return this.allowedLoginMethods.includes('saml');
},
},
created() {

View File

@@ -23,6 +23,7 @@ class InstallationConfig < ApplicationRecord
before_validation :set_lock
validates :name, presence: true
validate :saml_sso_users_check, if: -> { name == 'ENABLE_SAML_SSO_LOGIN' }
# TODO: Get rid of default scope
# https://stackoverflow.com/a/1834250/939299
@@ -54,4 +55,11 @@ class InstallationConfig < ApplicationRecord
def clear_cache
GlobalConfig.clear_cache
end
def saml_sso_users_check
return unless value == false || value == 'false'
return unless User.exists?(provider: 'saml')
errors.add(:base, 'Cannot disable SAML SSO login while users are using SAML authentication')
end
end

View File

@@ -38,6 +38,7 @@
instagramAppId: '<%= @global_config['INSTAGRAM_APP_ID'] %>',
googleOAuthClientId: '<%= ENV.fetch('GOOGLE_OAUTH_CLIENT_ID', nil) %>',
googleOAuthCallbackUrl: '<%= ENV.fetch('GOOGLE_OAUTH_CALLBACK_URL', nil) %>',
allowedLoginMethods: <%= @global_config['ALLOWED_LOGIN_METHODS'].to_json.html_safe %>,
fbApiVersion: '<%= @global_config['FACEBOOK_API_VERSION'] %>',
whatsappAppId: '<%= @global_config['WHATSAPP_APP_ID'] %>',
whatsappConfigurationId: '<%= @global_config['WHATSAPP_CONFIGURATION_ID'] %>',