aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--templates/user/auth/webauthn.tmpl33
-rw-r--r--templates/user/auth/webauthn_error.tmpl22
-rw-r--r--templates/user/settings/security/webauthn.tmpl6
-rw-r--r--web_src/css/form.css4
-rw-r--r--web_src/js/features/user-auth-webauthn.js43
-rw-r--r--web_src/js/utils.test.js15
6 files changed, 57 insertions, 66 deletions
diff --git a/templates/user/auth/webauthn.tmpl b/templates/user/auth/webauthn.tmpl
index f1c4f29fd9..ef04886405 100644
--- a/templates/user/auth/webauthn.tmpl
+++ b/templates/user/auth/webauthn.tmpl
@@ -1,21 +1,22 @@
{{template "base/head" .}}
-<div class="user signin webauthn-prompt">
- <div class="ui middle centered very relaxed page grid">
+<div role="main" aria-label="{{.Title}}" class="page-content user signin webauthn-prompt">
+ <div class="ui page grid">
<div class="column center aligned">
- <h3 class="ui top attached header">
- {{.locale.Tr "twofa"}}
- </h3>
- {{template "user/auth/webauthn_error" .}}
- <div class="ui attached segment">
- {{svg "octicon-key" 56}}
- <h3>{{.locale.Tr "webauthn_insert_key"}}</h3>
- {{template "base/alert" .}}
- <p>{{.locale.Tr "webauthn_sign_in"}}</p>
- </div>
- <div class="ui attached segment"><div class="ui active indeterminate inline loader"></div> {{.locale.Tr "webauthn_press_button"}} </div>
- <div class="ui attached segment">
- <a href="{{AppSubUrl}}/user/two_factor">{{.locale.Tr "webauthn_use_twofa"}}</a>
- </div>
+ {{template "user/auth/webauthn_error" .}}
+ <h3 class="ui top attached header">{{.locale.Tr "twofa"}}</h3>
+ <div class="ui attached segment">
+ {{svg "octicon-key" 56}}
+ <h3>{{.locale.Tr "webauthn_insert_key"}}</h3>
+ {{template "base/alert" .}}
+ <p>{{.locale.Tr "webauthn_sign_in"}}</p>
+ </div>
+ <div class="ui attached segment">
+ <div class="ui active indeterminate inline loader"></div>
+ {{.locale.Tr "webauthn_press_button"}}
+ </div>
+ <div class="ui attached segment">
+ <a href="{{AppSubUrl}}/user/two_factor">{{.locale.Tr "webauthn_use_twofa"}}</a>
+ </div>
</div>
</div>
</div>
diff --git a/templates/user/auth/webauthn_error.tmpl b/templates/user/auth/webauthn_error.tmpl
index f90882ef12..59029bbf91 100644
--- a/templates/user/auth/webauthn_error.tmpl
+++ b/templates/user/auth/webauthn_error.tmpl
@@ -1,13 +1,13 @@
-<div id="webauthn-error" class="ui small gt-hidden">
- <div class="content ui negative message gt-df gt-fc gt-gap-3">
- <div class="header">{{.locale.Tr "webauthn_error"}}</div>
- <div id="webauthn-error-msg"></div>
- <div class="gt-hidden" data-webauthn-error-msg="browser">{{.locale.Tr "webauthn_unsupported_browser"}}</div>
- <div class="gt-hidden" data-webauthn-error-msg="unknown">{{.locale.Tr "webauthn_error_unknown"}}</div>
- <div class="gt-hidden" data-webauthn-error-msg="insecure">{{.locale.Tr "webauthn_error_insecure"}}</div>
- <div class="gt-hidden" data-webauthn-error-msg="unable-to-process">{{.locale.Tr "webauthn_error_unable_to_process"}}</div>
- <div class="gt-hidden" data-webauthn-error-msg="duplicated">{{.locale.Tr "webauthn_error_duplicated"}}</div>
- <div class="gt-hidden" data-webauthn-error-msg="empty">{{.locale.Tr "webauthn_error_empty"}}</div>
- <div class="gt-hidden" data-webauthn-error-msg="timeout">{{.locale.Tr "webauthn_error_timeout"}}</div>
+<div id="webauthn-error" class="ui negative message gt-hidden">
+ <div class="header">{{.locale.Tr "webauthn_error"}}</div>
+ <div id="webauthn-error-msg" class="gt-pt-3"></div>
+ <div class="gt-hidden">
+ <div data-webauthn-error-msg="browser">{{.locale.Tr "webauthn_unsupported_browser"}}</div>
+ <div data-webauthn-error-msg="unknown">{{.locale.Tr "webauthn_error_unknown"}}</div>
+ <div data-webauthn-error-msg="insecure">{{.locale.Tr "webauthn_error_insecure"}}</div>
+ <div data-webauthn-error-msg="unable-to-process">{{.locale.Tr "webauthn_error_unable_to_process"}}</div>
+ <div data-webauthn-error-msg="duplicated">{{.locale.Tr "webauthn_error_duplicated"}}</div>
+ <div data-webauthn-error-msg="empty">{{.locale.Tr "webauthn_error_empty"}}</div>
+ <div data-webauthn-error-msg="timeout">{{.locale.Tr "webauthn_error_timeout"}}</div>
</div>
</div>
diff --git a/templates/user/settings/security/webauthn.tmpl b/templates/user/settings/security/webauthn.tmpl
index e541f764bc..ec1df7cdcd 100644
--- a/templates/user/settings/security/webauthn.tmpl
+++ b/templates/user/settings/security/webauthn.tmpl
@@ -1,6 +1,4 @@
-<h4 class="ui top attached header">
-{{.locale.Tr "settings.webauthn"}}
-</h4>
+<h4 class="ui top attached header">{{.locale.Tr "settings.webauthn"}}</h4>
<div class="ui attached segment">
<p>{{.locale.Tr "settings.webauthn_desc" | Str2html}}</p>
{{template "user/auth/webauthn_error" .}}
@@ -20,7 +18,6 @@
{{end}}
</div>
<div class="ui form">
- {{.CsrfTokenHtml}}
<div class="required field">
<label for="nickname">{{.locale.Tr "settings.webauthn_nickname"}}</label>
<input id="nickname" name="nickname" type="text" required>
@@ -29,7 +26,6 @@
</div>
</div>
-
<div class="ui g-modal-confirm delete modal" id="delete-registration">
<div class="header">
{{svg "octicon-trash"}}
diff --git a/web_src/css/form.css b/web_src/css/form.css
index 9e1a72380b..dfa7208ca2 100644
--- a/web_src/css/form.css
+++ b/web_src/css/form.css
@@ -394,10 +394,6 @@ textarea:focus,
margin: 0;
}
-.user.signin.webauthn-prompt {
- margin-top: 15px;
-}
-
.repository.new.repo form,
.repository.new.migrate form,
.repository.new.fork form {
diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js
index 8085313d22..c4c2356cb3 100644
--- a/web_src/js/features/user-auth-webauthn.js
+++ b/web_src/js/features/user-auth-webauthn.js
@@ -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);
}
diff --git a/web_src/js/utils.test.js b/web_src/js/utils.test.js
index cf73b63b99..812ef3bedf 100644
--- a/web_src/js/utils.test.js
+++ b/web_src/js/utils.test.js
@@ -133,8 +133,17 @@ test('toAbsoluteUrl', () => {
expect(() => toAbsoluteUrl('path')).toThrowError('unsupported');
});
+const uint8array = (s) => new TextEncoder().encode(s);
test('encodeURLEncodedBase64, decodeURLEncodedBase64', () => {
- expect(encodeURLEncodedBase64(decodeURLEncodedBase64('foo'))).toEqual('foo'); // No = padding
- expect(encodeURLEncodedBase64(decodeURLEncodedBase64('a-minus'))).toEqual('a-minus');
- expect(encodeURLEncodedBase64(decodeURLEncodedBase64('_underscorc'))).toEqual('_underscorc');
+ expect(encodeURLEncodedBase64(uint8array('AA?'))).toEqual('QUE_'); // standard base64: "QUE/"
+ expect(encodeURLEncodedBase64(uint8array('AA~'))).toEqual('QUF-'); // standard base64: "QUF+"
+
+ expect(decodeURLEncodedBase64('QUE/')).toEqual(uint8array('AA?'));
+ expect(decodeURLEncodedBase64('QUF+')).toEqual(uint8array('AA~'));
+ expect(decodeURLEncodedBase64('QUE_')).toEqual(uint8array('AA?'));
+ expect(decodeURLEncodedBase64('QUF-')).toEqual(uint8array('AA~'));
+
+ expect(encodeURLEncodedBase64(uint8array('a'))).toEqual('YQ'); // standard base64: "YQ=="
+ expect(decodeURLEncodedBase64('YQ')).toEqual(uint8array('a'));
+ expect(decodeURLEncodedBase64('YQ==')).toEqual(uint8array('a'));
});