mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-09 22:24:05 -08:00
refactor(editor): Migrate AuthView
and associated components to composition api (#10713)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
This commit is contained in:
parent
ee7147c6b3
commit
91008b2676
|
@ -1,44 +1,37 @@
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { type PropType, defineComponent } from 'vue';
|
|
||||||
|
|
||||||
import Logo from '@/components/Logo.vue';
|
|
||||||
import SSOLogin from '@/components/SSOLogin.vue';
|
import SSOLogin from '@/components/SSOLogin.vue';
|
||||||
import type { IFormBoxConfig } from '@/Interface';
|
import type { IFormBoxConfig } from '@/Interface';
|
||||||
|
|
||||||
export default defineComponent({
|
withDefaults(
|
||||||
name: 'AuthView',
|
defineProps<{
|
||||||
components: {
|
form: IFormBoxConfig;
|
||||||
Logo,
|
formLoading?: boolean;
|
||||||
SSOLogin,
|
subtitle?: string;
|
||||||
|
withSso?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
formLoading: false,
|
||||||
|
withSso: false,
|
||||||
},
|
},
|
||||||
props: {
|
);
|
||||||
form: {
|
|
||||||
type: Object as PropType<IFormBoxConfig>,
|
const emit = defineEmits<{
|
||||||
},
|
update: [{ name: string; value: string }];
|
||||||
formLoading: {
|
submit: [values: { [key: string]: string }];
|
||||||
type: Boolean,
|
secondaryClick: [];
|
||||||
default: false,
|
}>();
|
||||||
},
|
|
||||||
subtitle: {
|
const onUpdate = (e: { name: string; value: string }) => {
|
||||||
type: String,
|
emit('update', e);
|
||||||
},
|
};
|
||||||
withSso: {
|
|
||||||
type: Boolean,
|
const onSubmit = (values: { [key: string]: string }) => {
|
||||||
default: false,
|
emit('submit', values);
|
||||||
},
|
};
|
||||||
},
|
|
||||||
methods: {
|
const onSecondaryClick = () => {
|
||||||
onUpdate(e: { name: string; value: string }) {
|
emit('secondaryClick');
|
||||||
this.$emit('update', e);
|
};
|
||||||
},
|
|
||||||
onSubmit(values: { [key: string]: string }) {
|
|
||||||
this.$emit('submit', values);
|
|
||||||
},
|
|
||||||
onSecondaryClick() {
|
|
||||||
this.$emit('secondaryClick');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,48 +1,111 @@
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import AuthView from '@/views/AuthView.vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { defineComponent } from 'vue';
|
import AuthView from '@/views/AuthView.vue';
|
||||||
import type { IFormBoxConfig } from '@/Interface';
|
|
||||||
import { MFA_AUTHENTICATION_TOKEN_INPUT_MAX_LENGTH, VIEWS } from '@/constants';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { mapStores } from 'pinia';
|
import { useToast } from '@/composables/useToast';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
|
|
||||||
export default defineComponent({
|
import type { IFormBoxConfig } from '@/Interface';
|
||||||
name: 'ChangePasswordView',
|
import { MFA_AUTHENTICATION_TOKEN_INPUT_MAX_LENGTH, VIEWS } from '@/constants';
|
||||||
components: {
|
|
||||||
AuthView,
|
const usersStore = useUsersStore();
|
||||||
},
|
|
||||||
setup() {
|
const locale = useI18n();
|
||||||
|
const toast = useToast();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const password = ref('');
|
||||||
|
const loading = ref(false);
|
||||||
|
const config = ref<IFormBoxConfig | null>(null);
|
||||||
|
|
||||||
|
const passwordsMatch = (value: string | number | boolean | null | undefined) => {
|
||||||
|
if (typeof value !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !== password.value) {
|
||||||
return {
|
return {
|
||||||
...useToast(),
|
messageKey: 'auth.changePassword.passwordsMustMatchError',
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
data() {
|
|
||||||
return {
|
return false;
|
||||||
password: '',
|
|
||||||
loading: false,
|
|
||||||
config: null as null | IFormBoxConfig,
|
|
||||||
};
|
};
|
||||||
},
|
|
||||||
computed: {
|
const getResetToken = () => {
|
||||||
...mapStores(useUsersStore),
|
return !router.currentRoute.value.query.token ||
|
||||||
},
|
typeof router.currentRoute.value.query.token !== 'string'
|
||||||
async mounted() {
|
? null
|
||||||
|
: router.currentRoute.value.query.token;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMfaEnabled = () => {
|
||||||
|
if (!router.currentRoute.value.query.mfaEnabled) return null;
|
||||||
|
return router.currentRoute.value.query.mfaEnabled === 'true' ? true : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFormWithMFAToken = (values: { [key: string]: string }): values is { mfaToken: string } => {
|
||||||
|
return 'mfaToken' in values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (values: { [key: string]: string }) => {
|
||||||
|
if (!isFormWithMFAToken(values)) return;
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
const token = getResetToken();
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
const changePasswordParameters = {
|
||||||
|
token,
|
||||||
|
password: password.value,
|
||||||
|
...(values.mfaToken && { mfaToken: values.mfaToken }),
|
||||||
|
};
|
||||||
|
|
||||||
|
await usersStore.changePassword(changePasswordParameters);
|
||||||
|
|
||||||
|
toast.showMessage({
|
||||||
|
type: 'success',
|
||||||
|
title: locale.baseText('auth.changePassword.passwordUpdated'),
|
||||||
|
message: locale.baseText('auth.changePassword.passwordUpdatedMessage'),
|
||||||
|
});
|
||||||
|
|
||||||
|
await router.push({ name: VIEWS.SIGNIN });
|
||||||
|
} else {
|
||||||
|
toast.showError(
|
||||||
|
new Error(locale.baseText('auth.validation.missingParameters')),
|
||||||
|
locale.baseText('auth.changePassword.error'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast.showError(error, locale.baseText('auth.changePassword.error'));
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onInput = (e: { name: string; value: string }) => {
|
||||||
|
if (e.name === 'password') {
|
||||||
|
password.value = e.value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
const form: IFormBoxConfig = {
|
const form: IFormBoxConfig = {
|
||||||
title: this.$locale.baseText('auth.changePassword'),
|
title: locale.baseText('auth.changePassword'),
|
||||||
buttonText: this.$locale.baseText('auth.changePassword'),
|
buttonText: locale.baseText('auth.changePassword'),
|
||||||
redirectText: this.$locale.baseText('auth.signin'),
|
redirectText: locale.baseText('auth.signin'),
|
||||||
redirectLink: '/signin',
|
redirectLink: '/signin',
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
name: 'password',
|
name: 'password',
|
||||||
properties: {
|
properties: {
|
||||||
label: this.$locale.baseText('auth.newPassword'),
|
label: locale.baseText('auth.newPassword'),
|
||||||
type: 'password',
|
type: 'password',
|
||||||
required: true,
|
required: true,
|
||||||
validationRules: [{ name: 'DEFAULT_PASSWORD_RULES' }],
|
validationRules: [{ name: 'DEFAULT_PASSWORD_RULES' }],
|
||||||
infoText: this.$locale.baseText('auth.defaultPasswordRequirements'),
|
infoText: locale.baseText('auth.defaultPasswordRequirements'),
|
||||||
autocomplete: 'new-password',
|
autocomplete: 'new-password',
|
||||||
capitalize: true,
|
capitalize: true,
|
||||||
},
|
},
|
||||||
|
@ -50,12 +113,12 @@ export default defineComponent({
|
||||||
{
|
{
|
||||||
name: 'password2',
|
name: 'password2',
|
||||||
properties: {
|
properties: {
|
||||||
label: this.$locale.baseText('auth.changePassword.reenterNewPassword'),
|
label: locale.baseText('auth.changePassword.reenterNewPassword'),
|
||||||
type: 'password',
|
type: 'password',
|
||||||
required: true,
|
required: true,
|
||||||
validators: {
|
validators: {
|
||||||
TWO_PASSWORDS_MATCH: {
|
TWO_PASSWORDS_MATCH: {
|
||||||
validate: this.passwordsMatch,
|
validate: passwordsMatch,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
validationRules: [{ name: 'TWO_PASSWORDS_MATCH' }],
|
validationRules: [{ name: 'TWO_PASSWORDS_MATCH' }],
|
||||||
|
@ -66,8 +129,8 @@ export default defineComponent({
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const token = this.getResetToken();
|
const token = getResetToken();
|
||||||
const mfaEnabled = this.getMfaEnabled();
|
const mfaEnabled = getMfaEnabled();
|
||||||
|
|
||||||
if (mfaEnabled) {
|
if (mfaEnabled) {
|
||||||
form.inputs.push({
|
form.inputs.push({
|
||||||
|
@ -75,8 +138,8 @@ export default defineComponent({
|
||||||
initialValue: '',
|
initialValue: '',
|
||||||
properties: {
|
properties: {
|
||||||
required: true,
|
required: true,
|
||||||
label: this.$locale.baseText('mfa.code.input.label'),
|
label: locale.baseText('mfa.code.input.label'),
|
||||||
placeholder: this.$locale.baseText('mfa.code.input.placeholder'),
|
placeholder: locale.baseText('mfa.code.input.placeholder'),
|
||||||
maxlength: MFA_AUTHENTICATION_TOKEN_INPUT_MAX_LENGTH,
|
maxlength: MFA_AUTHENTICATION_TOKEN_INPUT_MAX_LENGTH,
|
||||||
capitalize: true,
|
capitalize: true,
|
||||||
validateOnBlur: true,
|
validateOnBlur: true,
|
||||||
|
@ -84,80 +147,18 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.config = form;
|
config.value = form;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
throw new Error(this.$locale.baseText('auth.changePassword.missingTokenError'));
|
throw new Error(locale.baseText('auth.changePassword.missingTokenError'));
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.usersStore.validatePasswordToken({ token });
|
await usersStore.validatePasswordToken({ token });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.showError(e, this.$locale.baseText('auth.changePassword.tokenValidationError'));
|
toast.showError(e, locale.baseText('auth.changePassword.tokenValidationError'));
|
||||||
void this.$router.replace({ name: VIEWS.SIGNIN });
|
void router.replace({ name: VIEWS.SIGNIN });
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
passwordsMatch(value: string | number | boolean | null | undefined) {
|
|
||||||
if (typeof value !== 'string') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value !== this.password) {
|
|
||||||
return {
|
|
||||||
messageKey: 'auth.changePassword.passwordsMustMatchError',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
onInput(e: { name: string; value: string }) {
|
|
||||||
if (e.name === 'password') {
|
|
||||||
this.password = e.value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getResetToken() {
|
|
||||||
return !this.$route.query.token || typeof this.$route.query.token !== 'string'
|
|
||||||
? null
|
|
||||||
: this.$route.query.token;
|
|
||||||
},
|
|
||||||
getMfaEnabled() {
|
|
||||||
if (!this.$route.query.mfaEnabled) return null;
|
|
||||||
return this.$route.query.mfaEnabled === 'true' ? true : false;
|
|
||||||
},
|
|
||||||
async onSubmit(values: { mfaToken: string }) {
|
|
||||||
try {
|
|
||||||
this.loading = true;
|
|
||||||
const token = this.getResetToken();
|
|
||||||
|
|
||||||
if (token) {
|
|
||||||
const changePasswordParameters = {
|
|
||||||
token,
|
|
||||||
password: this.password,
|
|
||||||
...(values.mfaToken && { mfaToken: values.mfaToken }),
|
|
||||||
};
|
|
||||||
|
|
||||||
await this.usersStore.changePassword(changePasswordParameters);
|
|
||||||
|
|
||||||
this.showMessage({
|
|
||||||
type: 'success',
|
|
||||||
title: this.$locale.baseText('auth.changePassword.passwordUpdated'),
|
|
||||||
message: this.$locale.baseText('auth.changePassword.passwordUpdatedMessage'),
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.$router.push({ name: VIEWS.SIGNIN });
|
|
||||||
} else {
|
|
||||||
this.showError(
|
|
||||||
new Error(this.$locale.baseText('auth.validation.missingParameters')),
|
|
||||||
this.$locale.baseText('auth.changePassword.error'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.showError(error, this.$locale.baseText('auth.changePassword.error'));
|
|
||||||
}
|
|
||||||
this.loading = false;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,36 +1,26 @@
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import AuthView from './AuthView.vue';
|
import AuthView from './AuthView.vue';
|
||||||
import { useToast } from '@/composables/useToast';
|
|
||||||
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import type { IFormBoxConfig } from '@/Interface';
|
import type { IFormBoxConfig } from '@/Interface';
|
||||||
import { mapStores } from 'pinia';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { useToast } from '@/composables/useToast';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
const settingsStore = useSettingsStore();
|
||||||
name: 'ForgotMyPasswordView',
|
const usersStore = useUsersStore();
|
||||||
components: {
|
|
||||||
AuthView,
|
const toast = useToast();
|
||||||
},
|
const locale = useI18n();
|
||||||
setup() {
|
|
||||||
return {
|
const loading = ref(false);
|
||||||
...useToast(),
|
|
||||||
};
|
const formConfig = computed(() => {
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
loading: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useSettingsStore, useUsersStore),
|
|
||||||
formConfig(): IFormBoxConfig {
|
|
||||||
const EMAIL_INPUTS: IFormBoxConfig['inputs'] = [
|
const EMAIL_INPUTS: IFormBoxConfig['inputs'] = [
|
||||||
{
|
{
|
||||||
name: 'email',
|
name: 'email',
|
||||||
properties: {
|
properties: {
|
||||||
label: this.$locale.baseText('auth.email'),
|
label: locale.baseText('auth.email'),
|
||||||
type: 'email',
|
type: 'email',
|
||||||
required: true,
|
required: true,
|
||||||
validationRules: [{ name: 'VALID_EMAIL' }],
|
validationRules: [{ name: 'VALID_EMAIL' }],
|
||||||
|
@ -44,22 +34,22 @@ export default defineComponent({
|
||||||
{
|
{
|
||||||
name: 'no-smtp-warning',
|
name: 'no-smtp-warning',
|
||||||
properties: {
|
properties: {
|
||||||
label: this.$locale.baseText('forgotPassword.noSMTPToSendEmailWarning'),
|
label: locale.baseText('forgotPassword.noSMTPToSendEmailWarning'),
|
||||||
type: 'info',
|
type: 'info',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const DEFAULT_FORM_CONFIG = {
|
const DEFAULT_FORM_CONFIG = {
|
||||||
title: this.$locale.baseText('forgotPassword.recoverPassword'),
|
title: locale.baseText('forgotPassword.recoverPassword'),
|
||||||
redirectText: this.$locale.baseText('forgotPassword.returnToSignIn'),
|
redirectText: locale.baseText('forgotPassword.returnToSignIn'),
|
||||||
redirectLink: '/signin',
|
redirectLink: '/signin',
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.settingsStore.isSmtpSetup) {
|
if (settingsStore.isSmtpSetup) {
|
||||||
return {
|
return {
|
||||||
...DEFAULT_FORM_CONFIG,
|
...DEFAULT_FORM_CONFIG,
|
||||||
buttonText: this.$locale.baseText('forgotPassword.getRecoveryLink'),
|
buttonText: locale.baseText('forgotPassword.getRecoveryLink'),
|
||||||
inputs: EMAIL_INPUTS,
|
inputs: EMAIL_INPUTS,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -67,42 +57,46 @@ export default defineComponent({
|
||||||
...DEFAULT_FORM_CONFIG,
|
...DEFAULT_FORM_CONFIG,
|
||||||
inputs: NO_SMTP_INPUTS,
|
inputs: NO_SMTP_INPUTS,
|
||||||
};
|
};
|
||||||
},
|
});
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async onSubmit(values: { email: string }) {
|
|
||||||
try {
|
|
||||||
this.loading = true;
|
|
||||||
await this.usersStore.sendForgotPasswordEmail(values);
|
|
||||||
|
|
||||||
this.showMessage({
|
const isFormWithEmail = (values: { [key: string]: string }): values is { email: string } => {
|
||||||
|
return 'email' in values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (values: { [key: string]: string }) => {
|
||||||
|
if (!isFormWithEmail(values)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
await usersStore.sendForgotPasswordEmail(values);
|
||||||
|
|
||||||
|
toast.showMessage({
|
||||||
type: 'success',
|
type: 'success',
|
||||||
title: this.$locale.baseText('forgotPassword.recoveryEmailSent'),
|
title: locale.baseText('forgotPassword.recoveryEmailSent'),
|
||||||
message: this.$locale.baseText('forgotPassword.emailSentIfExists', {
|
message: locale.baseText('forgotPassword.emailSentIfExists', {
|
||||||
interpolate: { email: values.email },
|
interpolate: { email: values.email },
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = this.$locale.baseText('forgotPassword.smtpErrorContactAdministrator');
|
let message = locale.baseText('forgotPassword.smtpErrorContactAdministrator');
|
||||||
if (error.httpStatusCode) {
|
if (error.httpStatusCode) {
|
||||||
const { httpStatusCode: status } = error;
|
const { httpStatusCode: status } = error;
|
||||||
if (status === 429) {
|
if (status === 429) {
|
||||||
message = this.$locale.baseText('forgotPassword.tooManyRequests');
|
message = locale.baseText('forgotPassword.tooManyRequests');
|
||||||
} else if (error.httpStatusCode === 422) {
|
} else if (error.httpStatusCode === 422) {
|
||||||
message = this.$locale.baseText(error.message);
|
message = locale.baseText(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.showMessage({
|
toast.showMessage({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
title: this.$locale.baseText('forgotPassword.sendingEmailError'),
|
title: locale.baseText('forgotPassword.sendingEmailError'),
|
||||||
message,
|
message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.loading = false;
|
loading.value = false;
|
||||||
},
|
};
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { ElNotification as Notification } from 'element-plus';
|
|
||||||
import type { IFormBoxConfig } from 'n8n-design-system';
|
import type { IFormBoxConfig } from 'n8n-design-system';
|
||||||
import AuthView from '@/views/AuthView.vue';
|
import AuthView from '@/views/AuthView.vue';
|
||||||
import { i18n as locale } from '@/plugins/i18n';
|
|
||||||
import { useSSOStore } from '@/stores/sso.store';
|
|
||||||
import { VIEWS } from '@/constants';
|
import { VIEWS } from '@/constants';
|
||||||
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { useToast } from '@/composables/useToast';
|
||||||
|
import { useSSOStore } from '@/stores/sso.store';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const locale = useI18n();
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
const ssoStore = useSSOStore();
|
const ssoStore = useSSOStore();
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
@ -40,18 +43,22 @@ const FORM_CONFIG: IFormBoxConfig = reactive({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const onSubmit = async (values: { firstName: string; lastName: string }) => {
|
|
||||||
|
const isFormWithFirstAndLastName = (values: {
|
||||||
|
[key: string]: string;
|
||||||
|
}): values is { firstName: string; lastName: string } => {
|
||||||
|
return 'firstName' in values && 'lastName' in values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (values: { [key: string]: string }) => {
|
||||||
|
if (!isFormWithFirstAndLastName(values)) return;
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
await ssoStore.updateUser(values);
|
await ssoStore.updateUser(values);
|
||||||
await router.push({ name: VIEWS.HOMEPAGE });
|
await router.push({ name: VIEWS.HOMEPAGE });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
Notification.error({
|
toast.showError(error, 'Error', error.message);
|
||||||
title: 'Error',
|
|
||||||
message: error.message,
|
|
||||||
position: 'bottom-right',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,33 +1,38 @@
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import AuthView from './AuthView.vue';
|
import { reactive, ref } from 'vue';
|
||||||
import { defineComponent } from 'vue';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
|
||||||
|
import { usePostHog } from '@/stores/posthog.store';
|
||||||
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
|
|
||||||
import type { IFormBoxConfig } from '@/Interface';
|
import type { IFormBoxConfig } from '@/Interface';
|
||||||
import { MORE_ONBOARDING_OPTIONS_EXPERIMENT, VIEWS } from '@/constants';
|
import { MORE_ONBOARDING_OPTIONS_EXPERIMENT, VIEWS } from '@/constants';
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
|
||||||
import { usePostHog } from '@/stores/posthog.store';
|
|
||||||
|
|
||||||
export default defineComponent({
|
import AuthView from '@/views/AuthView.vue';
|
||||||
name: 'SetupView',
|
|
||||||
components: {
|
const posthogStore = usePostHog();
|
||||||
AuthView,
|
const settingsStore = useSettingsStore();
|
||||||
},
|
const uiStore = useUIStore();
|
||||||
setup() {
|
const usersStore = useUsersStore();
|
||||||
return useToast();
|
|
||||||
},
|
const toast = useToast();
|
||||||
data() {
|
const locale = useI18n();
|
||||||
const FORM_CONFIG: IFormBoxConfig = {
|
const router = useRouter();
|
||||||
title: this.$locale.baseText('auth.setup.setupOwner'),
|
|
||||||
buttonText: this.$locale.baseText('auth.setup.next'),
|
const loading = ref(false);
|
||||||
|
const formConfig: IFormBoxConfig = reactive({
|
||||||
|
title: locale.baseText('auth.setup.setupOwner'),
|
||||||
|
buttonText: locale.baseText('auth.setup.next'),
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
name: 'email',
|
name: 'email',
|
||||||
properties: {
|
properties: {
|
||||||
label: this.$locale.baseText('auth.email'),
|
label: locale.baseText('auth.email'),
|
||||||
type: 'email',
|
type: 'email',
|
||||||
required: true,
|
required: true,
|
||||||
validationRules: [{ name: 'VALID_EMAIL' }],
|
validationRules: [{ name: 'VALID_EMAIL' }],
|
||||||
|
@ -38,7 +43,7 @@ export default defineComponent({
|
||||||
{
|
{
|
||||||
name: 'firstName',
|
name: 'firstName',
|
||||||
properties: {
|
properties: {
|
||||||
label: this.$locale.baseText('auth.firstName'),
|
label: locale.baseText('auth.firstName'),
|
||||||
maxlength: 32,
|
maxlength: 32,
|
||||||
required: true,
|
required: true,
|
||||||
autocomplete: 'given-name',
|
autocomplete: 'given-name',
|
||||||
|
@ -48,7 +53,7 @@ export default defineComponent({
|
||||||
{
|
{
|
||||||
name: 'lastName',
|
name: 'lastName',
|
||||||
properties: {
|
properties: {
|
||||||
label: this.$locale.baseText('auth.lastName'),
|
label: locale.baseText('auth.lastName'),
|
||||||
maxlength: 32,
|
maxlength: 32,
|
||||||
required: true,
|
required: true,
|
||||||
autocomplete: 'family-name',
|
autocomplete: 'family-name',
|
||||||
|
@ -58,11 +63,11 @@ export default defineComponent({
|
||||||
{
|
{
|
||||||
name: 'password',
|
name: 'password',
|
||||||
properties: {
|
properties: {
|
||||||
label: this.$locale.baseText('auth.password'),
|
label: locale.baseText('auth.password'),
|
||||||
type: 'password',
|
type: 'password',
|
||||||
required: true,
|
required: true,
|
||||||
validationRules: [{ name: 'DEFAULT_PASSWORD_RULES' }],
|
validationRules: [{ name: 'DEFAULT_PASSWORD_RULES' }],
|
||||||
infoText: this.$locale.baseText('auth.defaultPasswordRequirements'),
|
infoText: locale.baseText('auth.defaultPasswordRequirements'),
|
||||||
autocomplete: 'new-password',
|
autocomplete: 'new-password',
|
||||||
capitalize: true,
|
capitalize: true,
|
||||||
},
|
},
|
||||||
|
@ -70,60 +75,49 @@ export default defineComponent({
|
||||||
{
|
{
|
||||||
name: 'agree',
|
name: 'agree',
|
||||||
properties: {
|
properties: {
|
||||||
label: this.$locale.baseText('auth.agreement.label'),
|
label: locale.baseText('auth.agreement.label'),
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
});
|
||||||
|
|
||||||
return {
|
const onSubmit = async (values: { [key: string]: string | boolean }) => {
|
||||||
FORM_CONFIG,
|
|
||||||
loading: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useSettingsStore, useUIStore, useUsersStore, usePostHog),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async onSubmit(values: { [key: string]: string | boolean }) {
|
|
||||||
try {
|
try {
|
||||||
const forceRedirectedHere = this.settingsStore.showSetupPage;
|
const forceRedirectedHere = settingsStore.showSetupPage;
|
||||||
const isPartOfOnboardingExperiment =
|
const isPartOfOnboardingExperiment =
|
||||||
this.posthogStore.getVariant(MORE_ONBOARDING_OPTIONS_EXPERIMENT.name) ===
|
posthogStore.getVariant(MORE_ONBOARDING_OPTIONS_EXPERIMENT.name) ===
|
||||||
MORE_ONBOARDING_OPTIONS_EXPERIMENT.variant;
|
MORE_ONBOARDING_OPTIONS_EXPERIMENT.variant;
|
||||||
this.loading = true;
|
loading.value = true;
|
||||||
await this.usersStore.createOwner(
|
await usersStore.createOwner(
|
||||||
values as { firstName: string; lastName: string; email: string; password: string },
|
values as { firstName: string; lastName: string; email: string; password: string },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (values.agree === true) {
|
if (values.agree === true) {
|
||||||
try {
|
try {
|
||||||
await this.uiStore.submitContactEmail(values.email.toString(), values.agree);
|
await uiStore.submitContactEmail(values.email.toString(), values.agree);
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (forceRedirectedHere) {
|
if (forceRedirectedHere) {
|
||||||
if (isPartOfOnboardingExperiment) {
|
if (isPartOfOnboardingExperiment) {
|
||||||
await this.$router.push({ name: VIEWS.WORKFLOWS });
|
await router.push({ name: VIEWS.WORKFLOWS });
|
||||||
} else {
|
} else {
|
||||||
await this.$router.push({ name: VIEWS.NEW_WORKFLOW });
|
await router.push({ name: VIEWS.NEW_WORKFLOW });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await this.$router.push({ name: VIEWS.USERS_SETTINGS });
|
await router.push({ name: VIEWS.USERS_SETTINGS });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.showError(error, this.$locale.baseText('auth.setup.settingUpOwnerError'));
|
toast.showError(error, locale.baseText('auth.setup.settingUpOwnerError'));
|
||||||
}
|
}
|
||||||
this.loading = false;
|
loading.value = false;
|
||||||
},
|
};
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AuthView
|
<AuthView
|
||||||
:form="FORM_CONFIG"
|
:form="formConfig"
|
||||||
:form-loading="loading"
|
:form-loading="loading"
|
||||||
data-test-id="setup-form"
|
data-test-id="setup-form"
|
||||||
@submit="onSubmit"
|
@submit="onSubmit"
|
||||||
|
|
|
@ -1,62 +1,68 @@
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { computed, reactive, ref } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
|
||||||
import AuthView from './AuthView.vue';
|
import AuthView from './AuthView.vue';
|
||||||
import MfaView from './MfaView.vue';
|
import MfaView from './MfaView.vue';
|
||||||
|
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import type { IFormBoxConfig } from '@/Interface';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
import { MFA_AUTHENTICATION_REQUIRED_ERROR_CODE, VIEWS, MFA_FORM } from '@/constants';
|
import { useTelemetry } from '@/composables/useTelemetry';
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
import { useUsersStore } from '@/stores/users.store';
|
||||||
import { useSettingsStore } from '@/stores/settings.store';
|
import { useSettingsStore } from '@/stores/settings.store';
|
||||||
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
import { useCloudPlanStore } from '@/stores/cloudPlan.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
|
||||||
|
|
||||||
export default defineComponent({
|
import type { IFormBoxConfig } from '@/Interface';
|
||||||
name: 'SigninView',
|
import { MFA_AUTHENTICATION_REQUIRED_ERROR_CODE, VIEWS, MFA_FORM } from '@/constants';
|
||||||
components: {
|
|
||||||
AuthView,
|
const usersStore = useUsersStore();
|
||||||
MfaView,
|
const settingsStore = useSettingsStore();
|
||||||
},
|
const cloudPlanStore = useCloudPlanStore();
|
||||||
setup() {
|
|
||||||
return {
|
const route = useRoute();
|
||||||
...useToast(),
|
const router = useRouter();
|
||||||
};
|
|
||||||
},
|
const toast = useToast();
|
||||||
data() {
|
const locale = useI18n();
|
||||||
return {
|
const telemetry = useTelemetry();
|
||||||
FORM_CONFIG: {} as IFormBoxConfig,
|
|
||||||
loading: false,
|
const loading = ref(false);
|
||||||
showMfaView: false,
|
const showMfaView = ref(false);
|
||||||
email: '',
|
const email = ref('');
|
||||||
password: '',
|
const password = ref('');
|
||||||
reportError: false,
|
const reportError = ref(false);
|
||||||
};
|
|
||||||
},
|
const ldapLoginLabel = computed(() => settingsStore.ldapLoginLabel);
|
||||||
computed: {
|
const isLdapLoginEnabled = computed(() => settingsStore.isLdapLoginEnabled);
|
||||||
...mapStores(useUsersStore, useSettingsStore, useUIStore, useCloudPlanStore),
|
const emailLabel = computed(() => {
|
||||||
userHasMfaEnabled() {
|
let label = locale.baseText('auth.email');
|
||||||
return !!this.usersStore.currentUser?.mfaEnabled;
|
if (isLdapLoginEnabled.value && ldapLoginLabel.value) {
|
||||||
},
|
label = ldapLoginLabel.value;
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
let emailLabel = this.$locale.baseText('auth.email');
|
|
||||||
const ldapLoginLabel = this.settingsStore.ldapLoginLabel;
|
|
||||||
const isLdapLoginEnabled = this.settingsStore.isLdapLoginEnabled;
|
|
||||||
if (isLdapLoginEnabled && ldapLoginLabel) {
|
|
||||||
emailLabel = ldapLoginLabel;
|
|
||||||
}
|
}
|
||||||
this.FORM_CONFIG = {
|
return label;
|
||||||
title: this.$locale.baseText('auth.signin'),
|
});
|
||||||
buttonText: this.$locale.baseText('auth.signin'),
|
|
||||||
redirectText: this.$locale.baseText('forgotPassword'),
|
const redirectLink = computed(() => {
|
||||||
|
if (!settingsStore.isDesktopDeployment) {
|
||||||
|
return '/forgot-password';
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const formConfig: IFormBoxConfig = reactive({
|
||||||
|
title: locale.baseText('auth.signin'),
|
||||||
|
buttonText: locale.baseText('auth.signin'),
|
||||||
|
redirectText: locale.baseText('forgotPassword'),
|
||||||
|
redirectLink: redirectLink.value,
|
||||||
inputs: [
|
inputs: [
|
||||||
{
|
{
|
||||||
name: 'email',
|
name: 'email',
|
||||||
properties: {
|
properties: {
|
||||||
label: emailLabel,
|
label: emailLabel.value,
|
||||||
type: 'email',
|
type: 'email',
|
||||||
required: true,
|
required: true,
|
||||||
...(!isLdapLoginEnabled && { validationRules: [{ name: 'VALID_EMAIL' }] }),
|
...(!isLdapLoginEnabled.value && { validationRules: [{ name: 'VALID_EMAIL' }] }),
|
||||||
showRequiredAsterisk: false,
|
showRequiredAsterisk: false,
|
||||||
validateOnBlur: false,
|
validateOnBlur: false,
|
||||||
autocomplete: 'email',
|
autocomplete: 'email',
|
||||||
|
@ -66,7 +72,7 @@ export default defineComponent({
|
||||||
{
|
{
|
||||||
name: 'password',
|
name: 'password',
|
||||||
properties: {
|
properties: {
|
||||||
label: this.$locale.baseText('auth.password'),
|
label: locale.baseText('auth.password'),
|
||||||
type: 'password',
|
type: 'password',
|
||||||
required: true,
|
required: true,
|
||||||
showRequiredAsterisk: false,
|
showRequiredAsterisk: false,
|
||||||
|
@ -76,116 +82,126 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
});
|
||||||
|
|
||||||
if (!this.settingsStore.isDesktopDeployment) {
|
const onMFASubmitted = async (form: { token?: string; recoveryCode?: string }) => {
|
||||||
this.FORM_CONFIG.redirectLink = '/forgot-password';
|
await login({
|
||||||
}
|
email: email.value,
|
||||||
},
|
password: password.value,
|
||||||
methods: {
|
|
||||||
async onMFASubmitted(form: { token?: string; recoveryCode?: string }) {
|
|
||||||
await this.login({
|
|
||||||
email: this.email,
|
|
||||||
password: this.password,
|
|
||||||
token: form.token,
|
token: form.token,
|
||||||
recoveryCode: form.recoveryCode,
|
recoveryCode: form.recoveryCode,
|
||||||
});
|
});
|
||||||
},
|
};
|
||||||
async onEmailPasswordSubmitted(form: { email: string; password: string }) {
|
|
||||||
await this.login(form);
|
const isFormWithEmailAndPassword = (values: {
|
||||||
},
|
[key: string]: string;
|
||||||
isRedirectSafe() {
|
}): values is { email: string; password: string } => {
|
||||||
const redirect = this.getRedirectQueryParameter();
|
return 'email' in values && 'password' in values;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onEmailPasswordSubmitted = async (form: { [key: string]: string }) => {
|
||||||
|
if (!isFormWithEmailAndPassword(form)) return;
|
||||||
|
await login(form);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isRedirectSafe = () => {
|
||||||
|
const redirect = getRedirectQueryParameter();
|
||||||
return redirect.startsWith('/') || redirect.startsWith(window.location.origin);
|
return redirect.startsWith('/') || redirect.startsWith(window.location.origin);
|
||||||
},
|
};
|
||||||
getRedirectQueryParameter() {
|
|
||||||
|
const getRedirectQueryParameter = () => {
|
||||||
let redirect = '';
|
let redirect = '';
|
||||||
if (typeof this.$route.query?.redirect === 'string') {
|
if (typeof route.query?.redirect === 'string') {
|
||||||
redirect = decodeURIComponent(this.$route.query?.redirect);
|
redirect = decodeURIComponent(route.query?.redirect);
|
||||||
}
|
}
|
||||||
return redirect;
|
return redirect;
|
||||||
},
|
};
|
||||||
async login(form: { email: string; password: string; token?: string; recoveryCode?: string }) {
|
|
||||||
|
const login = async (form: {
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
token?: string;
|
||||||
|
recoveryCode?: string;
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
this.loading = true;
|
loading.value = true;
|
||||||
await this.usersStore.loginWithCreds({
|
await usersStore.loginWithCreds({
|
||||||
email: form.email,
|
email: form.email,
|
||||||
password: form.password,
|
password: form.password,
|
||||||
mfaToken: form.token,
|
mfaToken: form.token,
|
||||||
mfaRecoveryCode: form.recoveryCode,
|
mfaRecoveryCode: form.recoveryCode,
|
||||||
});
|
});
|
||||||
this.loading = false;
|
loading.value = false;
|
||||||
if (this.settingsStore.isCloudDeployment) {
|
if (settingsStore.isCloudDeployment) {
|
||||||
try {
|
try {
|
||||||
await this.cloudPlanStore.checkForCloudPlanData();
|
await cloudPlanStore.checkForCloudPlanData();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Failed to check for cloud plan data', error);
|
console.warn('Failed to check for cloud plan data', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.settingsStore.getSettings();
|
await settingsStore.getSettings();
|
||||||
this.clearAllStickyNotifications();
|
toast.clearAllStickyNotifications();
|
||||||
|
|
||||||
this.$telemetry.track('User attempted to login', {
|
telemetry.track('User attempted to login', {
|
||||||
result: this.showMfaView ? 'mfa_success' : 'success',
|
result: showMfaView.value ? 'mfa_success' : 'success',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.isRedirectSafe()) {
|
if (isRedirectSafe()) {
|
||||||
const redirect = this.getRedirectQueryParameter();
|
const redirect = getRedirectQueryParameter();
|
||||||
if (redirect.startsWith('http')) {
|
if (redirect.startsWith('http')) {
|
||||||
window.location.href = redirect;
|
window.location.href = redirect;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void this.$router.push(redirect);
|
void router.push(redirect);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.$router.push({ name: VIEWS.HOMEPAGE });
|
await router.push({ name: VIEWS.HOMEPAGE });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.errorCode === MFA_AUTHENTICATION_REQUIRED_ERROR_CODE) {
|
if (error.errorCode === MFA_AUTHENTICATION_REQUIRED_ERROR_CODE) {
|
||||||
this.showMfaView = true;
|
showMfaView.value = true;
|
||||||
this.cacheCredentials(form);
|
cacheCredentials(form);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$telemetry.track('User attempted to login', {
|
telemetry.track('User attempted to login', {
|
||||||
result: this.showMfaView ? 'mfa_token_rejected' : 'credentials_error',
|
result: showMfaView.value ? 'mfa_token_rejected' : 'credentials_error',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.showMfaView) {
|
if (!showMfaView.value) {
|
||||||
this.showError(error, this.$locale.baseText('auth.signin.error'));
|
toast.showError(error, locale.baseText('auth.signin.error'));
|
||||||
this.loading = false;
|
loading.value = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.reportError = true;
|
reportError.value = true;
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
onBackClick(fromForm: string) {
|
|
||||||
this.reportError = false;
|
const onBackClick = (fromForm: string) => {
|
||||||
|
reportError.value = false;
|
||||||
if (fromForm === MFA_FORM.MFA_TOKEN) {
|
if (fromForm === MFA_FORM.MFA_TOKEN) {
|
||||||
this.showMfaView = false;
|
showMfaView.value = false;
|
||||||
this.loading = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
onFormChanged(toForm: string) {
|
const onFormChanged = (toForm: string) => {
|
||||||
if (toForm === MFA_FORM.MFA_RECOVERY_CODE) {
|
if (toForm === MFA_FORM.MFA_RECOVERY_CODE) {
|
||||||
this.reportError = false;
|
reportError.value = false;
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
cacheCredentials(form: { email: string; password: string }) {
|
const cacheCredentials = (form: { email: string; password: string }) => {
|
||||||
this.email = form.email;
|
email.value = form.email;
|
||||||
this.password = form.password;
|
password.value = form.password;
|
||||||
},
|
};
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<AuthView
|
<AuthView
|
||||||
v-if="!showMfaView"
|
v-if="!showMfaView"
|
||||||
:form="FORM_CONFIG"
|
:form="formConfig"
|
||||||
:form-loading="loading"
|
:form-loading="loading"
|
||||||
:with-sso="true"
|
:with-sso="true"
|
||||||
data-test-id="signin-form"
|
data-test-id="signin-form"
|
||||||
|
|
Loading…
Reference in a new issue