后端移除: - 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 配置段
210 lines
5.9 KiB
Vue
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>
|