const AUTH_REGISTER = 'auth_register';
const MFA_SETUP_METHOD = 'mfa_setup_method';
+ const MFA_REMOVE_METHOD = 'mfa_remove_method';
}
const METHOD_TOTP = 'totp';
const METHOD_BACKUP_CODES = 'backup_codes';
+ /**
+ * Get all the MFA methods available.
+ */
+ public static function allMethods(): array
+ {
+ return [self::METHOD_TOTP, self::METHOD_BACKUP_CODES];
+ }
+
/**
* Upsert a new MFA value for the given user and method
* using the provided value.
namespace BookStack\Http\Controllers\Auth;
+use BookStack\Actions\ActivityType;
+use BookStack\Auth\Access\Mfa\MfaValue;
use BookStack\Http\Controllers\Controller;
class MfaController extends Controller
'userMethods' => $userMethods,
]);
}
+
+ /**
+ * Remove an MFA method for the current user.
+ * @throws \Exception
+ */
+ public function remove(string $method)
+ {
+ if (in_array($method, MfaValue::allMethods())) {
+ $value = user()->mfaValues()->where('method', '=', $method)->first();
+ if ($value) {
+ $value->delete();
+ $this->logActivity(ActivityType::MFA_REMOVE_METHOD, $method);
+ }
+ }
+
+ return redirect('/mfa/setup');
+ }
}
// MFA
'mfa_setup_method_notification' => 'Multi-factor method successfully configured',
+ 'mfa_remove_method_notification' => 'Multi-factor method successfully removed',
// Other
'commented_on' => 'commented on',
display: inline-block !important;
}
+.relative {
+ position: relative;
+}
+
.hidden {
display: none !important;
}
Already configured
</div>
<a href="{{ url('/mfa/totp-generate') }}" class="button outline small">Reconfigure</a>
+ <div component="dropdown" class="inline relative">
+ <button type="button" refs="dropdown@toggle" class="button outline small">Remove</button>
+ <div refs="dropdown@menu" class="dropdown-menu">
+ <p class="text-neg small px-m mb-xs">Are you sure you want to remove this multi-factor authentication method?</p>
+ <form action="{{ url('/mfa/remove/totp') }}" method="post">
+ {{ csrf_field() }}
+ {{ method_field('delete') }}
+ <button class="text-primary small delete">{{ trans('common.confirm') }}</button>
+ </form>
+ </div>
+ </div>
@else
<a href="{{ url('/mfa/totp-generate') }}" class="button outline">Setup</a>
@endif
Already configured
</div>
<a href="{{ url('/mfa/backup-codes-generate') }}" class="button outline small">Reconfigure</a>
+ <div component="dropdown" class="inline relative">
+ <button type="button" refs="dropdown@toggle" class="button outline small">Remove</button>
+ <div refs="dropdown@menu" class="dropdown-menu">
+ <p class="text-neg small px-m mb-xs">Are you sure you want to remove this multi-factor authentication method?</p>
+ <form action="{{ url('/mfa/remove/backup_codes') }}" method="post">
+ {{ csrf_field() }}
+ {{ method_field('delete') }}
+ <button class="text-primary small delete">{{ trans('common.confirm') }}</button>
+ </form>
+ </div>
+ </div>
@else
<a href="{{ url('/mfa/backup-codes-generate') }}" class="button outline">Setup</a>
@endif
Route::post('/mfa/totp-confirm', 'Auth\MfaTotpController@confirm');
Route::get('/mfa/backup-codes-generate', 'Auth\MfaBackupCodesController@generate');
Route::post('/mfa/backup-codes-confirm', 'Auth\MfaBackupCodesController@confirm');
+ Route::delete('/mfa/remove/{method}', 'Auth\MfaController@remove');
});
// Social auth routes
namespace Tests\Auth;
+use BookStack\Actions\ActivityType;
use BookStack\Auth\Access\Mfa\MfaValue;
use BookStack\Auth\User;
use PragmaRX\Google2FA\Google2FA;
$resp->assertElementExists('[title="MFA Configured"] svg');
}
+ public function test_remove_mfa_method()
+ {
+ $admin = $this->getAdmin();
+
+ MfaValue::upsertWithValue($admin, MfaValue::METHOD_TOTP, 'test');
+ $this->assertEquals(1, $admin->mfaValues()->count());
+ $resp = $this->actingAs($admin)->get('/mfa/setup');
+ $resp->assertElementExists('form[action$="/mfa/remove/totp"]');
+
+ $resp = $this->delete("/mfa/remove/totp");
+ $resp->assertRedirect("/mfa/setup");
+ $resp = $this->followRedirects($resp);
+ $resp->assertSee('Multi-factor method successfully removed');
+
+ $this->assertActivityExists(ActivityType::MFA_REMOVE_METHOD);
+ $this->assertEquals(0, $admin->mfaValues()->count());
+ }
+
}
\ No newline at end of file