allow totp and passkey to co-exist
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
|
||||
import { ref, useTemplateRef } from 'vue';
|
||||
import { Button, ButtonGroup, ClipboardAction, Dialog, TextInput, InputGroup, FormGroup } from '@cloudron/pankow';
|
||||
import { Button, ClipboardAction, Dialog, TextInput, InputGroup, FormGroup } from '@cloudron/pankow';
|
||||
import { startRegistration } from '@simplewebauthn/browser';
|
||||
import ProfileModel from '../models/ProfileModel.js';
|
||||
|
||||
@@ -64,37 +64,42 @@ async function onRegisterPasskey() {
|
||||
passkeyRegisterBusy.value = false;
|
||||
}
|
||||
|
||||
async function loadTotpSecret() {
|
||||
const [error, result] = await profileModel.setTotpSecret();
|
||||
if (error) return console.error(error);
|
||||
|
||||
totpSecret.value = result.secret;
|
||||
totpQRCode.value = result.qrcode;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
async open() {
|
||||
setupMode.value = 'passkey';
|
||||
async open(method) {
|
||||
setupMode.value = method || 'passkey';
|
||||
totpEnableError.value = '';
|
||||
totpToken.value = '';
|
||||
passkeyRegisterError.value = '';
|
||||
|
||||
dialog.value.open();
|
||||
|
||||
const [error, result] = await profileModel.setTotpSecret();
|
||||
if (error) return console.error(error);
|
||||
|
||||
totpSecret.value = result.secret;
|
||||
totpQRCode.value = result.qrcode;
|
||||
if (setupMode.value === 'totp') await loadTotpSecret();
|
||||
},
|
||||
close() {
|
||||
dialog.value.close();
|
||||
}
|
||||
});
|
||||
|
||||
async function switchMode(mode) {
|
||||
setupMode.value = mode;
|
||||
if (mode === 'totp' && !totpSecret.value) await loadTotpSecret();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog ref="dialog" :title="$t('profile.enable2FA.title')" :dismissable="!props.mandatory2FA || props.has2FA">
|
||||
<Dialog ref="dialog" :title="setupMode === 'totp' ? $t('profile.enableTotp.title') : $t('profile.enablePasskey.title')" :dismissable="!props.mandatory2FA || props.has2FA">
|
||||
<div>
|
||||
<p class="text-warning" v-if="props.mandatory2FA && !props.has2FA">{{ $t('profile.enable2FA.mandatorySetup') }}</p>
|
||||
|
||||
<ButtonGroup style="display: flex; justify-content: center;"> <Button secondary @click="setupMode = 'passkey'" :outline="setupMode !== 'passkey' || null">{{ $t('profile.enable2FA.passkeyOption') }}</Button>
|
||||
<Button secondary @click="setupMode = 'totp'" :outline="setupMode !== 'totp' || null">{{ $t('profile.enable2FA.totpOption') }}</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<!-- Passkey Setup -->
|
||||
<div v-if="setupMode === 'passkey'">
|
||||
<p v-html="$t('profile.enable2FA.passkeyDescription', { 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>
|
||||
@@ -102,6 +107,9 @@ defineExpose({
|
||||
<Button @click="onRegisterPasskey()" :loading="passkeyRegisterBusy" :disabled="passkeyRegisterBusy">{{ $t('profile.enable2FA.registerPasskey') }}</Button>
|
||||
<div class="error-label" v-if="passkeyRegisterError">{{ passkeyRegisterError }}</div>
|
||||
</div>
|
||||
<p v-if="props.mandatory2FA && !props.has2FA" style="text-align: center; margin-top: 15px;">
|
||||
<a href="#" @click.prevent="switchMode('totp')">{{ $t('profile.enable2FA.switchToTotp') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- TOTP Setup -->
|
||||
@@ -123,6 +131,9 @@ defineExpose({
|
||||
<div class="error-label" v-if="totpEnableError">{{ totpEnableError }}</div>
|
||||
</FormGroup>
|
||||
</form>
|
||||
<p v-if="props.mandatory2FA && !props.has2FA" style="text-align: center; margin-top: 15px;">
|
||||
<a href="#" @click.prevent="switchMode('passkey')">{{ $t('profile.enable2FA.switchToPasskey') }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
@@ -110,8 +110,8 @@ async function loadPasskey() {
|
||||
userPasskey.value = result;
|
||||
}
|
||||
|
||||
async function onOpenTwoFASetupDialog() {
|
||||
enableTwoFADialog.value.open();
|
||||
async function onOpenTwoFASetupDialog(method) {
|
||||
enableTwoFADialog.value.open(method);
|
||||
}
|
||||
|
||||
async function onEnableTwoFASuccess() {
|
||||
@@ -119,8 +119,8 @@ async function onEnableTwoFASuccess() {
|
||||
await loadPasskey();
|
||||
}
|
||||
|
||||
async function onTwoFADisable() {
|
||||
disableTwoFADialog.value.open(userPasskey.value ? 'passkey' : 'totp');
|
||||
async function onTwoFADisable(method) {
|
||||
disableTwoFADialog.value.open(method);
|
||||
}
|
||||
|
||||
async function onTwoFADisableSuccess() {
|
||||
@@ -230,13 +230,25 @@ onMounted(async () => {
|
||||
|
||||
<SettingsItem v-if="!profile.source || !config.external2FA">
|
||||
<FormGroup>
|
||||
<label>{{ $t('profile.twoFactorAuth.title') }}</label>
|
||||
<div v-if="!has2FA">{{ $t('profile.twoFactorAuth.disabled') }}</div>
|
||||
<div v-else-if="profile.totpEnabled">{{ $t('profile.twoFactorAuth.totpEnabled') }} <i class="fa-solid fa-check text-success"></i></div>
|
||||
<div v-else-if="userPasskey">{{ $t('profile.twoFactorAuth.passkeyEnabled') }} <i class="fa-solid fa-check text-success"></i></div>
|
||||
<label>{{ $t('profile.twoFactorAuth.totpTitle') }}</label>
|
||||
<div v-if="profile.totpEnabled">{{ $t('profile.twoFactorAuth.totpEnabled') }} <i class="fa-solid fa-check text-success"></i></div>
|
||||
<div v-else>{{ $t('profile.twoFactorAuth.disabled') }}</div>
|
||||
</FormGroup>
|
||||
<div style="display: flex; align-items: center">
|
||||
<Button tool plain @click="has2FA ? onTwoFADisable() : onOpenTwoFASetupDialog()">{{ $t(has2FA ? 'profile.disable2FAAction' : 'profile.enable2FAAction') }}</Button>
|
||||
<Button tool plain v-if="profile.totpEnabled" @click="onTwoFADisable('totp')">{{ $t('profile.disable2FAAction') }}</Button>
|
||||
<Button tool plain v-else @click="onOpenTwoFASetupDialog('totp')">{{ $t('profile.enable2FAAction') }}</Button>
|
||||
</div>
|
||||
</SettingsItem>
|
||||
|
||||
<SettingsItem v-if="!profile.source || !config.external2FA">
|
||||
<FormGroup>
|
||||
<label>{{ $t('profile.twoFactorAuth.passkeyTitle') }}</label>
|
||||
<div v-if="userPasskey">{{ $t('profile.twoFactorAuth.passkeyEnabled') }} <i class="fa-solid fa-check text-success"></i></div>
|
||||
<div v-else>{{ $t('profile.twoFactorAuth.disabled') }}</div>
|
||||
</FormGroup>
|
||||
<div style="display: flex; align-items: center">
|
||||
<Button tool plain v-if="userPasskey" @click="onTwoFADisable('passkey')">{{ $t('profile.disable2FAAction') }}</Button>
|
||||
<Button tool plain v-else @click="onOpenTwoFASetupDialog('passkey')">{{ $t('profile.enable2FAAction') }}</Button>
|
||||
</div>
|
||||
</SettingsItem>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user