Replace the useWebauthnAsDefault option by a client side form toggle

This commit is contained in:
Bubka 2023-03-15 14:44:51 +01:00
parent f359a1ade3
commit 4d8180a8c1
10 changed files with 30 additions and 24 deletions

View File

@ -58,7 +58,6 @@ class WebAuthnManageController extends Controller
// no more registered device exists. // no more registered device exists.
// See #110 // See #110
if (blank($user->webAuthnCredentials()->WhereEnabled()->get())) { if (blank($user->webAuthnCredentials()->WhereEnabled()->get())) {
Settings::delete('useWebauthnAsDefault');
Settings::delete('useWebauthnOnly'); Settings::delete('useWebauthnOnly');
Log::notice('No Webauthn credential enabled, Webauthn settings reset to default'); Log::notice('No Webauthn credential enabled, Webauthn settings reset to default');
} }

View File

@ -80,7 +80,6 @@ return [
'defaultGroup' => 0, 'defaultGroup' => 0,
'defaultCaptureMode' => 'livescan', 'defaultCaptureMode' => 'livescan',
'useDirectCapture' => false, 'useDirectCapture' => false,
'useWebauthnAsDefault' => false,
'useWebauthnOnly' => false, 'useWebauthnOnly' => false,
'getOfficialIcons' => true, 'getOfficialIcons' => true,
'theme' => 'system', 'theme' => 'system',

View File

@ -24,6 +24,11 @@ return new class extends Migration
DB::table('users')->update(['is_admin' => 1]); DB::table('users')->update(['is_admin' => 1]);
// The 'useWebauthnAsDefault' option is replaced by a local storage record
// so we delete it form the Options table to prevent its conversion to
// a user preference
DB::table('options')->where('key', 'useWebauthnAsDefault')->delete();
// User options are converted as user preferences // User options are converted as user preferences
$options = DB::table('options')->get(); $options = DB::table('options')->get();
$preferences = config('2fauth.preferences'); $preferences = config('2fauth.preferences');

View File

@ -20,11 +20,17 @@ Vue.mixin({
} }
else { else {
await this.axios.get('/user/logout') await this.axios.get('/user/logout')
this.$storage.clear() this.clearStorage()
this.$router.push({ name: 'login', params: { forceRefresh: true } }) this.$router.push({ name: 'login', params: { forceRefresh: true } })
} }
}, },
clearStorage() {
this.$storage.set('accounts')
this.$storage.set('groups')
this.$storage.set('lastRoute')
},
exitSettings: function (event) { exitSettings: function (event) {
if (event) { if (event) {
this.$notify({ clean: true }) this.$notify({ clean: true })

View File

@ -21,7 +21,7 @@
// there is nothing to do, we simply catch the error to avoid redondant navigation // there is nothing to do, we simply catch the error to avoid redondant navigation
}); });
this.$storage.clear() this.clearStorage()
}, },
} }
</script> </script>

View File

@ -12,7 +12,7 @@
<div class="nav-links"> <div class="nav-links">
<p>{{ $t('auth.webauthn.lost_your_device') }}&nbsp;<router-link id="lnkRecoverAccount" :to="{ name: 'webauthn.lost' }" class="is-link">{{ $t('auth.webauthn.recover_your_account') }}</router-link></p> <p>{{ $t('auth.webauthn.lost_your_device') }}&nbsp;<router-link id="lnkRecoverAccount" :to="{ name: 'webauthn.lost' }" class="is-link">{{ $t('auth.webauthn.recover_your_account') }}</router-link></p>
<p v-if="!this.$root.userPreferences.useWebauthnOnly">{{ $t('auth.sign_in_using') }}&nbsp; <p v-if="!this.$root.userPreferences.useWebauthnOnly">{{ $t('auth.sign_in_using') }}&nbsp;
<a id="lnkSignWithLegacy" role="button" class="is-link" @keyup.enter="showWebauthn = false" @click="showWebauthn = false" tabindex="0">{{ $t('auth.login_and_password') }}</a> <a id="lnkSignWithLegacy" role="button" class="is-link" @keyup.enter="toggleForm" @click="toggleForm" tabindex="0">{{ $t('auth.login_and_password') }}</a>
</p> </p>
</div> </div>
</form-wrapper> </form-wrapper>
@ -28,7 +28,7 @@
<div class="nav-links"> <div class="nav-links">
<p>{{ $t('auth.forms.forgot_your_password') }}&nbsp;<router-link id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link" :aria-label="$t('auth.forms.reset_your_password')">{{ $t('auth.forms.request_password_reset') }}</router-link></p> <p>{{ $t('auth.forms.forgot_your_password') }}&nbsp;<router-link id="lnkResetPwd" :to="{ name: 'password.request' }" class="is-link" :aria-label="$t('auth.forms.reset_your_password')">{{ $t('auth.forms.request_password_reset') }}</router-link></p>
<p >{{ $t('auth.sign_in_using') }}&nbsp; <p >{{ $t('auth.sign_in_using') }}&nbsp;
<a id="lnkSignWithWebauthn" role="button" class="is-link" @keyup.enter="showWebauthn = true" @click="showWebauthn = true" tabindex="0" :aria-label="$t('auth.sign_in_using_security_device')">{{ $t('auth.webauthn.security_device') }}</a> <a id="lnkSignWithWebauthn" role="button" class="is-link" @keyup.enter="toggleForm" @click="toggleForm" tabindex="0" :aria-label="$t('auth.sign_in_using_security_device')">{{ $t('auth.webauthn.security_device') }}</a>
</p> </p>
<p class="mt-4">{{ $t('auth.forms.dont_have_account_yet') }}&nbsp;<router-link id="lnkRegister" :to="{ name: 'register' }" class="is-link">{{ $t('auth.register') }}</router-link></p> <p class="mt-4">{{ $t('auth.forms.dont_have_account_yet') }}&nbsp;<router-link id="lnkRegister" :to="{ name: 'register' }" class="is-link">{{ $t('auth.register') }}</router-link></p>
</div> </div>
@ -53,17 +53,26 @@
password: '' password: ''
}), }),
isBusy: false, isBusy: false,
showWebauthn: this.$root.userPreferences.useWebauthnAsDefault || this.$root.userPreferences.useWebauthnOnly, showWebauthn: this.$root.userPreferences.useWebauthnOnly,
csrfRefresher: null, csrfRefresher: null,
webauthn: new WebAuthn() webauthn: new WebAuthn()
} }
}, },
mounted: function() { mounted: function() {
this.csrfRefresher = setInterval(this.refreshToken, 300000); // 5 min this.csrfRefresher = setInterval(this.refreshToken, 300000) // 5 min
this.showWebauthn = this.$storage.get('showWebauthnForm', false)
}, },
methods : { methods : {
/**
* Toggle the form between legacy and webauthn method
*/
toggleForm() {
this.showWebauthn = ! this.showWebauthn
this.$storage.set('showWebauthnForm', this.showWebauthn)
},
/** /**
* Sign in using the login/password form * Sign in using the login/password form
*/ */

View File

@ -42,8 +42,6 @@
<form> <form>
<!-- use webauthn only --> <!-- use webauthn only -->
<form-checkbox v-on:useWebauthnOnly="savePreference('useWebauthnOnly', $event)" :form="form" fieldName="useWebauthnOnly" :label="$t('auth.webauthn.use_webauthn_only.label')" :help="$t('auth.webauthn.use_webauthn_only.help')" :disabled="isRemoteUser || credentials.length === 0" /> <form-checkbox v-on:useWebauthnOnly="savePreference('useWebauthnOnly', $event)" :form="form" fieldName="useWebauthnOnly" :label="$t('auth.webauthn.use_webauthn_only.label')" :help="$t('auth.webauthn.use_webauthn_only.help')" :disabled="isRemoteUser || credentials.length === 0" />
<!-- default sign in method -->
<form-checkbox v-on:useWebauthnAsDefault="savePreference('useWebauthnAsDefault', $event)" :form="form" fieldName="useWebauthnAsDefault" :label="$t('auth.webauthn.use_webauthn_as_default.label')" :help="$t('auth.webauthn.use_webauthn_as_default.help')" :disabled="isRemoteUser || credentials.length === 0" />
</form> </form>
<!-- footer --> <!-- footer -->
<vue-footer :showButtons="true"> <vue-footer :showButtons="true">
@ -67,7 +65,6 @@
return { return {
form: new Form({ form: new Form({
useWebauthnOnly: null, useWebauthnOnly: null,
useWebauthnAsDefault: null,
}), }),
credentials: [], credentials: [],
isFetching: false, isFetching: false,
@ -193,9 +190,7 @@
if (this.credentials.length == 0) { if (this.credentials.length == 0) {
this.form.useWebauthnOnly = false this.form.useWebauthnOnly = false
this.form.useWebauthnAsDefault = false
this.$root.userPreferences['useWebauthnOnly'] = false this.$root.userPreferences['useWebauthnOnly'] = false
this.$root.userPreferences['useWebauthnAsDefault'] = false
} }
this.$notify({ type: 'is-success', text: this.$t('auth.webauthn.device_revoked') }) this.$notify({ type: 'is-success', text: this.$t('auth.webauthn.device_revoked') })

View File

@ -68,15 +68,11 @@ return [
'unknown_device' => 'Unknown device', 'unknown_device' => 'Unknown device',
'use_webauthn_only' => [ 'use_webauthn_only' => [
'label' => 'Use WebAuthn only', 'label' => 'Use WebAuthn only',
'help' => 'Make WebAuthn the only available method to sign in 2FAuth. This is the recommended setup to take advantage of the WebAuthn enhanced security.<br /> 'help' => 'Make WebAuthn the only authorized method to log into your 2FAuth account. This is the recommended setup to take advantage of the WebAuthn enhanced security.<br /><br />
In case of device lost, you will be able to recover your account by resetting this option and signing in using your email and password.' In case of device lost, you will be able to recover your account by resetting this option and signing in using your email and password.<br /><br />
], Attention! The Email & Password form remains available despite this option being enabled, but it will always return an \'Authentication failed\' response.'
'need_a_security_device_to_enable_options' => 'Set at least one device to enable these options',
'use_webauthn_as_default' => [
'label' => 'Use WebAuthn as default sign in method',
'help' => 'Set the 2FAuth sign in form to propose the WebAuthn authentication at first. The Login/password method is then available as an alternative/fallback solution.<br />
This has no effect if you only use WebAuthn.'
], ],
'need_a_security_device_to_enable_options' => 'Set at least one device to enable the following options',
], ],
'forms' => [ 'forms' => [
'name' => 'Name', 'name' => 'Name',

View File

@ -95,7 +95,6 @@ class UserControllerTest extends FeatureTestCase
'defaultGroup' => 1, 'defaultGroup' => 1,
'defaultCaptureMode' => 'advancedForm', 'defaultCaptureMode' => 'advancedForm',
'useDirectCapture' => true, 'useDirectCapture' => true,
'useWebauthnAsDefault' => true,
'useWebauthnOnly' => true, 'useWebauthnOnly' => true,
'getOfficialIcons' => false, 'getOfficialIcons' => false,
'theme' => 'dark', 'theme' => 'dark',
@ -116,7 +115,6 @@ class UserControllerTest extends FeatureTestCase
$this->user['preferences->defaultGroup'] = $userPrefs['defaultGroup']; $this->user['preferences->defaultGroup'] = $userPrefs['defaultGroup'];
$this->user['preferences->defaultCaptureMode'] = $userPrefs['defaultCaptureMode']; $this->user['preferences->defaultCaptureMode'] = $userPrefs['defaultCaptureMode'];
$this->user['preferences->useDirectCapture'] = $userPrefs['useDirectCapture']; $this->user['preferences->useDirectCapture'] = $userPrefs['useDirectCapture'];
$this->user['preferences->useWebauthnAsDefault'] = $userPrefs['useWebauthnAsDefault'];
$this->user['preferences->useWebauthnOnly'] = $userPrefs['useWebauthnOnly']; $this->user['preferences->useWebauthnOnly'] = $userPrefs['useWebauthnOnly'];
$this->user['preferences->getOfficialIcons'] = $userPrefs['getOfficialIcons']; $this->user['preferences->getOfficialIcons'] = $userPrefs['getOfficialIcons'];
$this->user['preferences->theme'] = $userPrefs['theme']; $this->user['preferences->theme'] = $userPrefs['theme'];

View File

@ -81,7 +81,6 @@ class SystemControllerTest extends FeatureTestCase
'defaultGroup', 'defaultGroup',
'defaultCaptureMode', 'defaultCaptureMode',
'useDirectCapture', 'useDirectCapture',
'useWebauthnAsDefault',
'useWebauthnOnly', 'useWebauthnOnly',
'getOfficialIcons', 'getOfficialIcons',
'lang', 'lang',