{{ $t('profile.title') }}
@@ -42,7 +63,7 @@
-
+
@@ -72,7 +93,7 @@ const i18n = useI18n();
const t = i18n.t;
import { ref, onMounted, useTemplateRef } from 'vue';
-import { Button, Dropdown, InputDialog } from 'pankow';
+import { Button, Dropdown, Dialog, InputDialog, Spinner, TextInput } from 'pankow';
import { TOKEN_TYPES } from '../constants.js';
import AppPasswords from './AppPasswords.vue';
import Card from './Card.vue';
@@ -217,6 +238,54 @@ async function onRevokeAllWebAndCliTokens() {
}
+// 2fa
+const mandatory2FAHelp = ref('');
+const twoFASecret = ref('');
+const twoFATotpToken = ref('');
+const twoFAQRCode = ref('');
+const twoFAEnableError = ref('');
+const twoFADialog = useTemplateRef('twoFADialog');
+
+async function onOpenTwoFASetupDialog() {
+ const [error, result] = await profileModel.setTwoFASecret();
+ if (error) return console.error(error);
+
+ twoFAEnableError.value = '';
+ twoFATotpToken.value = '';
+ twoFASecret.value = result.secret;
+ twoFAQRCode.value = result.qrcode;
+
+ twoFADialog.value.open();
+}
+
+async function onTwoFAEnable() {
+ const [error] = await profileModel.enableTwoFA(twoFATotpToken.value);
+ if (error) return twoFAEnableError.value = error.body ? error.body.message : 'Internal error';
+ user.value = await profileModel.get();
+
+ twoFADialog.value.close();
+}
+
+async function onTwoFADisable() {
+ const password = await inputDialog.value.prompt({
+ message: t('profile.disable2FA.title'),
+ modal: true,
+ placeholder: t('appstore.accountDialog.password'),
+ type: 'password',
+ confirmStyle: 'danger',
+ confirmLabel: t('main.dialog.yes'),
+ rejectLabel: t('main.dialog.no')
+ });
+
+ if (!password) return;
+
+ const [error] = await profileModel.disableTwoFA(password);
+ if (error) return onTwoFADisable();
+
+ user.value = await profileModel.get();
+}
+
+
// Init
onMounted(async () => {
user.value = await profileModel.get();
diff --git a/dashboard/src/models/ProfileModel.js b/dashboard/src/models/ProfileModel.js
index 828dcf48b..d5fd75c7a 100644
--- a/dashboard/src/models/ProfileModel.js
+++ b/dashboard/src/models/ProfileModel.js
@@ -124,6 +124,39 @@ function create(origin, accessToken) {
return null;
},
+ async setTwoFASecret() {
+ let error, result;
+ try {
+ result = await fetcher.post(`${origin}/api/v1/profile/twofactorauthentication_secret`, {}, { access_token: accessToken });
+ } catch (e) {
+ error = e;
+ }
+
+ if (error || result.status !== 200) return [error || result];
+ return [null, result.body];
+ },
+ async enableTwoFA(totpToken) {
+ let error, result;
+ try {
+ result = await fetcher.post(`${origin}/api/v1/profile/twofactorauthentication_enable`, { totpToken }, { access_token: accessToken });
+ } catch (e) {
+ error = e;
+ }
+
+ if (error || result.status !== 204) return [error || result];
+ return [null];
+ },
+ async disableTwoFA(password) {
+ let error, result;
+ try {
+ result = await fetcher.post(`${origin}/api/v1/profile/twofactorauthentication_disable`, { password }, { access_token: accessToken });
+ } catch (e) {
+ error = e;
+ }
+
+ if (error || result.status !== 204) return [error || result];
+ return [null];
+ },
};
}