Fix webauthn regression and improve code (#25113)

Follow:

* #22697

There are some bugs in #22697:

* https://github.com/go-gitea/gitea/pull/22697#issuecomment-1577957966
* the webauthn failure message is never shown and causes console error
* The `document.getElementById('register-button')` and
`document.getElementById('login-button')` is wrong
    * there is no such element in code
    * it causes JS error when a browser doesn't provide webauthn
    * the end user can't see the real error message

These bugs are fixed in this PR.

Other changes:

* Use simple HTML/CSS layouts, no need to use too many `gt-` patches
* Make the webauthn page have correct "page-content" layout
* The "data-webauthn-error-msg" elements are only used to provide locale
texts, so move them into a single "gt-hidden", then no need to repeat a
lot of "gt-hidden" in code
* The `{{.CsrfTokenHtml}}`  is a no-op because there is no form
* Many `hideElem('#webauthn-error')` in code is no-op because the
`webauthn-error` already has "gt-hidden" by default
* Make the tests for "URLEncodedBase64" really test with concrete cases.


Screenshots:

* Error message when webauthn fails (before, there is no error message):

<details>


![image](93cf9559-d93b-4f06-9d98-0f7032d9c65b)

</details>

* Error message when webauthn is unavailable 

<details>


![image](ffc0fcd9-b93b-4418-979c-c89bb627aaf2)

</details>
This commit is contained in:
wxiaoguang 2023-06-07 19:20:18 +08:00 committed by GitHub
parent 58536093b3
commit 027014d7de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 66 deletions

View file

@ -1,11 +1,9 @@
import {encodeURLEncodedBase64, decodeURLEncodedBase64} from '../utils.js';
import {showElem, hideElem} from '../utils/dom.js';
import {showElem} from '../utils/dom.js';
const {appSubUrl, csrfToken} = window.config;
export async function initUserAuthWebAuthn() {
hideElem('#webauthn-error');
const elPrompt = document.querySelector('.user.signin.webauthn-prompt');
if (!elPrompt) {
return;
@ -25,10 +23,10 @@ export async function initUserAuthWebAuthn() {
for (const cred of options.publicKey.allowCredentials) {
cred.id = decodeURLEncodedBase64(cred.id);
}
const credential = await navigator.credentials.get({
publicKey: options.publicKey
});
try {
const credential = await navigator.credentials.get({
publicKey: options.publicKey
});
await verifyAssertion(credential);
} catch (err) {
if (!options.publicKey.extensions?.appid) {
@ -36,10 +34,10 @@ export async function initUserAuthWebAuthn() {
return;
}
delete options.publicKey.extensions.appid;
const credential = await navigator.credentials.get({
publicKey: options.publicKey
});
try {
const credential = await navigator.credentials.get({
publicKey: options.publicKey
});
await verifyAssertion(credential);
} catch (err) {
webAuthnError('general', err.message);
@ -48,7 +46,7 @@ export async function initUserAuthWebAuthn() {
}
async function verifyAssertion(assertedCredential) {
// Move data into Arrays incase it is super long
// Move data into Arrays in case it is super long
const authData = new Uint8Array(assertedCredential.response.authenticatorData);
const clientDataJSON = new Uint8Array(assertedCredential.response.clientDataJSON);
const rawId = new Uint8Array(assertedCredential.rawId);
@ -137,15 +135,11 @@ function webAuthnError(errorType, message) {
function detectWebAuthnSupport() {
if (!window.isSecureContext) {
document.getElementById('register-button').disabled = true;
document.getElementById('login-button').disabled = true;
webAuthnError('insecure');
return false;
}
if (typeof window.PublicKeyCredential !== 'function') {
document.getElementById('register-button').disabled = true;
document.getElementById('login-button').disabled = true;
webAuthnError('browser');
return false;
}
@ -158,15 +152,13 @@ export function initUserAuthWebAuthnRegister() {
if (!elRegister) {
return;
}
hideElem('#webauthn-error');
elRegister.addEventListener('click', (e) => {
if (!detectWebAuthnSupport()) {
elRegister.disabled = true;
return;
}
elRegister.addEventListener('click', async (e) => {
e.preventDefault();
if (!detectWebAuthnSupport()) {
return;
}
webAuthnRegisterRequest();
await webAuthnRegisterRequest();
});
}
@ -203,15 +195,12 @@ async function webAuthnRegisterRequest() {
}
}
let credential;
try {
credential = await navigator.credentials.create({
const credential = await navigator.credentials.create({
publicKey: options.publicKey
});
await webauthnRegistered(credential);
} catch (err) {
webAuthnError('unknown', err);
return;
}
webauthnRegistered(credential);
}