Merge pull request #3109 from strongloop/fix/flag-to-invalidate-tokens

Add app setting logoutSessionsOnSensitiveChanges
This commit is contained in:
Miroslav Bajtoš 2017-01-20 15:09:08 +01:00 committed by GitHub
commit b541c5bff8
13 changed files with 60 additions and 3 deletions

View File

@ -807,6 +807,31 @@ module.exports = function(User) {
UserModel.validatesUniquenessOf('username', {message: 'User already exists'});
}
UserModel.once('attached', function() {
if (UserModel.app.get('logoutSessionsOnSensitiveChanges') !== undefined)
return;
g.warn([
'',
'The user model %j is attached to an application that does not specify',
'whether other sessions should be invalidated when a password or',
'an email has changed. Session invalidation is important for security',
'reasons as it allows users to recover from various account breach',
'situations.',
'',
'We recommend turning this feature on by setting',
'"{{logoutSessionsOnSensitiveChanges}}" to {{true}} in',
'{{server/config.json}} (unless you have implemented your own solution',
'for token invalidation).',
'',
'We also recommend enabling "{{injectOptionsFromRemoteContext}}" in',
'%s\'s settings (typically via common/models/*.json file).',
'This setting is required for the invalidation algorithm to keep ',
'the current session valid.',
''
].join('\n'), UserModel.modelName, UserModel.modelName);
});
return UserModel;
};
@ -832,6 +857,8 @@ module.exports = function(User) {
// Delete old sessions once email is updated
User.observe('before save', function beforeEmailUpdate(ctx, next) {
if (!ctx.Model.app.get('logoutSessionsOnSensitiveChanges')) return next();
var emailChanged;
if (ctx.isNewInstance) return next();
if (!ctx.where && !ctx.instance) return next();
@ -872,6 +899,8 @@ module.exports = function(User) {
});
User.observe('after save', function afterEmailUpdate(ctx, next) {
if (!ctx.Model.app.get('logoutSessionsOnSensitiveChanges')) return next();
if (!ctx.instance && !ctx.data) return next();
if (!ctx.hookState.originalUserData) return next();

View File

@ -590,6 +590,7 @@ function createTestApp(testToken, settings, done) {
}, settings.token);
var app = loopback();
app.set('logoutSessionsOnSensitiveChanges', true);
app.use(cookieParser('secret'));
app.use(loopback.token(tokenSettings));
@ -652,6 +653,7 @@ function createTestApp(testToken, settings, done) {
function givenLocalTokenModel() {
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
app.set('logoutSessionsOnSensitiveChanges', true);
app.dataSource('db', { connector: 'memory' });
var User = app.registry.getModel('User');

View File

@ -718,6 +718,7 @@ describe('app', function() {
beforeEach(function() {
app = loopback();
app.set('logoutSessionsOnSensitiveChanges', true);
app.dataSource('db', {
connector: 'memory'
});
@ -922,6 +923,7 @@ describe('app', function() {
var AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping'];
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
require('../lib/builtin-models')(app.registry);
app.set('logoutSessionsOnSensitiveChanges', true);
var db = app.dataSource('db', { connector: 'memory' });
app.enableAuth({ dataSource: 'db' });
@ -937,6 +939,7 @@ describe('app', function() {
it('detects already configured subclass of a required model', function() {
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
app.set('logoutSessionsOnSensitiveChanges', true);
var db = app.dataSource('db', { connector: 'memory' });
var Customer = app.registry.createModel('Customer', {}, { base: 'User' });
app.model(Customer, { dataSource: 'db' });

View File

@ -1,5 +1,6 @@
{
"port": 3000,
"host": "0.0.0.0",
"logoutSessionsOnSensitiveChanges": true,
"legacyExplorer": false
}
}

View File

@ -23,6 +23,7 @@
"disableStackTrace": false
}
},
"logoutSessionsOnSensitiveChanges": true,
"legacyExplorer": false
}

View File

@ -11,5 +11,6 @@
"limit": "8kb"
}
},
"logoutSessionsOnSensitiveChanges": true,
"legacyExplorer": false
}
}

View File

@ -11,5 +11,6 @@
"limit": "8kb"
}
},
"logoutSessionsOnSensitiveChanges": true,
"legacyExplorer": false
}
}

View File

@ -856,6 +856,7 @@ describe.onServer('Remote Methods', function() {
function setupAppAndRequest() {
app = loopback({localRegistry: true, loadBuiltinModels: true});
app.set('logoutSessionsOnSensitiveChanges', true);
app.dataSource('db', {connector: 'memory'});

View File

@ -462,6 +462,7 @@ describe('Replication over REST', function() {
function setupServer(done) {
serverApp = loopback();
serverApp.set('logoutSessionsOnSensitiveChanges', true);
serverApp.enableAuth();
serverApp.dataSource('db', { connector: 'memory' });
@ -514,6 +515,7 @@ describe('Replication over REST', function() {
function setupClient() {
clientApp = loopback();
clientApp.set('logoutSessionsOnSensitiveChanges', true);
clientApp.dataSource('db', { connector: 'memory' });
clientApp.dataSource('remote', {
connector: 'remote',

View File

@ -13,6 +13,7 @@ describe('loopback.rest', function() {
// override the global app object provided by test/support.js
// and create a local one that does not share state with other tests
app = loopback({ localRegistry: true, loadBuiltinModels: true });
app.set('logoutSessionsOnSensitiveChanges', true);
var db = app.dataSource('db', { connector: 'memory' });
MyModel = app.registry.createModel('MyModel');
MyModel.attachTo(db);

View File

@ -23,6 +23,7 @@ describe('role model', function() {
// Use local app registry to ensure models are isolated to avoid
// pollutions from other tests
app = loopback({ localRegistry: true, loadBuiltinModels: true });
app.set('logoutSessionsOnSensitiveChanges', true);
app.dataSource('db', { connector: 'memory' });
ACL = app.registry.getModel('ACL');
@ -735,6 +736,7 @@ describe('role model', function() {
describe('isOwner', function() {
it('supports app-local model registry', function(done) {
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
app.set('logoutSessionsOnSensitiveChanges', true);
app.dataSource('db', { connector: 'memory' });
// attach all auth-related models to 'db' datasource
app.enableAuth({ dataSource: 'db' });

View File

@ -23,6 +23,7 @@ loopback.User.settings.saltWorkFactor = 4;
beforeEach(function() {
this.app = app = loopback();
app.set('logoutSessionsOnSensitiveChanges', true);
// setup default data sources
loopback.setDefaultDataSourceForType('db', {

View File

@ -31,6 +31,7 @@ describe('User', function() {
// override the global app object provided by test/support.js
// and create a local one that does not share state with other tests
app = loopback({ localRegistry: true, loadBuiltinModels: true });
app.set('logoutSessionsOnSensitiveChanges', true);
app.dataSource('db', { connector: 'memory' });
// setup Email model, it's needed by User tests
@ -2326,6 +2327,17 @@ describe('User', function() {
});
});
it('preserves all sessions when logoutSessionsOnSensitiveChanges is disabled',
function(done) {
app.set('logoutSessionsOnSensitiveChanges', false);
user.updateAttributes(
{email: updatedEmailCredentials.email},
function(err, userInstance) {
if (err) return done(err);
assertPreservedTokens(done);
});
});
function assertPreservedTokens(done) {
AccessToken.find({where: {userId: user.id}}, function(err, tokens) {
if (err) return done(err);