Commit a6d3727f authored by Jan Beckmann's avatar Jan Beckmann Committed by Phil Hughes

Resolve "2FA should not attempt to use U2F in unsupported browsers"

parent ff9b99ca
...@@ -11,7 +11,6 @@ export default class U2FAuthenticate { ...@@ -11,7 +11,6 @@ export default class U2FAuthenticate {
constructor(container, form, u2fParams, fallbackButton, fallbackUI) { constructor(container, form, u2fParams, fallbackButton, fallbackUI) {
this.u2fUtils = null; this.u2fUtils = null;
this.container = container; this.container = container;
this.renderNotSupported = this.renderNotSupported.bind(this);
this.renderAuthenticated = this.renderAuthenticated.bind(this); this.renderAuthenticated = this.renderAuthenticated.bind(this);
this.renderError = this.renderError.bind(this); this.renderError = this.renderError.bind(this);
this.renderInProgress = this.renderInProgress.bind(this); this.renderInProgress = this.renderInProgress.bind(this);
...@@ -41,7 +40,6 @@ export default class U2FAuthenticate { ...@@ -41,7 +40,6 @@ export default class U2FAuthenticate {
this.signRequests = u2fParams.sign_requests.map(request => _(request).omit('challenge')); this.signRequests = u2fParams.sign_requests.map(request => _(request).omit('challenge'));
this.templates = { this.templates = {
notSupported: '#js-authenticate-u2f-not-supported',
setup: '#js-authenticate-u2f-setup', setup: '#js-authenticate-u2f-setup',
inProgress: '#js-authenticate-u2f-in-progress', inProgress: '#js-authenticate-u2f-in-progress',
error: '#js-authenticate-u2f-error', error: '#js-authenticate-u2f-error',
...@@ -55,7 +53,7 @@ export default class U2FAuthenticate { ...@@ -55,7 +53,7 @@ export default class U2FAuthenticate {
this.u2fUtils = utils; this.u2fUtils = utils;
this.renderInProgress(); this.renderInProgress();
}) })
.catch(() => this.renderNotSupported()); .catch(() => this.switchToFallbackUI());
} }
authenticate() { authenticate() {
...@@ -96,10 +94,6 @@ export default class U2FAuthenticate { ...@@ -96,10 +94,6 @@ export default class U2FAuthenticate {
this.fallbackButton.classList.add('hidden'); this.fallbackButton.classList.add('hidden');
} }
renderNotSupported() {
return this.renderTemplate('notSupported');
}
switchToFallbackUI() { switchToFallbackUI() {
this.fallbackButton.classList.add('hidden'); this.fallbackButton.classList.add('hidden');
this.container[0].classList.add('hidden'); this.container[0].classList.add('hidden');
......
...@@ -2,9 +2,6 @@ ...@@ -2,9 +2,6 @@
%a.btn.btn-block.btn-info#js-login-2fa-device{ href: '#' } Sign in via 2FA code %a.btn.btn-block.btn-info#js-login-2fa-device{ href: '#' } Sign in via 2FA code
-# haml-lint:disable InlineJavaScript -# haml-lint:disable InlineJavaScript
%script#js-authenticate-u2f-not-supported{ type: "text/template" }
%p Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer).
%script#js-authenticate-u2f-in-progress{ type: "text/template" } %script#js-authenticate-u2f-in-progress{ type: "text/template" }
%p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now. %p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now.
......
---
title: Improve U2F workflow when using unsupported browsers
merge_request: 19938
author: Jan Beckmann
type: changed
...@@ -6,7 +6,7 @@ import MockU2FDevice from './mock_u2f_device'; ...@@ -6,7 +6,7 @@ import MockU2FDevice from './mock_u2f_device';
describe('U2FAuthenticate', function () { describe('U2FAuthenticate', function () {
preloadFixtures('u2f/authenticate.html.raw'); preloadFixtures('u2f/authenticate.html.raw');
beforeEach((done) => { beforeEach(() => {
loadFixtures('u2f/authenticate.html.raw'); loadFixtures('u2f/authenticate.html.raw');
this.u2fDevice = new MockU2FDevice(); this.u2fDevice = new MockU2FDevice();
this.container = $('#js-authenticate-u2f'); this.container = $('#js-authenticate-u2f');
...@@ -19,46 +19,70 @@ describe('U2FAuthenticate', function () { ...@@ -19,46 +19,70 @@ describe('U2FAuthenticate', function () {
document.querySelector('#js-login-2fa-device'), document.querySelector('#js-login-2fa-device'),
document.querySelector('.js-2fa-form'), document.querySelector('.js-2fa-form'),
); );
});
// bypass automatic form submission within renderAuthenticated describe('with u2f unavailable', () => {
spyOn(this.component, 'renderAuthenticated').and.returnValue(true); beforeEach(() => {
spyOn(this.component, 'switchToFallbackUI');
this.oldu2f = window.u2f;
window.u2f = null;
});
this.component.start().then(done).catch(done.fail); afterEach(() => {
}); window.u2f = this.oldu2f;
});
it('allows authenticating via a U2F device', () => { it('falls back to normal 2fa', (done) => {
const inProgressMessage = this.container.find('p'); this.component.start().then(() => {
expect(inProgressMessage.text()).toContain('Trying to communicate with your device'); expect(this.component.switchToFallbackUI).toHaveBeenCalled();
this.u2fDevice.respondToAuthenticateRequest({ done();
deviceData: 'this is data from the device', }).catch(done.fail);
}); });
expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}');
}); });
describe('errors', () => { describe('with u2f available', () => {
it('displays an error message', () => { beforeEach((done) => {
const setupButton = this.container.find('#js-login-u2f-device'); // bypass automatic form submission within renderAuthenticated
setupButton.trigger('click'); spyOn(this.component, 'renderAuthenticated').and.returnValue(true);
this.u2fDevice.respondToAuthenticateRequest({ this.u2fDevice = new MockU2FDevice();
errorCode: 'error!',
}); this.component.start().then(done).catch(done.fail);
const errorMessage = this.container.find('p');
return expect(errorMessage.text()).toContain('There was a problem communicating with your device');
}); });
return it('allows retrying authentication after an error', () => {
let setupButton = this.container.find('#js-login-u2f-device'); it('allows authenticating via a U2F device', () => {
setupButton.trigger('click'); const inProgressMessage = this.container.find('p');
this.u2fDevice.respondToAuthenticateRequest({ expect(inProgressMessage.text()).toContain('Trying to communicate with your device');
errorCode: 'error!',
});
const retryButton = this.container.find('#js-u2f-try-again');
retryButton.trigger('click');
setupButton = this.container.find('#js-login-u2f-device');
setupButton.trigger('click');
this.u2fDevice.respondToAuthenticateRequest({ this.u2fDevice.respondToAuthenticateRequest({
deviceData: 'this is data from the device', deviceData: 'this is data from the device',
}); });
expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}'); expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}');
}); });
describe('errors', () => {
it('displays an error message', () => {
const setupButton = this.container.find('#js-login-u2f-device');
setupButton.trigger('click');
this.u2fDevice.respondToAuthenticateRequest({
errorCode: 'error!',
});
const errorMessage = this.container.find('p');
return expect(errorMessage.text()).toContain('There was a problem communicating with your device');
});
return it('allows retrying authentication after an error', () => {
let setupButton = this.container.find('#js-login-u2f-device');
setupButton.trigger('click');
this.u2fDevice.respondToAuthenticateRequest({
errorCode: 'error!',
});
const retryButton = this.container.find('#js-u2f-try-again');
retryButton.trigger('click');
setupButton = this.container.find('#js-login-u2f-device');
setupButton.trigger('click');
this.u2fDevice.respondToAuthenticateRequest({
deviceData: 'this is data from the device',
});
expect(this.component.renderAuthenticated).toHaveBeenCalledWith('{"deviceData":"this is data from the device"}');
});
});
}); });
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment