Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add 2fa[WTEL-4430] #72

Merged
merged 3 commits into from
May 16, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feature: add 2fa[WTEL-4430]
Lera24 committed May 15, 2024
commit 968db75d7a500ff5d6bd2ae759afcc2918bec3dd
16 changes: 15 additions & 1 deletion src/api/auth/auth.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
import instance, { config } from '../instance';

export const login = async (credentials) => {
export const login = async (credentials, isToken = true) => {
dlohvinov marked this conversation as resolved.
Show resolved Hide resolved
const url = '/login';

try {
const response = await instance.post(url, credentials);
if(isToken) {
localStorage.setItem('access-token', response.accessToken);
return postToken();
} return response;
} catch (err) {
throw err;
}
};

export const login2fa = async (credentials) => {
const url = '/login/2fa';
try {
const response = await instance.post(url, credentials);
localStorage.setItem('access-token', response.accessToken);
@@ -105,6 +118,7 @@ const checkDomainExistence = async (domain) => {

const AuthAPI = {
login,
login2fa,
register,
checkCurrentSession,
loadServiceProviders,
58 changes: 58 additions & 0 deletions src/components/auth/login/steps/the-login-third-step.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<template>
<div>
<wt-input
v-model.trim="totp"
:label="$t('auth.code')"
/>

<div class="auth-form-actions">
<wt-button
color="secondary"
@click="emit('back')"
>{{ $t('reusable.back') }}
</wt-button>

<wt-button
:disabled="v$.$invalid"
@click="emit('next')"
>{{ $t('auth.login') }}
</wt-button>
</div>
</div>
</template>

<script setup>
import { useVuelidate } from '@vuelidate/core';
import { required } from '@vuelidate/validators';
import { computed, onMounted } from 'vue';
import { useStore } from 'vuex';
import { useNextOnEnter } from '../../../../composables/useNextOnEnter.js';

const emit = defineEmits(['back', 'next']);

const store = useStore();

const totp = computed({
get: () => store.state.auth.totp,
set: (value) => setProp({ prop: 'totp', value })
});

const v$ = useVuelidate(
computed(() => ({
totp: {
required,
},
})),
{ totp },
{ $autoDirty: true },
);

onMounted(() => { v$.value.$touch() });

useNextOnEnter(() => !v$.value.$invalid && emit('next'));

async function setProp(payload) {
return store.dispatch('auth/SET_PROPERTY', payload);
}

</script>
70 changes: 54 additions & 16 deletions src/components/auth/login/the-login.vue
Original file line number Diff line number Diff line change
@@ -21,31 +21,52 @@
@back="backPrevStep"
@next="goNextStep"
></second-step>

<third-step
v-if="activeStep === 3"
@back="backPrevStep"
@next="goNextStep"
></third-step>
</div>

</template>
</wt-stepper>
</template>

<script>
import { mapActions } from 'vuex';
import { mapActions, mapState } from 'vuex';
import FirstStep from '../login/steps/the-login-first-step.vue';
import SecondStep from '../login/steps/the-login-second-step.vue';

import ThirdStep from './steps/the-login-third-step.vue';
export default {
name: 'the-login',
data: () => ({
activeStep: 1,
isFirstStepSubmitting: false,
steps: [],
}),
components: {
FirstStep,
SecondStep,
ThirdStep,
},

computed: {
steps() {
return [
...mapState('auth', {
enabledTfa: (state) => state.enabledTfa,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tfa?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enabledTfa (Two Factor Authorization)
Раз така назва з беку приходить, я б її не змінювала)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а чому десь 2фа, а десь Тфа тоді?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2фа у url API та змінній enable_2FA
У enabledTfa 2 погано сприймається у назві на бекенді

}),
},

methods: {
...mapActions('auth', {
setProp: 'SET_PROPERTY',
resetState: 'RESET_STATE',
checkDomain: 'CHECK_DOMAIN',
get2faSessionId: 'GET_2FA_SESSION_ID',
}),

setSteps() {
this.steps = [
{
name: this.$t('reusable.step', { count: 1 }),
description: this.$t('auth.enterDomain'),
@@ -54,23 +75,23 @@ export default {
name: this.$t('reusable.step', { count: 2 }),
description: this.$t('auth.enterUsername'),
},
];
]
},
},

methods: {
...mapActions('auth', {
setProp: 'SET_PROPERTY',
resetState: 'RESET_STATE',
checkDomain: 'CHECK_DOMAIN',
}),

backPrevStep() {
if (this.activeStep === 1) {
this.$emit('change-tab', { value: 'register' });
} else {
this.activeStep = this.activeStep - 1;
}

if (this.activeStep === 2) {
this.setProp({ prop: 'password', value: '' });
}

if (this.activeStep === 3) {
this.setProp({ prop: 'totp', value: '' });
}
},

async goNextStep() {
@@ -85,20 +106,37 @@ export default {
}
}

if (this.activeStep === 2) {
await this.setProp({ prop: 'password', value: '' });
this.activeStep = this.activeStep + 1;

if (this.activeStep === 3) {
await this.get2faSessionId();
}

this.activeStep = this.activeStep + 1;
} else {
this.$emit('submit');
}
},
},

created() {
this.setSteps();
},

unmounted() {
this.resetState();
},

watch: {
enabledTfa: {
handler(value) {
if (value) this.steps.push({
name: this.$t('reusable.step', { count: 3 }),
description: this.$t('auth.enterAuthenticationCode'),
})
Lera24 marked this conversation as resolved.
Show resolved Hide resolved
},
},
},

};
</script>

2 changes: 2 additions & 0 deletions src/locale/en/en.js
Original file line number Diff line number Diff line change
@@ -16,6 +16,8 @@ export default {
signIn: 'Sign in',
login: 'Log in',
remember: 'Remember',
code: 'Code',
enterAuthenticationCode: 'Enter your two-factor authentication code',
carousel: {
title1: 'Cloud vs. On-Site',
text1: 'Security policy does not allow to store data and use cloud services? With Webitel, you can build a contact center on your site!',
2 changes: 2 additions & 0 deletions src/locale/ru/ru.js
Original file line number Diff line number Diff line change
@@ -16,5 +16,7 @@ export default {
signIn: 'Войти',
login: 'Войти',
remember: 'Запомнить',
code: 'Код',
enterAuthenticationCode: 'Введите ваш код двухфакторной аутентификации',
},
};
2 changes: 2 additions & 0 deletions src/locale/ua/ua.js
Original file line number Diff line number Diff line change
@@ -16,5 +16,7 @@ export default {
signIn: 'Увійти',
login: 'Увійти',
remember: 'Запам\'ятати',
code: 'Код',
enterAuthenticationCode: 'Введіть ваш код двофакторної автентифікації',
},
};
44 changes: 33 additions & 11 deletions src/store/modules/auth/auth.js
Original file line number Diff line number Diff line change
@@ -14,23 +14,25 @@ const state = {
...defaultState(),
rememberCredentials: localStorage.getItem('auth/rememberCredentials') === 'true',
loginProviders: {},
enabledTfa: '',
totp: '',
id: '',
Lera24 marked this conversation as resolved.
Show resolved Hide resolved
};

const getters = {};

const actions = {
SUBMIT_AUTH: async (context, action) => {
let accessToken;
switch (action) {
case 'login':
accessToken = await context.dispatch('LOGIN');
break;
case 'register':
accessToken = await context.dispatch('REGISTER');
break;
default:
throw new Error(`Invalid action: ${action}`);
}

if(action === 'login') {
if(context.state.id) {
accessToken = await context.dispatch('LOGIN_2FA');
}
accessToken = await context.dispatch('LOGIN');
} else if(action === 'register') {
accessToken = await context.dispatch('REGISTER');
} else throw new Error(`Invalid action: ${action}`);

return context.dispatch('ON_AUTH_SUCCESS', { accessToken });
},
@@ -43,6 +45,25 @@ const actions = {
});
},

GET_2FA_SESSION_ID: async (context) => {
const { id } = await AuthAPI.login({
username: context.state.username,
password: context.state.password,
domain: context.state.domain,
}, false);

if(id) {
await context.dispatch('SET_PROPERTY', { prop: 'id', value: id });
}
},

LOGIN_2FA: (context) => {
dlohvinov marked this conversation as resolved.
Show resolved Hide resolved
return AuthAPI.login2fa({
id: context.state.id,
totp: context.state.totp,
})
},

REGISTER: (context) => {
return AuthAPI.register({
username: context.state.username,
@@ -55,8 +76,9 @@ const actions = {
LOAD_SERVICE_PROVIDERS: async (context) => {
const domain = context.state.domain;
const response = await AuthAPI.loadServiceProviders({ domain });
const { federation = {} } = response;
const { federation = {}, enabledTfa } = response;
context.commit('SET_SERVICE_PROVIDERS', federation);
await context.dispatch('SET_PROPERTY', { prop: 'enabledTfa', value: enabledTfa });
},

EXECUTE_PROVIDER: (context, { ticket }) => {
Loading