diff --git a/app/assets/javascripts/u2f/authenticate.js b/app/assets/javascripts/u2f/authenticate.js index 96af6d2fcca..78fd7ad441f 100644 --- a/app/assets/javascripts/u2f/authenticate.js +++ b/app/assets/javascripts/u2f/authenticate.js @@ -11,7 +11,6 @@ export default class U2FAuthenticate { constructor(container, form, u2fParams, fallbackButton, fallbackUI) { this.u2fUtils = null; this.container = container; - this.renderNotSupported = this.renderNotSupported.bind(this); this.renderAuthenticated = this.renderAuthenticated.bind(this); this.renderError = this.renderError.bind(this); this.renderInProgress = this.renderInProgress.bind(this); @@ -41,7 +40,6 @@ export default class U2FAuthenticate { this.signRequests = u2fParams.sign_requests.map(request => _(request).omit('challenge')); this.templates = { - notSupported: '#js-authenticate-u2f-not-supported', setup: '#js-authenticate-u2f-setup', inProgress: '#js-authenticate-u2f-in-progress', error: '#js-authenticate-u2f-error', @@ -55,7 +53,7 @@ export default class U2FAuthenticate { this.u2fUtils = utils; this.renderInProgress(); }) - .catch(() => this.renderNotSupported()); + .catch(() => this.switchToFallbackUI()); } authenticate() { @@ -96,10 +94,6 @@ export default class U2FAuthenticate { this.fallbackButton.classList.add('hidden'); } - renderNotSupported() { - return this.renderTemplate('notSupported'); - } - switchToFallbackUI() { this.fallbackButton.classList.add('hidden'); this.container[0].classList.add('hidden'); diff --git a/app/views/u2f/_authenticate.html.haml b/app/views/u2f/_authenticate.html.haml index 7eb221620ad..1c788b9a737 100644 --- a/app/views/u2f/_authenticate.html.haml +++ b/app/views/u2f/_authenticate.html.haml @@ -2,9 +2,6 @@ %a.btn.btn-block.btn-info#js-login-2fa-device{ href: '#' } Sign in via 2FA code -# 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" } %p Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now. diff --git a/changelogs/unreleased/40005-u2f-unspported-browsers.yml b/changelogs/unreleased/40005-u2f-unspported-browsers.yml new file mode 100644 index 00000000000..eb5ff99246e --- /dev/null +++ b/changelogs/unreleased/40005-u2f-unspported-browsers.yml @@ -0,0 +1,5 @@ +--- +title: Improve U2F workflow when using unsupported browsers +merge_request: 19938 +author: Jan Beckmann +type: changed diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js index d84b13b07c4..57e0caa692c 100644 --- a/spec/javascripts/u2f/authenticate_spec.js +++ b/spec/javascripts/u2f/authenticate_spec.js @@ -6,7 +6,7 @@ import MockU2FDevice from './mock_u2f_device'; describe('U2FAuthenticate', function () { preloadFixtures('u2f/authenticate.html.raw'); - beforeEach((done) => { + beforeEach(() => { loadFixtures('u2f/authenticate.html.raw'); this.u2fDevice = new MockU2FDevice(); this.container = $('#js-authenticate-u2f'); @@ -19,46 +19,70 @@ describe('U2FAuthenticate', function () { document.querySelector('#js-login-2fa-device'), document.querySelector('.js-2fa-form'), ); - - // bypass automatic form submission within renderAuthenticated - spyOn(this.component, 'renderAuthenticated').and.returnValue(true); - - this.component.start().then(done).catch(done.fail); }); - it('allows authenticating via a U2F device', () => { - const inProgressMessage = this.container.find('p'); - expect(inProgressMessage.text()).toContain('Trying to communicate with your device'); - this.u2fDevice.respondToAuthenticateRequest({ - deviceData: 'this is data from the device', + describe('with u2f unavailable', () => { + beforeEach(() => { + spyOn(this.component, 'switchToFallbackUI'); + this.oldu2f = window.u2f; + window.u2f = null; + }); + + afterEach(() => { + window.u2f = this.oldu2f; + }); + + it('falls back to normal 2fa', (done) => { + this.component.start().then(() => { + expect(this.component.switchToFallbackUI).toHaveBeenCalled(); + done(); + }).catch(done.fail); }); - 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'); + describe('with u2f available', () => { + beforeEach((done) => { + // bypass automatic form submission within renderAuthenticated + spyOn(this.component, 'renderAuthenticated').and.returnValue(true); + this.u2fDevice = new MockU2FDevice(); + + this.component.start().then(done).catch(done.fail); }); - 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'); + + it('allows authenticating via a U2F device', () => { + const inProgressMessage = this.container.find('p'); + expect(inProgressMessage.text()).toContain('Trying to communicate with your device'); this.u2fDevice.respondToAuthenticateRequest({ 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"}'); + }); + }); }); });