diff --git a/models/u2f.go b/models/u2f.go index 28341906face6..9e38dbd10e1d0 100644 --- a/models/u2f.go +++ b/models/u2f.go @@ -52,7 +52,7 @@ func (list U2FRegistrationList) ToRegistrations() []u2f.Registration { for _, reg := range list { r, err := reg.Parse() if err != nil { - log.Fatal("parsing u2f registration: %v", err) + log.Warn("parsing u2f registration: %v", err) continue } regs = append(regs, *r) diff --git a/modules/markup/common/footnote.go b/modules/markup/common/footnote.go index ad4cd7f2e16b3..860e2d2f70042 100644 --- a/modules/markup/common/footnote.go +++ b/modules/markup/common/footnote.go @@ -358,7 +358,7 @@ func (a *footnoteASTTransformer) Transform(node *ast.Document, reader text.Reade } pc.Set(footnoteListKey, nil) for footnote := list.FirstChild(); footnote != nil; { - var container ast.Node = footnote + var container = footnote next := footnote.NextSibling() if fc := container.LastChild(); fc != nil && ast.IsParagraph(fc) { container = fc diff --git a/modules/markup/common/linkify.go b/modules/markup/common/linkify.go index 25621bf8019e0..f4075a64fc1e3 100644 --- a/modules/markup/common/linkify.go +++ b/modules/markup/common/linkify.go @@ -58,7 +58,7 @@ func (s *linkifyParser) Parse(parent ast.Node, block text.Reader, pc parser.Cont var m []int var protocol []byte - var typ ast.AutoLinkType = ast.AutoLinkURL + var typ = ast.AutoLinkURL if bytes.HasPrefix(line, protoHTTP) || bytes.HasPrefix(line, protoHTTPS) || bytes.HasPrefix(line, protoFTP) { m = LinkRegex.FindSubmatchIndex(line) } diff --git a/package.json b/package.json index b76d9162c416e..d3d76ad8becbd 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "webpack": "4.43.0", "webpack-cli": "3.3.11", "webpack-fix-style-only-entries": "0.4.0", + "whatwg-fetch": "3.0.0", "worker-loader": "2.0.0" }, "devDependencies": { diff --git a/web_src/js/features/webauthn.js b/web_src/js/features/webauthn.js new file mode 100644 index 0000000000000..80685c55bbc56 --- /dev/null +++ b/web_src/js/features/webauthn.js @@ -0,0 +1,134 @@ +const {AppSubUrl, csrf} = window.config; + +export async function initU2FAuth() { + if ($('#wait-for-key').length === 0) { + return; + } + try { + await u2fApi.ensureSupport(); + } catch (e) { + // Fallback in case browser do not support U2F + window.location.href = `${AppSubUrl}/user/two_factor`; + } + try { + const response = await fetch(`${AppSubUrl}/user/u2f/challenge`); + if (!response.ok) throw new Error('cannot retrieve challenge'); + const {appId, challenge, registeredKeys} = await response.json(); + const signature = await u2fApi.sign(appId, challenge, registeredKeys, 30); + u2fSigned(signature); + } catch (e) { + if (e === undefined || e.metaData === undefined) { + u2fError(1); + return; + } + u2fError(e.metaData.code); + } +} + +function u2fSigned(resp) { + $.ajax({ + url: `${AppSubUrl}/user/u2f/sign`, + type: 'POST', + headers: {'X-Csrf-Token': csrf}, + data: JSON.stringify(resp), + contentType: 'application/json; charset=utf-8', + }).done((res) => { + window.location.replace(res); + }).fail(() => { + u2fError(1); + }); +} + +function u2fRegistered(resp) { + if (checkError(resp)) { + return; + } + $.ajax({ + url: `${AppSubUrl}/user/settings/security/u2f/register`, + type: 'POST', + headers: {'X-Csrf-Token': csrf}, + data: JSON.stringify(resp), + contentType: 'application/json; charset=utf-8', + success() { + window.location.reload(); + }, + fail() { + u2fError(1); + } + }); +} + +function checkError(resp) { + if (!('errorCode' in resp)) { + return false; + } + if (resp.errorCode === 0) { + return false; + } + u2fError(resp.errorCode); + return true; +} + +function u2fError(errorType) { + const u2fErrors = { + browser: $('#unsupported-browser'), + 1: $('#u2f-error-1'), + 2: $('#u2f-error-2'), + 3: $('#u2f-error-3'), + 4: $('#u2f-error-4'), + 5: $('.u2f-error-5') + }; + u2fErrors[errorType].removeClass('hide'); + + Object.keys(u2fErrors).forEach((type) => { + if (type !== errorType) { + u2fErrors[type].addClass('hide'); + } + }); + $('#u2f-error').modal('show'); +} + +export function initU2FRegister() { + $('#register-device').modal({allowMultiple: false}); + $('#u2f-error').modal({allowMultiple: false}); + $('#register-security-key').on('click', (e) => { + e.preventDefault(); + u2fApi.ensureSupport() + .then(u2fRegisterRequest) + .catch(() => { + u2fError('browser'); + }); + }); +} + +async function u2fRegisterRequest() { + const body = new FormData(); + body.append('_csrf', csrf); + body.append('name', $('#nickname').val()); + const response = await fetch(`${AppSubUrl}/user/settings/security/u2f/request_register`, { + method: 'POST', + body, + }); + if (!response.ok) { + if (response.status === 409) { + $('#nickname').closest('div.field').addClass('error'); + return; + } + throw new Error('request register failed'); + } + let {appId, registerRequests, registeredKeys} = await response.json(); + $('#nickname').closest('div.field').removeClass('error'); + $('#register-device').modal('show'); + if (registeredKeys === null) { + registeredKeys = []; + } + u2fApi.register(appId, registerRequests, registeredKeys, 30) + .then(u2fRegistered) + .catch((reason) => { + if (reason === undefined) { + u2fError(1); + return; + } + u2fError(reason.metaData.code); + }); +} diff --git a/web_src/js/index.js b/web_src/js/index.js index 6ac4a1de5d4e7..a96e95b7af5a0 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -20,6 +20,7 @@ import createDropzone from './features/dropzone.js'; import highlight from './features/highlight.js'; import ActivityTopAuthors from './components/ActivityTopAuthors.vue'; import {initNotificationsTable, initNotificationCount} from './features/notification.js'; +import {initU2FAuth, initU2FRegister} from './features/webauthn.js'; const {AppSubUrl, StaticUrlPrefix, csrf} = window.config; @@ -2186,130 +2187,6 @@ function initCodeView() { $('.ui.blob-excerpt').on('click', (e) => { insertBlobExcerpt(e) }); } -function initU2FAuth() { - if ($('#wait-for-key').length === 0) { - return; - } - u2fApi.ensureSupport() - .then(() => { - $.getJSON(`${AppSubUrl}/user/u2f/challenge`).success((req) => { - u2fApi.sign(req.appId, req.challenge, req.registeredKeys, 30) - .then(u2fSigned) - .catch((err) => { - if (err === undefined) { - u2fError(1); - return; - } - u2fError(err.metaData.code); - }); - }); - }).catch(() => { - // Fallback in case browser do not support U2F - window.location.href = `${AppSubUrl}/user/two_factor`; - }); -} -function u2fSigned(resp) { - $.ajax({ - url: `${AppSubUrl}/user/u2f/sign`, - type: 'POST', - headers: {'X-Csrf-Token': csrf}, - data: JSON.stringify(resp), - contentType: 'application/json; charset=utf-8', - }).done((res) => { - window.location.replace(res); - }).fail(() => { - u2fError(1); - }); -} - -function u2fRegistered(resp) { - if (checkError(resp)) { - return; - } - $.ajax({ - url: `${AppSubUrl}/user/settings/security/u2f/register`, - type: 'POST', - headers: {'X-Csrf-Token': csrf}, - data: JSON.stringify(resp), - contentType: 'application/json; charset=utf-8', - success() { - reload(); - }, - fail() { - u2fError(1); - } - }); -} - -function checkError(resp) { - if (!('errorCode' in resp)) { - return false; - } - if (resp.errorCode === 0) { - return false; - } - u2fError(resp.errorCode); - return true; -} - -function u2fError(errorType) { - const u2fErrors = { - browser: $('#unsupported-browser'), - 1: $('#u2f-error-1'), - 2: $('#u2f-error-2'), - 3: $('#u2f-error-3'), - 4: $('#u2f-error-4'), - 5: $('.u2f-error-5') - }; - u2fErrors[errorType].removeClass('hide'); - - Object.keys(u2fErrors).forEach((type) => { - if (type !== errorType) { - u2fErrors[type].addClass('hide'); - } - }); - $('#u2f-error').modal('show'); -} - -function initU2FRegister() { - $('#register-device').modal({allowMultiple: false}); - $('#u2f-error').modal({allowMultiple: false}); - $('#register-security-key').on('click', (e) => { - e.preventDefault(); - u2fApi.ensureSupport() - .then(u2fRegisterRequest) - .catch(() => { - u2fError('browser'); - }); - }); -} - -function u2fRegisterRequest() { - $.post(`${AppSubUrl}/user/settings/security/u2f/request_register`, { - _csrf: csrf, - name: $('#nickname').val() - }).success((req) => { - $('#nickname').closest('div.field').removeClass('error'); - $('#register-device').modal('show'); - if (req.registeredKeys === null) { - req.registeredKeys = []; - } - u2fApi.register(req.appId, req.registerRequests, req.registeredKeys, 30) - .then(u2fRegistered) - .catch((reason) => { - if (reason === undefined) { - u2fError(1); - return; - } - u2fError(reason.metaData.code); - }); - }).fail((xhr) => { - if (xhr.status === 409) { - $('#nickname').closest('div.field').addClass('error'); - } - }); -} - function initWipTitle() { $('.title_wip_desc > a').on('click', (e) => { e.preventDefault(); diff --git a/web_src/js/polyfills.js b/web_src/js/polyfills.js index 0063b6d253247..036993aa97585 100644 --- a/web_src/js/polyfills.js +++ b/web_src/js/polyfills.js @@ -1,3 +1,5 @@ +import 'whatwg-fetch'; + // compat: IE11 if (!Element.prototype.matches) { Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;