Browse Source

Implement a "Remove Security Key" feature

pull/456/head
Pēteris Caune 4 years ago
parent
commit
2ac0f87560
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
9 changed files with 118 additions and 12 deletions
  1. +1
    -1
      hc/accounts/forms.py
  2. +2
    -2
      hc/accounts/migrations/0034_credential.py
  3. +1
    -1
      hc/accounts/models.py
  4. +5
    -0
      hc/accounts/urls.py
  5. +33
    -1
      hc/accounts/views.py
  6. +8
    -1
      static/js/add_credential.js
  7. +6
    -1
      templates/accounts/add_credential.html
  8. +20
    -5
      templates/accounts/profile.html
  9. +42
    -0
      templates/accounts/remove_credential.html

+ 1
- 1
hc/accounts/forms.py View File

@ -119,7 +119,7 @@ class TransferForm(forms.Form):
class AddCredentialForm(forms.Form):
name = forms.CharField(max_length=100, required=False)
name = forms.CharField(max_length=100)
client_data_json = forms.CharField(required=True)
attestation_object = forms.CharField(required=True)


+ 2
- 2
hc/accounts/migrations/0034_credential.py View File

@ -1,4 +1,4 @@
# Generated by Django 3.1.2 on 2020-11-12 15:29
# Generated by Django 3.1.2 on 2020-11-14 09:29
from django.conf import settings
from django.db import migrations, models
@ -19,7 +19,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.UUIDField(default=uuid.uuid4, unique=True)),
('name', models.CharField(blank=True, max_length=200)),
('name', models.CharField(max_length=100)),
('created', models.DateTimeField(auto_now_add=True)),
('data', models.BinaryField()),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='credentials', to=settings.AUTH_USER_MODEL)),


+ 1
- 1
hc/accounts/models.py View File

@ -395,7 +395,7 @@ class Member(models.Model):
class Credential(models.Model):
code = models.UUIDField(default=uuid.uuid4, unique=True)
name = models.CharField(max_length=200, blank=True)
name = models.CharField(max_length=100)
user = models.ForeignKey(User, models.CASCADE, related_name="credentials")
created = models.DateTimeField(auto_now_add=True)
data = models.BinaryField()


+ 5
- 0
hc/accounts/urls.py View File

@ -24,4 +24,9 @@ urlpatterns = [
path("change_email/done/", views.change_email_done, name="hc-change-email-done"),
path("change_email/<slug:token>/", views.change_email, name="hc-change-email"),
path("two_factor/add/", views.add_credential, name="hc-add-credential"),
path(
"two_factor/<uuid:code>/remove/",
views.remove_credential,
name="hc-remove-credential",
),
]

+ 33
- 1
hc/accounts/views.py View File

@ -203,7 +203,21 @@ def check_token(request, username, token):
def profile(request):
profile = request.profile
ctx = {"page": "profile", "profile": profile, "my_projects_status": "default"}
ctx = {
"page": "profile",
"profile": profile,
"my_projects_status": "default",
"tfa_status": "default",
"added_credential_name": request.session.pop("added_credential_name", ""),
"removed_credential_name": request.session.pop("removed_credential_name", ""),
"credentials": request.user.credentials.order_by("id"),
}
if ctx["added_credential_name"]:
ctx["tfa_status"] = "success"
if ctx["removed_credential_name"]:
ctx["tfa_status"] = "info"
if request.method == "POST":
if "change_email" in request.POST:
@ -575,6 +589,7 @@ def add_credential(request):
c.data = auth_data.credential_data
c.save()
request.session["added_credential_name"] = c.name
return redirect("hc-profile")
credentials = [c.unpack() for c in request.user.credentials.all()]
@ -591,3 +606,20 @@ def add_credential(request):
ctx = {"options": base64.b64encode(cbor.encode(options)).decode()}
return render(request, "accounts/add_credential.html", ctx)
@login_required
@require_sudo_mode
def remove_credential(request, code):
try:
credential = Credential.objects.get(user=request.user, code=code)
except Credential.DoesNotExist:
return HttpResponseBadRequest()
if request.method == "POST" and "remove_credential" in request.POST:
request.session["removed_credential_name"] = credential.name
credential.delete()
return redirect("hc-profile")
ctx = {"credential": credential}
return render(request, "accounts/remove_credential.html", ctx)

+ 8
- 1
static/js/add_credential.js View File

@ -30,7 +30,14 @@ $(function() {
});
}
$("#name").on('keypress',function(e) {
if (e.which == 13) {
e.preventDefault();
requestCredentials();
}
});
$("#name-next").click(requestCredentials);
$("#retry").click(requestCredentials);
});
});

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

@ -19,7 +19,12 @@
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" id="name" name="name">
<input
type="text"
class="form-control"
id="name"
name="name"
required>
<div class="help-block">
Give this credential a descriptive name. Example: "My primary Yubikey"
</div>


+ 20
- 5
templates/accounts/profile.html View File

@ -59,30 +59,33 @@
</div>
</div>
<div class="panel panel-default">
<div class="panel panel-{{ tfa_status }}">
<div class="panel-body settings-block">
<form method="post">
{% csrf_token %}
<h2>Two-factor Authentication</h2>
{% if profile.user.credentials.exists %}
{% if credentials.exists %}
<table id="my-keys" class="table">
<tr>
<th>Security keys</th>
</tr>
{% for credential in profile.user.credentials.all %}
{% for credential in credentials %}
<tr>
<td>
<strong>{{ credential.name|default:"unnamed" }}</strong>
– registered on {{ credential.created|date:"M j, Y" }}
</td>
<td class="text-right"><a href="#">Remove</a></td>
<td class="text-right">
<a href="{% url 'hc-remove-credential' credential.code %}">Remove</a>
</td>
</tr>
{% endfor %}
</table>
{% else %}
<p>
Your account has no registered security keys.
Your account has no registered security keys.<br />
Two-factor authentication is disabled.
</p>
{% endif %}
@ -93,6 +96,18 @@
</a>
</form>
</div>
{% if added_credential_name %}
<div class="panel-footer">
Added security key <strong>{{ added_credential_name }}</strong>.
</div>
{% endif %}
{% if removed_credential_name %}
<div class="panel-footer">
Removed security key <strong>{{ removed_credential_name }}</strong>.
</div>
{% endif %}
</div>


+ 42
- 0
templates/accounts/remove_credential.html View File

@ -0,0 +1,42 @@
{% extends "base.html" %}
{% load compress static hc_extras %}
{% block content %}
{{ registration_dict|json_script:"registration" }}
<div class="row">
<form class="col-sm-6 col-sm-offset-3" method="post">
{% csrf_token %}
<div class="panel panel-default">
<div class="panel-body settings-block">
<h2>Remove Security Key?</h2>
<p></p>
<p>You are about to remove
the security key <strong>{{ credential.name|default:'unnamed' }}</strong>
from your two-factor authentication methods. Are you sure?
</p>
<div class="text-right">
<a
href="{% url 'hc-profile' %}"
class="btn btn-default">Cancel</a>
<button
type="submit"
name="remove_credential"
class="btn btn-danger">Remove Security Key</button>
</div>
</div>
</div>
</form>
</div>
{% endblock %}
{% block scripts %}
{% compress js %}
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/cbor.js' %}"></script>
<script src="{% static 'js/add_credential.js' %}"></script>
{% endcompress %}
{% endblock %}

Loading…
Cancel
Save