Skip to content

Commit

Permalink
Merge pull request #666 from webitel/feature/add-2fa
Browse files Browse the repository at this point in the history
feature: add 2fa[WTEL-3405]
  • Loading branch information
dlohvinov authored May 16, 2024
2 parents 4f9316c + e919702 commit 2ce7e4d
Show file tree
Hide file tree
Showing 10 changed files with 211 additions and 13 deletions.
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"jszip-utils": "^0.1.0",
"monaco-editor": "^0.44.0",
"path": "^0.12.7",
"qrcode.vue": "^3.4.1",
"query-string": "^7.1.1",
"sortablejs": "^1.10.2",
"vue": "^3.2.47",
Expand Down
3 changes: 3 additions & 0 deletions src/app/locale/en/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ export default {
tokenPopupSave: 'Save as TXT',
userIp: 'User IP',
userId: 'User ID',
download: 'Download',
regenerate: 'Regenerate',
askingAlert: 'Are you sure you want to regenerate the code? The user won’t be able to log in',
},
license: {
customers: 'Customers',
Expand Down
3 changes: 3 additions & 0 deletions src/app/locale/ru/ru.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ export default {
tokenPopupSave: 'Сохранить в формате TXT',
userIp: 'IP пользователя',
userId: 'ID пользователя',
download: 'Скачать',
regenerate: 'Перегенерировать',
askingAlert: 'Вы уверены, что хотите перегенерировать QR-код? Пользователь потеряет возможность войти в систему',
},
license: {
customers: 'Пользователи',
Expand Down
3 changes: 3 additions & 0 deletions src/app/locale/ua/ua.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ export default {
tokenPopupSave: 'Зберегти у форматі TXT',
userIp: 'IP користувача',
userId: 'ID користувача',
download: 'Завантажити',
regenerate: 'Перегенерувати',
askingAlert: 'Ви впевнені, що хочете перегенерувати QR-код? Користувач втратить можливість зайти в систему',
},
license: {
customers: 'Користувачі',
Expand Down
21 changes: 21 additions & 0 deletions src/modules/directory/modules/users/api/users-2fa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import applyTransform, { notify } from '@webitel/ui-sdk/src/api/transformers/index.js';

Check failure on line 1 in src/modules/directory/modules/users/api/users-2fa.js

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package
import instance from '../../../../../app/api/instance';

const generateUrl = async ({ id }) => {
const url = `users/${id}/2fa`;

try {
const response = await instance.post(url,{});
return applyTransform(response.data, []);
} catch (err) {
throw applyTransform(err, [
notify,
]);
}
};

const Users2faAPI = {
generate: generateUrl,
};

export default Users2faAPI;
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<template>

Check failure on line 1 in src/modules/directory/modules/users/components/_internals/qrcode-two-factor-auth.vue

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Install the 'eslint' package
<div class="qrcode-two-factor-auth">

<wt-popup
v-if="isConfirmationPopup"
@close="closeConfirmationPopup"
>

<template v-slot:title>
{{ $t('reusable.warning') }}
</template>

<template v-slot:main>
{{ $t('objects.directory.users.askingAlert') }}
</template>

<template v-slot:actions>
<wt-button
color="secondary"
@click="closeConfirmationPopup"
>
{{ $t('vocabulary.no') }}
</wt-button>

<wt-button
color="error"
@click="regenerateUrl"
>
{{ $t('vocabulary.yes') }}
</wt-button>
</template>
</wt-popup>

<div
ref="qrcodeContainer"
class="qrcode-two-factor-auth__canvas"
>
<qrcode-vue
ref="qrcode"
:value="props.url"
level="H"
/>
</div>

<div class="qrcode-two-factor-auth__wrapper">
<wt-button
color="secondary"
@click="download"
>
{{ $t('objects.directory.users.download') }}
</wt-button>

<wt-button
color="error"
@click="openConfirmationPopup"
>
{{ $t('objects.directory.users.regenerate') }}
</wt-button>
</div>

</div>
</template>

<script setup>
import { ref } from 'vue';
import { useStore } from 'vuex';
import QrcodeVue from 'qrcode.vue';
const props = defineProps({
namespace: {
type: String,
},
url: {
type: String,
required: true,
},
});
const store = useStore();
const qrcodeContainer = ref();
const isConfirmationPopup = ref(false);
function download() {
const canvas = qrcodeContainer.value.querySelector('canvas');
const link = document.createElement('a');
link.download = 'qr-code.png';
link.href = canvas.toDataURL('image/png');
link.click();
}
async function regenerateUrl() {
await store.dispatch(`${props.namespace}/REGENERATE_2FA_URL`);
closeConfirmationPopup();
}
function openConfirmationPopup() {
isConfirmationPopup.value = true;
}
function closeConfirmationPopup() {
isConfirmationPopup.value = false;
}
</script>

<style lang="scss" scoped>
.qrcode-two-factor-auth {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-sm);
&__wrapper {
display: flex;
gap: var(--spacing-sm);
}
&__canvas {
box-shadow: var(--elevation-5);
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@

<wt-input
:disabled="disableUserInput"
:label="$t('objects.directory.users.login')"
:v="v.itemInstance.username"
:value="itemInstance.username"
required
@input="setItemProp({ prop: 'username', value: $event })"
:label="$t('objects.email')"
:value="itemInstance.email"
@input="setItemProp({ prop: 'email', value: $event })"
/>

<password-input
Expand All @@ -32,30 +30,46 @@

<wt-input
:disabled="disableUserInput"
:label="$t('objects.directory.users.extensions')"
:value="itemInstance.extension"
@input="setItemProp({ prop: 'extension', value: $event })"
:label="$t('objects.directory.users.login')"
:v="v.itemInstance.username"
:value="itemInstance.username"
required
@input="setItemProp({ prop: 'username', value: $event })"
/>

<qrcode
v-if="isDisplayQRCode"
:namespace="namespace"
:url="itemInstance.totpUrl"
/>

<wt-input
:disabled="disableUserInput"
:label="$t('objects.email')"
:value="itemInstance.email"
@input="setItemProp({ prop: 'email', value: $event })"
:label="$t('objects.directory.users.extensions')"
:value="itemInstance.extension"
@input="setItemProp({ prop: 'extension', value: $event })"
/>

</div>
</section>
</template>

<script>
import { mapGetters } from 'vuex';
import PasswordInput from '../../../../../app/components/utils/generate-password-input.vue';
import openedTabComponentMixin
from '../../../../../app/mixins/objectPagesMixins/openedObjectTabMixin/openedTabComponentMixin';
import Qrcode from './_internals/qrcode-two-factor-auth.vue';
export default {
name: 'OpenedUserGeneral',
components: { PasswordInput },
components: { PasswordInput, Qrcode },
mixins: [openedTabComponentMixin],
computed: {
...mapGetters('directory/users', {
isDisplayQRCode: 'IS_DISPLAY_QR_CODE',
}),
},
};
</script>

Expand Down
22 changes: 21 additions & 1 deletion src/modules/directory/modules/users/store/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ObjectStoreModule
import PermissionsStoreModule
from '../../../../../app/store/BaseStoreModules/StoreModules/PermissionsStoreModule/PermissionsStoreModule';
import UsersAPI from '../api/users';
import Users2faAPI from '../api/users-2fa.js';
import logs from '../modules/logs/store/logs';
import tokens from '../modules/tokens/store/usersTokens';
import headers from './_internals/headers';
Expand All @@ -23,9 +24,20 @@ const resettableState = {
note: '',
},
variables: [],
totpUrl: '',
},
};

const getters = {
IS_DISPLAY_QR_CODE: (
state,
getters,
rootState,
rootGetters,
) => rootGetters['userinfo/IS_CHANGE_USER_PASSWORD_ALLOW'] &&
!!state.itemInstance.totpUrl,
};

const actions = {
ADD_VARIABLE_PAIR: (context) => {
const pair = { key: '', value: '' };
Expand Down Expand Up @@ -55,6 +67,14 @@ const actions = {
context.commit('RESET_ITEM_STATE');
context.dispatch('directory/users/tokens/RESET_STATE', {}, { root: true });
},
REGENERATE_2FA_URL: async (context) => {
try {
await Users2faAPI.generate({ id: context.state.itemId });
await context.dispatch('LOAD_ITEM');
} catch (err) {
throw err;
}
},
};

const mutations = {
Expand All @@ -78,6 +98,6 @@ const users = new ObjectStoreModule({ resettableState, headers })
.attachAPIModule(UsersAPI)
.generateAPIActions()
.setChildModules({ tokens, logs, permissions })
.getModule({ actions, mutations });
.getModule({ getters, actions, mutations });

export default users;
2 changes: 2 additions & 0 deletions src/modules/userinfo/store/userinfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const getters = {
}
return accumulator;
}, []),

IS_CHANGE_USER_PASSWORD_ALLOW: (state) => !!state.permissions['change_user_password'],
};

const actions = {
Expand Down

0 comments on commit 2ce7e4d

Please sign in to comment.