Skip to content

Commit 6898180

Browse files
committed
fix(user): restrict SSO users from disabling MFA
1 parent 543265c commit 6898180

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

src/i18n/General.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,10 @@ export default {
707707
en: 'Disabling MFA may affect account security, confirm to disable?',
708708
zh: '禁用 MFA 可能影响账户安全,确认禁用?',
709709
},
710+
disableMFAForbiddenBySSO: {
711+
en: 'MFA cannot be disabled because force MFA is enabled in the current SSO configuration.',
712+
zh: '当前无法停用 MFA,因为当前类型单点登录配置启用了强制 MFA。',
713+
},
710714
enableMFA: {
711715
en: 'Enable MFA',
712716
zh: '启用 MFA',

src/views/General/components/UserMFASettingDialog.vue

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,20 @@
1111
<p>{{ t('General.currentMFA') }}: {{ getMFAMethodLabel(props.user?.mfa ?? '') }}</p>
1212
</el-card>
1313
<template v-if="withMFA">
14+
<el-alert v-if="disableMfaBlocked" type="warning" :closable="false" class="mfa-alert">
15+
{{ t('General.disableMFAForbiddenBySSO') }}
16+
</el-alert>
1417
<div class="buttons">
1518
<el-button type="primary" plain :loading="submitLoading" @click="resetTOTPSecret">
1619
{{ tl('resetTOTPSecret') }}
1720
</el-button>
18-
<el-button type="danger" plain :loading="submitLoading" @click="deleteMFA">
21+
<el-button
22+
type="danger"
23+
plain
24+
:loading="submitLoading"
25+
:disabled="disableMfaBlocked || ssoConfigLoading"
26+
@click="deleteMFA"
27+
>
1928
{{ tl('disableMFA') }}
2029
</el-button>
2130
</div>
@@ -41,6 +50,7 @@
4150

4251
<script setup lang="ts">
4352
import { deleteUserMfa, updateUserMfa } from '@/api/function'
53+
import { getSSOBackend } from '@/api/sso'
4454
import { type User, UserMFA } from '@/types/typeAlias'
4555
4656
const props = defineProps<{
@@ -54,6 +64,12 @@ const { t, tl } = useI18nTl('General')
5464
const { mfaOptions, isMFAEnabled, getMFAMethodLabel } = useMFAMethods()
5565
const withMFA = computed(() => isMFAEnabled(props.user.mfa ?? ''))
5666
const isSSOUser = computed(() => !!props.user?.backend && props.user.backend !== 'local')
67+
const ssoConfigLoading = ref(false)
68+
const ssoBackendConfig = ref<Record<string, any> | null>(null)
69+
const isSSOBackendMfaEnforced = computed(
70+
() => !!(ssoBackendConfig.value?.force_mfa || ssoBackendConfig.value?.enforce_mfa),
71+
)
72+
const disableMfaBlocked = computed(() => isSSOUser.value && isSSOBackendMfaEnforced.value)
5773
5874
const defaultMFA = mfaOptions[0].value
5975
@@ -69,12 +85,31 @@ const showDialog = computed({
6985
watch(showDialog, async (value: boolean) => {
7086
if (!value) {
7187
initData()
88+
return
7289
}
90+
await loadSSOBackendConfig()
7391
})
7492
7593
const initData = () => {
7694
submitLoading.value = false
7795
selectedMFA.value = defaultMFA
96+
ssoConfigLoading.value = false
97+
ssoBackendConfig.value = null
98+
}
99+
100+
const loadSSOBackendConfig = async () => {
101+
if (!isSSOUser.value || !props.user?.backend) {
102+
ssoBackendConfig.value = null
103+
return
104+
}
105+
try {
106+
ssoConfigLoading.value = true
107+
ssoBackendConfig.value = (await getSSOBackend(props.user.backend as any)) as Record<string, any>
108+
} catch (error) {
109+
ssoBackendConfig.value = null
110+
} finally {
111+
ssoConfigLoading.value = false
112+
}
78113
}
79114
80115
const resetTOTPSecret = async () => {
@@ -122,6 +157,10 @@ const deleteMFA = async () => {
122157
if (!username) {
123158
return
124159
}
160+
if (disableMfaBlocked.value) {
161+
ElMessage.warning(t('General.disableMFAForbiddenBySSO'))
162+
return
163+
}
125164
await operationWarning(t('General.confirmDisableMFA'))
126165
submitLoading.value = true
127166
if (isSSOUser.value) {
@@ -142,6 +181,9 @@ const deleteMFA = async () => {
142181

143182
<style lang="scss">
144183
.mfa-setting-dialog {
184+
.mfa-alert {
185+
margin-bottom: 12px;
186+
}
145187
.buttons {
146188
margin-top: 12px;
147189
}

0 commit comments

Comments
 (0)