diff --git a/packages/oauth/end_of_popup_response.js b/packages/oauth/end_of_popup_response.js index 739830a241..e142f9c30d 100644 --- a/packages/oauth/end_of_popup_response.js +++ b/packages/oauth/end_of_popup_response.js @@ -1,15 +1,14 @@ -(function () { +(() => { - var config = JSON.parse(document.getElementById("config").innerHTML); + const config = JSON.parse(document.getElementById("config").innerHTML); if (config.setCredentialToken) { - var credentialToken = config.credentialToken; - var credentialSecret = config.credentialSecret; + const { credentialToken, credentialSecret } = config; if (config.isCordova) { - var credentialString = JSON.stringify({ - credentialToken: credentialToken, - credentialSecret: credentialSecret + const credentialString = JSON.stringify({ + credentialToken, + credentialSecret, }); window.location.hash = credentialString; @@ -31,7 +30,7 @@ if (! config.isCordova) { document.getElementById("completedText").style.display = "block"; - document.getElementById("loginCompleted").onclick = function(){ window.close(); }; + document.getElementById("loginCompleted").onclick = () => window.close(); window.close(); } })(); diff --git a/packages/oauth/end_of_redirect_response.js b/packages/oauth/end_of_redirect_response.js index f196db2283..4c88aa356f 100644 --- a/packages/oauth/end_of_redirect_response.js +++ b/packages/oauth/end_of_redirect_response.js @@ -1,6 +1,6 @@ -(function () { +(() => { - var config = JSON.parse(document.getElementById("config").innerHTML); + const config = JSON.parse(document.getElementById("config").innerHTML); if (config.setCredentialToken) { sessionStorage[config.storagePrefix + config.credentialToken] = diff --git a/packages/oauth/oauth_browser.js b/packages/oauth/oauth_browser.js index f1e435903b..a4e8a4bf5b 100644 --- a/packages/oauth/oauth_browser.js +++ b/packages/oauth/oauth_browser.js @@ -8,20 +8,21 @@ // arguments. // @param dimensions {optional Object(width, height)} The dimensions of // the popup. If not passed defaults to something sane. -OAuth.showPopup = function (url, callback, dimensions) { +OAuth.showPopup = (url, callback, dimensions) => { // default dimensions that worked well for facebook and google - var popup = openCenteredPopup( + const popup = openCenteredPopup( url, (dimensions && dimensions.width) || 650, (dimensions && dimensions.height) || 331 ); - var checkPopupOpen = setInterval(function() { + const checkPopupOpen = setInterval(() => { + let popupClosed; try { // Fix for #328 - added a second test criteria (popup.closed === undefined) // to humour this Android quirk: // http://code.google.com/p/android/issues/detail?id=21061 - var popupClosed = popup.closed || popup.closed === undefined; + popupClosed = popup.closed || popup.closed === undefined; } catch (e) { // For some unknown reason, IE9 (and others?) sometimes (when // the popup closes too quickly?) throws "SCRIPT16386: No such @@ -37,29 +38,29 @@ OAuth.showPopup = function (url, callback, dimensions) { }, 100); }; -var openCenteredPopup = function(url, width, height) { - var screenX = typeof window.screenX !== 'undefined' +const openCenteredPopup = function(url, width, height) { + const screenX = typeof window.screenX !== 'undefined' ? window.screenX : window.screenLeft; - var screenY = typeof window.screenY !== 'undefined' + const screenY = typeof window.screenY !== 'undefined' ? window.screenY : window.screenTop; - var outerWidth = typeof window.outerWidth !== 'undefined' + const outerWidth = typeof window.outerWidth !== 'undefined' ? window.outerWidth : document.body.clientWidth; - var outerHeight = typeof window.outerHeight !== 'undefined' + const outerHeight = typeof window.outerHeight !== 'undefined' ? window.outerHeight : (document.body.clientHeight - 22); // XXX what is the 22? // Use `outerWidth - width` and `outerHeight - height` for help in // positioning the popup centered relative to the current window - var left = screenX + (outerWidth - width) / 2; - var top = screenY + (outerHeight - height) / 2; - var features = ('width=' + width + ',height=' + height + - ',left=' + left + ',top=' + top + ',scrollbars=yes'); + const left = screenX + (outerWidth - width) / 2; + const top = screenY + (outerHeight - height) / 2; + const features = (`width=${width},height=${height}` + + `,left=${left},top=${top},scrollbars=yes'`); - var newwindow = window.open(url, 'Login', features); + const newwindow = window.open(url, 'Login', features); if (typeof newwindow === 'undefined') { // blocked by a popup blocker maybe? - var err = new Error("The login popup was blocked by the browser"); + const err = new Error("The login popup was blocked by the browser"); err.attemptedUrl = url; throw err; } diff --git a/packages/oauth/oauth_client.js b/packages/oauth/oauth_client.js index 45314f0e9e..810c0847ee 100644 --- a/packages/oauth/oauth_client.js +++ b/packages/oauth/oauth_client.js @@ -1,27 +1,27 @@ // credentialToken -> credentialSecret. You must provide both the // credentialToken and the credentialSecret to retrieve an access token from // the _pendingCredentials collection. -var credentialSecrets = {}; +const credentialSecrets = {}; OAuth = {}; -OAuth.showPopup = function (url, callback, dimensions) { +OAuth.showPopup = (url, callback, dimensions) => { throw new Error("OAuth.showPopup must be implemented on this arch."); }; // Determine the login style (popup or redirect) for this login flow. // // -OAuth._loginStyle = function (service, config, options) { +OAuth._loginStyle = (service, config, options) => { if (Meteor.isCordova) { return "popup"; } - var loginStyle = (options && options.loginStyle) || config.loginStyle || 'popup'; + let loginStyle = (options && options.loginStyle) || config.loginStyle || 'popup'; - if (! _.contains(["popup", "redirect"], loginStyle)) - throw new Error("Invalid login style: " + loginStyle); + if (! ["popup", "redirect"].includes(loginStyle)) + throw new Error(`Invalid login style: ${loginStyle}`); // If we don't have session storage (for example, Safari in private // mode), the redirect login flow won't work, so fallback to the @@ -38,10 +38,10 @@ OAuth._loginStyle = function (service, config, options) { return loginStyle; }; -OAuth._stateParam = function (loginStyle, credentialToken, redirectUrl) { - var state = { - loginStyle: loginStyle, - credentialToken: credentialToken, +OAuth._stateParam = (loginStyle, credentialToken, redirectUrl) => { + const state = { + loginStyle, + credentialToken, isCordova: Meteor.isCordova }; @@ -59,10 +59,8 @@ OAuth._stateParam = function (loginStyle, credentialToken, redirectUrl) { // the login service, save the credential token for this login attempt // in the reload migration data. // -OAuth.saveDataForRedirect = function (loginService, credentialToken) { - Reload._onMigrate('oauth', function () { - return [true, {loginService: loginService, credentialToken: credentialToken}]; - }); +OAuth.saveDataForRedirect = (loginService, credentialToken) => { + Reload._onMigrate('oauth', () => [true, { loginService, credentialToken }]); Reload._migrate(null, {immediateMigration: true}); }; @@ -74,15 +72,15 @@ OAuth.saveDataForRedirect = function (loginService, credentialToken) { // application startup and we weren't just redirected at the end of // the login flow. // -OAuth.getDataAfterRedirect = function () { - var migrationData = Reload._migrationData('oauth'); +OAuth.getDataAfterRedirect = () => { + const migrationData = Reload._migrationData('oauth'); if (! (migrationData && migrationData.credentialToken)) return null; - var credentialToken = migrationData.credentialToken; - var key = OAuth._storageTokenPrefix + credentialToken; - var credentialSecret; + const { credentialToken } = migrationData; + const key = OAuth._storageTokenPrefix + credentialToken; + let credentialSecret; try { credentialSecret = sessionStorage.getItem(key); sessionStorage.removeItem(key); @@ -91,8 +89,8 @@ OAuth.getDataAfterRedirect = function () { } return { loginService: migrationData.loginService, - credentialToken: credentialToken, - credentialSecret: credentialSecret + credentialToken, + credentialSecret, }; }; @@ -109,13 +107,13 @@ OAuth.getDataAfterRedirect = function () { // is closed and we have the credential from the login service. // credentialToken: our identifier for this login flow. // -OAuth.launchLogin = function (options) { +OAuth.launchLogin = options => { if (! options.loginService) throw new Error('loginService required'); if (options.loginStyle === 'popup') { OAuth.showPopup( options.loginUrl, - _.bind(options.credentialRequestCompleteCallback, null, options.credentialToken), + options.credentialRequestCompleteCallback.bind(null, options.credentialToken), options.popupOptions); } else if (options.loginStyle === 'redirect') { OAuth.saveDataForRedirect(options.loginService, options.credentialToken); @@ -127,20 +125,20 @@ OAuth.launchLogin = function (options) { // XXX COMPAT WITH 0.7.0.1 // Private interface but probably used by many oauth clients in atmosphere. -OAuth.initiateLogin = function (credentialToken, url, callback, dimensions) { +OAuth.initiateLogin = (credentialToken, url, callback, dimensions) => { OAuth.showPopup( url, - _.bind(callback, null, credentialToken), + callback.bind(null, credentialToken), dimensions ); }; // Called by the popup when the OAuth flow is completed, right before // the popup closes. -OAuth._handleCredentialSecret = function (credentialToken, secret) { +OAuth._handleCredentialSecret = (credentialToken, secret) => { check(credentialToken, String); check(secret, String); - if (! _.has(credentialSecrets,credentialToken)) { + if (! Object.prototype.hasOwnProperty.call(credentialSecrets, credentialToken)) { credentialSecrets[credentialToken] = secret; } else { throw new Error("Duplicate credential token from OAuth login"); @@ -149,13 +147,13 @@ OAuth._handleCredentialSecret = function (credentialToken, secret) { // Used by accounts-oauth, which needs both a credentialToken and the // corresponding to credential secret to call the `login` method over DDP. -OAuth._retrieveCredentialSecret = function (credentialToken) { +OAuth._retrieveCredentialSecret = credentialToken => { // First check the secrets collected by OAuth._handleCredentialSecret, // then check localStorage. This matches what we do in // end_of_login_response.html. - var secret = credentialSecrets[credentialToken]; + let secret = credentialSecrets[credentialToken]; if (! secret) { - var localStorageKey = OAuth._storageTokenPrefix + credentialToken; + const localStorageKey = OAuth._storageTokenPrefix + credentialToken; secret = Meteor._localStorage.getItem(localStorageKey); Meteor._localStorage.removeItem(localStorageKey); } else { diff --git a/packages/oauth/oauth_common.js b/packages/oauth/oauth_common.js index 57ec936317..68fe3319fe 100644 --- a/packages/oauth/oauth_common.js +++ b/packages/oauth/oauth_common.js @@ -1,6 +1,8 @@ +import url from 'url'; + OAuth._storageTokenPrefix = "Meteor.oauth.credentialSecret-"; -OAuth._redirectUri = function (serviceName, config, params, absoluteUrlOptions) { +OAuth._redirectUri = (serviceName, config, params, absoluteUrlOptions) => { // XXX COMPAT WITH 0.9.0 // The redirect URI used to have a "?close" query argument. We // detect whether we need to be backwards compatible by checking for @@ -8,26 +10,26 @@ OAuth._redirectUri = function (serviceName, config, params, absoluteUrlOptions) // code which had the "?close" argument. // This logic is duplicated in the tool so that the tool can do OAuth // flow with <= 0.9.0 servers (tools/auth.js). - var query = config.loginStyle ? null : "close"; + const query = config.loginStyle ? null : "close"; // Clone because we're going to mutate 'params'. The 'cordova' and // 'android' parameters are only used for picking the host of the // redirect URL, and not actually included in the redirect URL itself. - var isCordova = false; - var isAndroid = false; + let isCordova = false; + let isAndroid = false; if (params) { - params = _.clone(params); + params = { ...params }; isCordova = params.cordova; isAndroid = params.android; delete params.cordova; delete params.android; - if (_.isEmpty(params)) { + if (Object.keys(params).length === 0) { params = undefined; } } if (Meteor.isServer && isCordova) { - var rootUrl = process.env.MOBILE_ROOT_URL || + let rootUrl = process.env.MOBILE_ROOT_URL || __meteor_runtime_config__.ROOT_URL; if (isAndroid) { @@ -36,8 +38,7 @@ OAuth._redirectUri = function (serviceName, config, params, absoluteUrlOptions) // XXX Maybe we should put this in a separate package or something // that is used here and by boilerplate-generator? Or maybe // `Meteor.absoluteUrl` should know how to do this? - var url = Npm.require("url"); - var parsedRootUrl = url.parse(rootUrl); + const parsedRootUrl = url.parse(rootUrl); if (parsedRootUrl.hostname === "localhost") { parsedRootUrl.hostname = "10.0.2.2"; delete parsedRootUrl.host; @@ -45,15 +46,16 @@ OAuth._redirectUri = function (serviceName, config, params, absoluteUrlOptions) rootUrl = url.format(parsedRootUrl); } - absoluteUrlOptions = _.extend({}, absoluteUrlOptions, { + absoluteUrlOptions = { + ...absoluteUrlOptions, // For Cordova clients, redirect to the special Cordova root url // (likely a local IP in development mode). - rootUrl: rootUrl - }); + rootUrl, + }; } return URL._constructUrl( - Meteor.absoluteUrl('_oauth/' + serviceName, absoluteUrlOptions), + Meteor.absoluteUrl(`_oauth/${serviceName}`, absoluteUrlOptions), query, params); }; diff --git a/packages/oauth/oauth_cordova.js b/packages/oauth/oauth_cordova.js index 9dfb189f0a..06c8870731 100644 --- a/packages/oauth/oauth_cordova.js +++ b/packages/oauth/oauth_cordova.js @@ -8,32 +8,31 @@ // arguments. // @param dimensions {optional Object(width, height)} The dimensions of // the popup. If not passed defaults to something sane. -OAuth.showPopup = function (url, callback, dimensions) { - var fail = function (err) { - Meteor._debug("Error from OAuth popup: " + JSON.stringify(err)); - }; +OAuth.showPopup = (url, callback, dimensions) => { + const fail = err => + Meteor._debug(`Error from OAuth popup: ${JSON.stringify(err)}`); // When running on an android device, we sometimes see the // `pageLoaded` callback fire twice for the final page in the OAuth // popup, even though the page only loads once. This is maybe an // Android bug or maybe something intentional about how onPageFinished // works that we don't understand and isn't well-documented. - var oauthFinished = false; + let oauthFinished = false; - var pageLoaded = function (event) { + const pageLoaded = event => { if (oauthFinished) { return; } if (event.url.indexOf(Meteor.absoluteUrl('_oauth')) === 0) { - var splitUrl = event.url.split("#"); - var hashFragment = splitUrl[1]; + const splitUrl = event.url.split("#"); + const hashFragment = splitUrl[1]; if (! hashFragment) { throw new Error("No hash fragment in OAuth popup?"); } - var credentials = JSON.parse(decodeURIComponent(hashFragment)); + const credentials = JSON.parse(decodeURIComponent(hashFragment)); OAuth._handleCredentialSecret(credentials.credentialToken, credentials.credentialSecret); @@ -47,20 +46,20 @@ OAuth.showPopup = function (url, callback, dimensions) { // https://issues.apache.org/jira/browse/CB-2285. // // XXX Can we make this timeout smaller? - setTimeout(function () { + setTimeout(() => { popup.close(); callback(); }, 100); } }; - var onExit = function () { + const onExit = () => { popup.removeEventListener('loadstop', pageLoaded); popup.removeEventListener('loaderror', fail); popup.removeEventListener('exit', onExit); }; - var popup = window.open(url, '_blank', 'location=yes,hidden=yes'); + const popup = window.open(url, '_blank', 'location=yes,hidden=yes'); popup.addEventListener('loadstop', pageLoaded); popup.addEventListener('loaderror', fail); popup.addEventListener('exit', onExit); diff --git a/packages/oauth/oauth_server.js b/packages/oauth/oauth_server.js index c12f169605..b537d624cd 100644 --- a/packages/oauth/oauth_server.js +++ b/packages/oauth/oauth_server.js @@ -1,12 +1,12 @@ -var Fiber = Npm.require('fibers'); -var url = Npm.require('url'); +import Fiber from 'fibers'; +import url from 'url'; OAuth = {}; OAuthTest = {}; RoutePolicy.declare('/_oauth/', 'network'); -var registeredServices = {}; +const registeredServices = {}; // Internal: Maps from service version to handler function. The // 'oauth1' and 'oauth2' packages manipulate this directly to register @@ -30,58 +30,57 @@ OAuth._requestHandlers = {}; // up in the user's services[name] field // - `null` if the user declined to give permissions // -OAuth.registerService = function (name, version, urls, handleOauthRequest) { +OAuth.registerService = (name, version, urls, handleOauthRequest) => { if (registeredServices[name]) - throw new Error("Already registered the " + name + " OAuth service"); + throw new Error(`Already registered the ${name} OAuth service`); registeredServices[name] = { serviceName: name, - version: version, - urls: urls, - handleOauthRequest: handleOauthRequest + version, + urls, + handleOauthRequest, }; }; // For test cleanup. -OAuthTest.unregisterService = function (name) { +OAuthTest.unregisterService = name => { delete registeredServices[name]; }; -OAuth.retrieveCredential = function(credentialToken, credentialSecret) { - return OAuth._retrievePendingCredential(credentialToken, credentialSecret); -}; +OAuth.retrieveCredential = (credentialToken, credentialSecret) => + OAuth._retrievePendingCredential(credentialToken, credentialSecret); // The state parameter is normally generated on the client using // `btoa`, but for tests we need a version that runs on the server. // -OAuth._generateState = function (loginStyle, credentialToken, redirectUrl) { +OAuth._generateState = (loginStyle, credentialToken, redirectUrl) => { return Buffer.from(JSON.stringify({ loginStyle: loginStyle, credentialToken: credentialToken, redirectUrl: redirectUrl})).toString('base64'); }; -OAuth._stateFromQuery = function (query) { - var string; +OAuth._stateFromQuery = query => { + let string; try { string = Buffer.from(query.state, 'base64').toString('binary'); } catch (e) { - Log.warn('Unable to base64 decode state from OAuth query: ' + query.state); + Log.warn(`Unable to base64 decode state from OAuth query: ${query.state}`); throw e; } try { return JSON.parse(string); } catch (e) { - Log.warn('Unable to parse state from OAuth query: ' + string); + Log.warn(`Unable to parse state from OAuth query: ${string}`); throw e; } }; -OAuth._loginStyleFromQuery = function (query) { - var style; +OAuth._loginStyleFromQuery = quer => { + let style; // For backwards-compatibility for older clients, catch any errors // that result from parsing the state parameter. If we can't parse it, // set login style to popup by default. @@ -91,13 +90,13 @@ OAuth._loginStyleFromQuery = function (query) { style = "popup"; } if (style !== "popup" && style !== "redirect") { - throw new Error("Unrecognized login style: " + style); + throw new Error(`Unrecognized login style: ${style}`); } return style; }; -OAuth._credentialTokenFromQuery = function (query) { - var state; +OAuth._credentialTokenFromQuery = query => { + let state; // For backwards-compatibility for older clients, catch any errors // that result from parsing the state parameter. If we can't parse it, // assume that the state parameter's value is the credential token, as @@ -110,7 +109,7 @@ OAuth._credentialTokenFromQuery = function (query) { return state.credentialToken; }; -OAuth._isCordovaFromQuery = function (query) { +OAuth._isCordovaFromQuery = query => { try { return !! OAuth._stateFromQuery(query).isCordova; } catch (err) { @@ -126,9 +125,9 @@ OAuth._isCordovaFromQuery = function (query) { // We export this function so that developers can override this // behavior to allow apps from external domains to login using the // redirect OAuth flow. -OAuth._checkRedirectUrlOrigin = function (redirectUrl) { - var appHost = Meteor.absoluteUrl(); - var appHostReplacedLocalhost = Meteor.absoluteUrl(undefined, { +OAuth._checkRedirectUrlOrigin = redirectUrl => { + const appHost = Meteor.absoluteUrl(); + const appHostReplacedLocalhost = Meteor.absoluteUrl(undefined, { replaceLocalhost: true }); return ( @@ -139,37 +138,35 @@ OAuth._checkRedirectUrlOrigin = function (redirectUrl) { // Listen to incoming OAuth http requests -WebApp.connectHandlers.use(function(req, res, next) { +WebApp.connectHandlers.use((req, res, next) => { // Need to create a Fiber since we're using synchronous http calls and nothing // else is wrapping this in a fiber automatically - Fiber(function () { - middleware(req, res, next); - }).run(); + Fiber(() => middleware(req, res, next)).run(); }); -var middleware = function (req, res, next) { +const middleware = (req, res, next) => { // Make sure to catch any exceptions because otherwise we'd crash // the runner try { - var serviceName = oauthServiceName(req); + const serviceName = oauthServiceName(req); if (!serviceName) { // not an oauth request. pass to next middleware. next(); return; } - var service = registeredServices[serviceName]; + const service = registeredServices[serviceName]; // Skip everything if there's no service set by the oauth middleware if (!service) - throw new Error("Unexpected OAuth service " + serviceName); + throw new Error(`Unexpected OAuth service ${serviceName}`); // Make sure we're configured ensureConfigured(serviceName); - var handler = OAuth._requestHandlers[service.version]; + const handler = OAuth._requestHandlers[service.version]; if (!handler) - throw new Error("Unexpected OAuth version " + service.version); + throw new Error(`Unexpected OAuth version ${service.version}`); handler(service, req.query, res); } catch (err) { // if we got thrown an error, save it off, it will get passed to @@ -213,15 +210,15 @@ OAuthTest.middleware = middleware; // // @returns {String|null} e.g. "facebook", or null if this isn't an // oauth request -var oauthServiceName = function (req) { +const oauthServiceName = req => { // req.url will be "/_oauth/" with an optional "?close". - var i = req.url.indexOf('?'); - var barePath; + const i = req.url.indexOf('?'); + let barePath; if (i === -1) barePath = req.url; else barePath = req.url.substring(0, i); - var splitPath = barePath.split('/'); + const splitPath = barePath.split('/'); // Any non-oauth request will continue down the default // middlewares. @@ -229,18 +226,18 @@ var oauthServiceName = function (req) { return null; // Find service based on url - var serviceName = splitPath[2]; + const serviceName = splitPath[2]; return serviceName; }; // Make sure we're configured -var ensureConfigured = function(serviceName) { +const ensureConfigured = serviceName => { if (!ServiceConfiguration.configurations.findOne({service: serviceName})) { throw new ServiceConfiguration.ConfigError(); } }; -var isSafe = function (value) { +const isSafe = value => { // This matches strings generated by `Random.secret` and // `Random.id`. return typeof value === "string" && @@ -248,7 +245,7 @@ var isSafe = function (value) { }; // Internal: used by the oauth1 and oauth2 packages -OAuth._renderOauthResults = function(res, query, credentialSecret) { +OAuth._renderOauthResults = (res, query, credentialSecret) => { // For tests, we support the `only_credential_secret_for_test` // parameter, which just returns the credential secret without any // surrounding HTML. (The test needs to be able to easily grab the @@ -262,15 +259,15 @@ OAuth._renderOauthResults = function(res, query, credentialSecret) { res.writeHead(200, {'Content-Type': 'text/html'}); res.end(credentialSecret, 'utf-8'); } else { - var details = { - query: query, + const details = { + query, loginStyle: OAuth._loginStyleFromQuery(query) }; if (query.error) { details.error = query.error; } else { - var token = OAuth._credentialTokenFromQuery(query); - var secret = credentialSecret; + const token = OAuth._credentialTokenFromQuery(query); + const secret = credentialSecret; if (token && secret && isSafe(token) && isSafe(secret)) { details.credentials = { token: token, secret: secret}; @@ -303,13 +300,13 @@ OAuth._endOfRedirectResponseTemplate = Assets.getText( // - redirectUrl // - isCordova (boolean) // -var renderEndOfLoginResponse = function (options) { +const renderEndOfLoginResponse = options => { // It would be nice to use Blaze here, but it's a little tricky // because our mustaches would be inside a