Skip to content

Fix U2F registration and signing by replacing jQuery requests by fetch #11311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion models/u2f.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion modules/markup/common/footnote.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion modules/markup/common/linkify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
134 changes: 134 additions & 0 deletions web_src/js/features/webauthn.js
Original file line number Diff line number Diff line change
@@ -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);
});
}
125 changes: 1 addition & 124 deletions web_src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions web_src/js/polyfills.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'whatwg-fetch';

// compat: IE11
if (!Element.prototype.matches) {
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
Expand Down