Implement 2FA setup and disabling
This commit is contained in:
@@ -2,6 +2,27 @@
|
||||
<div class="content">
|
||||
<InputDialog ref="inputDialog" />
|
||||
|
||||
<Dialog ref="twoFADialog"
|
||||
:title="$t('profile.enable2FA.title')">
|
||||
<div style="text-align: center; max-width: 420px">
|
||||
<p v-show="mandatory2FAHelp">{{ $t('profile.enable2FA.description') }}</p>
|
||||
<p v-html="$t('profile.enable2FA.authenticatorAppDescription', { googleAuthenticatorPlayStoreLink: 'https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2', googleAuthenticatorITunesLink: 'https://itunes.apple.com/us/app/google-authenticator/id388497605', freeOTPPlayStoreLink: 'https://play.google.com/store/apps/details?id=org.fedorahosted.freeotp', freeOTPITunesLink: 'https://itunes.apple.com/us/app/freeotp-authenticator/id872559395'})"></p>
|
||||
<img :src="twoFAQRCode" style="border-radius: 10px; margin-bottom: 10px"/>
|
||||
<small>{{ twoFASecret }}</small>
|
||||
<br/>
|
||||
<br/>
|
||||
<p class="has-error" v-show="twoFAEnableError">{{ twoFAEnableError }} </p>
|
||||
<form @submit.prevent="onTwoFAEnable()">
|
||||
<input type="submit" style="display: none;" :disabled="!twoFATotpToken"/>
|
||||
<FormGroup>
|
||||
<label for="totpTokenInput">{{ $t('profile.enable2FA.token') }}</label>
|
||||
<TextInput v-model="twoFATotpToken" id="totpTokenInput" />
|
||||
</FormGroup>
|
||||
<Button @click="onTwoFAEnable()" :disabled="!twoFATotpToken">{{ $t('profile.enable2FA.enable') }}</Button>
|
||||
</form>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<h1>{{ $t('profile.title') }}</h1>
|
||||
<Card>
|
||||
<div style="display: flex;">
|
||||
@@ -42,7 +63,7 @@
|
||||
<td colspan="3" class="text-right">
|
||||
<!-- <Button tool @click="onPasswordReset()">{{ $t('profile.passwordResetAction') }}</Button> -->
|
||||
<Button tool @click="onPasswordChange()">{{ $t('profile.changePasswordAction') }}</Button>
|
||||
<Button tool v-show="!user.source && !config.external2FA" @click="on2FactorAuthConfig()">{{ $t(user.twoFactorAuthenticationEnabled ? 'profile.disable2FAAction' : 'profile.enable2FAAction') }}</Button>
|
||||
<Button tool v-show="!user.source && !config.external2FA" @click="user.twoFactorAuthenticationEnabled ? onTwoFADisable() : onOpenTwoFASetupDialog()">{{ $t(user.twoFactorAuthenticationEnabled ? 'profile.disable2FAAction' : 'profile.enable2FAAction') }}</Button>
|
||||
<Button tool @click="profileModel.logout()" icon="fa fa-sign-out">{{ $t('main.logout') }}</Button>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -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();
|
||||
|
||||
@@ -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];
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user