From 1c8a3cd1cf0730046c6557299b2e97a2dc078c65 Mon Sep 17 00:00:00 2001 From: Tommaso Bailetti Date: Tue, 5 Nov 2024 17:31:58 +0100 Subject: [PATCH 1/2] feat(controller): added 2fa --- src/components/TwoFaComponent.vue | 22 ++ .../controller/ControllerAppLogin.vue | 201 +++++++++++------- .../account/two_fa/ConfigureTwoFaDrawer.vue | 6 +- .../account/two_fa/RevokeTwoFaModal.vue | 10 +- src/lib/standalone/twoFa.ts | 24 ++- src/stores/controller/controllerLogin.ts | 54 +++-- src/views/controller/AccountView.vue | 4 + src/views/controller/UsersView.vue | 8 +- 8 files changed, 217 insertions(+), 112 deletions(-) create mode 100644 src/components/TwoFaComponent.vue diff --git a/src/components/TwoFaComponent.vue b/src/components/TwoFaComponent.vue new file mode 100644 index 000000000..e3d40ca7b --- /dev/null +++ b/src/components/TwoFaComponent.vue @@ -0,0 +1,22 @@ + + + diff --git a/src/components/controller/ControllerAppLogin.vue b/src/components/controller/ControllerAppLogin.vue index 1e39f30b0..7ff4f790f 100644 --- a/src/components/controller/ControllerAppLogin.vue +++ b/src/components/controller/ControllerAppLogin.vue @@ -5,34 +5,36 @@ @@ -148,29 +159,59 @@ function validate() { {{ t('login.privacy_policy') }} -
+ + + + {{ t('standalone.two_fa.verify_code') }} + + +
@@ -200,6 +241,8 @@ function validate() { kind="primary" size="lg" @click.prevent="login" + :disabled="loading" + :loading="loading" type="submit" class="w-full" >{{ t('login.sign_in') }} { }) const isAdmin = computed(() => role.value === 'admin') + const twoFaActive = computed((): boolean => { + try { + return jwtDecode(token.value)['2fa'] + } catch (e) { + return false + } + }) + const loadUserFromStorage = () => { const loginInfo = getJsonFromStorage('controllerLoginInfo') @@ -39,27 +53,39 @@ export const useLoginStore = defineStore('controllerLogin', () => { } } - const login = async (user: string, password: string) => { + const login = async (user: string, password: string, rememberMe = false) => { const res = await axios.post(`${getControllerApiEndpoint()}/login`, { username: user, password }) - const jwtToken = res.data.token - tokenRefreshedTime.value = new Date().getTime() - const loginInfo = { - username: user, - token: jwtToken, - tokenRefreshedTime: tokenRefreshedTime.value + // set or remove username to/from local storage + if (rememberMe) { + saveToStorage('controllerUsername', username.value) + } else { + deleteFromStorage('controllerUsername') } - saveToStorage('controllerLoginInfo', loginInfo) + + token.value = res.data.token + tokenRefreshedTime.value = new Date().getTime() + if (!twoFaActive.value) { + username.value = user + role.value = JSON.parse(atob(token.value.split('.')[1])).role + } + } + + async function verifyTwoFaToken(user: string, otp: string) { + await verifyTwoFaOtp(user, token.value, otp) username.value = user - token.value = jwtToken - role.value = JSON.parse(atob(jwtToken.split('.')[1])).role + role.value = JSON.parse(atob(token.value.split('.')[1])).role + saveToStorage('controllerLoginInfo', { + username: username.value, + token: token.value, + tokenRefreshedTime: tokenRefreshedTime.value + }) const themeStore = useThemeStore() themeStore.loadTheme() isSessionExpired.value = false - router.push(`${getControllerRoutePrefix()}/`) } const logout = async () => { @@ -121,6 +147,8 @@ export const useLoginStore = defineStore('controllerLogin', () => { login, logout, refreshToken, - isAdmin + isAdmin, + twoFaActive, + verifyTwoFaToken } }) diff --git a/src/views/controller/AccountView.vue b/src/views/controller/AccountView.vue index 909bc1921..b852adb5d 100644 --- a/src/views/controller/AccountView.vue +++ b/src/views/controller/AccountView.vue @@ -20,6 +20,9 @@ import GenerateSSHKeyPairDrawer from '@/components/controller/account_settings/G import DeleteSSHKeyModal from '@/components/controller/account_settings/DeleteSSHKeyModal.vue' import { useNotificationsStore } from '@/stores/notifications' import ChangeLangCombobox from '@/components/ChangeLangCombobox.vue' +import DeleteUserModal from '@/components/controller/users/DeleteUserModal.vue' +import ConfigureTwoFaDrawer from '@/components/standalone/account/two_fa/ConfigureTwoFaDrawer.vue' +import TwoFaComponent from '@/components/TwoFaComponent.vue' const { t } = useI18n() @@ -97,6 +100,7 @@ onMounted(() => { }} +
- - diff --git a/src/components/controller/users/UsersTable.vue b/src/components/controller/users/UsersTable.vue index 189a108ec..46e4916f0 100644 --- a/src/components/controller/users/UsersTable.vue +++ b/src/components/controller/users/UsersTable.vue @@ -20,6 +20,8 @@ import { NeButton } from '@nethesis/vue-components' import { type ControllerAccount } from '@/stores/controller/accounts' import { useLoginStore } from '@/stores/controller/controllerLogin' import { ref } from 'vue' +import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' +import { faCheck, faCircleCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons' const props = defineProps<{ users: ControllerAccount[] @@ -63,6 +65,9 @@ function getDropdownItems(item: ControllerAccount) { {{ t('controller.users.display_name') }} + + {{ t('controller.users.two_fa_status') }} + @@ -75,6 +80,18 @@ function getDropdownItems(item: ControllerAccount) { {{ item.display_name }} + + + + + +
diff --git a/src/components/standalone/StandaloneAppLogin.vue b/src/components/standalone/StandaloneAppLogin.vue index a719df768..f719377ed 100644 --- a/src/components/standalone/StandaloneAppLogin.vue +++ b/src/components/standalone/StandaloneAppLogin.vue @@ -22,7 +22,7 @@ import { MessageBag, validateRequired, validateSixDigitCode } from '@/lib/valida import { useI18n } from 'vue-i18n' import { getProductName, getCompanyName, getPrivacyPolicyUrl } from '@/lib/config' import { jwtDecode } from 'jwt-decode' -import { verifyTwoFaOtp } from '@/lib/standalone/twoFa' +import { verifyTwoFaOtp } from '@/lib/twoFa' import { ValidationError } from '@/lib/standalone/ubus' let username = ref('') diff --git a/src/components/standalone/account/two_fa/TwoFactorAuth.vue b/src/components/standalone/account/two_fa/TwoFactorAuth.vue index 759e373b9..f12d58414 100644 --- a/src/components/standalone/account/two_fa/TwoFactorAuth.vue +++ b/src/components/standalone/account/two_fa/TwoFactorAuth.vue @@ -6,7 +6,7 @@