mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 05:17:28 -08:00
refactor(editor): Migrate MfaView.vue
to use script setup (no-changelog) (#9764)
This commit is contained in:
parent
4541a8f2d1
commit
21a9682862
|
@ -690,6 +690,11 @@ export const EXPERIMENTS_TO_TRACK = [
|
||||||
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.name,
|
CANVAS_AUTO_ADD_MANUAL_TRIGGER_EXPERIMENT.name,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const MFA_FORM = {
|
||||||
|
MFA_TOKEN: 'MFA_TOKEN',
|
||||||
|
MFA_RECOVERY_CODE: 'MFA_RECOVERY_CODE',
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const MFA_AUTHENTICATION_REQUIRED_ERROR_CODE = 998;
|
export const MFA_AUTHENTICATION_REQUIRED_ERROR_CODE = 998;
|
||||||
|
|
||||||
export const MFA_AUTHENTICATION_TOKEN_WINDOW_EXPIRED = 997;
|
export const MFA_AUTHENTICATION_TOKEN_WINDOW_EXPIRED = 997;
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
<div :class="$style.headerContainer">
|
<div :class="$style.headerContainer">
|
||||||
<n8n-heading size="xlarge" color="text-dark">{{
|
<n8n-heading size="xlarge" color="text-dark">{{
|
||||||
showRecoveryCodeForm
|
showRecoveryCodeForm
|
||||||
? $locale.baseText('mfa.recovery.modal.title')
|
? i18.baseText('mfa.recovery.modal.title')
|
||||||
: $locale.baseText('mfa.code.modal.title')
|
: i18.baseText('mfa.code.modal.title')
|
||||||
}}</n8n-heading>
|
}}</n8n-heading>
|
||||||
</div>
|
</div>
|
||||||
<div :class="[$style.formContainer, reportError ? $style.formError : '']">
|
<div :class="[$style.formContainer, reportError ? $style.formError : '']">
|
||||||
|
@ -26,9 +26,9 @@
|
||||||
size="small"
|
size="small"
|
||||||
color="text-base"
|
color="text-base"
|
||||||
:bold="false"
|
:bold="false"
|
||||||
>{{ $locale.baseText('mfa.code.input.info') }}
|
>{{ i18.baseText('mfa.code.input.info') }}
|
||||||
<a data-test-id="mfa-enter-recovery-code-button" @click="onRecoveryCodeClick">{{
|
<a data-test-id="mfa-enter-recovery-code-button" @click="onRecoveryCodeClick">{{
|
||||||
$locale.baseText('mfa.code.input.info.action')
|
i18.baseText('mfa.code.input.info.action')
|
||||||
}}</a></n8n-text
|
}}</a></n8n-text
|
||||||
>
|
>
|
||||||
<n8n-text v-if="reportError" color="danger" size="small"
|
<n8n-text v-if="reportError" color="danger" size="small"
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
:class="$style.recoveryCodeLink"
|
:class="$style.recoveryCodeLink"
|
||||||
@click="onRecoveryCodeClick"
|
@click="onRecoveryCodeClick"
|
||||||
>
|
>
|
||||||
{{ $locale.baseText('mfa.recovery.input.info.action') }}</a
|
{{ i18.baseText('mfa.recovery.input.info.action') }}</a
|
||||||
>
|
>
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
</div>
|
</div>
|
||||||
|
@ -49,8 +49,8 @@
|
||||||
:loading="verifyingMfaToken"
|
:loading="verifyingMfaToken"
|
||||||
:label="
|
:label="
|
||||||
showRecoveryCodeForm
|
showRecoveryCodeForm
|
||||||
? $locale.baseText('mfa.recovery.button.verify')
|
? i18.baseText('mfa.recovery.button.verify')
|
||||||
: $locale.baseText('mfa.code.button.continue')
|
: i18.baseText('mfa.code.button.continue')
|
||||||
"
|
"
|
||||||
size="large"
|
size="large"
|
||||||
:disabled="!hasAnyChanges"
|
:disabled="!hasAnyChanges"
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
/>
|
/>
|
||||||
<n8n-button
|
<n8n-button
|
||||||
float="left"
|
float="left"
|
||||||
:label="$locale.baseText('mfa.button.back')"
|
:label="i18.baseText('mfa.button.back')"
|
||||||
size="large"
|
size="large"
|
||||||
type="tertiary"
|
type="tertiary"
|
||||||
@click="onBackClick"
|
@click="onBackClick"
|
||||||
|
@ -68,135 +68,165 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import type { IFormInputs } from '@/Interface';
|
import type { IFormInputs } from '@/Interface';
|
||||||
import Logo from '../components/Logo.vue';
|
import Logo from '../components/Logo.vue';
|
||||||
import {
|
import {
|
||||||
MFA_AUTHENTICATION_RECOVERY_CODE_INPUT_MAX_LENGTH,
|
MFA_AUTHENTICATION_RECOVERY_CODE_INPUT_MAX_LENGTH,
|
||||||
MFA_AUTHENTICATION_TOKEN_INPUT_MAX_LENGTH,
|
MFA_AUTHENTICATION_TOKEN_INPUT_MAX_LENGTH,
|
||||||
|
MFA_FORM,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import { useUsersStore } from '@/stores/users.store';
|
|
||||||
import { mapStores } from 'pinia';
|
|
||||||
import { mfaEventBus } from '@/event-bus';
|
import { mfaEventBus } from '@/event-bus';
|
||||||
import { defineComponent } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import { toRefs } from '@vueuse/core';
|
||||||
|
|
||||||
export const FORM = {
|
// ---------------------------------------------------------------------------
|
||||||
MFA_TOKEN: 'MFA_TOKEN',
|
// #region Props
|
||||||
MFA_RECOVERY_CODE: 'MFA_RECOVERY_CODE',
|
// ---------------------------------------------------------------------------
|
||||||
} as const;
|
|
||||||
|
|
||||||
export default defineComponent({
|
const props = defineProps({
|
||||||
name: 'MfaView',
|
reportError: Boolean,
|
||||||
components: {
|
|
||||||
Logo,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
reportError: Boolean,
|
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
return {
|
|
||||||
...useToast(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
hasAnyChanges: false,
|
|
||||||
formBus: mfaEventBus,
|
|
||||||
formInputs: null as null | IFormInputs,
|
|
||||||
showRecoveryCodeForm: false,
|
|
||||||
verifyingMfaToken: false,
|
|
||||||
formError: '',
|
|
||||||
};
|
|
||||||
},
|
|
||||||
async mounted() {
|
|
||||||
this.formInputs = [this.mfaTokenFieldWithDefaults()];
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapStores(useUsersStore),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onRecoveryCodeClick() {
|
|
||||||
this.formError = '';
|
|
||||||
this.showRecoveryCodeForm = true;
|
|
||||||
this.hasAnyChanges = false;
|
|
||||||
this.formInputs = [this.mfaRecoveryCodeFieldWithDefaults()];
|
|
||||||
this.$emit('onFormChanged', FORM.MFA_RECOVERY_CODE);
|
|
||||||
},
|
|
||||||
onBackClick() {
|
|
||||||
if (!this.showRecoveryCodeForm) {
|
|
||||||
this.$emit('onBackClick', FORM.MFA_TOKEN);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.showRecoveryCodeForm = false;
|
|
||||||
this.hasAnyChanges = true;
|
|
||||||
this.formInputs = [this.mfaTokenFieldWithDefaults()];
|
|
||||||
this.$emit('onBackClick', FORM.MFA_RECOVERY_CODE);
|
|
||||||
},
|
|
||||||
onInput({ target: { value, name } }: { target: { value: string; name: string } }) {
|
|
||||||
const isSubmittingMfaToken = name === 'token';
|
|
||||||
const inputValidLength = isSubmittingMfaToken
|
|
||||||
? MFA_AUTHENTICATION_TOKEN_INPUT_MAX_LENGTH
|
|
||||||
: MFA_AUTHENTICATION_RECOVERY_CODE_INPUT_MAX_LENGTH;
|
|
||||||
|
|
||||||
if (value.length !== inputValidLength) {
|
|
||||||
this.hasAnyChanges = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.verifyingMfaToken = true;
|
|
||||||
this.hasAnyChanges = true;
|
|
||||||
|
|
||||||
const dataToSubmit = isSubmittingMfaToken
|
|
||||||
? { token: value, recoveryCode: '' }
|
|
||||||
: { token: '', recoveryCode: value };
|
|
||||||
|
|
||||||
this.onSubmit(dataToSubmit)
|
|
||||||
.catch(() => {})
|
|
||||||
.finally(() => (this.verifyingMfaToken = false));
|
|
||||||
},
|
|
||||||
async onSubmit(form: { token: string; recoveryCode: string }) {
|
|
||||||
this.formError = !this.showRecoveryCodeForm
|
|
||||||
? this.$locale.baseText('mfa.code.invalid')
|
|
||||||
: this.$locale.baseText('mfa.recovery.invalid');
|
|
||||||
this.$emit('submit', form);
|
|
||||||
},
|
|
||||||
onSaveClick() {
|
|
||||||
this.formBus.emit('submit');
|
|
||||||
},
|
|
||||||
mfaTokenFieldWithDefaults() {
|
|
||||||
return this.formField(
|
|
||||||
'token',
|
|
||||||
this.$locale.baseText('mfa.code.input.label'),
|
|
||||||
this.$locale.baseText('mfa.code.input.placeholder'),
|
|
||||||
MFA_AUTHENTICATION_TOKEN_INPUT_MAX_LENGTH,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
mfaRecoveryCodeFieldWithDefaults() {
|
|
||||||
return this.formField(
|
|
||||||
'recoveryCode',
|
|
||||||
this.$locale.baseText('mfa.recovery.input.label'),
|
|
||||||
this.$locale.baseText('mfa.recovery.input.placeholder'),
|
|
||||||
MFA_AUTHENTICATION_RECOVERY_CODE_INPUT_MAX_LENGTH,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
formField(name: string, label: string, placeholder: string, maxlength: number, focus = true) {
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
initialValue: '',
|
|
||||||
properties: {
|
|
||||||
label,
|
|
||||||
placeholder,
|
|
||||||
maxlength,
|
|
||||||
capitalize: true,
|
|
||||||
validateOnBlur: false,
|
|
||||||
focusInitially: focus,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// #region Reactive properties
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const hasAnyChanges = ref(false);
|
||||||
|
const formBus = ref(mfaEventBus);
|
||||||
|
const formInputs = ref<null | IFormInputs>(null);
|
||||||
|
const showRecoveryCodeForm = ref(false);
|
||||||
|
const verifyingMfaToken = ref(false);
|
||||||
|
const formError = ref('');
|
||||||
|
const { reportError } = toRefs(props);
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// #region Composable
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const i18 = useI18n();
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// #region Emit
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const emit = defineEmits(['onFormChanged', 'onBackClick', 'submit']);
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// #region Methods
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const formField = (
|
||||||
|
name: string,
|
||||||
|
label: string,
|
||||||
|
placeholder: string,
|
||||||
|
maxlength: number,
|
||||||
|
focus = true,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
initialValue: '',
|
||||||
|
properties: {
|
||||||
|
label,
|
||||||
|
placeholder,
|
||||||
|
maxlength,
|
||||||
|
capitalize: true,
|
||||||
|
validateOnBlur: false,
|
||||||
|
focusInitially: focus,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRecoveryCodeClick = () => {
|
||||||
|
formError.value = '';
|
||||||
|
showRecoveryCodeForm.value = true;
|
||||||
|
hasAnyChanges.value = false;
|
||||||
|
formInputs.value = [mfaRecoveryCodeFieldWithDefaults()];
|
||||||
|
emit('onFormChanged', MFA_FORM.MFA_RECOVERY_CODE);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBackClick = () => {
|
||||||
|
if (!showRecoveryCodeForm.value) {
|
||||||
|
emit('onBackClick', MFA_FORM.MFA_TOKEN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showRecoveryCodeForm.value = false;
|
||||||
|
hasAnyChanges.value = true;
|
||||||
|
formInputs.value = [mfaTokenFieldWithDefaults()];
|
||||||
|
emit('onBackClick', MFA_FORM.MFA_RECOVERY_CODE);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSubmit = async (form: { token: string; recoveryCode: string }) => {
|
||||||
|
formError.value = !showRecoveryCodeForm.value
|
||||||
|
? i18.baseText('mfa.code.invalid')
|
||||||
|
: i18.baseText('mfa.recovery.invalid');
|
||||||
|
emit('submit', form);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onInput = ({ target: { value, name } }: { target: { value: string; name: string } }) => {
|
||||||
|
const isSubmittingMfaToken = name === 'token';
|
||||||
|
const inputValidLength = isSubmittingMfaToken
|
||||||
|
? MFA_AUTHENTICATION_TOKEN_INPUT_MAX_LENGTH
|
||||||
|
: MFA_AUTHENTICATION_RECOVERY_CODE_INPUT_MAX_LENGTH;
|
||||||
|
|
||||||
|
if (value.length !== inputValidLength) {
|
||||||
|
hasAnyChanges.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyingMfaToken.value = true;
|
||||||
|
hasAnyChanges.value = true;
|
||||||
|
|
||||||
|
const dataToSubmit = isSubmittingMfaToken
|
||||||
|
? { token: value, recoveryCode: '' }
|
||||||
|
: { token: '', recoveryCode: value };
|
||||||
|
|
||||||
|
onSubmit(dataToSubmit)
|
||||||
|
.catch(() => {})
|
||||||
|
.finally(() => (verifyingMfaToken.value = false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const mfaRecoveryCodeFieldWithDefaults = () => {
|
||||||
|
return formField(
|
||||||
|
'recoveryCode',
|
||||||
|
i18.baseText('mfa.recovery.input.label'),
|
||||||
|
i18.baseText('mfa.recovery.input.placeholder'),
|
||||||
|
MFA_AUTHENTICATION_RECOVERY_CODE_INPUT_MAX_LENGTH,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mfaTokenFieldWithDefaults = () => {
|
||||||
|
return formField(
|
||||||
|
'token',
|
||||||
|
i18.baseText('mfa.code.input.label'),
|
||||||
|
i18.baseText('mfa.code.input.placeholder'),
|
||||||
|
MFA_AUTHENTICATION_TOKEN_INPUT_MAX_LENGTH,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSaveClick = () => {
|
||||||
|
formBus.value.emit('submit');
|
||||||
|
};
|
||||||
|
|
||||||
|
// #endregion
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// #region Lifecycle hooks
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
formInputs.value = [mfaTokenFieldWithDefaults()];
|
||||||
|
});
|
||||||
|
|
||||||
|
// #endregion
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -21,10 +21,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import AuthView from './AuthView.vue';
|
import AuthView from './AuthView.vue';
|
||||||
import MfaView, { FORM } from './MfaView.vue';
|
import MfaView from './MfaView.vue';
|
||||||
import { useToast } from '@/composables/useToast';
|
import { useToast } from '@/composables/useToast';
|
||||||
import type { IFormBoxConfig } from '@/Interface';
|
import type { IFormBoxConfig } from '@/Interface';
|
||||||
import { MFA_AUTHENTICATION_REQUIRED_ERROR_CODE, VIEWS } from '@/constants';
|
import { MFA_AUTHENTICATION_REQUIRED_ERROR_CODE, VIEWS, MFA_FORM } from '@/constants';
|
||||||
import { mapStores } from 'pinia';
|
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';
|
||||||
|
@ -183,13 +183,13 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
onBackClick(fromForm: string) {
|
onBackClick(fromForm: string) {
|
||||||
this.reportError = false;
|
this.reportError = false;
|
||||||
if (fromForm === FORM.MFA_TOKEN) {
|
if (fromForm === MFA_FORM.MFA_TOKEN) {
|
||||||
this.showMfaView = false;
|
this.showMfaView = false;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFormChanged(toForm: string) {
|
onFormChanged(toForm: string) {
|
||||||
if (toForm === FORM.MFA_RECOVERY_CODE) {
|
if (toForm === MFA_FORM.MFA_RECOVERY_CODE) {
|
||||||
this.reportError = false;
|
this.reportError = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue