From e580eda3e13f08393a967b45e3a85f6f7248ff8b Mon Sep 17 00:00:00 2001 From: Christian Thieme Date: Tue, 5 Nov 2024 09:50:36 +0100 Subject: [PATCH 1/2] - Javascript refactored into one .js file - using a namespace for the functions - adapted urls and html --- README.md | 84 ++-- example/test_app/settings.py | 28 +- example/test_app/templates/home.html | 56 ++- example/test_app/templates/login.html | 98 ++-- passkeys/static/passkeys/js/base64url.js | 73 --- passkeys/static/passkeys/js/helpers.js | 25 -- passkeys/static/passkeys/js/passkeys.js | 421 ++++++++++++++++++ passkeys/templates/passkeys/check_passkeys.js | 33 -- passkeys/templates/passkeys/modal.html | 34 +- passkeys/templates/passkeys/passkeys.html | 187 ++------ passkeys/templates/passkeys/passkeys.js | 81 ---- passkeys/urls.py | 23 +- 12 files changed, 648 insertions(+), 495 deletions(-) delete mode 100644 passkeys/static/passkeys/js/base64url.js delete mode 100644 passkeys/static/passkeys/js/helpers.js create mode 100644 passkeys/static/passkeys/js/passkeys.js delete mode 100644 passkeys/templates/passkeys/check_passkeys.js delete mode 100644 passkeys/templates/passkeys/passkeys.js diff --git a/README.md b/README.md index e841201..b328794 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ Passkeys are now supported on On May 3, 2023, Google allowed the use of Passkeys for the users to login, killing the password for enrolled users. -# Installation +## Installation `pip install django-passkeys` Currently, it support Django 2.0+, Python 3.7+ -# Usage +## Usage 1. in your settings.py add the application to your installed apps ```python INSTALLED_APPS=( @@ -44,23 +44,29 @@ Currently, it support Django 2.0+, Python 3.7+ 4. Add the following settings to your file ```python - AUTHENTICATION_BACKENDS = ['passkeys.backend.PasskeyModelBackend'] # Change your authentication backend - FIDO_SERVER_ID="localhost" # Server rp id for FIDO2, it the full domain of your project - FIDO_SERVER_NAME="TestApp" - import passkeys - KEY_ATTACHMENT = None | passkeys.Attachment.CROSS_PLATFORM | passkeys.Attachment.PLATFORM + import passkeys + + # Change your authentication backend + AUTHENTICATION_BACKENDS = ['passkeys.backend.PasskeyModelBackend'] + + # Server rp id for FIDO2, it the full domain of your project + FIDO_SERVER_ID="localhost" + + FIDO_SERVER_NAME="TestApp" + + KEY_ATTACHMENT = None | passkeys.Attachment.CROSS_PLATFORM | passkeys.Attachment.PLATFORM ``` **Note**: Starting v1.1, `FIDO_SERVER_ID` and/or `FIDO_SERVER_NAME` can be a callable to support multi-tenants web applications, the `request` is passed to the called function. 5. Add passkeys to urls.py - ```python - + ```python urls_patterns= [ - '...', - url(r'^passkeys/', include('passkeys.urls')), - '....', + '...', + path('passkeys/', include('passkeys.urls')), + '....', ] ``` 6. To match the look and feel of your project, Passkeys includes `base.html` but it needs blocks named `head` & `content` to added its content to it. + **Notes:** 1. You can override `passkeys/passkeys_base.html` which is used by `passkeys/passkeys.html` so you can control the styling better and current `passkeys/passkeys_base.html` extends `base.html` @@ -77,13 +83,20 @@ Currently, it support Django 2.0+, Python 3.7+ * Give an id to your login form e.g 'loginForm', the id should be provided when calling `authn` function * Inside the form, add ```html - - - {%include 'passkeys/passkeys.js' %} +
{% csrf_token %} + ... + + + +
+ ``` For Example, See 'example' app and look at EXAMPLE.md to see how to set it up. -# Detect if user is using passkeys +## Detect if user is using passkeys + Once the backend is used, there will be a `passkey` key in request.session. If the user used a passkey then `request.session['passkey']['passkey']` will be True and the key information will be there like this ```python @@ -95,20 +108,33 @@ If the user didn't use a passkey then it will be set to False {'passkey':False} ``` - -# Check if the user can be enrolled for a platform authenticator +## Check if the user can be enrolled for a platform authenticator If you want to check if the user can be enrolled to use a platform authenticator, you can do the following in your main page. ```html - - + ``` check_passkey function paramters are as follows @@ -121,7 +147,7 @@ check_passkey function paramters are as follows Conditional UI is a way for the browser to prompt the user to use the passkey to login to the system as shown in -![conditionalUI.png](imgs%2FconditionalUI.png) +![conditionalUI.png](imgs/conditionalUI.png) Starting version v1.2. you can use Conditional UI by adding the following to your login page @@ -132,9 +158,9 @@ Starting version v1.2. you can use Conditional UI by adding the following to you add the following to the page js. ```js -window.onload = checkConditionalUI('loginForm'); +window.onload = checkConditionalUI('login-form'); ``` -where `loginForm` is name of your login form. +where `login-form` is name of your login form. ## Security contact information diff --git a/example/test_app/settings.py b/example/test_app/settings.py index db758fb..29ce705 100644 --- a/example/test_app/settings.py +++ b/example/test_app/settings.py @@ -18,7 +18,6 @@ # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ @@ -30,7 +29,6 @@ ALLOWED_HOSTS = ['*'] - # Application definition INSTALLED_APPS = [ @@ -60,7 +58,7 @@ TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [os.path.join(BASE_DIR ,'example','templates' )], + 'DIRS': [os.path.join(BASE_DIR, 'example', 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -75,7 +73,6 @@ WSGI_APPLICATION = 'test_app.wsgi.application' - # Database # https://docs.djangoproject.com/en/2.0/ref/settings/#databases @@ -86,7 +83,6 @@ } } - # Password validation # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators @@ -105,7 +101,6 @@ }, ] - # Internationalization # https://docs.djangoproject.com/en/2.0/topics/i18n/ @@ -119,17 +114,24 @@ USE_TZ = True - # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.0/howto/static-files/ STATIC_URL = '/static/' -#STATIC_ROOT=(os.path.join(BASE_DIR,'static')) -STATICFILES_DIRS=[os.path.join(BASE_DIR,'static')] -LOGIN_URL="/auth/login" +# STATIC_ROOT=(os.path.join(BASE_DIR,'static')) +STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] +LOGIN_URL = "/auth/login" AUTHENTICATION_BACKENDS = ['passkeys.backend.PasskeyModelBackend'] -FIDO_SERVER_ID="localhost" # Server rp id for FIDO2, it the full domain of your project -FIDO_SERVER_NAME="TestApp" -KEY_ATTACHMENT = None # Set None to allow all authenticator attachment +# Server rp id for FIDO2, it the full domain of your project +FIDO_SERVER_ID = "localhost" + +FIDO_SERVER_NAME = "TestApp" + +# Set None to allow all authenticator attachment +KEY_ATTACHMENT = None + +CSRF_TRUSTED_ORIGINS = [ + "https://localhost" +] diff --git a/example/test_app/templates/home.html b/example/test_app/templates/home.html index f788751..0fca189 100644 --- a/example/test_app/templates/home.html +++ b/example/test_app/templates/home.html @@ -1,33 +1,47 @@ {% extends 'base.html' %} {% load static %} -{% block content %} -
+{% block content %} +
- {% if registered %} -
Registered Successfully
- {% endif %} + {% if registered %} +
Registered Successfully
+ {% endif %}

Welcome {{ request.user.username }}!

-
- +
+ + + + -
- - {% endblock %} \ No newline at end of file + + +{% endblock %} \ No newline at end of file diff --git a/example/test_app/templates/login.html b/example/test_app/templates/login.html index fd76e09..e3ce134 100644 --- a/example/test_app/templates/login.html +++ b/example/test_app/templates/login.html @@ -1,64 +1,64 @@ -{% load static %} +{% load static %} - - - - - - - - - Login - - - - - - - + + + + + + Login + -
+
-
+
- - + + + - - diff --git a/passkeys/static/passkeys/js/base64url.js b/passkeys/static/passkeys/js/base64url.js deleted file mode 100644 index 067b7d9..0000000 --- a/passkeys/static/passkeys/js/base64url.js +++ /dev/null @@ -1,73 +0,0 @@ -(function(){ - 'use strict'; - - let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; - - // Use a lookup table to find the index. - let lookup = new Uint8Array(256); - for (let i = 0; i < chars.length; i++) { - lookup[chars.charCodeAt(i)] = i; - } - - let encode = function(arraybuffer) { - let bytes = new Uint8Array(arraybuffer), - i, len = bytes.length, base64url = ''; - - for (i = 0; i < len; i+=3) { - base64url += chars[bytes[i] >> 2]; - base64url += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; - base64url += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; - base64url += chars[bytes[i + 2] & 63]; - } - - if ((len % 3) === 2) { - base64url = base64url.substring(0, base64url.length - 1); - } else if (len % 3 === 1) { - base64url = base64url.substring(0, base64url.length - 2); - } - - return base64url; - }; - - let decode = function(base64string) { - let bufferLength = base64string.length * 0.75, - len = base64string.length, i, p = 0, - encoded1, encoded2, encoded3, encoded4; - - let bytes = new Uint8Array(bufferLength); - - for (i = 0; i < len; i+=4) { - encoded1 = lookup[base64string.charCodeAt(i)]; - encoded2 = lookup[base64string.charCodeAt(i+1)]; - encoded3 = lookup[base64string.charCodeAt(i+2)]; - encoded4 = lookup[base64string.charCodeAt(i+3)]; - - bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); - bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); - bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); - } - - return bytes.buffer - }; - - let methods = { - 'decode': decode, - 'encode': encode - } - - /** - * Exporting and stuff - */ - if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { - module.exports = methods; - - } else { - if (typeof define === 'function' && define.amd) { - define([], function() { - return methods - }); - } else { - window.base64url = methods; - } - } - })(); \ No newline at end of file diff --git a/passkeys/static/passkeys/js/helpers.js b/passkeys/static/passkeys/js/helpers.js deleted file mode 100644 index d6b70ab..0000000 --- a/passkeys/static/passkeys/js/helpers.js +++ /dev/null @@ -1,25 +0,0 @@ -var publicKeyCredentialToJSON = (pubKeyCred) => { - if(pubKeyCred instanceof Array) { - let arr = []; - for(let i of pubKeyCred) - arr.push(publicKeyCredentialToJSON(i)); - - return arr - } - - if(pubKeyCred instanceof ArrayBuffer) { - return base64url.encode(pubKeyCred) - } - - if(pubKeyCred instanceof Object) { - let obj = {}; - - for (let key in pubKeyCred) { - obj[key] = publicKeyCredentialToJSON(pubKeyCred[key]) - } - - return obj - } - - return pubKeyCred - } \ No newline at end of file diff --git a/passkeys/static/passkeys/js/passkeys.js b/passkeys/static/passkeys/js/passkeys.js new file mode 100644 index 0000000..e007c4b --- /dev/null +++ b/passkeys/static/passkeys/js/passkeys.js @@ -0,0 +1,421 @@ +(function () { + 'use strict'; + + let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; + + // Use a lookup table to find the index. + let lookup = new Uint8Array(256); + for (let i = 0; i < chars.length; i++) { + lookup[chars.charCodeAt(i)] = i; + } + + let encode = function (arraybuffer) { + let bytes = new Uint8Array(arraybuffer), + i, len = bytes.length, base64url = ''; + + for (i = 0; i < len; i += 3) { + base64url += chars[bytes[i] >> 2]; + base64url += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; + base64url += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; + base64url += chars[bytes[i + 2] & 63]; + } + + if ((len % 3) === 2) { + base64url = base64url.substring(0, base64url.length - 1); + } else if (len % 3 === 1) { + base64url = base64url.substring(0, base64url.length - 2); + } + + return base64url; + }; + + let decode = function (base64string) { + let bufferLength = base64string.length * 0.75, + len = base64string.length, i, p = 0, + encoded1, encoded2, encoded3, encoded4; + + let bytes = new Uint8Array(bufferLength); + + for (i = 0; i < len; i += 4) { + encoded1 = lookup[base64string.charCodeAt(i)]; + encoded2 = lookup[base64string.charCodeAt(i + 1)]; + encoded3 = lookup[base64string.charCodeAt(i + 2)]; + encoded4 = lookup[base64string.charCodeAt(i + 3)]; + + bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); + bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2); + bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63); + } + + return bytes.buffer + }; + + let methods = { + 'decode': decode, + 'encode': encode + } + + /** + * Exporting and stuff + */ + if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = methods; + + } else { + if (typeof define === 'function' && define.amd) { + define([], function () { + return methods + }); + } else { + window.base64url = methods; + } + } +})(); + + +(function () { + let urlBase = '/passkeys/'; + + if (location.protocol !== 'https:') { + console.error("Passkeys must work under secure context."); + } + + const modalElem = document.querySelector('#django-passkeys-modal'); + if (modalElem !== null) { + modalElem.addEventListener('hidden.bs.modal', event => { + window.location.href = urlBase; + }); + } + + + window.conditionalUI = false; + window.conditionUIAbortController = new AbortController(); + window.conditionUIAbortSignal = conditionUIAbortController.signal; + + + const urlAuthBegin = () => `${urlBase}auth/begin/`; + const urlKeyToggle = () => `${urlBase}toggle/`; + const urlKeyDelete = () => `${urlBase}del/`; + const urlRegBegin = () => `${urlBase}reg/begin/`; + const urlRegComplete = () => `${urlBase}reg/complete/`; + + + /** + * change the base url for the passkeys app + * + * @param url + */ + const init = function (url) { + baseUrl = ""; + if (!baseUrl.endsWith('/')) { + baseUrl += '/;' + } + } + + const publicKeyCredentialToJSON = function (pubKeyCred) { + if (pubKeyCred instanceof Array) { + let arr = []; + for (let i of pubKeyCred) + arr.push(publicKeyCredentialToJSON(i)); + + return arr; + } + + if (pubKeyCred instanceof ArrayBuffer) { + return base64url.encode(pubKeyCred); + } + + if (pubKeyCred instanceof Object) { + let obj = {}; + + for (let key in pubKeyCred) { + obj[key] = publicKeyCredentialToJSON(pubKeyCred[key]); + } + + return obj; + } + + return pubKeyCred; + } + + + const checkConditionalUI = function (form) { + if (window.PublicKeyCredential && PublicKeyCredential.isConditionalMediationAvailable) { + // Check if conditional mediation is available. + PublicKeyCredential.isConditionalMediationAvailable().then((result) => { + window.conditionalUI = result; + if (window.conditionalUI) { + authn(form, true); + } + }); + } + } + + const getAssertReq = function (getAssert) { + getAssert.publicKey.challenge = base64url.decode(getAssert.publicKey.challenge); + + for (let allowCred of getAssert.publicKey.allowCredentials) { + allowCred.id = base64url.decode(allowCred.id); + } + + return getAssert; + } + + /** + * start the authentication process + * + * @param loginFormId + * @param conditionalUI + */ + const authn = function (loginFormId, conditionalUI = false) { + fetch(urlAuthBegin(), { + method: 'GET', + }).then(function (response) { + if (response.ok) { + return response.json().then(function (req) { + return getAssertReq(req) + }); + } + throw new Error('No credential available to authenticate!'); + }).then(function (options) { + if (conditionalUI) { + options.mediation = 'conditional'; + options.signal = window.conditionUIAbortSignal; + } else { + window.conditionUIAbortController.abort(); + } + + return navigator.credentials.get(options); + }).then(function (assertion) { + const passkeysInput = document.querySelector("#id_passkeys"); + if (passkeysInput === null) { + console.error("Did you add the 'passkeys' hidden input field") + return + } + passkeysInput.value = JSON.stringify(publicKeyCredentialToJSON(assertion)); + + const loginForm = document.getElementById(loginFormId); + + if (loginForm === null) { + console.error("Did you pass the correct form id to auth function") + return; + } + + loginForm.submit() + }); + } + + + /** + * Check if the platform supports passkeys. + * + * @param success_func + * @param fail_func + */ + const checkPasskeysSupport = function (success_func, fail_func) { + PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() + .then((available) => { + if (available) { + success_func(); + } else { + fail_func() + } + }).catch((err) => { + // Something went wrong + console.error(err); + }); + } + + /** + * (De-)activate a passkey. + * + * @param id + */ + const keyToggle = function (id) { + const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value; + + fetch(urlKeyToggle(), { + method: "post", + headers: { + "X-CSRFToken": csrfToken, + "Content-Type": "application/x-www-form-urlencoded" + }, + body: `id=${id}` + } + ).then(response => response.text()).then((response) => { + if (response === "Error") + document.querySelector("#toggle_" + id).toggle(); + }).catch(() => { + document.querySelector("#toggle_" + id).toggle(); + }) + } + + /** + * Confirm deleting a passkey. + * + * @param id + * @param name + */ + const keyDeleteConfirm = function (id, name) { + const title = "Delete passkey"; + const body = ` +

Are you sure you want to delete '${name}'?

+

You may lose access to this system if this your only 2FA.

+ `; + + const button = document.createElement('button'); + button.setAttribute('class', 'btn btn-danger btn-action'); + button.onclick = () => keyDelete(id); + button.innerText = "Confirm"; + + showModal(title, body, button); + } + + /** + * Delete a passkey. + * + * @param id + */ + const keyDelete = function (id) { + const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value; + fetch(urlKeyDelete(), { + method: "post", + headers: { + "X-CSRFToken": csrfToken, + "Content-Type": "application/x-www-form-urlencoded" + }, + body: `id=${id}` + }) + .then(response => response.text()) + .then(data => { + const title = "Confirm delete"; + const body = '

The key has been deleted successfully.

' + updateModal(title, body); + }); + } + + + const makeCredReq = function (makeCredReq) { + makeCredReq.publicKey.challenge = base64url.decode(makeCredReq.publicKey.challenge); + makeCredReq.publicKey.user.id = base64url.decode(makeCredReq.publicKey.user.id); + + for (let excludeCred of makeCredReq.publicKey.excludeCredentials) { + excludeCred.id = base64url.decode(excludeCred.id); + } + + return makeCredReq + } + + /** + * Start passkey registration + */ + const beginReg = function () { + fetch(urlRegBegin(), {}).then(function (response) { + if (response.ok) { + return response.json().then(function (req) { + return makeCredReq(req) + }); + } + throw new Error('Error getting registration data!'); + }).then(function (options) { + //options.publicKey.attestation="direct" + return navigator.credentials.create(options); + }).then(function (attestation) { + attestation["key_name"] = document.getElementById("key_name").value; + return fetch(urlRegComplete(), { + method: 'POST', + body: JSON.stringify(publicKeyCredentialToJSON(attestation)) + } + ); + }).then(function (response) { + const stat = response.ok ? 'successful' : 'unsuccessful'; + return response.json() + }).then(function (res) { + if (res["status"] === 'OK') { + const title = "Register new passkey"; + const body = '

Passkey was successfully registered.

'; + updateModal(title, body); + } else { + const title = "Register new passkey"; + const body = `

Passkey registration failed!

${res["message"]}

`; + updateModal(title, body); + } + }, function (reason) { + const title = "Register new passkey"; + const body = `

Passkey registration failed!

${reason}

`; + updateModal(title, body); + }); + } + + /** + * Open passkey registration dialog. + */ + const registration = function () { + const title = "Register new passkey" + const body = ` +

Please enter a name for your new token.

+ + `; + + const button = document.createElement('button'); + button.setAttribute('class', 'btn btn-primary btn-action'); + button.onclick = beginReg; + button.innerText = "Start"; + + showModal(title, body, button); + } + + /** + * Update and show the passkey modal. + * + * @param title + * @param body + * @param actionButton + */ + const showModal = function (title, body, actionButton) { + updateModal(title, body, actionButton); + const modal = new bootstrap.Modal('#django-passkeys-modal', {}); + modal.show(); + } + + /** + * Update the already visible passkey modal. + * + * @param title + * @param body + * @param actionButton + */ + const updateModal = function (title, body, actionButton) { + // update title + const titleElem = document.querySelector('#django-passkeys-modal .modal-title'); + titleElem.innerText = title; + + // update body + const bodyElem = document.querySelector('#django-passkeys-modal .modal-body'); + bodyElem.innerHTML = body; + + // remove existing action buttons + for (let button of document.querySelectorAll('#django-passkeys-modal .btn-action')) { + button.remove(); + } + + // insert action button + if (actionButton) { + const footer = document.querySelector('#django-passkeys-modal .modal-footer'); + footer.prepend(actionButton); + } + } + + // create the global DjangoPasskey variable + if (typeof window.DjangoPasskeys === 'undefined') { + window.DjangoPasskeys = { + 'init': init, + 'checkConditionalUI': checkConditionalUI, + 'authn': authn, + 'checkPasskeysSupport': checkPasskeysSupport, + 'keyToggle': keyToggle, + 'keyDeleteConfirm': keyDeleteConfirm, + 'registration': registration, + } + } +})(); \ No newline at end of file diff --git a/passkeys/templates/passkeys/check_passkeys.js b/passkeys/templates/passkeys/check_passkeys.js deleted file mode 100644 index 2241ddd..0000000 --- a/passkeys/templates/passkeys/check_passkeys.js +++ /dev/null @@ -1,33 +0,0 @@ -function check_passkey(platform_authenticator = true,success_func, fail_func) -{ - {% if request.session.passkey.cross_platform != False %} - if (platform_authenticator) - { - PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() - .then((available) => { - if (available) { - success_func(); - } - else{ - fail_func(); - } - }) - } - success_func(); - {% endif%} -} - -function check_passkeys(success_func, fail_func) -{ - PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() - .then((available) => { - if (available) { - success_func(); - } else { - fail_func() - } - }).catch((err) => { - // Something went wrong - console.error(err); - }); -} \ No newline at end of file diff --git a/passkeys/templates/passkeys/modal.html b/passkeys/templates/passkeys/modal.html index 2e43f02..ce481b1 100644 --- a/passkeys/templates/passkeys/modal.html +++ b/passkeys/templates/passkeys/modal.html @@ -1,18 +1,16 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/passkeys/templates/passkeys/passkeys.html b/passkeys/templates/passkeys/passkeys.html index 9ddd1f6..55f428f 100644 --- a/passkeys/templates/passkeys/passkeys.html +++ b/passkeys/templates/passkeys/passkeys.html @@ -1,168 +1,69 @@ {% extends "passkeys/passkeys_base.html" %} {% load static %} + {% block head %} -{{block.super}} + {{ block.super }} - - - + + {% if enroll %} + + {% endif %} - + {% endblock %} + {% block content %} -{{block.super}} -
-
+ {{ block.super }}
-
-
-
- -
+
+

Your passkeys

+
+

- - - - - - - - - - {% if keys %} - {% for key in keys %} +
NameDate AddedPlatformLast UsedStatusDelete
+ + + + + + + + + {% for key in keys %} - - - + + - {% endfor %} - {% else %} - - - - {% endif %} + {% empty %} + + + + {% endfor %}
NameDate AddedPlatformLast UsedStatusDelete
{{ key.name }} {{ key.added_on }} {{ key.platform }} {% if key.last_used %}{{ key.last_used }}{% else %}Never{% endif %} + + + +
You didn't have any keys yet.
You didn't have any keys yet.
+ {% csrf_token %} {% include "passkeys/modal.html" %} {% endblock %} diff --git a/passkeys/templates/passkeys/passkeys.js b/passkeys/templates/passkeys/passkeys.js deleted file mode 100644 index cb0b45b..0000000 --- a/passkeys/templates/passkeys/passkeys.js +++ /dev/null @@ -1,81 +0,0 @@ -{% load static %} - - - \ No newline at end of file diff --git a/passkeys/urls.py b/passkeys/urls.py index eeba07b..519e42f 100644 --- a/passkeys/urls.py +++ b/passkeys/urls.py @@ -1,16 +1,19 @@ from django.urls import path -from . import FIDO2,views +from . import FIDO2, views app_name = 'passkeys' urlpatterns = [ - path('auth/begin',FIDO2.auth_begin, name='auth_begin'), - path('auth/complete',FIDO2.auth_complete, name='auth_complete'), - path('reg/begin',FIDO2.reg_begin,name = 'reg_begin'), - path('reg/complete',FIDO2.reg_complete,name = 'reg_complete'), - path('',views.index, name='home'), - path('enroll/',views.index, name='enroll',kwargs={'enroll':True}), + # AUTHENTICATION + path('auth/begin/', FIDO2.auth_begin, name='auth_begin'), + path('auth/complete/', FIDO2.auth_complete, name='auth_complete'), + # REGISTRATION + path('reg/begin/', FIDO2.reg_begin, name='reg_begin'), + path('reg/complete/', FIDO2.reg_complete, name='reg_complete'), - path('del/',views.delKey, name='delKey'), - path('toggle/',views.toggleKey, name='toggle'), - ] \ No newline at end of file + # KEY MANAGEMENT + path('', views.index, name='home'), + path('enroll/', views.index, name='enroll', kwargs={'enroll': True}), + path('del/', views.delKey, name='delKey'), + path('toggle/', views.toggleKey, name='toggle'), +] From d967818f5684ee448c651ace487306b8bed038f4 Mon Sep 17 00:00:00 2001 From: Christian Thieme Date: Tue, 5 Nov 2024 10:33:04 +0100 Subject: [PATCH 2/2] README fix --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b328794..82838a0 100644 --- a/README.md +++ b/README.md @@ -158,9 +158,9 @@ Starting version v1.2. you can use Conditional UI by adding the following to you add the following to the page js. ```js -window.onload = checkConditionalUI('login-form'); +window.onload = DjangoPasskeys.checkConditionalUI('login-form'); ``` -where `login-form` is name of your login form. +where `login-form` is id of your login form. ## Security contact information