Improve the "add security key" UX, require sudo mode

This commit is contained in:
Pēteris Caune 2020-11-13 16:23:28 +02:00
parent e3aedd3b03
commit 2c3286c280
No known key found for this signature in database
GPG Key ID: E28D7679E9A9EDE2
5 changed files with 82 additions and 27 deletions

View File

@ -22,6 +22,7 @@ from fido2.server import Fido2Server
from fido2.webauthn import PublicKeyCredentialRpEntity from fido2.webauthn import PublicKeyCredentialRpEntity
from fido2 import cbor from fido2 import cbor
from hc.accounts import forms from hc.accounts import forms
from hc.accounts.decorators import require_sudo_mode
from hc.accounts.models import Credential, Profile, Project, Member from hc.accounts.models import Credential, Profile, Project, Member
from hc.api.models import Channel, Check, TokenBucket from hc.api.models import Channel, Check, TokenBucket
from hc.lib.date import choose_next_report_date from hc.lib.date import choose_next_report_date
@ -552,6 +553,7 @@ def _verify_origin(aaa):
@login_required @login_required
@require_sudo_mode
def add_credential(request): def add_credential(request):
rp = PublicKeyCredentialRpEntity("localhost", "Healthchecks") rp = PublicKeyCredentialRpEntity("localhost", "Healthchecks")
# FIXME use HTTPS, remove the verify_origin hack # FIXME use HTTPS, remove the verify_origin hack

View File

@ -0,0 +1,8 @@
#add-credential-waiting .spinner {
margin: 0;
}
#add-credential-error-text {
font-family: "Lucida Console", Monaco, monospace;
margin: 16px 0;
}

View File

@ -3,23 +3,34 @@ $(function() {
var optionsBytes = Uint8Array.from(atob(form.dataset.options), c => c.charCodeAt(0)); var optionsBytes = Uint8Array.from(atob(form.dataset.options), c => c.charCodeAt(0));
// cbor.js expects ArrayBuffer as input when decoding // cbor.js expects ArrayBuffer as input when decoding
var options = CBOR.decode(optionsBytes.buffer); var options = CBOR.decode(optionsBytes.buffer);
console.log("decoded options:", options);
function b64(arraybuffer) { function b64(arraybuffer) {
return btoa(String.fromCharCode.apply(null, new Uint8Array(arraybuffer))); return btoa(String.fromCharCode.apply(null, new Uint8Array(arraybuffer)));
} }
navigator.credentials.create(options).then(function(attestation) { function requestCredentials() {
console.log("got attestation: ", attestation); // Hide error & success messages, show the "waiting" message
$("#name-next").addClass("hide");
$("#add-credential-waiting").removeClass("hide");
$("#add-credential-error").addClass("hide");
$("#add-credential-success").addClass("hide");
navigator.credentials.create(options).then(function(attestation) {
$("#attestation_object").val(b64(attestation.response.attestationObject)); $("#attestation_object").val(b64(attestation.response.attestationObject));
$("#client_data_json").val(b64(attestation.response.clientDataJSON)); $("#client_data_json").val(b64(attestation.response.clientDataJSON));
console.log("form updated, all is well");
$("#add-credential-submit").prop("disabled", ""); // Show the success message and save button
$("#add-credential-waiting").addClass("hide");
$("#add-credential-success").removeClass("hide"); $("#add-credential-success").removeClass("hide");
}).catch(function(err) { }).catch(function(err) {
$("#add-credential-error span").text(err); // Show the error message
$("#add-credential-waiting").addClass("hide");
$("#add-credential-error-text").text(err);
$("#add-credential-error").removeClass("hide"); $("#add-credential-error").removeClass("hide");
}); });
}
$("#name-next").click(requestCredentials);
$("#retry").click(requestCredentials);
}); });

View File

@ -1,5 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load compress static %} {% load compress static hc_extras %}
{% block content %} {% block content %}
@ -11,7 +11,7 @@
data-options="{{ options }}" data-options="{{ options }}"
method="post" method="post"
encrypt="multipart/form-data"> encrypt="multipart/form-data">
<h1>Add Credential</h1> <h1>Add Security Key</h1>
{% csrf_token %} {% csrf_token %}
<input id="attestation_object" type="hidden" name="attestation_object"> <input id="attestation_object" type="hidden" name="attestation_object">
@ -25,23 +25,56 @@
</div> </div>
</div> </div>
<div id="add-credential-error" class="alert alert-danger hide"> <div class="form-group text-right">
<strong>Something went wrong.</strong> <button
<span></span> id="name-next"
class="btn btn-default" type="button">
Confirm Name and Continue
</button>
</div> </div>
<div id="add-credential-success" class="alert alert-success hide"> <div id="add-credential-waiting" class="hide">
<h2>Waiting for security key</h2>
<p>
Follow your browser's steps to register your security key
with {% site_name %}.
</p>
<div class="spinner started">
<div class="d1"></div>
<div class="d2"></div>
<div class="d3"></div>
</div>
</div>
<div id="add-credential-error" class="alert alert-danger hide">
<p>
<strong>Something went wrong.</strong>
</p>
<p id="add-credential-error-text"></p>
<div class="text-right">
<button id="retry" type="button" class="btn btn-danger">
Try Again
</button>
</div>
</div>
<div id="add-credential-success" class="hide">
<div class="alert alert-success">
<strong>Success!</strong> <strong>Success!</strong>
Credential acquired. Credential acquired.
</div> </div>
<div class="form-group text-right">
<input <input
id="add-credential-submit" id="add-credential-submit"
class="btn btn-default pull-right" class="btn btn-primary"
type="submit" type="submit"
name="" name=""
value="Save Credential" disabled> value="Save Security Key">
</div>
</div>
</form> </form>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -20,6 +20,7 @@
<link rel="stylesheet" href="{% static 'css/bootstrap-select.min.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/bootstrap-select.min.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/selectize.hc.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/selectize.hc.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/add_credential.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/add_project_modal.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/add_project_modal.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/add_pushover.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/add_pushover.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/webhook_form.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/webhook_form.css' %}" type="text/css">