From df2adb203c423dbcbd8672705027dfd1eb868053 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sun, 29 Jan 2023 10:01:19 +0000 Subject: [PATCH 1/5] Fix Uncaught DOMException: Failed to execute 'atob' on 'Window' There is a missing import within the `uint8-to-base64` javascript package which assumes that `atob` and `btoa` are present and exported instead of using the `window.atob` and `window.btoa` functions. This previously worked but as far as I can see things have become more strict and this no longer works. The dependency is small and I do not believe that we gain much from having this code as an external dependency. I think instead we should just consume this dependency and bring the code directly into Gitea itself - the code is itself just some standard incantation for creating base64 arrays in javascript. Therefore this PR simply removes the dependency on `uint8-to-base64` and rewrites the functions used in it. Fix #22507 Signed-off-by: Andrew Thornton --- package-lock.json | 11 ---------- package.json | 1 - web_src/js/features/user-auth-webauthn.js | 25 ++++++++++++++++------- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 60f0a0f8e6235..50dddde13f897 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,7 +39,6 @@ "swagger-ui-dist": "4.15.5", "tippy.js": "6.3.7", "tributejs": "5.1.3", - "uint8-to-base64": "0.2.0", "vue": "3.2.45", "vue-bar-graph": "2.0.0", "vue-loader": "17.0.1", @@ -8856,11 +8855,6 @@ "integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==", "dev": true }, - "node_modules/uint8-to-base64": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/uint8-to-base64/-/uint8-to-base64-0.2.0.tgz", - "integrity": "sha512-r13jrghEYZAN99GeYpEjM107DOxqB65enskpwce8rRHVAGEtaWmsF5GqoGdPMf8DIXc9XyAJTdvlvRZi4LsszA==" - }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -16366,11 +16360,6 @@ "integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==", "dev": true }, - "uint8-to-base64": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/uint8-to-base64/-/uint8-to-base64-0.2.0.tgz", - "integrity": "sha512-r13jrghEYZAN99GeYpEjM107DOxqB65enskpwce8rRHVAGEtaWmsF5GqoGdPMf8DIXc9XyAJTdvlvRZi4LsszA==" - }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index 842804211cf86..7821fbb580054 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "swagger-ui-dist": "4.15.5", "tippy.js": "6.3.7", "tributejs": "5.1.3", - "uint8-to-base64": "0.2.0", "vue": "3.2.45", "vue-bar-graph": "2.0.0", "vue-loader": "17.0.1", diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js index f11a49864dedc..c86f6314edf55 100644 --- a/web_src/js/features/user-auth-webauthn.js +++ b/web_src/js/features/user-auth-webauthn.js @@ -1,8 +1,19 @@ import $ from 'jquery'; -import {encode, decode} from 'uint8-to-base64'; const {appSubUrl, csrfToken} = window.config; +function encodeToBase64(toEncode) { + const output = []; + for (let i = 0; i < toEncode.length; i++) { + output.push(String.fromCharCode(toEncode[i])); + } + return window.btoa(output.join('')); +} + +function decodeFromBase64(toDecode) { + return Uint8Array.from(window.atob(toDecode), (c) => c.charCodeAt(0)); +} + export function initUserAuthWebAuthn() { if ($('.user.signin.webauthn-prompt').length === 0) { return; @@ -14,9 +25,9 @@ export function initUserAuthWebAuthn() { $.getJSON(`${appSubUrl}/user/webauthn/assertion`, {}) .done((makeAssertionOptions) => { - makeAssertionOptions.publicKey.challenge = decode(makeAssertionOptions.publicKey.challenge); + makeAssertionOptions.publicKey.challenge = decodeFromBase64(makeAssertionOptions.publicKey.challenge); for (let i = 0; i < makeAssertionOptions.publicKey.allowCredentials.length; i++) { - makeAssertionOptions.publicKey.allowCredentials[i].id = decode(makeAssertionOptions.publicKey.allowCredentials[i].id); + makeAssertionOptions.publicKey.allowCredentials[i].id = decodeFromBase64(makeAssertionOptions.publicKey.allowCredentials[i].id); } navigator.credentials.get({ publicKey: makeAssertionOptions.publicKey @@ -87,7 +98,7 @@ function verifyAssertion(assertedCredential) { // Encode an ArrayBuffer into a base64 string. function bufferEncode(value) { - return encode(value) + return encodeToBase64(value) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); @@ -184,11 +195,11 @@ function webAuthnRegisterRequest() { }).done((makeCredentialOptions) => { $('#nickname').closest('div.field').removeClass('error'); - makeCredentialOptions.publicKey.challenge = decode(makeCredentialOptions.publicKey.challenge); - makeCredentialOptions.publicKey.user.id = decode(makeCredentialOptions.publicKey.user.id); + makeCredentialOptions.publicKey.challenge = decodeFromBase64(makeCredentialOptions.publicKey.challenge); + makeCredentialOptions.publicKey.user.id = decodeFromBase64(makeCredentialOptions.publicKey.user.id); if (makeCredentialOptions.publicKey.excludeCredentials) { for (let i = 0; i < makeCredentialOptions.publicKey.excludeCredentials.length; i++) { - makeCredentialOptions.publicKey.excludeCredentials[i].id = decode(makeCredentialOptions.publicKey.excludeCredentials[i].id); + makeCredentialOptions.publicKey.excludeCredentials[i].id = decodeFromBase64(makeCredentialOptions.publicKey.excludeCredentials[i].id); } } From f40e5161a22fbc6d2822430ad859295eb7e39c5e Mon Sep 17 00:00:00 2001 From: zeripath Date: Sun, 29 Jan 2023 12:31:08 +0000 Subject: [PATCH 2/5] Update web_src/js/features/user-auth-webauthn.js Co-authored-by: delvh --- web_src/js/features/user-auth-webauthn.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js index c86f6314edf55..180309eefb9f6 100644 --- a/web_src/js/features/user-auth-webauthn.js +++ b/web_src/js/features/user-auth-webauthn.js @@ -2,7 +2,7 @@ import $ from 'jquery'; const {appSubUrl, csrfToken} = window.config; -function encodeToBase64(toEncode) { +function encodeToBase64(toEncode /* Uint8Array */) { const output = []; for (let i = 0; i < toEncode.length; i++) { output.push(String.fromCharCode(toEncode[i])); From dcd63076afe9ae81b1ce46ed6b73c89a369e677b Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Sun, 29 Jan 2023 21:21:14 +0000 Subject: [PATCH 3/5] Include findings from #22654 Signed-off-by: Andrew Thornton --- web_src/js/features/user-auth-webauthn.js | 37 ++++++++++++++--------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js index c86f6314edf55..3b00bf1e96441 100644 --- a/web_src/js/features/user-auth-webauthn.js +++ b/web_src/js/features/user-auth-webauthn.js @@ -25,9 +25,9 @@ export function initUserAuthWebAuthn() { $.getJSON(`${appSubUrl}/user/webauthn/assertion`, {}) .done((makeAssertionOptions) => { - makeAssertionOptions.publicKey.challenge = decodeFromBase64(makeAssertionOptions.publicKey.challenge); + makeAssertionOptions.publicKey.challenge = decodeURLEncodedBase64(makeAssertionOptions.publicKey.challenge); for (let i = 0; i < makeAssertionOptions.publicKey.allowCredentials.length; i++) { - makeAssertionOptions.publicKey.allowCredentials[i].id = decodeFromBase64(makeAssertionOptions.publicKey.allowCredentials[i].id); + makeAssertionOptions.publicKey.allowCredentials[i].id = decodeURLEncodedBase64(makeAssertionOptions.publicKey.allowCredentials[i].id); } navigator.credentials.get({ publicKey: makeAssertionOptions.publicKey @@ -67,14 +67,14 @@ function verifyAssertion(assertedCredential) { type: 'POST', data: JSON.stringify({ id: assertedCredential.id, - rawId: bufferEncode(rawId), + rawId: bufferURLEncodedBase64(rawId), type: assertedCredential.type, clientExtensionResults: assertedCredential.getClientExtensionResults(), response: { - authenticatorData: bufferEncode(authData), - clientDataJSON: bufferEncode(clientDataJSON), - signature: bufferEncode(sig), - userHandle: bufferEncode(userHandle), + authenticatorData: bufferURLEncodedBase64(authData), + clientDataJSON: bufferURLEncodedBase64(clientDataJSON), + signature: bufferURLEncodedBase64(sig), + userHandle: bufferURLEncodedBase64(userHandle), }, }), contentType: 'application/json; charset=utf-8', @@ -96,14 +96,21 @@ function verifyAssertion(assertedCredential) { }); } -// Encode an ArrayBuffer into a base64 string. -function bufferEncode(value) { +// Encode an ArrayBuffer into a URLEncoded base64 string. +function bufferURLEncodedBase64(value) { return encodeToBase64(value) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); } +// Dccode a URLEncoded base64 to an ArrayBuffer string. +function decodeURLEncodedBase64(value) { + return decodeFromBase64(value + .replace(/_/g, '/') + .replace(/-/g, '+')); +} + function webauthnRegistered(newCredential) { const attestationObject = new Uint8Array(newCredential.response.attestationObject); const clientDataJSON = new Uint8Array(newCredential.response.clientDataJSON); @@ -115,11 +122,11 @@ function webauthnRegistered(newCredential) { headers: {'X-Csrf-Token': csrfToken}, data: JSON.stringify({ id: newCredential.id, - rawId: bufferEncode(rawId), + rawId: bufferURLEncodedBase64(rawId), type: newCredential.type, response: { - attestationObject: bufferEncode(attestationObject), - clientDataJSON: bufferEncode(clientDataJSON), + attestationObject: bufferURLEncodedBase64(attestationObject), + clientDataJSON: bufferURLEncodedBase64(clientDataJSON), }, }), dataType: 'json', @@ -195,11 +202,11 @@ function webAuthnRegisterRequest() { }).done((makeCredentialOptions) => { $('#nickname').closest('div.field').removeClass('error'); - makeCredentialOptions.publicKey.challenge = decodeFromBase64(makeCredentialOptions.publicKey.challenge); - makeCredentialOptions.publicKey.user.id = decodeFromBase64(makeCredentialOptions.publicKey.user.id); + makeCredentialOptions.publicKey.challenge = decodeURLEncodedBase64(makeCredentialOptions.publicKey.challenge); + makeCredentialOptions.publicKey.user.id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.user.id); if (makeCredentialOptions.publicKey.excludeCredentials) { for (let i = 0; i < makeCredentialOptions.publicKey.excludeCredentials.length; i++) { - makeCredentialOptions.publicKey.excludeCredentials[i].id = decodeFromBase64(makeCredentialOptions.publicKey.excludeCredentials[i].id); + makeCredentialOptions.publicKey.excludeCredentials[i].id = decodeURLEncodedBase64(makeCredentialOptions.publicKey.excludeCredentials[i].id); } } From f03824463e3993d1ab7fa845cf369daaf98e1516 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Mon, 30 Jan 2023 21:08:27 +0000 Subject: [PATCH 4/5] As per wxiaoguang Signed-off-by: Andrew Thornton --- modules/auth/webauthn/webauthn.go | 2 +- package-lock.json | 11 +++++++ package.json | 1 + web_src/js/features/user-auth-webauthn.js | 35 ++++++++--------------- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/modules/auth/webauthn/webauthn.go b/modules/auth/webauthn/webauthn.go index d08f7bf7cc715..937da872ca1af 100644 --- a/modules/auth/webauthn/webauthn.go +++ b/modules/auth/webauthn/webauthn.go @@ -28,7 +28,7 @@ func Init() { Config: &webauthn.Config{ RPDisplayName: setting.AppName, RPID: setting.Domain, - RPOrigin: appURL, + RPOrigins: []string{appURL}, AuthenticatorSelection: protocol.AuthenticatorSelection{ UserVerification: "discouraged", }, diff --git a/package-lock.json b/package-lock.json index 50dddde13f897..60f0a0f8e6235 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "swagger-ui-dist": "4.15.5", "tippy.js": "6.3.7", "tributejs": "5.1.3", + "uint8-to-base64": "0.2.0", "vue": "3.2.45", "vue-bar-graph": "2.0.0", "vue-loader": "17.0.1", @@ -8855,6 +8856,11 @@ "integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==", "dev": true }, + "node_modules/uint8-to-base64": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uint8-to-base64/-/uint8-to-base64-0.2.0.tgz", + "integrity": "sha512-r13jrghEYZAN99GeYpEjM107DOxqB65enskpwce8rRHVAGEtaWmsF5GqoGdPMf8DIXc9XyAJTdvlvRZi4LsszA==" + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -16360,6 +16366,11 @@ "integrity": "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==", "dev": true }, + "uint8-to-base64": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/uint8-to-base64/-/uint8-to-base64-0.2.0.tgz", + "integrity": "sha512-r13jrghEYZAN99GeYpEjM107DOxqB65enskpwce8rRHVAGEtaWmsF5GqoGdPMf8DIXc9XyAJTdvlvRZi4LsszA==" + }, "unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index 7821fbb580054..842804211cf86 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "swagger-ui-dist": "4.15.5", "tippy.js": "6.3.7", "tributejs": "5.1.3", + "uint8-to-base64": "0.2.0", "vue": "3.2.45", "vue-bar-graph": "2.0.0", "vue-loader": "17.0.1", diff --git a/web_src/js/features/user-auth-webauthn.js b/web_src/js/features/user-auth-webauthn.js index e7116210768a6..9c9fffd995279 100644 --- a/web_src/js/features/user-auth-webauthn.js +++ b/web_src/js/features/user-auth-webauthn.js @@ -1,19 +1,8 @@ import $ from 'jquery'; +import {encode, decode} from 'uint8-to-base64'; const {appSubUrl, csrfToken} = window.config; -function encodeToBase64(toEncode /* Uint8Array */) { - const output = []; - for (let i = 0; i < toEncode.length; i++) { - output.push(String.fromCharCode(toEncode[i])); - } - return window.btoa(output.join('')); -} - -function decodeFromBase64(toDecode) { - return Uint8Array.from(window.atob(toDecode), (c) => c.charCodeAt(0)); -} - export function initUserAuthWebAuthn() { if ($('.user.signin.webauthn-prompt').length === 0) { return; @@ -67,14 +56,14 @@ function verifyAssertion(assertedCredential) { type: 'POST', data: JSON.stringify({ id: assertedCredential.id, - rawId: bufferURLEncodedBase64(rawId), + rawId: encodeURLEncodedBase64(rawId), type: assertedCredential.type, clientExtensionResults: assertedCredential.getClientExtensionResults(), response: { - authenticatorData: bufferURLEncodedBase64(authData), - clientDataJSON: bufferURLEncodedBase64(clientDataJSON), - signature: bufferURLEncodedBase64(sig), - userHandle: bufferURLEncodedBase64(userHandle), + authenticatorData: encodeURLEncodedBase64(authData), + clientDataJSON: encodeURLEncodedBase64(clientDataJSON), + signature: encodeURLEncodedBase64(sig), + userHandle: encodeURLEncodedBase64(userHandle), }, }), contentType: 'application/json; charset=utf-8', @@ -97,8 +86,8 @@ function verifyAssertion(assertedCredential) { } // Encode an ArrayBuffer into a URLEncoded base64 string. -function bufferURLEncodedBase64(value) { - return encodeToBase64(value) +function encodeURLEncodedBase64(value) { + return encode(value) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); @@ -106,7 +95,7 @@ function bufferURLEncodedBase64(value) { // Dccode a URLEncoded base64 to an ArrayBuffer string. function decodeURLEncodedBase64(value) { - return decodeFromBase64(value + return decode(value .replace(/_/g, '/') .replace(/-/g, '+')); } @@ -122,11 +111,11 @@ function webauthnRegistered(newCredential) { headers: {'X-Csrf-Token': csrfToken}, data: JSON.stringify({ id: newCredential.id, - rawId: bufferURLEncodedBase64(rawId), + rawId: encodeURLEncodedBase64(rawId), type: newCredential.type, response: { - attestationObject: bufferURLEncodedBase64(attestationObject), - clientDataJSON: bufferURLEncodedBase64(clientDataJSON), + attestationObject: encodeURLEncodedBase64(attestationObject), + clientDataJSON: encodeURLEncodedBase64(clientDataJSON), }, }), dataType: 'json', From b37e6a1aa16c57bab6852f8f0495721b34551b3e Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Tue, 31 Jan 2023 20:17:49 +0000 Subject: [PATCH 5/5] fix test Signed-off-by: Andrew Thornton --- modules/auth/webauthn/webauthn_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auth/webauthn/webauthn_test.go b/modules/auth/webauthn/webauthn_test.go index 1beeb64cd6ca0..15a8d7182833d 100644 --- a/modules/auth/webauthn/webauthn_test.go +++ b/modules/auth/webauthn/webauthn_test.go @@ -15,11 +15,11 @@ func TestInit(t *testing.T) { setting.Domain = "domain" setting.AppName = "AppName" setting.AppURL = "https://domain/" - rpOrigin := "https://domain" + rpOrigin := []string{"https://domain"} Init() assert.Equal(t, setting.Domain, WebAuthn.Config.RPID) assert.Equal(t, setting.AppName, WebAuthn.Config.RPDisplayName) - assert.Equal(t, rpOrigin, WebAuthn.Config.RPOrigin) + assert.Equal(t, rpOrigin, WebAuthn.Config.RPOrigins) }