diff --git a/uffd/mfa/templates/auth.html b/uffd/mfa/templates/auth.html index 57f5d588454bfbe7237c99cdcdc7a148576d3983..033b98fcc29572c5f7f852d69e3dace4f9f0a9bc 100644 --- a/uffd/mfa/templates/auth.html +++ b/uffd/mfa/templates/auth.html @@ -51,7 +51,7 @@ function begin_webauthn() { method: 'POST', }).then(function(response) { if(response.ok) return response.arrayBuffer(); - throw new Error('No credential available to authenticate!'); + throw new Error('You have not registered any U2F/FIDO2 devices for your account'); }).then(CBOR.decode).then(function(options) { $('#webauthn-btn-text').text('Waiting for response from your device'); return navigator.credentials.get(options); @@ -75,8 +75,26 @@ function begin_webauthn() { } else { throw new Error('Response from authenticator rejected'); } - }, function(reason) { - $('#webauthn-alert').text('Authentication with your FIDO token failed!'); + }, function(err) { + console.log(err); + /* various webauthn errors */ + if (err.name == 'NotAllowedError') + $('#webauthn-alert').text('Authentication timed out, was aborted or not allowed'); + else if (err.name == 'InvalidStateError') + $('#webauthn-alert').text('Device is not registered for your account'); + else if (err.name == 'AbortError') + $('#webauthn-alert').text('Authentication was aborted'); + else if (err.name == 'NotSupportedError') + $('#webauthn-alert').text('U2F and FIDO2 devices are not supported by your browser'); + /* errors from fetch() */ + else if (err.name == 'TypeError') + $('#webauthn-alert').text('Could not connect to server'); + /* our own errors */ + else if (err.name == 'Error') + $('#webauthn-alert').text(err.message); + /* fallback */ + else + $('#webauthn-alert').text('Authentication failed ('+err+')'); $('#webauthn-alert').removeClass('d-none'); $('#webauthn-spinner').addClass('d-none'); $('#webauthn-btn-text').text('Try FIDO token again'); diff --git a/uffd/mfa/templates/setup.html b/uffd/mfa/templates/setup.html index 48388149007311f58184c627b243fb34a9929586..6ffeac9bae91fd2d0fa5f9c50cd6d898f2dbe286 100644 --- a/uffd/mfa/templates/setup.html +++ b/uffd/mfa/templates/setup.html @@ -128,7 +128,7 @@ You need to setup at least one authentication method to enable two-factor authen <form id="webauthn-form" class="form mb-2"> <div class="row m-0"> <label class="sr-only" for="webauthn-name">Name</label> - <input type="text" class="form-control mb-2 col-12 col-lg-auto mr-2" style="width: 15em;" id="webauthn-name" placeholder="Name" required {{ 'disabled' if mfa_init }}> + <input type="text" class="form-control mb-2 col-12 col-lg-auto mr-2" style="width: 15em;" id="webauthn-name" placeholder="Name" required disabled> <button type="submit" id="webauthn-btn" class="btn btn-primary mb-2 col" disabled> <span id="webauthn-spinner" class="spinner-border spinner-border-sm d-none" role="status" aria-hidden="true"></span> <span id="webauthn-btn-text">Setup new device</span> @@ -173,8 +173,11 @@ $('#webauthn-form').on('submit', function(e) { fetch({{ url_for('mfa.setup_webauthn_begin')|tojson }}, { method: 'POST', }).then(function(response) { - if(response.ok) return response.arrayBuffer(); - throw new Error('Error getting registration data!'); + if (response.ok) + return response.arrayBuffer(); + if (response.status == 403) + throw new Error('You need to generate recovery codes first'); + throw new Error('Server error'); }).then(CBOR.decode).then(function(options) { $('#webauthn-btn-text').text('Waiting for response from your device'); return navigator.credentials.create(options); @@ -194,10 +197,28 @@ $('#webauthn-form').on('submit', function(e) { $('#webauthn-btn-text').text('Success'); window.location = {{ url_for('mfa.setup')|tojson }}; } else { - throw new Error('Server rejected authenticator response'); + throw new Error('Response from authenticator rejected'); } - }, function(reason) { - $('#webauthn-alert').text('Registration failed!'); + }, function(err) { + console.log(err); + /* various webauthn errors */ + if (err.name == 'NotAllowedError') + $('#webauthn-alert').text('Registration timed out, was aborted or not allowed'); + else if (err.name == 'InvalidStateError') + $('#webauthn-alert').text('You attempted to register a device that is already registered'); + else if (err.name == 'AbortError') + $('#webauthn-alert').text('Registration was aborted'); + else if (err.name == 'NotSupportedError') + $('#webauthn-alert').text('U2F and FIDO2 devices are not supported by your browser'); + /* errors from fetch() */ + else if (err.name == 'TypeError') + $('#webauthn-alert').text('Could not connect to server'); + /* our own errors */ + else if (err.name == 'Error') + $('#webauthn-alert').text(err.message); + /* fallback */ + else + $('#webauthn-alert').text('Registration failed ('+err+')'); $('#webauthn-alert').removeClass('d-none'); $('#webauthn-spinner').addClass('d-none'); $('#webauthn-btn-text').text('Retry registration'); @@ -212,7 +233,7 @@ if (typeof(PublicKeyCredential) != "undefined") { $('#webauthn-name').prop('disabled', false); {% endif %} } else { - $('#webauthn-alert').text('U2F and FIDO2 devices are not supported by your browser!'); + $('#webauthn-alert').text('U2F and FIDO2 devices are not supported by your browser'); $('#webauthn-alert').removeClass('d-none'); } diff --git a/uffd/mfa/views.py b/uffd/mfa/views.py index 408e6a982e1d65edf09735b98b58ca7753e9d742..a0259797d94edb4d18f88f63423efaac333c6d7b 100644 --- a/uffd/mfa/views.py +++ b/uffd/mfa/views.py @@ -1,4 +1,4 @@ -from flask import Blueprint, render_template, session, request, redirect, url_for, flash, current_app +from flask import Blueprint, render_template, session, request, redirect, url_for, flash, current_app, abort import urllib.parse from fido2.client import ClientData @@ -117,7 +117,7 @@ def setup_webauthn_begin(): server = get_webauthn_server() registration_data, state = server.register_begin( { - "id": user.loginname.encode(), + "id": user.dn.encode(), "name": user.loginname, "displayName": user.displayname, }, @@ -140,7 +140,6 @@ def setup_webauthn_complete(): method = WebauthnMethod(user, auth_data, name=data['name']) db.session.add(method) db.session.commit() - print("REGISTERED CREDENTIAL:", auth_data.credential_data) return cbor.dumps({"status": "OK"}) @bp.route('/setup/webauthn/<int:id>/delete') @@ -178,6 +177,8 @@ def auth_webauthn_complete(): client_data = ClientData(data["clientDataJSON"]) auth_data = AuthenticatorData(data["authenticatorData"]) signature = data["signature"] + # authenticate_complete() (as of python-fido2 v0.5.0, the version in Debian Buster) + # does not check signCount, although the spec recommends it server.authenticate_complete( session.pop("webauthn-state"), creds,