diff --git a/Pipfile.lock b/Pipfile.lock index 6d4450b..7aea13d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -382,11 +382,11 @@ }, "webauthn": { "hashes": [ - "sha256:79fca835027d3b39290bfd175d09ca7a2bd6e12163790feb6d9c0b746e4c2ede", - "sha256:872668fd8f32e256e76e4251e04eb0737e77e0760b1db3912af11346cbacef9e" + "sha256:2bf59646e1ad2aed113d16a1ca90196b45f1c4d160964d6271a181e60d0d03b1", + "sha256:9bb4f95c5d2377f9e1abd156ca5a23cbb5def69ef1ed60a7ab70028cc68b741e" ], "index": "pypi", - "version": "==2.3.0" + "version": "==2.4.0" } }, "develop": { diff --git a/_app/homepage/services/registration.py b/_app/homepage/services/registration.py index 2c93348..0c827c0 100644 --- a/_app/homepage/services/registration.py +++ b/_app/homepage/services/registration.py @@ -19,6 +19,7 @@ AuthenticatorSelectionCriteria, AuthenticatorAttachment, PublicKeyCredentialDescriptor, + PublicKeyCredentialHint, ResidentKeyRequirement, ) from webauthn.helpers.cose import COSEAlgorithmIdentifier @@ -110,6 +111,8 @@ def generate_registration_options( if "rs256" in algorithms: supported_pub_key_algs.append(COSEAlgorithmIdentifier.RSASSA_PKCS1_v1_5_SHA_256) + _hints = [PublicKeyCredentialHint(hint) for hint in hints] + registration_options = generate_registration_options( rp_id=settings.RP_ID, rp_name=settings.RP_NAME, @@ -125,6 +128,7 @@ def generate_registration_options( ) for cred in existing_credentials ], + hints=_hints, ) # py_webauthn will default to all supported algorithms on an empty `algorithms` list diff --git a/_app/homepage/static/js/simplewebauthn-browser.12.0.0.umd.min.js b/_app/homepage/static/js/simplewebauthn-browser.12.0.0.umd.min.js new file mode 100644 index 0000000..98c8d85 --- /dev/null +++ b/_app/homepage/static/js/simplewebauthn-browser.12.0.0.umd.min.js @@ -0,0 +1,2 @@ +/* [@simplewebauthn/browser@12.0.0] */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).SimpleWebAuthnBrowser={})}(this,(function(e){"use strict";function t(e){const t=new Uint8Array(e);let r="";for(const e of t)r+=String.fromCharCode(e);return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function r(e){const t=e.replace(/-/g,"+").replace(/_/g,"/"),r=(4-t.length%4)%4,n=t.padEnd(t.length+r,"="),o=atob(n),i=new ArrayBuffer(o.length),a=new Uint8Array(i);for(let e=0;ee};function i(e){const{id:t}=e;return{...e,id:r(t),transports:e.transports}}function a(e){return"localhost"===e||/^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i.test(e)}class s extends Error{constructor({message:e,code:t,cause:r,name:n}){super(e,{cause:r}),Object.defineProperty(this,"code",{enumerable:!0,configurable:!0,writable:!0,value:void 0}),this.name=n??r.name,this.code=t}}const c=new class{constructor(){Object.defineProperty(this,"controller",{enumerable:!0,configurable:!0,writable:!0,value:void 0})}createNewAbortSignal(){if(this.controller){const e=new Error("Cancelling existing WebAuthn API call for new one");e.name="AbortError",this.controller.abort(e)}const e=new AbortController;return this.controller=e,e.signal}cancelCeremony(){if(this.controller){const e=new Error("Manually cancelling existing WebAuthn API call");e.name="AbortError",this.controller.abort(e),this.controller=void 0}}},l=["cross-platform","platform"];function u(e){if(e&&!(l.indexOf(e)<0))return e}function d(e,t){console.warn(`The browser extension that intercepted this WebAuthn API call incorrectly implemented ${e}. You should report this error to them.\n`,t)}function h(){if(!n())return f.stubThis(new Promise((e=>e(!1))));const e=globalThis.PublicKeyCredential;return void 0===e?.isConditionalMediationAvailable?f.stubThis(new Promise((e=>e(!1)))):f.stubThis(e.isConditionalMediationAvailable())}const f={stubThis:e=>e};e.WebAuthnAbortService=c,e.WebAuthnError=s,e.base64URLStringToBuffer=r,e.browserSupportsWebAuthn=n,e.browserSupportsWebAuthnAutofill=h,e.bufferToBase64URLString=t,e.platformAuthenticatorIsAvailable=function(){return n()?PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable():new Promise((e=>e(!1)))},e.startAuthentication=async function(e){const{optionsJSON:o,useBrowserAutofill:l=!1,verifyBrowserAutofillInput:d=!0}=e;if(!n())throw new Error("WebAuthn is not supported in this browser");let f;0!==o.allowCredentials?.length&&(f=o.allowCredentials?.map(i));const R={...o,challenge:r(o.challenge),allowCredentials:f},p={};if(l){if(!await h())throw Error("Browser does not support WebAuthn autofill");if(document.querySelectorAll("input[autocomplete$='webauthn']").length<1&&d)throw Error('No with "webauthn" as the only or last value in its `autocomplete` attribute was detected');p.mediation="conditional",R.allowCredentials=[]}let b;p.publicKey=R,p.signal=c.createNewAbortSignal();try{b=await navigator.credentials.get(p)}catch(e){throw function({error:e,options:t}){const{publicKey:r}=t;if(!r)throw Error("options was missing required publicKey property");if("AbortError"===e.name){if(t.signal instanceof AbortSignal)return new s({message:"Authentication ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:e})}else{if("NotAllowedError"===e.name)return new s({message:e.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:e});if("SecurityError"===e.name){const t=globalThis.location.hostname;if(!a(t))return new s({message:`${globalThis.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:e});if(r.rpId!==t)return new s({message:`The RP ID "${r.rpId}" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:e})}else if("UnknownError"===e.name)return new s({message:"The authenticator was unable to process the specified options, or could not create a new assertion signature",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:e})}return e}({error:e,options:p})}if(!b)throw new Error("Authentication was not completed");const{id:g,rawId:E,response:A,type:w}=b;let m;return A.userHandle&&(m=t(A.userHandle)),{id:g,rawId:t(E),response:{authenticatorData:t(A.authenticatorData),clientDataJSON:t(A.clientDataJSON),signature:t(A.signature),userHandle:m},type:w,clientExtensionResults:b.getClientExtensionResults(),authenticatorAttachment:u(b.authenticatorAttachment)}},e.startRegistration=async function(e){const{optionsJSON:o,useAutoRegister:l=!1}=e;if(!n())throw new Error("WebAuthn is not supported in this browser");const h={...o,challenge:r(o.challenge),user:{...o.user,id:r(o.user.id)},excludeCredentials:o.excludeCredentials?.map(i)},f={};let R;l&&(f.mediation="conditional"),f.publicKey=h,f.signal=c.createNewAbortSignal();try{R=await navigator.credentials.create(f)}catch(e){throw function({error:e,options:t}){const{publicKey:r}=t;if(!r)throw Error("options was missing required publicKey property");if("AbortError"===e.name){if(t.signal instanceof AbortSignal)return new s({message:"Registration ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:e})}else if("ConstraintError"===e.name){if(!0===r.authenticatorSelection?.requireResidentKey)return new s({message:"Discoverable credentials were required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT",cause:e});if("conditional"===t.mediation&&"required"===r.authenticatorSelection?.userVerification)return new s({message:"User verification was required during automatic registration but it could not be performed",code:"ERROR_AUTO_REGISTER_USER_VERIFICATION_FAILURE",cause:e});if("required"===r.authenticatorSelection?.userVerification)return new s({message:"User verification was required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT",cause:e})}else{if("InvalidStateError"===e.name)return new s({message:"The authenticator was previously registered",code:"ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED",cause:e});if("NotAllowedError"===e.name)return new s({message:e.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:e});if("NotSupportedError"===e.name)return 0===r.pubKeyCredParams.filter((e=>"public-key"===e.type)).length?new s({message:'No entry in pubKeyCredParams was of type "public-key"',code:"ERROR_MALFORMED_PUBKEYCREDPARAMS",cause:e}):new s({message:"No available authenticator supported any of the specified pubKeyCredParams algorithms",code:"ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG",cause:e});if("SecurityError"===e.name){const t=globalThis.location.hostname;if(!a(t))return new s({message:`${globalThis.location.hostname} is an invalid domain`,code:"ERROR_INVALID_DOMAIN",cause:e});if(r.rp.id!==t)return new s({message:`The RP ID "${r.rp.id}" is invalid for this domain`,code:"ERROR_INVALID_RP_ID",cause:e})}else if("TypeError"===e.name){if(r.user.id.byteLength<1||r.user.id.byteLength>64)return new s({message:"User ID was not between 1 and 64 characters",code:"ERROR_INVALID_USER_ID_LENGTH",cause:e})}else if("UnknownError"===e.name)return new s({message:"The authenticator was unable to process the specified options, or could not create a new credential",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:e})}return e}({error:e,options:f})}if(!R)throw new Error("Registration was not completed");const{id:p,rawId:b,response:g,type:E}=R;let A,w,m,y;if("function"==typeof g.getTransports&&(A=g.getTransports()),"function"==typeof g.getPublicKeyAlgorithm)try{w=g.getPublicKeyAlgorithm()}catch(e){d("getPublicKeyAlgorithm()",e)}if("function"==typeof g.getPublicKey)try{const e=g.getPublicKey();null!==e&&(m=t(e))}catch(e){d("getPublicKey()",e)}if("function"==typeof g.getAuthenticatorData)try{y=t(g.getAuthenticatorData())}catch(e){d("getAuthenticatorData()",e)}return{id:p,rawId:t(b),response:{attestationObject:t(g.attestationObject),clientDataJSON:t(g.clientDataJSON),transports:A,publicKeyAlgorithm:w,publicKey:m,authenticatorData:y},type:E,clientExtensionResults:R.getClientExtensionResults(),authenticatorAttachment:u(R.authenticatorAttachment)}}})); diff --git a/_app/homepage/static/js/simplewebauthn-browser.8.2.1.umd.min.js b/_app/homepage/static/js/simplewebauthn-browser.8.2.1.umd.min.js deleted file mode 100644 index 5cc1a29..0000000 --- a/_app/homepage/static/js/simplewebauthn-browser.8.2.1.umd.min.js +++ /dev/null @@ -1,2 +0,0 @@ -/* [@simplewebauthn/browser@8.2.1] */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).SimpleWebAuthnBrowser={})}(this,(function(e){"use strict";var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])},t(e,r)};var r=function(){return r=Object.assign||function(e){for(var t,r=1,n=arguments.length;r0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}(n),a=i.next();!a.done;a=i.next()){var s=a.value;o+=String.fromCharCode(s)}}catch(e){t={error:e}}finally{try{a&&!a.done&&(r=i.return)&&r.call(i)}finally{if(t)throw t.error}}return btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function a(e){for(var t=e.replace(/-/g,"+").replace(/_/g,"/"),r=(4-t.length%4)%4,n=t.padEnd(t.length+r,"="),o=atob(n),i=new ArrayBuffer(o.length),a=new Uint8Array(i),s=0;s with `"webauthn"` in its `autocomplete` attribute was detected');R.mediation="conditional",b.allowCredentials=[],o.label=2;case 2:R.publicKey=b,R.signal=d.createNewAbortSignal(),o.label=3;case 3:return o.trys.push([3,5,,6]),[4,navigator.credentials.get(R)];case 4:return y=o.sent(),[3,6];case 5:throw function(e){var t=e.error,r=e.options,n=r.publicKey;if(!n)throw Error("options was missing required publicKey property");if("AbortError"===t.name){if(r.signal instanceof AbortSignal)return new l({message:"Authentication ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:t})}else{if("NotAllowedError"===t.name)return new l({message:t.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:t});if("SecurityError"===t.name){var o=window.location.hostname;if(!u(o))return new l({message:"".concat(window.location.hostname," is an invalid domain"),code:"ERROR_INVALID_DOMAIN",cause:t});if(n.rpId!==o)return new l({message:'The RP ID "'.concat(n.rpId,'" is invalid for this domain'),code:"ERROR_INVALID_RP_ID",cause:t})}else if("UnknownError"===t.name)return new l({message:"The authenticator was unable to process the specified options, or could not create a new assertion signature",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:t})}return t}({error:o.sent(),options:R});case 6:if(!y)throw new Error("Authentication was not completed");return g=y.id,E=y.rawId,m=y.response,A=y.type,v=void 0,m.userHandle&&(_=m.userHandle,v=new TextDecoder("utf-8").decode(_)),[2,{id:g,rawId:i(E),response:{authenticatorData:i(m.authenticatorData),clientDataJSON:i(m.clientDataJSON),signature:i(m.signature),userHandle:v},type:A,clientExtensionResults:y.getClientExtensionResults(),authenticatorAttachment:p(y.authenticatorAttachment)}]}var _}))}))},e.startRegistration=function(e){var t;return n(this,void 0,void 0,(function(){var n,f,w,b,R,y,g,E,m,A,v,_;return o(this,(function(o){switch(o.label){case 0:if(!s())throw new Error("WebAuthn is not supported in this browser");n=r(r({},e),{challenge:a(e.challenge),user:r(r({},e.user),{id:(O=e.user.id,(new TextEncoder).encode(O))}),excludeCredentials:null===(t=e.excludeCredentials)||void 0===t?void 0:t.map(c)}),(f={publicKey:n}).signal=d.createNewAbortSignal(),o.label=1;case 1:return o.trys.push([1,3,,4]),[4,navigator.credentials.create(f)];case 2:return w=o.sent(),[3,4];case 3:throw function(e){var t,r,n=e.error,o=e.options,i=o.publicKey;if(!i)throw Error("options was missing required publicKey property");if("AbortError"===n.name){if(o.signal instanceof AbortSignal)return new l({message:"Registration ceremony was sent an abort signal",code:"ERROR_CEREMONY_ABORTED",cause:n})}else if("ConstraintError"===n.name){if(!0===(null===(t=i.authenticatorSelection)||void 0===t?void 0:t.requireResidentKey))return new l({message:"Discoverable credentials were required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT",cause:n});if("required"===(null===(r=i.authenticatorSelection)||void 0===r?void 0:r.userVerification))return new l({message:"User verification was required but no available authenticator supported it",code:"ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT",cause:n})}else{if("InvalidStateError"===n.name)return new l({message:"The authenticator was previously registered",code:"ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED",cause:n});if("NotAllowedError"===n.name)return new l({message:n.message,code:"ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY",cause:n});if("NotSupportedError"===n.name)return 0===i.pubKeyCredParams.filter((function(e){return"public-key"===e.type})).length?new l({message:'No entry in pubKeyCredParams was of type "public-key"',code:"ERROR_MALFORMED_PUBKEYCREDPARAMS",cause:n}):new l({message:"No available authenticator supported any of the specified pubKeyCredParams algorithms",code:"ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG",cause:n});if("SecurityError"===n.name){var a=window.location.hostname;if(!u(a))return new l({message:"".concat(window.location.hostname," is an invalid domain"),code:"ERROR_INVALID_DOMAIN",cause:n});if(i.rp.id!==a)return new l({message:'The RP ID "'.concat(i.rp.id,'" is invalid for this domain'),code:"ERROR_INVALID_RP_ID",cause:n})}else if("TypeError"===n.name){if(i.user.id.byteLength<1||i.user.id.byteLength>64)return new l({message:"User ID was not between 1 and 64 characters",code:"ERROR_INVALID_USER_ID_LENGTH",cause:n})}else if("UnknownError"===n.name)return new l({message:"The authenticator was unable to process the specified options, or could not create a new credential",code:"ERROR_AUTHENTICATOR_GENERAL_ERROR",cause:n})}return n}({error:o.sent(),options:f});case 4:if(!w)throw new Error("Registration was not completed");if(b=w.id,R=w.rawId,y=w.response,g=w.type,E=void 0,"function"==typeof y.getTransports&&(E=y.getTransports()),m=void 0,"function"==typeof y.getPublicKeyAlgorithm)try{m=y.getPublicKeyAlgorithm()}catch(e){h("getPublicKeyAlgorithm()",e)}if(A=void 0,"function"==typeof y.getPublicKey)try{null!==(v=y.getPublicKey())&&(A=i(v))}catch(e){h("getPublicKey()",e)}if("function"==typeof y.getAuthenticatorData)try{_=i(y.getAuthenticatorData())}catch(e){h("getAuthenticatorData()",e)}return[2,{id:b,rawId:i(R),response:{attestationObject:i(y.attestationObject),clientDataJSON:i(y.clientDataJSON),transports:E,publicKeyAlgorithm:m,publicKey:A,authenticatorData:_},type:g,clientExtensionResults:w.getClientExtensionResults(),authenticatorAttachment:p(w.authenticatorAttachment)}]}var O}))}))},Object.defineProperty(e,"__esModule",{value:!0})})); diff --git a/_app/homepage/templates/homepage/sections/webauthn_form.html b/_app/homepage/templates/homepage/sections/webauthn_form.html index 63984e3..144f58a 100644 --- a/_app/homepage/templates/homepage/sections/webauthn_form.html +++ b/_app/homepage/templates/homepage/sections/webauthn_form.html @@ -317,7 +317,7 @@

WebAuthn isn't supported. Please consider switching to a modern browser.

- +