Implement 2FA setup and disabling
This commit is contained in:
@@ -2,6 +2,27 @@
|
|||||||
<div class="content">
|
<div class="content">
|
||||||
<InputDialog ref="inputDialog" />
|
<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>
|
<h1>{{ $t('profile.title') }}</h1>
|
||||||
<Card>
|
<Card>
|
||||||
<div style="display: flex;">
|
<div style="display: flex;">
|
||||||
@@ -42,7 +63,7 @@
|
|||||||
<td colspan="3" class="text-right">
|
<td colspan="3" class="text-right">
|
||||||
<!-- <Button tool @click="onPasswordReset()">{{ $t('profile.passwordResetAction') }}</Button> -->
|
<!-- <Button tool @click="onPasswordReset()">{{ $t('profile.passwordResetAction') }}</Button> -->
|
||||||
<Button tool @click="onPasswordChange()">{{ $t('profile.changePasswordAction') }}</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>
|
<Button tool @click="profileModel.logout()" icon="fa fa-sign-out">{{ $t('main.logout') }}</Button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -72,7 +93,7 @@ const i18n = useI18n();
|
|||||||
const t = i18n.t;
|
const t = i18n.t;
|
||||||
|
|
||||||
import { ref, onMounted, useTemplateRef } from 'vue';
|
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 { TOKEN_TYPES } from '../constants.js';
|
||||||
import AppPasswords from './AppPasswords.vue';
|
import AppPasswords from './AppPasswords.vue';
|
||||||
import Card from './Card.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
|
// Init
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
user.value = await profileModel.get();
|
user.value = await profileModel.get();
|
||||||
|
|||||||
@@ -124,6 +124,39 @@ function create(origin, accessToken) {
|
|||||||
|
|
||||||
return null;
|
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