diff --git a/dashboard/src/components/EnableTwoFADialog.vue b/dashboard/src/components/EnableTwoFADialog.vue index 7ab0de4ed..b4dd70c4f 100644 --- a/dashboard/src/components/EnableTwoFADialog.vue +++ b/dashboard/src/components/EnableTwoFADialog.vue @@ -1,7 +1,7 @@ - + {{ $t('profile.enable2FA.mandatorySetup') }} - {{ $t('profile.enable2FA.passkeyOption') }} - {{ $t('profile.enable2FA.totpOption') }} - - @@ -102,6 +107,9 @@ defineExpose({ {{ $t('profile.enable2FA.registerPasskey') }} {{ passkeyRegisterError }} + + {{ $t('profile.enable2FA.switchToTotp') }} + @@ -123,6 +131,9 @@ defineExpose({ {{ totpEnableError }} + + {{ $t('profile.enable2FA.switchToPasskey') }} + diff --git a/dashboard/src/views/ProfileView.vue b/dashboard/src/views/ProfileView.vue index 0d8eed623..6df4cf2c8 100644 --- a/dashboard/src/views/ProfileView.vue +++ b/dashboard/src/views/ProfileView.vue @@ -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 () => { - {{ $t('profile.twoFactorAuth.title') }} - {{ $t('profile.twoFactorAuth.disabled') }} - {{ $t('profile.twoFactorAuth.totpEnabled') }} - {{ $t('profile.twoFactorAuth.passkeyEnabled') }} + {{ $t('profile.twoFactorAuth.totpTitle') }} + {{ $t('profile.twoFactorAuth.totpEnabled') }} + {{ $t('profile.twoFactorAuth.disabled') }} - {{ $t(has2FA ? 'profile.disable2FAAction' : 'profile.enable2FAAction') }} + {{ $t('profile.disable2FAAction') }} + {{ $t('profile.enable2FAAction') }} + + + + + + {{ $t('profile.twoFactorAuth.passkeyTitle') }} + {{ $t('profile.twoFactorAuth.passkeyEnabled') }} + {{ $t('profile.twoFactorAuth.disabled') }} + + + {{ $t('profile.disable2FAAction') }} + {{ $t('profile.enable2FAAction') }} diff --git a/src/passkeys.js b/src/passkeys.js index bd14421a2..049139111 100644 --- a/src/passkeys.js +++ b/src/passkeys.js @@ -132,8 +132,6 @@ async function getRegistrationOptions(user) { const existingPasskeys = await listByUserId(user.id); if (existingPasskeys.length > 0) throw new BoxError(BoxError.ALREADY_EXISTS, 'User already has a passkey registered'); - if (user.totpEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, 'Cannot register passkey when TOTP is enabled'); - const { rpId, rpName } = await getRpInfo(); const options = await generateRegistrationOptions({ @@ -164,7 +162,6 @@ async function verifyRegistration(user, response, name) { // check should ideally be in a transaction const existingPasskeys = await listByUserId(user.id); if (existingPasskeys.length > 0) throw new BoxError(BoxError.ALREADY_EXISTS, 'User already has a passkey registered'); - if (user.totpEnabled) throw new BoxError(BoxError.ALREADY_EXISTS, 'Cannot register passkey when TOTP is enabled'); const expectedChallenge = getAndDeleteChallenge(user.id); if (!expectedChallenge) throw new BoxError(BoxError.BAD_FIELD, 'Challenge expired or not found'); diff --git a/src/users.js b/src/users.js index b03ea26b5..fe67fddc8 100644 --- a/src/users.js +++ b/src/users.js @@ -941,10 +941,6 @@ async function enableTotp(user, totpToken, auditSource) { const externalLdapConfig = await externalLdap.getConfig(); if (user.source === 'ldap' && externalLdap.supports2FA(externalLdapConfig)) throw new BoxError(BoxError.BAD_STATE, 'Cannot enable 2FA of external auth user'); - // Cannot enable TOTP if user has a passkey (user must choose one or the other) - const userPasskeys = await passkeys.listByUserId(user.id); - if (userPasskeys.length > 0) throw new BoxError(BoxError.ALREADY_EXISTS, 'Cannot enable TOTP when passkey is registered'); - const verified = speakeasy.totp.verify({ secret: user.totpSecret, encoding: 'base32', token: totpToken, window: 2 }); if (!verified) throw new BoxError(BoxError.INVALID_CREDENTIALS, 'Invalid 2FA code');
{{ $t('profile.enable2FA.mandatorySetup') }}
+ {{ $t('profile.enable2FA.switchToTotp') }} +
+ {{ $t('profile.enable2FA.switchToPasskey') }} +