From 55e41f265b7d9751510aabed390ca6c568cd5e4f Mon Sep 17 00:00:00 2001 From: Patrick Ruckstuhl Date: Sat, 26 Nov 2016 19:52:13 +0100 Subject: [PATCH 1/4] Switch to newer u2f-api.js --- Resources/public/js/auth.js | 4 +- Resources/public/js/u2f-api.js | 748 ++++++++++++++++++ Resources/public/js/u2f.js | 651 --------------- Resources/views/Authentication/form.html.twig | 2 +- .../views/Registration/register.html.twig | 2 +- .../Prodiver/U2F/U2FAuthenticator.php | 2 +- 6 files changed, 753 insertions(+), 656 deletions(-) create mode 100644 Resources/public/js/u2f-api.js delete mode 100644 Resources/public/js/u2f.js diff --git a/Resources/public/js/auth.js b/Resources/public/js/auth.js index 8b38120..498c6e4 100644 --- a/Resources/public/js/auth.js +++ b/Resources/public/js/auth.js @@ -12,7 +12,7 @@ var ready = function(fn) { }; var authenticate = function(request, codeField, form) { - u2f.sign(request, function(data){ + u2f.sign(request[0]['appId'], request[0]['challenge'], request, function(data){ if(!data.errorCode) { codeField.value = JSON.stringify(data); form.submit(); @@ -23,7 +23,7 @@ var authenticate = function(request, codeField, form) { }; var register = function(request, codeField, form) { - u2f.register([request[0]], request[1], function(data){ + u2f.register(request[0]['appId'], [request[0]], request[1], function(data){ if(!data.errorCode) { codeField.value = JSON.stringify(data); form.submit(); diff --git a/Resources/public/js/u2f-api.js b/Resources/public/js/u2f-api.js new file mode 100644 index 0000000..1d229db --- /dev/null +++ b/Resources/public/js/u2f-api.js @@ -0,0 +1,748 @@ +//Copyright 2014-2015 Google Inc. All rights reserved. + +//Use of this source code is governed by a BSD-style +//license that can be found in the LICENSE file or at +//https://developers.google.com/open-source/licenses/bsd + +/** + * @fileoverview The U2F api. + */ +'use strict'; + + +/** + * Namespace for the U2F api. + * @type {Object} + */ +var u2f = u2f || {}; + +/** + * FIDO U2F Javascript API Version + * @number + */ +var js_api_version; + +/** + * The U2F extension id + * @const {string} + */ +// The Chrome packaged app extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the package Chrome app and does not require installing the U2F Chrome extension. + u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; +// The U2F Chrome extension ID. +// Uncomment this if you want to deploy a server instance that uses +// the U2F Chrome extension to authenticate. +// u2f.EXTENSION_ID = 'pfboblefjcgdjicmnffhdgionmgcdmne'; + + +/** + * Message types for messsages to/from the extension + * @const + * @enum {string} + */ +u2f.MessageTypes = { + 'U2F_REGISTER_REQUEST': 'u2f_register_request', + 'U2F_REGISTER_RESPONSE': 'u2f_register_response', + 'U2F_SIGN_REQUEST': 'u2f_sign_request', + 'U2F_SIGN_RESPONSE': 'u2f_sign_response', + 'U2F_GET_API_VERSION_REQUEST': 'u2f_get_api_version_request', + 'U2F_GET_API_VERSION_RESPONSE': 'u2f_get_api_version_response' +}; + + +/** + * Response status codes + * @const + * @enum {number} + */ +u2f.ErrorCodes = { + 'OK': 0, + 'OTHER_ERROR': 1, + 'BAD_REQUEST': 2, + 'CONFIGURATION_UNSUPPORTED': 3, + 'DEVICE_INELIGIBLE': 4, + 'TIMEOUT': 5 +}; + + +/** + * A message for registration requests + * @typedef {{ + * type: u2f.MessageTypes, + * appId: ?string, + * timeoutSeconds: ?number, + * requestId: ?number + * }} + */ +u2f.U2fRequest; + + +/** + * A message for registration responses + * @typedef {{ + * type: u2f.MessageTypes, + * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), + * requestId: ?number + * }} + */ +u2f.U2fResponse; + + +/** + * An error object for responses + * @typedef {{ + * errorCode: u2f.ErrorCodes, + * errorMessage: ?string + * }} + */ +u2f.Error; + +/** + * Data object for a single sign request. + * @typedef {enum {BLUETOOTH_RADIO, BLUETOOTH_LOW_ENERGY, USB, NFC}} + */ +u2f.Transport; + + +/** + * Data object for a single sign request. + * @typedef {Array} + */ +u2f.Transports; + +/** + * Data object for a single sign request. + * @typedef {{ + * version: string, + * challenge: string, + * keyHandle: string, + * appId: string + * }} + */ +u2f.SignRequest; + + +/** + * Data object for a sign response. + * @typedef {{ + * keyHandle: string, + * signatureData: string, + * clientData: string + * }} + */ +u2f.SignResponse; + + +/** + * Data object for a registration request. + * @typedef {{ + * version: string, + * challenge: string + * }} + */ +u2f.RegisterRequest; + + +/** + * Data object for a registration response. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: Transports, + * appId: string + * }} + */ +u2f.RegisterResponse; + + +/** + * Data object for a registered key. + * @typedef {{ + * version: string, + * keyHandle: string, + * transports: ?Transports, + * appId: ?string + * }} + */ +u2f.RegisteredKey; + + +/** + * Data object for a get API register response. + * @typedef {{ + * js_api_version: number + * }} + */ +u2f.GetJsApiVersionResponse; + + +//Low level MessagePort API support + +/** + * Sets up a MessagePort to the U2F extension using the + * available mechanisms. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + */ +u2f.getMessagePort = function(callback) { + if (typeof chrome != 'undefined' && chrome.runtime) { + // The actual message here does not matter, but we need to get a reply + // for the callback to run. Thus, send an empty signature request + // in order to get a failure response. + var msg = { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: [] + }; + chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { + if (!chrome.runtime.lastError) { + // We are on a whitelisted origin and can talk directly + // with the extension. + u2f.getChromeRuntimePort_(callback); + } else { + // chrome.runtime was available, but we couldn't message + // the extension directly, use iframe + u2f.getIframePort_(callback); + } + }); + } else if (u2f.isAndroidChrome_()) { + u2f.getAuthenticatorPort_(callback); + } else if (u2f.isIosChrome_()) { + u2f.getIosPort_(callback); + } else { + // chrome.runtime was not available at all, which is normal + // when this origin doesn't have access to any extensions. + u2f.getIframePort_(callback); + } +}; + +/** + * Detect chrome running on android based on the browser's useragent. + * @private + */ +u2f.isAndroidChrome_ = function() { + var userAgent = navigator.userAgent; + return userAgent.indexOf('Chrome') != -1 && + userAgent.indexOf('Android') != -1; +}; + +/** + * Detect chrome running on iOS based on the browser's platform. + * @private + */ +u2f.isIosChrome_ = function() { + return $.inArray(navigator.platform, ["iPhone", "iPad", "iPod"]) > -1; +}; + +/** + * Connects directly to the extension via chrome.runtime.connect. + * @param {function(u2f.WrappedChromeRuntimePort_)} callback + * @private + */ +u2f.getChromeRuntimePort_ = function(callback) { + var port = chrome.runtime.connect(u2f.EXTENSION_ID, + {'includeTlsChannelId': true}); + setTimeout(function() { + callback(new u2f.WrappedChromeRuntimePort_(port)); + }, 0); +}; + +/** + * Return a 'port' abstraction to the Authenticator app. + * @param {function(u2f.WrappedAuthenticatorPort_)} callback + * @private + */ +u2f.getAuthenticatorPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedAuthenticatorPort_()); + }, 0); +}; + +/** + * Return a 'port' abstraction to the iOS client app. + * @param {function(u2f.WrappedIosPort_)} callback + * @private + */ +u2f.getIosPort_ = function(callback) { + setTimeout(function() { + callback(new u2f.WrappedIosPort_()); + }, 0); +}; + +/** + * A wrapper for chrome.runtime.Port that is compatible with MessagePort. + * @param {Port} port + * @constructor + * @private + */ +u2f.WrappedChromeRuntimePort_ = function(port) { + this.port_ = port; +}; + +/** + * Format and return a sign request compliant with the JS API version supported by the extension. + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatSignRequest_ = + function(appId, challenge, registeredKeys, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: challenge, + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + signRequests: signRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_SIGN_REQUEST, + appId: appId, + challenge: challenge, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + +/** + * Format and return a register request compliant with the JS API version supported by the extension.. + * @param {Array} signRequests + * @param {Array} signRequests + * @param {number} timeoutSeconds + * @param {number} reqId + * @return {Object} + */ +u2f.formatRegisterRequest_ = + function(appId, registeredKeys, registerRequests, timeoutSeconds, reqId) { + if (js_api_version === undefined || js_api_version < 1.1) { + // Adapt request to the 1.0 JS API + for (var i = 0; i < registerRequests.length; i++) { + registerRequests[i].appId = appId; + } + var signRequests = []; + for (var i = 0; i < registeredKeys.length; i++) { + signRequests[i] = { + version: registeredKeys[i].version, + challenge: registerRequests[0], + keyHandle: registeredKeys[i].keyHandle, + appId: appId + }; + } + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + signRequests: signRequests, + registerRequests: registerRequests, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; + } + // JS 1.1 API + return { + type: u2f.MessageTypes.U2F_REGISTER_REQUEST, + appId: appId, + registerRequests: registerRequests, + registeredKeys: registeredKeys, + timeoutSeconds: timeoutSeconds, + requestId: reqId + }; +}; + + +/** + * Posts a message on the underlying channel. + * @param {Object} message + */ +u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { + this.port_.postMessage(message); +}; + + +/** + * Emulates the HTML 5 addEventListener interface. Works only for the + * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedChromeRuntimePort_.prototype.addEventListener = + function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message' || name == 'onmessage') { + this.port_.onMessage.addListener(function(message) { + // Emulate a minimal MessageEvent object + handler({'data': message}); + }); + } else { + console.error('WrappedChromeRuntimePort only supports onMessage'); + } +}; + +/** + * Wrap the Authenticator app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedAuthenticatorPort_ = function() { + this.requestId_ = -1; + this.requestObject_ = null; +} + +/** + * Launch the Authenticator intent. + * @param {Object} message + */ +u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { + var intentUrl = + u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + + ';S.request=' + encodeURIComponent(JSON.stringify(message)) + + ';end'; + document.location = intentUrl; +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedAuthenticatorPort_.prototype.getPortType = function() { + return "WrappedAuthenticatorPort_"; +}; + + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedAuthenticatorPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name == 'message') { + var self = this; + /* Register a callback to that executes when + * chrome injects the response. */ + window.addEventListener( + 'message', self.onRequestUpdate_.bind(self, handler), false); + } else { + console.error('WrappedAuthenticatorPort only supports message'); + } +}; + +/** + * Callback invoked when a response is received from the Authenticator. + * @param function({data: Object}) callback + * @param {Object} message message Object + */ +u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = + function(callback, message) { + var messageObject = JSON.parse(message.data); + var intentUrl = messageObject['intentURL']; + + var errorCode = messageObject['errorCode']; + var responseObject = null; + if (messageObject.hasOwnProperty('data')) { + responseObject = /** @type {Object} */ ( + JSON.parse(messageObject['data'])); + } + + callback({'data': responseObject}); +}; + +/** + * Base URL for intents to Authenticator. + * @const + * @private + */ +u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = + 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; + +/** + * Wrap the iOS client app with a MessagePort interface. + * @constructor + * @private + */ +u2f.WrappedIosPort_ = function() {}; + +/** + * Launch the iOS client app request + * @param {Object} message + */ +u2f.WrappedIosPort_.prototype.postMessage = function(message) { + var str = JSON.stringify(message); + var url = "u2f://auth?" + encodeURI(str); + location.replace(url); +}; + +/** + * Tells what type of port this is. + * @return {String} port type + */ +u2f.WrappedIosPort_.prototype.getPortType = function() { + return "WrappedIosPort_"; +}; + +/** + * Emulates the HTML 5 addEventListener interface. + * @param {string} eventName + * @param {function({data: Object})} handler + */ +u2f.WrappedIosPort_.prototype.addEventListener = function(eventName, handler) { + var name = eventName.toLowerCase(); + if (name !== 'message') { + console.error('WrappedIosPort only supports message'); + } +}; + +/** + * Sets up an embedded trampoline iframe, sourced from the extension. + * @param {function(MessagePort)} callback + * @private + */ +u2f.getIframePort_ = function(callback) { + // Create the iframe + var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; + var iframe = document.createElement('iframe'); + iframe.src = iframeOrigin + '/u2f-comms.html'; + iframe.setAttribute('style', 'display:none'); + document.body.appendChild(iframe); + + var channel = new MessageChannel(); + var ready = function(message) { + if (message.data == 'ready') { + channel.port1.removeEventListener('message', ready); + callback(channel.port1); + } else { + console.error('First event on iframe port was not "ready"'); + } + }; + channel.port1.addEventListener('message', ready); + channel.port1.start(); + + iframe.addEventListener('load', function() { + // Deliver the port to the iframe and initialize + iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); + }); +}; + + +//High-level JS API + +/** + * Default extension response timeout in seconds. + * @const + */ +u2f.EXTENSION_TIMEOUT_SEC = 30; + +/** + * A singleton instance for a MessagePort to the extension. + * @type {MessagePort|u2f.WrappedChromeRuntimePort_} + * @private + */ +u2f.port_ = null; + +/** + * Callbacks waiting for a port + * @type {Array} + * @private + */ +u2f.waitingForPort_ = []; + +/** + * A counter for requestIds. + * @type {number} + * @private + */ +u2f.reqCounter_ = 0; + +/** + * A map from requestIds to client callbacks + * @type {Object.} + * @private + */ +u2f.callbackMap_ = {}; + +/** + * Creates or retrieves the MessagePort singleton to use. + * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback + * @private + */ +u2f.getPortSingleton_ = function(callback) { + if (u2f.port_) { + callback(u2f.port_); + } else { + if (u2f.waitingForPort_.length == 0) { + u2f.getMessagePort(function(port) { + u2f.port_ = port; + u2f.port_.addEventListener('message', + /** @type {function(Event)} */ (u2f.responseHandler_)); + + // Careful, here be async callbacks. Maybe. + while (u2f.waitingForPort_.length) + u2f.waitingForPort_.shift()(u2f.port_); + }); + } + u2f.waitingForPort_.push(callback); + } +}; + +/** + * Handles response messages from the extension. + * @param {MessageEvent.} message + * @private + */ +u2f.responseHandler_ = function(message) { + var response = message.data; + var reqId = response['requestId']; + if (!reqId || !u2f.callbackMap_[reqId]) { + console.error('Unknown or missing requestId in response.'); + return; + } + var cb = u2f.callbackMap_[reqId]; + delete u2f.callbackMap_[reqId]; + cb(response['responseData']); +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the sign request. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sign = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual sign request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0 : response['js_api_version']; + console.log("Extension JS API Version: ", js_api_version); + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual sign request in the supported API version. + u2f.sendSignRequest(appId, challenge, registeredKeys, callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches an array of sign requests to available U2F tokens. + * @param {string=} appId + * @param {string=} challenge + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.SignResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendSignRequest = function(appId, challenge, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatSignRequest_(appId, challenge, registeredKeys, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * If the JS API version supported by the extension is unknown, it first sends a + * message to the extension to find out the supported API version and then it sends + * the register request. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.register = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + if (js_api_version === undefined) { + // Send a message to get the extension to JS API version, then send the actual register request. + u2f.getApiVersion( + function (response) { + js_api_version = response['js_api_version'] === undefined ? 0: response['js_api_version']; + console.log("Extension JS API Version: ", js_api_version); + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + }); + } else { + // We know the JS API version. Send the actual register request in the supported API version. + u2f.sendRegisterRequest(appId, registerRequests, registeredKeys, + callback, opt_timeoutSeconds); + } +}; + +/** + * Dispatches register requests to available U2F tokens. An array of sign + * requests identifies already registered tokens. + * @param {string=} appId + * @param {Array} registerRequests + * @param {Array} registeredKeys + * @param {function((u2f.Error|u2f.RegisterResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.sendRegisterRequest = function(appId, registerRequests, registeredKeys, callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); + var req = u2f.formatRegisterRequest_( + appId, registeredKeys, registerRequests, timeoutSeconds, reqId); + port.postMessage(req); + }); +}; + + +/** + * Dispatches a message to the extension to find out the supported + * JS API version. + * If the user is on a mobile phone and is thus using Google Authenticator instead + * of the Chrome extension, don't send the request and simply return 0. + * @param {function((u2f.Error|u2f.GetJsApiVersionResponse))} callback + * @param {number=} opt_timeoutSeconds + */ +u2f.getApiVersion = function(callback, opt_timeoutSeconds) { + u2f.getPortSingleton_(function(port) { + // If we are using Android Google Authenticator or iOS client app, + // do not fire an intent to ask which JS API version to use. + if (port.getPortType) { + var apiVersion; + switch (port.getPortType()) { + case 'WrappedIosPort_': + case 'WrappedAuthenticatorPort_': + apiVersion = 1.1; + break; + + default: + apiVersion = 0; + break; + } + callback({ 'js_api_version': apiVersion }); + return; + } + var reqId = ++u2f.reqCounter_; + u2f.callbackMap_[reqId] = callback; + var req = { + type: u2f.MessageTypes.U2F_GET_API_VERSION_REQUEST, + timeoutSeconds: (typeof opt_timeoutSeconds !== 'undefined' ? + opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC), + requestId: reqId + }; + port.postMessage(req); + }); +}; diff --git a/Resources/public/js/u2f.js b/Resources/public/js/u2f.js deleted file mode 100644 index b49d635..0000000 --- a/Resources/public/js/u2f.js +++ /dev/null @@ -1,651 +0,0 @@ -// Copyright 2014-2015 Google Inc. All rights reserved. -// -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file or at -// https://developers.google.com/open-source/licenses/bsd - -/** - * @fileoverview The U2F api. - */ - -'use strict'; - -/** Namespace for the U2F api. - * @type {Object} - */ -var u2f = u2f || {}; - -/** - * The U2F extension id - * @type {string} - * @const - */ -u2f.EXTENSION_ID = 'kmendfapggjehodndflmmgagdbamhnfd'; - -/** - * Message types for messsages to/from the extension - * @const - * @enum {string} - */ -u2f.MessageTypes = { - 'U2F_REGISTER_REQUEST': 'u2f_register_request', - 'U2F_SIGN_REQUEST': 'u2f_sign_request', - 'U2F_REGISTER_RESPONSE': 'u2f_register_response', - 'U2F_SIGN_RESPONSE': 'u2f_sign_response' -}; - -/** - * Response status codes - * @const - * @enum {number} - */ -u2f.ErrorCodes = { - 'OK': 0, - 'OTHER_ERROR': 1, - 'BAD_REQUEST': 2, - 'CONFIGURATION_UNSUPPORTED': 3, - 'DEVICE_INELIGIBLE': 4, - 'TIMEOUT': 5 -}; - -/** - * A message type for registration requests - * @typedef {{ - * type: u2f.MessageTypes, - * signRequests: Array, - * registerRequests: ?Array, - * timeoutSeconds: ?number, - * requestId: ?number - * }} - */ -u2f.Request; - -/** - * A message for registration responses - * @typedef {{ - * type: u2f.MessageTypes, - * responseData: (u2f.Error | u2f.RegisterResponse | u2f.SignResponse), - * requestId: ?number - * }} - */ -u2f.Response; - -/** - * An error object for responses - * @typedef {{ - * errorCode: u2f.ErrorCodes, - * errorMessage: ?string - * }} - */ -u2f.Error; - -/** - * Data object for a single sign request. - * @typedef {{ - * version: string, - * challenge: string, - * keyHandle: string, - * appId: string - * }} - */ -u2f.SignRequest; - -/** - * Data object for a sign response. - * @typedef {{ - * keyHandle: string, - * signatureData: string, - * clientData: string - * }} - */ -u2f.SignResponse; - -/** - * Data object for a registration request. - * @typedef {{ - * version: string, - * challenge: string, - * appId: string - * }} - */ -u2f.RegisterRequest; - -/** - * Data object for a registration response. - * @typedef {{ - * registrationData: string, - * clientData: string - * }} - */ -u2f.RegisterResponse; - - -// Low level MessagePort API support - -/** - * Sets up a MessagePort to the U2F extension using the - * available mechanisms. - * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback - */ -u2f.getMessagePort = function(callback) { - if (typeof chrome != 'undefined' && chrome.runtime) { - // The actual message here does not matter, but we need to get a reply - // for the callback to run. Thus, send an empty signature request - // in order to get a failure response. - var msg = { - type: u2f.MessageTypes.U2F_SIGN_REQUEST, - signRequests: [] - }; - chrome.runtime.sendMessage(u2f.EXTENSION_ID, msg, function() { - if (!chrome.runtime.lastError) { - // We are on a whitelisted origin and can talk directly - // with the extension. - u2f.getChromeRuntimePort_(callback); - } else { - // chrome.runtime was available, but we couldn't message - // the extension directly, use iframe - u2f.getIframePort_(callback); - } - }); - } else if (u2f.isAndroidChrome_()) { - u2f.getAuthenticatorPort_(callback); - } else { - // chrome.runtime was not available at all, which is normal - // when this origin doesn't have access to any extensions. - u2f.getIframePort_(callback); - } -}; - -/** - * Detect chrome running on android based on the browser's useragent. - * @private - */ -u2f.isAndroidChrome_ = function() { - var userAgent = navigator.userAgent; - return userAgent.indexOf('Chrome') != -1 && - userAgent.indexOf('Android') != -1; -}; - -/** - * Connects directly to the extension via chrome.runtime.connect - * @param {function(u2f.WrappedChromeRuntimePort_)} callback - * @private - */ -u2f.getChromeRuntimePort_ = function(callback) { - var port = chrome.runtime.connect(u2f.EXTENSION_ID, - {'includeTlsChannelId': true}); - setTimeout(function() { - callback(new u2f.WrappedChromeRuntimePort_(port)); - }, 0); -}; - -/** - * Return a 'port' abstraction to the Authenticator app. - * @param {function(u2f.WrappedAuthenticatorPort_)} callback - * @private - */ -u2f.getAuthenticatorPort_ = function(callback) { - setTimeout(function() { - callback(new u2f.WrappedAuthenticatorPort_()); - }, 0); -}; - -/** - * A wrapper for chrome.runtime.Port that is compatible with MessagePort. - * @param {Port} port - * @constructor - * @private - */ -u2f.WrappedChromeRuntimePort_ = function(port) { - this.port_ = port; -}; - -/** - * Format a return a sign request. - * @param {Array} signRequests - * @param {number} timeoutSeconds - * @param {number} reqId - * @return {Object} - */ -u2f.WrappedChromeRuntimePort_.prototype.formatSignRequest_ = - function(signRequests, timeoutSeconds, reqId) { - return { - type: u2f.MessageTypes.U2F_SIGN_REQUEST, - signRequests: signRequests, - timeoutSeconds: timeoutSeconds, - requestId: reqId - }; - }; - -/** - * Format a return a register request. - * @param {Array} signRequests - * @param {Array} signRequests - * @param {number} timeoutSeconds - * @param {number} reqId - * @return {Object} - */ -u2f.WrappedChromeRuntimePort_.prototype.formatRegisterRequest_ = - function(signRequests, registerRequests, timeoutSeconds, reqId) { - return { - type: u2f.MessageTypes.U2F_REGISTER_REQUEST, - signRequests: signRequests, - registerRequests: registerRequests, - timeoutSeconds: timeoutSeconds, - requestId: reqId - }; - }; - -/** - * Posts a message on the underlying channel. - * @param {Object} message - */ -u2f.WrappedChromeRuntimePort_.prototype.postMessage = function(message) { - this.port_.postMessage(message); -}; - -/** - * Emulates the HTML 5 addEventListener interface. Works only for the - * onmessage event, which is hooked up to the chrome.runtime.Port.onMessage. - * @param {string} eventName - * @param {function({data: Object})} handler - */ -u2f.WrappedChromeRuntimePort_.prototype.addEventListener = - function(eventName, handler) { - var name = eventName.toLowerCase(); - if (name == 'message' || name == 'onmessage') { - this.port_.onMessage.addListener(function(message) { - // Emulate a minimal MessageEvent object - handler({'data': message}); - }); - } else { - console.error('WrappedChromeRuntimePort only supports onMessage'); - } - }; - -/** - * Wrap the Authenticator app with a MessagePort interface. - * @constructor - * @private - */ -u2f.WrappedAuthenticatorPort_ = function() { - this.requestId_ = -1; - this.requestObject_ = null; -} - -/** - * Launch the Authenticator intent. - * @param {Object} message - */ -u2f.WrappedAuthenticatorPort_.prototype.postMessage = function(message) { - var intentLocation = /** @type {string} */ (message); - document.location = intentLocation; -}; - -/** - * Emulates the HTML 5 addEventListener interface. - * @param {string} eventName - * @param {function({data: Object})} handler - */ -u2f.WrappedAuthenticatorPort_.prototype.addEventListener = - function(eventName, handler) { - var name = eventName.toLowerCase(); - if (name == 'message') { - var self = this; - /* Register a callback to that executes when - * chrome injects the response. */ - window.addEventListener( - 'message', self.onRequestUpdate_.bind(self, handler), false); - } else { - console.error('WrappedAuthenticatorPort only supports message'); - } - }; - -/** - * Callback invoked when a response is received from the Authenticator. - * @param function({data: Object}) callback - * @param {Object} message message Object - */ -u2f.WrappedAuthenticatorPort_.prototype.onRequestUpdate_ = - function(callback, message) { - var messageObject = JSON.parse(message.data); - var intentUrl = messageObject['intentURL']; - - var errorCode = messageObject['errorCode']; - var responseObject = null; - if (messageObject.hasOwnProperty('data')) { - responseObject = /** @type {Object} */ ( - JSON.parse(messageObject['data'])); - responseObject['requestId'] = this.requestId_; - } - - /* Sign responses from the authenticator do not conform to U2F, - * convert to U2F here. */ - responseObject = this.doResponseFixups_(responseObject); - callback({'data': responseObject}); - }; - -/** - * Fixup the response provided by the Authenticator to conform with - * the U2F spec. - * @param {Object} responseData - * @return {Object} the U2F compliant response object - */ -u2f.WrappedAuthenticatorPort_.prototype.doResponseFixups_ = - function(responseObject) { - if (responseObject.hasOwnProperty('responseData')) { - return responseObject; - } else if (this.requestObject_['type'] != u2f.MessageTypes.U2F_SIGN_REQUEST) { - // Only sign responses require fixups. If this is not a response - // to a sign request, then an internal error has occurred. - return { - 'type': u2f.MessageTypes.U2F_REGISTER_RESPONSE, - 'responseData': { - 'errorCode': u2f.ErrorCodes.OTHER_ERROR, - 'errorMessage': 'Internal error: invalid response from Authenticator' - } - }; - } - - /* Non-conformant sign response, do fixups. */ - var encodedChallengeObject = responseObject['challenge']; - if (typeof encodedChallengeObject !== 'undefined') { - var challengeObject = JSON.parse(atob(encodedChallengeObject)); - var serverChallenge = challengeObject['challenge']; - var challengesList = this.requestObject_['signData']; - var requestChallengeObject = null; - for (var i = 0; i < challengesList.length; i++) { - var challengeObject = challengesList[i]; - if (challengeObject['keyHandle'] == responseObject['keyHandle']) { - requestChallengeObject = challengeObject; - break; - } - } - } - var responseData = { - 'errorCode': responseObject['resultCode'], - 'keyHandle': responseObject['keyHandle'], - 'signatureData': responseObject['signature'], - 'clientData': encodedChallengeObject - }; - return { - 'type': u2f.MessageTypes.U2F_SIGN_RESPONSE, - 'responseData': responseData, - 'requestId': responseObject['requestId'] - } - }; - -/** - * Base URL for intents to Authenticator. - * @const - * @private - */ -u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ = - 'intent:#Intent;action=com.google.android.apps.authenticator.AUTHENTICATE'; - -/** - * Format a return a sign request. - * @param {Array} signRequests - * @param {number} timeoutSeconds (ignored for now) - * @param {number} reqId - * @return {string} - */ -u2f.WrappedAuthenticatorPort_.prototype.formatSignRequest_ = - function(signRequests, timeoutSeconds, reqId) { - if (!signRequests || signRequests.length == 0) { - return null; - } - /* TODO(fixme): stash away requestId, as the authenticator app does - * not return it for sign responses. */ - this.requestId_ = reqId; - /* TODO(fixme): stash away the signRequests, to deal with the legacy - * response format returned by the Authenticator app. */ - this.requestObject_ = { - 'type': u2f.MessageTypes.U2F_SIGN_REQUEST, - 'signData': signRequests, - 'requestId': reqId, - 'timeout': timeoutSeconds - }; - - var appId = signRequests[0]['appId']; - var intentUrl = - u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + - ';S.appId=' + encodeURIComponent(appId) + - ';S.eventId=' + reqId + - ';S.challenges=' + - encodeURIComponent( - JSON.stringify(this.getBrowserDataList_(signRequests))) + ';end'; - return intentUrl; - }; - -/** - * Get the browser data objects from the challenge list - * @param {Array} challenges list of challenges - * @return {Array} list of browser data objects - * @private - */ -u2f.WrappedAuthenticatorPort_ - .prototype.getBrowserDataList_ = function(challenges) { - return challenges - .map(function(challenge) { - var browserData = { - 'typ': 'navigator.id.getAssertion', - 'challenge': challenge['challenge'] - }; - var challengeObject = { - 'challenge' : browserData, - 'keyHandle' : challenge['keyHandle'] - }; - return challengeObject; - }); -}; - -/** - * Format a return a register request. - * @param {Array} signRequests - * @param {Array} enrollChallenges - * @param {number} timeoutSeconds (ignored for now) - * @param {number} reqId - * @return {Object} - */ -u2f.WrappedAuthenticatorPort_.prototype.formatRegisterRequest_ = - function(signRequests, enrollChallenges, timeoutSeconds, reqId) { - if (!enrollChallenges || enrollChallenges.length == 0) { - return null; - } - // Assume the appId is the same for all enroll challenges. - var appId = enrollChallenges[0]['appId']; - var registerRequests = []; - for (var i = 0; i < enrollChallenges.length; i++) { - var registerRequest = { - 'challenge': enrollChallenges[i]['challenge'], - 'version': enrollChallenges[i]['version'] - }; - if (enrollChallenges[i]['appId'] != appId) { - // Only include the appId when it differs from the first appId. - registerRequest['appId'] = enrollChallenges[i]['appId']; - } - registerRequests.push(registerRequest); - } - var registeredKeys = []; - if (signRequests) { - for (i = 0; i < signRequests.length; i++) { - var key = { - 'keyHandle': signRequests[i]['keyHandle'], - 'version': signRequests[i]['version'] - }; - // Only include the appId when it differs from the appId that's - // being registered now. - if (signRequests[i]['appId'] != appId) { - key['appId'] = signRequests[i]['appId']; - } - registeredKeys.push(key); - } - } - var request = { - 'type': u2f.MessageTypes.U2F_REGISTER_REQUEST, - 'appId': appId, - 'registerRequests': registerRequests, - 'registeredKeys': registeredKeys, - 'requestId': reqId, - 'timeoutSeconds': timeoutSeconds - }; - var intentUrl = - u2f.WrappedAuthenticatorPort_.INTENT_URL_BASE_ + - ';S.request=' + encodeURIComponent(JSON.stringify(request)) + - ';end'; - /* TODO(fixme): stash away requestId, this is is not necessary for - * register requests, but here to keep parity with sign. - */ - this.requestId_ = reqId; - return intentUrl; - }; - - -/** - * Sets up an embedded trampoline iframe, sourced from the extension. - * @param {function(MessagePort)} callback - * @private - */ -u2f.getIframePort_ = function(callback) { - // Create the iframe - var iframeOrigin = 'chrome-extension://' + u2f.EXTENSION_ID; - var iframe = document.createElement('iframe'); - iframe.src = iframeOrigin + '/u2f-comms.html'; - iframe.setAttribute('style', 'display:none'); - document.body.appendChild(iframe); - - var channel = new MessageChannel(); - var ready = function(message) { - if (message.data == 'ready') { - channel.port1.removeEventListener('message', ready); - callback(channel.port1); - } else { - console.error('First event on iframe port was not "ready"'); - } - }; - channel.port1.addEventListener('message', ready); - channel.port1.start(); - - iframe.addEventListener('load', function() { - // Deliver the port to the iframe and initialize - iframe.contentWindow.postMessage('init', iframeOrigin, [channel.port2]); - }); -}; - - -// High-level JS API - -/** - * Default extension response timeout in seconds. - * @const - */ -u2f.EXTENSION_TIMEOUT_SEC = 30; - -/** - * A singleton instance for a MessagePort to the extension. - * @type {MessagePort|u2f.WrappedChromeRuntimePort_} - * @private - */ -u2f.port_ = null; - -/** - * Callbacks waiting for a port - * @type {Array} - * @private - */ -u2f.waitingForPort_ = []; - -/** - * A counter for requestIds. - * @type {number} - * @private - */ -u2f.reqCounter_ = 0; - -/** - * A map from requestIds to client callbacks - * @type {Object.} - * @private - */ -u2f.callbackMap_ = {}; - -/** - * Creates or retrieves the MessagePort singleton to use. - * @param {function((MessagePort|u2f.WrappedChromeRuntimePort_))} callback - * @private - */ -u2f.getPortSingleton_ = function(callback) { - if (u2f.port_) { - callback(u2f.port_); - } else { - if (u2f.waitingForPort_.length == 0) { - u2f.getMessagePort(function(port) { - u2f.port_ = port; - u2f.port_.addEventListener('message', - /** @type {function(Event)} */ (u2f.responseHandler_)); - - // Careful, here be async callbacks. Maybe. - while (u2f.waitingForPort_.length) - u2f.waitingForPort_.shift()(u2f.port_); - }); - } - u2f.waitingForPort_.push(callback); - } -}; - -/** - * Handles response messages from the extension. - * @param {MessageEvent.} message - * @private - */ -u2f.responseHandler_ = function(message) { - var response = message.data; - var reqId = response['requestId']; - if (!reqId || !u2f.callbackMap_[reqId]) { - console.error('Unknown or missing requestId in response.'); - return; - } - var cb = u2f.callbackMap_[reqId]; - delete u2f.callbackMap_[reqId]; - cb(response['responseData']); -}; - -/** - * Dispatches an array of sign requests to available U2F tokens. - * @param {Array} signRequests - * @param {function((u2f.Error|u2f.SignResponse))} callback - * @param {number=} opt_timeoutSeconds - */ -u2f.sign = function(signRequests, callback, opt_timeoutSeconds) { - u2f.getPortSingleton_(function(port) { - var reqId = ++u2f.reqCounter_; - u2f.callbackMap_[reqId] = callback; - var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? - opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); - var req = port.formatSignRequest_(signRequests, timeoutSeconds, reqId); - port.postMessage(req); - }); -}; - -/** - * Dispatches register requests to available U2F tokens. An array of sign - * requests identifies already registered tokens. - * @param {Array} registerRequests - * @param {Array} signRequests - * @param {function((u2f.Error|u2f.RegisterResponse))} callback - * @param {number=} opt_timeoutSeconds - */ -u2f.register = function(registerRequests, signRequests, - callback, opt_timeoutSeconds) { - u2f.getPortSingleton_(function(port) { - var reqId = ++u2f.reqCounter_; - u2f.callbackMap_[reqId] = callback; - var timeoutSeconds = (typeof opt_timeoutSeconds !== 'undefined' ? - opt_timeoutSeconds : u2f.EXTENSION_TIMEOUT_SEC); - var req = port.formatRegisterRequest_( - signRequests, registerRequests, timeoutSeconds, reqId); - port.postMessage(req); - }); -}; diff --git a/Resources/views/Authentication/form.html.twig b/Resources/views/Authentication/form.html.twig index a72edd9..f151f35 100644 --- a/Resources/views/Authentication/form.html.twig +++ b/Resources/views/Authentication/form.html.twig @@ -6,5 +6,5 @@ {% if useTrustedOption %}

{% endif %}
- + diff --git a/Resources/views/Registration/register.html.twig b/Resources/views/Registration/register.html.twig index 4b9e279..a0e16c0 100644 --- a/Resources/views/Registration/register.html.twig +++ b/Resources/views/Registration/register.html.twig @@ -3,5 +3,5 @@
- + diff --git a/Security/TwoFactor/Prodiver/U2F/U2FAuthenticator.php b/Security/TwoFactor/Prodiver/U2F/U2FAuthenticator.php index 41454ad..999bd6c 100644 --- a/Security/TwoFactor/Prodiver/U2F/U2FAuthenticator.php +++ b/Security/TwoFactor/Prodiver/U2F/U2FAuthenticator.php @@ -26,7 +26,7 @@ public function __construct(RequestStack $requestStack) $scheme = $requestStack->getCurrentRequest()->getScheme(); $host = $requestStack->getCurrentRequest()->getHost(); $port = $requestStack->getCurrentRequest()->getPort(); - $this->u2f = new \u2flib_server\U2F($scheme.'://'.$host.(('80' !== $port && '443' !== $port)?':'.$port:'')); + $this->u2f = new \u2flib_server\U2F($scheme.'://'.$host.((80 !== $port && 443 !== $port)?':'.$port:'')); } /** * generateRequest From d68fe46ec3fad1367b90e00dbf4f26a95a3c80af Mon Sep 17 00:00:00 2001 From: Patrick Ruckstuhl Date: Sun, 27 Nov 2016 11:09:52 +0100 Subject: [PATCH 2/4] some cleaning of auth.js, split entering tokenname from actual registration --- Resources/public/js/auth.js | 54 ++++++++++--------- .../views/Registration/register.html.twig | 7 ++- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/Resources/public/js/auth.js b/Resources/public/js/auth.js index 498c6e4..8b18b0f 100644 --- a/Resources/public/js/auth.js +++ b/Resources/public/js/auth.js @@ -1,4 +1,12 @@ -var ready = function(fn) { +'use strict'; + +var u2fauth = u2fauth || {}; + +u2fauth.formId = 'u2fForm'; +u2fauth.authCodeId = '_auth_code'; +u2fauth.keynameId = 'u2fkeyname'; + +u2fauth.ready = function(fn) { if ('loading' !== document.readyState){ fn(); } else if (document.addEventListener) { @@ -11,29 +19,39 @@ var ready = function(fn) { } }; -var authenticate = function(request, codeField, form) { - u2f.sign(request[0]['appId'], request[0]['challenge'], request, function(data){ +u2fauth.authenticate = function() { + var form = document.getElementById(u2fauth.formId); + var codeField = document.getElementById(u2fauth.authCodeId); + var request = JSON.parse(form.dataset.request); + + u2f.sign(request[0].appId, request[0].challenge, request, function(data){ if(!data.errorCode) { codeField.value = JSON.stringify(data); form.submit(); } else { - showError(data.errorCode, function(){authenticate(request, codeField, form);}); + u2fauth.showError(data.errorCode, function(){u2fauth.authenticate(request, codeField, form);}); } }); }; -var register = function(request, codeField, form) { - u2f.register(request[0]['appId'], [request[0]], request[1], function(data){ +u2fauth.register = function() { + var keyname = document.getElementById(u2fauth.keynameId); + keyname.style.display = "none"; + var form = document.getElementById(u2fauth.formId); + var codeField = document.getElementById(u2fauth.authCodeId); + var request = JSON.parse(form.dataset.request); + + u2f.register(request[0].appId, [request[0]], request[1], function(data){ if(!data.errorCode) { codeField.value = JSON.stringify(data); form.submit(); } else { - showError(data.errorCode, function(){register(request, codeField, form);}); + u2fauth.showError(data.errorCode, function(){u2fauth.register(request, codeField, form);}); } }); }; -var showError = function(error, callback) { +u2fauth.showError = function(error, callback) { var errorDisplay; errorDisplay = document.getElementById('u2fError'); @@ -41,23 +59,11 @@ var showError = function(error, callback) { errorDisplay.onclick = callback; }; -ready(function(){ - var form, - codeField, - request, - type; - - form = document.getElementById('u2fForm'); - codeField = document.getElementById('_auth_code'); - - type = form.dataset.action; - request = JSON.parse(form.dataset.request); +u2fauth.ready(function(){ + var form = document.getElementById('u2fForm'); + var type = form.dataset.action; if('auth' === type) { - authenticate(request, codeField, form); - } - - if('reg' === type) { - register(request, codeField, form); + u2fauth.authenticate(); } }); diff --git a/Resources/views/Registration/register.html.twig b/Resources/views/Registration/register.html.twig index a0e16c0..f20121f 100644 --- a/Resources/views/Registration/register.html.twig +++ b/Resources/views/Registration/register.html.twig @@ -1,7 +1,10 @@
- +
+ + +
- + \ No newline at end of file From e7c99c28bc36afeb92d9ee2ba240c570c20fa356 Mon Sep 17 00:00:00 2001 From: Patrick Ruckstuhl Date: Sun, 27 Nov 2016 11:20:15 +0100 Subject: [PATCH 3/4] translate error codes --- Resources/public/js/auth.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Resources/public/js/auth.js b/Resources/public/js/auth.js index 8b18b0f..2ba3917 100644 --- a/Resources/public/js/auth.js +++ b/Resources/public/js/auth.js @@ -5,6 +5,13 @@ var u2fauth = u2fauth || {}; u2fauth.formId = 'u2fForm'; u2fauth.authCodeId = '_auth_code'; u2fauth.keynameId = 'u2fkeyname'; +u2fauth.errorTranslation = { + 1: 'Unknown Error', + 2: 'Bad Request', + 3: 'Client configuration not supported', + 4: 'Device already registered or ineligible', + 5: 'Timeout', +}; u2fauth.ready = function(fn) { if ('loading' !== document.readyState){ @@ -55,7 +62,7 @@ u2fauth.showError = function(error, callback) { var errorDisplay; errorDisplay = document.getElementById('u2fError'); - errorDisplay.innerText = error; + errorDisplay.innerText = u2fauth.errorTranslation[error]; errorDisplay.onclick = callback; }; From ce3b19336ec6871d722c7a2bc9154d2d8bf20b49 Mon Sep 17 00:00:00 2001 From: Patrick Ruckstuhl Date: Sun, 27 Nov 2016 12:26:54 +0100 Subject: [PATCH 4/4] show message to press button, restructured js a bit --- Resources/public/js/auth.js | 57 ++++++++++++++----- Resources/views/Authentication/form.html.twig | 1 + .../views/Registration/register.html.twig | 3 +- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/Resources/public/js/auth.js b/Resources/public/js/auth.js index 2ba3917..3685620 100644 --- a/Resources/public/js/auth.js +++ b/Resources/public/js/auth.js @@ -5,6 +5,8 @@ var u2fauth = u2fauth || {}; u2fauth.formId = 'u2fForm'; u2fauth.authCodeId = '_auth_code'; u2fauth.keynameId = 'u2fkeyname'; +u2fauth.pressButtonId = 'u2fpressbutton'; +u2fauth.errorId = 'u2fError'; u2fauth.errorTranslation = { 1: 'Unknown Error', 2: 'Bad Request', @@ -27,41 +29,70 @@ u2fauth.ready = function(fn) { }; u2fauth.authenticate = function() { + u2fauth.clearError(); + u2fauth.showPressButton(); + var form = document.getElementById(u2fauth.formId); - var codeField = document.getElementById(u2fauth.authCodeId); var request = JSON.parse(form.dataset.request); u2f.sign(request[0].appId, request[0].challenge, request, function(data){ + u2fauth.hidePressButton(); if(!data.errorCode) { - codeField.value = JSON.stringify(data); - form.submit(); + u2fauth.submit(form, data); } else { - u2fauth.showError(data.errorCode, function(){u2fauth.authenticate(request, codeField, form);}); + u2fauth.showError(data.errorCode, u2fauth.authenticate); } }); }; u2fauth.register = function() { - var keyname = document.getElementById(u2fauth.keynameId); - keyname.style.display = "none"; + u2fauth.clearError(); + u2fauth.hideKeyname(); + u2fauth.showPressButton(); + var form = document.getElementById(u2fauth.formId); - var codeField = document.getElementById(u2fauth.authCodeId); var request = JSON.parse(form.dataset.request); u2f.register(request[0].appId, [request[0]], request[1], function(data){ + u2fauth.hidePressButton(); if(!data.errorCode) { - codeField.value = JSON.stringify(data); - form.submit(); + u2fauth.submit(form, data); } else { - u2fauth.showError(data.errorCode, function(){u2fauth.register(request, codeField, form);}); + u2fauth.showError(data.errorCode, u2fauth.register); } }); }; -u2fauth.showError = function(error, callback) { - var errorDisplay; +u2fauth.submit = function(form, data) { + var codeField = document.getElementById(u2fauth.authCodeId); + codeField.value = JSON.stringify(data); + form.submit(); +} - errorDisplay = document.getElementById('u2fError'); +u2fauth.hideKeyname = function() { + var keyname = document.getElementById(u2fauth.keynameId); + keyname.style.display = 'none'; +} + +u2fauth.hidePressButton = function() { + var pressButton = document.getElementById(u2fauth.pressButtonId); + pressButton.style.display = 'none'; +} + +u2fauth.showPressButton = function() { + var pressButton = document.getElementById(u2fauth.pressButtonId); + pressButton.style.display = 'block'; +} + +u2fauth.clearError = function() { + var errorDisplay = document.getElementById(u2fauth.errorId); + errorDisplay.style.display = 'none'; + errorDisplay.innerText = ''; +} + +u2fauth.showError = function(error, callback) { + var errorDisplay = document.getElementById(u2fauth.errorId); + errorDisplay.style.display = 'block'; errorDisplay.innerText = u2fauth.errorTranslation[error]; errorDisplay.onclick = callback; }; diff --git a/Resources/views/Authentication/form.html.twig b/Resources/views/Authentication/form.html.twig index f151f35..d550319 100644 --- a/Resources/views/Authentication/form.html.twig +++ b/Resources/views/Authentication/form.html.twig @@ -2,6 +2,7 @@ {% for flashMessage in app.session.flashbag.get('two_factor') %}

{{ flashMessage|trans }}

{% endfor %} +

{% if useTrustedOption %}

{% endif %}
diff --git a/Resources/views/Registration/register.html.twig b/Resources/views/Registration/register.html.twig index f20121f..70f44f1 100644 --- a/Resources/views/Registration/register.html.twig +++ b/Resources/views/Registration/register.html.twig @@ -3,8 +3,9 @@ + -
+ \ No newline at end of file