Files
gochat/frontend/app/javascript/v3/views/login/Index.vue
T
Rogee 851ca7e372 refactor: 移除 SAML/LDAP/MFA 登录方式,仅保留本地账号密码和 OIDC
后端移除:
- SAML: auth/saml.go, handler/saml_handler.go, account_saml_settings_handler.go,
  model/account_saml_settings.go, model/saml_idp_config.go, repo/*.go
- LDAP: auth/ldap.go, handler/ldap_handler.go, model/account_ldap_settings.go,
  repo/account_ldap_settings_repo.go
- MFA: auth/mfa.go, handler/mfa_handler.go
- auth_service: 移除 mfaService 依赖、MFARequired 字段、LoginWithMFA 方法
- auth_handler: 移除 LoginMFA handler、MFA 分支逻辑
- bootstrap: 移除 SAML/LDAP/MFA service 初始化和 handler 注册
- sso_middleware: 精简为仅支持 OIDC provider
- router: 移除 SAML/LDAP/MFA 路由注册
- config: 移除 SAMLConfig/LDAPConfig struct 和 defaults

前端移除:
- v3/login: 移除 MFA 验证流程和 SAML 登录入口
- v3/api/auth: 移除 MFA 响应处理
- v3/routes: 移除 SSO login 路由
- dashboard: 移除 MFA 设置页面、SAML 安全设置页面
- i18n: 移除 mfa.json
- featureFlags: 移除 SAML feature flag

.env.example / .env: 移除 SAML/LDAP 配置段
2026-07-29 19:03:04 +08:00

210 lines
5.9 KiB
Vue

<script>
// utils and composables
import { login } from '../../api/auth';
import { useAlert } from 'dashboard/composables';
import { required, email } from '@vuelidate/validators';
import { useVuelidate } from '@vuelidate/core';
import { SESSION_STORAGE_KEYS } from 'dashboard/constants/sessionStorage';
import SessionStorage from 'shared/helpers/sessionStorage';
// components
import SimpleDivider from '../../components/Divider/SimpleDivider.vue';
import FormInput from '../../components/Form/Input.vue';
import Spinner from 'shared/components/Spinner.vue';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import NextButton from 'dashboard/components-next/button/Button.vue';
const ERROR_MESSAGES = {
'no-account-found': 'LOGIN.OAUTH.NO_ACCOUNT_FOUND',
'business-account-only': 'LOGIN.OAUTH.BUSINESS_ACCOUNTS_ONLY',
};
const IMPERSONATION_URL_SEARCH_KEY = 'impersonation';
const USER_NOT_CONFIRMED_ERROR_CODE = 'user_not_confirmed';
export default {
components: {
FormInput,
Spinner,
NextButton,
SimpleDivider,
Icon,
},
props: {
ssoAuthToken: { type: String, default: '' },
ssoAccountId: { type: String, default: '' },
ssoConversationId: { type: String, default: '' },
email: { type: String, default: '' },
authError: { type: String, default: '' },
},
setup() {
return {
v$: useVuelidate(),
};
},
data() {
return {
credentials: {
email: '',
password: '',
},
loginApi: {
message: '',
showLoading: false,
hasErrored: false,
},
error: '',
};
},
validations() {
return {
credentials: {
password: {
required,
},
email: {
required,
email,
},
},
};
},
created() {
if (this.ssoAuthToken) {
this.submitLogin();
}
if (this.authError) {
this.showAlertMessage(this.$t('LOGIN.API.UNAUTH'));
this.requestIdleCallbackPolyfill(() => {
const { query } = this.$route;
this.$router.replace({ query: { ...query, error: undefined } });
});
}
},
methods: {
requestIdleCallbackPolyfill(callback) {
if (window.requestIdleCallback) {
window.requestIdleCallback(callback);
} else {
setTimeout(callback, 0);
}
},
showAlertMessage(message) {
this.loginApi.showLoading = false;
this.loginApi.message = message;
useAlert(this.loginApi.message);
},
handleImpersonation() {
const urlParams = new URLSearchParams(window.location.search);
const impersonation = urlParams.get(IMPERSONATION_URL_SEARCH_KEY);
if (impersonation) {
SessionStorage.set(SESSION_STORAGE_KEYS.IMPERSONATION_USER, true);
}
},
submitLogin() {
this.loginApi.hasErrored = false;
this.loginApi.showLoading = true;
const credentials = {
email: this.email
? decodeURIComponent(this.email)
: this.credentials.email,
password: this.credentials.password,
sso_auth_token: this.ssoAuthToken,
ssoAccountId: this.ssoAccountId,
ssoConversationId: this.ssoConversationId,
};
login(credentials)
.then(() => {
this.handleImpersonation();
this.showAlertMessage(this.$t('LOGIN.API.SUCCESS_MESSAGE'));
})
.catch(response => {
if (response?.errorCode === USER_NOT_CONFIRMED_ERROR_CODE) {
this.loginApi.showLoading = false;
this.$router.push({
name: 'auth_verify_email',
state: { email: credentials.email },
});
return;
}
if (this.email) {
window.location = '/app/login';
}
this.loginApi.hasErrored = true;
this.showAlertMessage(
response?.message || this.$t('LOGIN.API.UNAUTH')
);
});
},
submitFormLogin() {
if (this.v$.credentials.email.$invalid && !this.email) {
this.showAlertMessage(this.$t('LOGIN.EMAIL.ERROR'));
return;
}
this.submitLogin();
},
},
};
</script>
<template>
<main
class="flex flex-col items-center justify-center w-full min-h-screen bg-n-brand/5 dark:bg-n-background sm:px-6 lg:px-8"
>
<!-- Regular Login Section -->
<section
class="bg-white shadow sm:mx-auto mt-11 sm:w-full sm:max-w-lg dark:bg-n-solid-2 p-11 sm:shadow-lg sm:rounded-lg"
:class="{
'mb-8 mt-15': true,
'animate-wiggle': loginApi.hasErrored,
}"
>
<div v-if="!email">
<form class="space-y-5" @submit.prevent="submitFormLogin">
<FormInput
v-model="credentials.email"
name="email_address"
type="text"
data-testid="email_input"
:tabindex="1"
required
:label="$t('LOGIN.EMAIL.LABEL')"
:placeholder="$t('LOGIN.EMAIL.PLACEHOLDER')"
:has-error="v$.credentials.email.$error"
@input="v$.credentials.email.$touch"
/>
<FormInput
v-model="credentials.password"
type="password"
name="password"
data-testid="password_input"
required
:tabindex="2"
:label="$t('LOGIN.PASSWORD.LABEL')"
:placeholder="$t('LOGIN.PASSWORD.PLACEHOLDER')"
:has-error="v$.credentials.password.$error"
@input="v$.credentials.password.$touch"
/>
<NextButton
lg
type="submit"
data-testid="submit_button"
class="w-full"
:tabindex="3"
:label="$t('LOGIN.SUBMIT')"
:disabled="loginApi.showLoading"
:is-loading="loginApi.showLoading"
/>
</form>
</div>
<div v-else class="flex items-center justify-center">
<Spinner color-scheme="primary" size="" />
</div>
</section>
</main>
</template>