Modernize oauth package

- Bumped patch version number 1.2.1 -> 1.2.2
- ES6 syntax and shorthand applied
- Underscore removed as a dependency
This commit is contained in:
James Burgess
2018-01-28 00:14:45 +01:00
parent 25074ad4ce
commit fcd650bd46
10 changed files with 226 additions and 236 deletions

View File

@@ -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();
}
})();

View File

@@ -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] =

View File

@@ -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;
}

View File

@@ -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 {

View File

@@ -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);
};

View File

@@ -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);

View File

@@ -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/<service name>" 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 <script> tag, and Blaze
// would treat the <script> tag contents as text (e.g. encode '&' as
// '&amp;'). So we just do a simple replace.
var escape = function (s) {
const escape = s => {
if (s) {
return s.replace(/&/g, "&amp;").
replace(/</g, "&lt;").
@@ -324,7 +321,7 @@ var renderEndOfLoginResponse = function (options) {
// Escape everything just to be safe (we've already checked that some
// of this data -- the token and secret -- are safe).
var config = {
const config = {
setCredentialToken: !! options.setCredentialToken,
credentialToken: escape(options.credentialToken),
credentialSecret: escape(options.credentialSecret),
@@ -333,21 +330,21 @@ var renderEndOfLoginResponse = function (options) {
isCordova: !! options.isCordova
};
var template;
let template;
if (options.loginStyle === 'popup') {
template = OAuth._endOfPopupResponseTemplate;
} else if (options.loginStyle === 'redirect') {
template = OAuth._endOfRedirectResponseTemplate;
} else {
throw new Error('invalid loginStyle: ' + options.loginStyle);
throw new Error(`invalid loginStyle: ${options.loginStyle}`);
}
var result = template.replace(/##CONFIG##/, JSON.stringify(config))
const result = template.replace(/##CONFIG##/, JSON.stringify(config))
.replace(
/##ROOT_URL_PATH_PREFIX##/, __meteor_runtime_config__.ROOT_URL_PATH_PREFIX
);
return "<!DOCTYPE html>\n" + result;
return `<!DOCTYPE html>\n${result}`;
};
// Writes an HTTP response to the popup window at the end of an OAuth
@@ -381,21 +378,21 @@ var renderEndOfLoginResponse = function (options) {
// so shouldn't be trusted for security decisions or included in
// the response without sanitizing it first. Only one of `error`
// or `credentials` should be set.
OAuth._endOfLoginResponse = function (res, details) {
OAuth._endOfLoginResponse = (res, details) => {
res.writeHead(200, {'Content-Type': 'text/html'});
var redirectUrl;
let redirectUrl;
if (details.loginStyle === 'redirect') {
redirectUrl = OAuth._stateFromQuery(details.query).redirectUrl;
var appHost = Meteor.absoluteUrl();
const appHost = Meteor.absoluteUrl();
if (OAuth._checkRedirectUrlOrigin(redirectUrl)) {
details.error = "redirectUrl (" + redirectUrl +
") is not on the same host as the app (" + appHost + ")";
details.error = `redirectUrl (${redirectUrl}` +
`) is not on the same host as the app (${appHost})`;
redirectUrl = appHost;
}
}
var isCordova = OAuth._isCordovaFromQuery(details.query);
const isCordova = OAuth._isCordovaFromQuery(details.query);
if (details.error) {
Log.warn("Error in OAuth Server: " +
@@ -404,8 +401,8 @@ OAuth._endOfLoginResponse = function (res, details) {
res.end(renderEndOfLoginResponse({
loginStyle: details.loginStyle,
setCredentialToken: false,
redirectUrl: redirectUrl,
isCordova: isCordova
redirectUrl,
isCordova,
}), "utf-8");
return;
}
@@ -418,17 +415,16 @@ OAuth._endOfLoginResponse = function (res, details) {
setCredentialToken: true,
credentialToken: details.credentials.token,
credentialSecret: details.credentials.secret,
redirectUrl: redirectUrl,
isCordova: isCordova
redirectUrl,
isCordova,
}), "utf-8");
};
var OAuthEncryption = Package["oauth-encryption"] && Package["oauth-encryption"].OAuthEncryption;
const OAuthEncryption = Package["oauth-encryption"] && Package["oauth-encryption"].OAuthEncryption;
var usingOAuthEncryption = function () {
return OAuthEncryption && OAuthEncryption.keyIsLoaded();
};
const usingOAuthEncryption = () =>
OAuthEncryption && OAuthEncryption.keyIsLoaded();
// Encrypt sensitive service data such as access tokens if the
// "oauth-encryption" package is loaded and the oauth secret key has
@@ -440,7 +436,7 @@ var usingOAuthEncryption = function () {
// will be re-encrypted with the user id included before inserting the
// service data into the user document.
//
OAuth.sealSecret = function (plaintext) {
OAuth.sealSecret = plaintext => {
if (usingOAuthEncryption())
return OAuthEncryption.seal(plaintext);
else
@@ -453,7 +449,7 @@ OAuth.sealSecret = function (plaintext) {
// Throws an error if the "oauth-encryption" package is loaded and the
// field is encrypted, but the oauth secret key hasn't been specified.
//
OAuth.openSecret = function (maybeSecret, userId) {
OAuth.openSecret = (maybeSecret, userId) => {
if (!Package["oauth-encryption"] || !OAuthEncryption.isSealed(maybeSecret))
return maybeSecret;
@@ -462,10 +458,10 @@ OAuth.openSecret = function (maybeSecret, userId) {
// Unencrypt fields in the service data object.
//
OAuth.openSecrets = function (serviceData, userId) {
var result = {};
_.each(_.keys(serviceData), function (key) {
result[key] = OAuth.openSecret(serviceData[key], userId);
});
OAuth.openSecrets = (serviceData, userId) => {
const result = {};
Object.keys(serviceData).forEach(key =>
result[key] = OAuth.openSecret(serviceData[key], userId)
);
return result;
};

View File

@@ -1,26 +1,26 @@
Tinytest.add("oauth - pendingCredential handles Errors", function (test) {
var credentialToken = Random.id();
Tinytest.add("oauth - pendingCredential handles Errors", test => {
const credentialToken = Random.id();
var testError = new Error("This is a test error");
const testError = new Error("This is a test error");
testError.stack = 'test stack';
OAuth._storePendingCredential(credentialToken, testError);
// Test that the result for the token is the expected error
var result = OAuth._retrievePendingCredential(credentialToken);
const result = OAuth._retrievePendingCredential(credentialToken);
test.instanceOf(result, Error);
test.equal(result.message, testError.message);
test.equal(result.stack, testError.stack);
});
Tinytest.add("oauth - pendingCredential handles Meteor.Errors", function (test) {
var credentialToken = Random.id();
Tinytest.add("oauth - pendingCredential handles Meteor.Errors", test => {
const credentialToken = Random.id();
var testError = new Meteor.Error(401, "This is a test error");
const testError = new Meteor.Error(401, "This is a test error");
testError.stack = 'test stack';
OAuth._storePendingCredential(credentialToken, testError);
// Test that the result for the token is the expected error
var result = OAuth._retrievePendingCredential(credentialToken);
const result = OAuth._retrievePendingCredential(credentialToken);
test.instanceOf(result, Meteor.Error);
test.equal(result.error, testError.error);
test.equal(result.message, testError.message);
@@ -29,49 +29,42 @@ Tinytest.add("oauth - pendingCredential handles Meteor.Errors", function (test)
test.isUndefined(result.meteorError);
});
Tinytest.add("oauth - null, undefined key for pendingCredential", function (test) {
var cred = Random.id();
test.throws(function () {
OAuth._storePendingCredential(null, cred);
});
test.throws(function () {
OAuth._storePendingCredential(undefined, cred);
});
Tinytest.add("oauth - null, undefined key for pendingCredential", test => {
const cred = Random.id();
test.throws(() => OAuth._storePendingCredential(null, cred));
test.throws(() => OAuth._storePendingCredential(undefined, cred));
});
Tinytest.add("oauth - pendingCredential handles duplicate key", function (test) {
var key = Random.id();
var cred = Random.id();
Tinytest.add("oauth - pendingCredential handles duplicate key", test => {
const key = Random.id();
const cred = Random.id();
OAuth._storePendingCredential(key, cred);
var newCred = Random.id();
const newCred = Random.id();
OAuth._storePendingCredential(key, newCred);
test.equal(OAuth._retrievePendingCredential(key), newCred);
});
Tinytest.add(
"oauth - pendingCredential requires credential secret",
function (test) {
var key = Random.id();
var cred = Random.id();
var secret = Random.id();
OAuth._storePendingCredential(key, cred, secret);
test.equal(OAuth._retrievePendingCredential(key), undefined);
test.equal(OAuth._retrievePendingCredential(key, secret), cred);
}
);
Tinytest.add("oauth - pendingCredential requires credential secret", test => {
const key = Random.id();
const cred = Random.id();
const secret = Random.id();
OAuth._storePendingCredential(key, cred, secret);
test.equal(OAuth._retrievePendingCredential(key), undefined);
test.equal(OAuth._retrievePendingCredential(key, secret), cred);
});
Tinytest.add("oauth - _endOfLoginResponse with popup loginStyle supports unspecified ROOT_URL_PATH_PREFIX",
function (test) {
var res = {
writeHead: function () {},
end: function (content) {
test => {
const res = {
writeHead: () => {},
end: content => {
test.matches(
content,
/\/packages\/oauth\/end_of_popup_response\.js/
);
}
};
var details = {
const details = {
credentials: {},
loginStyle: 'popup'
};
@@ -80,12 +73,12 @@ Tinytest.add("oauth - _endOfLoginResponse with popup loginStyle supports unspeci
);
Tinytest.add("oauth - _endOfLoginResponse with popup loginStyle supports ROOT_URL_PATH_PREFIX",
function (test) {
var rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX;
test => {
const rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX;
__meteor_runtime_config__.ROOT_URL_PATH_PREFIX = '/test-root-url-prefix';
var res = {
writeHead: function () {},
end: function (content) {
const res = {
writeHead: () => {},
end: content => {
__meteor_runtime_config__.ROOT_URL_PATH_PREFIX = rootUrlPathPrefix;
test.matches(
content,
@@ -93,7 +86,7 @@ Tinytest.add("oauth - _endOfLoginResponse with popup loginStyle supports ROOT_UR
);
}
};
var details = {
const details = {
credentials: {},
loginStyle: 'popup'
};
@@ -102,17 +95,17 @@ Tinytest.add("oauth - _endOfLoginResponse with popup loginStyle supports ROOT_UR
);
Tinytest.add("oauth - _endOfLoginResponse with redirect loginStyle supports unspecified ROOT_URL_PATH_PREFIX",
function (test) {
var res = {
writeHead: function () {},
end: function (content) {
test => {
const res = {
writeHead: () => {},
end: content => {
test.matches(
content,
/\/packages\/oauth\/end_of_redirect_response\.js/
);
}
};
var details = {
const details = {
credentials: {},
loginStyle: 'redirect',
query: {
@@ -127,12 +120,12 @@ Tinytest.add("oauth - _endOfLoginResponse with redirect loginStyle supports unsp
Tinytest.add("oauth - _endOfLoginResponse with redirect loginStyle supports ROOT_URL_PATH_PREFIX",
function (test) {
var rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX;
test => {
const rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX;
__meteor_runtime_config__.ROOT_URL_PATH_PREFIX = '/test-root-url-prefix';
var res = {
writeHead: function () {},
end: function (content) {
const res = {
writeHead: () => {},
end: content => {
__meteor_runtime_config__.ROOT_URL_PATH_PREFIX = rootUrlPathPrefix;
test.matches(
content,
@@ -140,7 +133,7 @@ Tinytest.add("oauth - _endOfLoginResponse with redirect loginStyle supports ROOT
);
}
};
var details = {
const details = {
credentials: {},
loginStyle: 'redirect',
query: {

View File

@@ -1,11 +1,11 @@
Package.describe({
summary: "Common code for OAuth-based services",
version: "1.2.1"
version: "1.2.2",
});
Package.onUse(function (api) {
Package.onUse(api => {
api.use('check');
api.use('underscore');
api.use('ecmascript');
api.use('routepolicy', 'server');
api.use('webapp', 'server');
@@ -48,7 +48,7 @@ Package.onUse(function (api) {
});
Package.onTest(function (api) {
Package.onTest(api => {
api.use('tinytest');
api.use('random');
api.use('service-configuration', 'server');

View File

@@ -23,13 +23,13 @@ OAuth._pendingCredentials._ensureIndex('createdAt');
// Periodically clear old entries that were never retrieved
var _cleanStaleResults = function() {
const _cleanStaleResults = () => {
// Remove credentials older than 1 minute
var timeCutoff = new Date();
const timeCutoff = new Date();
timeCutoff.setMinutes(timeCutoff.getMinutes() - 1);
OAuth._pendingCredentials.remove({ createdAt: { $lt: timeCutoff } });
};
var _cleanupHandle = Meteor.setInterval(_cleanStaleResults, 60 * 1000);
const _cleanupHandle = Meteor.setInterval(_cleanStaleResults, 60 * 1000);
// Stores the key and credential in the _pendingCredentials collection.
@@ -40,9 +40,9 @@ var _cleanupHandle = Meteor.setInterval(_cleanStaleResults, 60 * 1000);
// @param credentialSecret {string} A secret that must be presented in
// addition to the `key` to retrieve the credential
//
OAuth._storePendingCredential = function (key, credential, credentialSecret) {
OAuth._storePendingCredential = (key, credential, credentialSecret = null) => {
check(key, String);
check(credentialSecret, Match.Optional(String));
check(credentialSecret, Match.Maybe(String));
if (credential instanceof Error) {
credential = storableError(credential);
@@ -54,11 +54,11 @@ OAuth._storePendingCredential = function (key, credential, credentialSecret) {
// to somehow send the same `state` parameter twice during an OAuth
// login; we don't want a duplicate key error.
OAuth._pendingCredentials.upsert({
key: key
key,
}, {
key: key,
credential: credential,
credentialSecret: credentialSecret || null,
key,
credential,
credentialSecret,
createdAt: new Date()
});
};
@@ -69,13 +69,14 @@ OAuth._storePendingCredential = function (key, credential, credentialSecret) {
// @param key {string}
// @param credentialSecret {string}
//
OAuth._retrievePendingCredential = function (key, credentialSecret) {
OAuth._retrievePendingCredential = (key, credentialSecret = null) => {
check(key, String);
var pendingCredential = OAuth._pendingCredentials.findOne({
key: key,
credentialSecret: credentialSecret || null
const pendingCredential = OAuth._pendingCredentials.findOne({
key,
credentialSecret,
});
if (pendingCredential) {
OAuth._pendingCredentials.remove({ _id: pendingCredential._id });
if (pendingCredential.credential.error)
@@ -91,11 +92,12 @@ OAuth._retrievePendingCredential = function (key, credentialSecret) {
// Convert an Error into an object that can be stored in mongo
// Note: A Meteor.Error is reconstructed as a Meteor.Error
// All other error classes are reconstructed as a plain Error.
var storableError = function(error) {
var plainObject = {};
Object.getOwnPropertyNames(error).forEach(function(key) {
plainObject[key] = error[key];
});
// TODO: Can we do this more simply with EJSON?
const storableError = error => {
const plainObject = {};
Object.getOwnPropertyNames(error).forEach(
key => plainObject[key] = error[key]
);
// Keep track of whether it's a Meteor.Error
if(error instanceof Meteor.Error) {
@@ -106,8 +108,8 @@ var storableError = function(error) {
};
// Create an error from the error format stored in mongo
var recreateError = function(errorDoc) {
var error;
const recreateError = errorDoc => {
let error;
if (errorDoc.meteorError) {
error = new Meteor.Error();
@@ -116,9 +118,9 @@ var recreateError = function(errorDoc) {
error = new Error();
}
Object.getOwnPropertyNames(errorDoc).forEach(function(key) {
error[key] = errorDoc[key];
});
Object.getOwnPropertyNames(errorDoc).forEach(key =>
error[key] = errorDoc[key]
);
return error;
};