Fix issues with Infinity based login token lifetimes (#9113)

* Fix issues with Infinity based login token lifetimes

Several areas of the Accounts system are using the current
login token expiration millisecond limit for different
`Date` based calculations. When `loginExpirationInDays` is set
to `null`, and the Accounts system uses an expiration limit of
`Infinity`, these `Date` based operations fail, since
`Infinity` can't be converted to a `Date`.

These changes replace the use of `Infinity` with a far future
fixed date representation, that's used when
`loginExpirationInDays` is set to `null`.

Fixes #9066.

* Small comment typo

* Remove unnecessary comment
This commit is contained in:
Hugh Willson
2017-09-20 11:44:59 -04:00
committed by Ben Newman
parent c6a643acf1
commit a549448e54
2 changed files with 60 additions and 24 deletions

View File

@@ -213,19 +213,20 @@ export class AccountsCommon {
}
}
// The options argument is only used by tests.
_getTokenLifetimeMs(options) {
options = options || this._options;
if (options.loginExpirationInDays === null) {
// We disable login expiration by returning Infinity
return Infinity;
}
return (options.loginExpirationInDays ||
DEFAULT_LOGIN_EXPIRATION_DAYS) * 24 * 60 * 60 * 1000;
_getTokenLifetimeMs() {
// When loginExpirationInDays is set to null, we'll use a really high
// number of days (LOGIN_UNEXPIRABLE_TOKEN_DAYS) to simulate an
// unexpiring token.
const loginExpirationInDays =
(this._options.loginExpirationInDays === null)
? LOGIN_UNEXPIRING_TOKEN_DAYS
: this._options.loginExpirationInDays;
return (loginExpirationInDays
|| DEFAULT_LOGIN_EXPIRATION_DAYS) * 24 * 60 * 60 * 1000;
}
_getPasswordResetTokenLifetimeMs() {
return (this._options.passwordResetTokenExpirationInDays ||
return (this._options.passwordResetTokenExpirationInDays ||
DEFAULT_PASSWORD_RESET_TOKEN_EXPIRATION_DAYS) * 24 * 60 * 60 * 1000;
}
@@ -273,7 +274,10 @@ Meteor.user = function () {
};
// how long (in days) until a login token expires
var DEFAULT_LOGIN_EXPIRATION_DAYS = 90;
const DEFAULT_LOGIN_EXPIRATION_DAYS = 90;
// Expose for testing.
Ap.DEFAULT_LOGIN_EXPIRATION_DAYS = DEFAULT_LOGIN_EXPIRATION_DAYS;
// how long (in days) until reset password token expires
var DEFAULT_PASSWORD_RESET_TOKEN_EXPIRATION_DAYS = 3;
// how long (in days) until enrol password token expires
@@ -288,6 +292,12 @@ EXPIRE_TOKENS_INTERVAL_MS = 600 * 1000; // 10 minutes
// called
CONNECTION_CLOSE_DELAY_MS = 10 * 1000;
// A large number of expiration days (approximately 100 years worth) that is
// used when creating unexpiring tokens.
const LOGIN_UNEXPIRING_TOKEN_DAYS = 365 * 100;
// Expose for testing.
Ap.LOGIN_UNEXPIRING_TOKEN_DAYS = LOGIN_UNEXPIRING_TOKEN_DAYS;
// loginServiceConfiguration and ConfigError are maintained for backwards compatibility
Meteor.startup(function () {
var ServiceConfiguration =

View File

@@ -13,23 +13,49 @@ Tinytest.add('accounts - config validates keys', function (test) {
});
});
// test the loginExpirationInDays config
Tinytest.add( 'accounts - config - token limetime', function (test) {
var config = { loginExpirationInDays: 2 };
test.equal(Accounts._getTokenLifetimeMs(config), 2 * 24 * 60 * 60 * 1000);
Tinytest.add('accounts - config - token lifetime', function (test) {
const loginExpirationInDays = Accounts._options.loginExpirationInDays;
Accounts._options.loginExpirationInDays = 2;
test.equal(Accounts._getTokenLifetimeMs(), 2 * 24 * 60 * 60 * 1000);
Accounts._options.loginExpirationInDays = loginExpirationInDays;
});
Tinytest.add( 'accounts - config - unexpiring tokens', function (test) {
var config = { loginExpirationInDays: null };
test.equal(Accounts._getTokenLifetimeMs(config), Infinity);
Tinytest.add('accounts - config - unexpiring tokens', function (test) {
const loginExpirationInDays = Accounts._options.loginExpirationInDays;
// When setting loginExpirationInDays to null in the global Accounts
// config object, make sure the returned token lifetime represents an
// unexpiring token date (is very far into the future).
Accounts._options.loginExpirationInDays = null;
test.equal(
Accounts._getTokenLifetimeMs(),
Accounts.LOGIN_UNEXPIRING_TOKEN_DAYS * 24 * 60 * 60 * 1000,
);
// Verify token expiration date retrieval returns a Date.
// (verifies https://github.com/meteor/meteor/issues/9066)
test.isTrue(
!isNaN(Accounts._tokenExpiration(new Date())),
'Returned token expiration should be a Date',
);
// Verify the token expiration check works properly.
// (verifies https://github.com/meteor/meteor/issues/9066)
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 200);
test.isFalse(Accounts._tokenExpiresSoon(futureDate));
Accounts._options.loginExpirationInDays = loginExpirationInDays;
});
Tinytest.add( 'accounts - config - default token limetime', function(test) {
var DEFAULT_LOGIN_EXPIRATION_DAYS = 90; // copied from accounts_common.js
var config1 = {};
var config2 = { loginExpirationInDays: DEFAULT_LOGIN_EXPIRATION_DAYS };
test.equal(Accounts._getTokenLifetimeMs(config1), Accounts._getTokenLifetimeMs(config2));
Tinytest.add('accounts - config - default token lifetime', function (test) {
const options = Accounts._options;
Accounts._options = {};
test.equal(
Accounts._getTokenLifetimeMs(),
Accounts.DEFAULT_LOGIN_EXPIRATION_DAYS * 24 * 60 * 60 * 1000,
);
Accounts._options = options;
});
var idsInValidateNewUser = {};