Browse Source

Add error handling on the client side, use Django form API

pull/456/head
Pēteris Caune 4 years ago
parent
commit
53688f1d87
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
4 changed files with 59 additions and 28 deletions
  1. +24
    -0
      hc/accounts/forms.py
  2. +7
    -16
      hc/accounts/views.py
  3. +9
    -6
      static/js/add_credential.js
  4. +19
    -6
      templates/accounts/add_credential.html

+ 24
- 0
hc/accounts/forms.py View File

@ -1,7 +1,11 @@
import base64
from datetime import timedelta as td from datetime import timedelta as td
from django import forms from django import forms
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.contrib.auth.models import User from django.contrib.auth.models import User
from fido2.ctap2 import AttestationObject
from fido2.client import ClientData
from hc.api.models import TokenBucket from hc.api.models import TokenBucket
@ -112,3 +116,23 @@ class ProjectNameForm(forms.Form):
class TransferForm(forms.Form): class TransferForm(forms.Form):
email = LowercaseEmailField() email = LowercaseEmailField()
class AddCredentialForm(forms.Form):
name = forms.CharField(max_length=100, required=False)
client_data_json = forms.CharField(required=True)
attestation_object = forms.CharField(required=True)
def clean_client_data_json(self):
v = self.cleaned_data["client_data_json"]
binary = base64.b64decode(v.encode())
obj = ClientData(binary)
return obj
def clean_attestation_object(self):
v = self.cleaned_data["attestation_object"]
binary = base64.b64decode(v.encode())
obj = AttestationObject(binary)
return obj

+ 7
- 16
hc/accounts/views.py View File

@ -18,8 +18,6 @@ from django.utils.timezone import now
from django.urls import resolve, Resolver404 from django.urls import resolve, Resolver404
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from fido2.client import ClientData
from fido2.ctap2 import AttestationObject
from fido2.server import Fido2Server from fido2.server import Fido2Server
from fido2.webauthn import PublicKeyCredentialRpEntity from fido2.webauthn import PublicKeyCredentialRpEntity
from fido2 import cbor from fido2 import cbor
@ -559,18 +557,15 @@ def add_credential(request):
# FIXME use HTTPS, remove the verify_origin hack # FIXME use HTTPS, remove the verify_origin hack
server = Fido2Server(rp, verify_origin=_verify_origin) server = Fido2Server(rp, verify_origin=_verify_origin)
def decode(form, key):
return base64.b64decode(request.POST[key].encode())
if request.method == "POST": if request.method == "POST":
# idea: use AddCredentialForm
client_data = ClientData(decode(request.POST, "clientDataJSON"))
att_obj = AttestationObject(decode(request.POST, "attestationObject"))
print("clientData", client_data)
print("AttestationObject:", att_obj)
form = forms.AddCredentialForm(request.POST)
if not form.is_valid():
return HttpResponseBadRequest()
auth_data = server.register_complete( auth_data = server.register_complete(
request.session["state"], client_data, att_obj
request.session["state"],
form.cleaned_data["client_data_json"],
form.cleaned_data["attestation_object"],
) )
c = Credential(user=request.user) c = Credential(user=request.user)
@ -578,12 +573,9 @@ def add_credential(request):
c.data = auth_data.credential_data c.data = auth_data.credential_data
c.save() c.save()
print("REGISTERED CREDENTIAL:", auth_data.credential_data)
return render(request, "accounts/success.html")
return redirect("hc-profile")
credentials = [c.unpack() for c in request.user.credentials.all()] credentials = [c.unpack() for c in request.user.credentials.all()]
print(credentials)
options, state = server.register_begin( options, state = server.register_begin(
{ {
"id": request.user.username.encode(), "id": request.user.username.encode(),
@ -595,6 +587,5 @@ def add_credential(request):
request.session["state"] = state request.session["state"] = state
# FIXME: avoid using cbor and cbor.js?
ctx = {"options": base64.b64encode(cbor.encode(options)).decode()} ctx = {"options": base64.b64encode(cbor.encode(options)).decode()}
return render(request, "accounts/add_credential.html", ctx) return render(request, "accounts/add_credential.html", ctx)

+ 9
- 6
static/js/add_credential.js View File

@ -1,8 +1,8 @@
$(function() { $(function() {
var form = document.getElementById("add-credential-form"); var form = document.getElementById("add-credential-form");
var optionsBinary = btoa(form.dataset.options);
var array = Uint8Array.from(atob(form.dataset.options), c => c.charCodeAt(0));
var options = CBOR.decode(array.buffer);
var optionsBytes = Uint8Array.from(atob(form.dataset.options), c => c.charCodeAt(0));
// cbor.js expects ArrayBuffer as input when decoding
var options = CBOR.decode(optionsBytes.buffer);
console.log("decoded options:", options); console.log("decoded options:", options);
function b64(arraybuffer) { function b64(arraybuffer) {
@ -12,11 +12,14 @@ $(function() {
navigator.credentials.create(options).then(function(attestation) { navigator.credentials.create(options).then(function(attestation) {
console.log("got attestation: ", attestation); console.log("got attestation: ", attestation);
document.getElementById("attestationObject").value = b64(attestation.response.attestationObject);
document.getElementById("clientDataJSON").value = b64(attestation.response.clientDataJSON);
$("#attestation_object").val(b64(attestation.response.attestationObject));
$("#client_data_json").val(b64(attestation.response.clientDataJSON));
console.log("form updated, all is well"); console.log("form updated, all is well");
$("#add-credential-submit").prop("disabled", ""); $("#add-credential-submit").prop("disabled", "");
$("#add-credential-success").removeClass("hide");
}).catch(function(err) { }).catch(function(err) {
console.log("Something went wrong", err);
$("#add-credential-error span").text(err);
$("#add-credential-error").removeClass("hide");
}); });
}); });

+ 19
- 6
templates/accounts/add_credential.html View File

@ -2,18 +2,20 @@
{% load compress static %} {% load compress static %}
{% block content %} {% block content %}
<h1>Add Credential</h1>
{{ registration_dict|json_script:"registration" }} {{ registration_dict|json_script:"registration" }}
<div class="row">
<form <form
id="add-credential-form" id="add-credential-form"
class="col-sm-6 col-sm-offset-3"
data-options="{{ options }}" data-options="{{ options }}"
method="post" method="post"
encrypt="multipart/form-data"> encrypt="multipart/form-data">
<h1>Add Credential</h1>
{% csrf_token %} {% csrf_token %}
<input id="attestationObject" type="hidden" name="attestationObject">
<input id="clientDataJSON" type="hidden" name="clientDataJSON">
<input id="attestation_object" type="hidden" name="attestation_object">
<input id="client_data_json" type="hidden" name="client_data_json">
<div class="form-group"> <div class="form-group">
<label for="name">Name</label> <label for="name">Name</label>
@ -23,14 +25,25 @@
</div> </div>
</div> </div>
<div id="add-credential-error" class="alert alert-danger hide">
<strong>Something went wrong.</strong>
<span></span>
</div>
<div id="add-credential-success" class="alert alert-success hide">
<strong>Success!</strong>
Credential acquired.
</div>
<input <input
id="add-credential-submit" id="add-credential-submit"
class="btn btn-default"
class="btn btn-default pull-right"
type="submit" type="submit"
name="" name=""
value="Save Credential" disabled> value="Save Credential" disabled>
</form> </form>
</div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}


Loading…
Cancel
Save