Skip to content

Commit f1e31ca

Browse files
committed
Add app setting logoutSessionsOnSensitiveChanges
Disable invalidation of access tokens by default to restore backwards compatibility with older 2.x versions. Add a new application-wide flag logoutSessionsOnSensitiveChanges that can be used to explicitly turn on/off the token invalidation. When the flag is not set, a verbose warning is printed to nudge the user to make a decision how they want to handle token invalidation.
1 parent f355f66 commit f1e31ca

File tree

13 files changed

+60
-3
lines changed

13 files changed

+60
-3
lines changed

common/models/user.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,31 @@ module.exports = function(User) {
807807
UserModel.validatesUniquenessOf('username', {message: 'User already exists'});
808808
}
809809

810+
UserModel.once('attached', function() {
811+
if (UserModel.app.get('logoutSessionsOnSensitiveChanges') !== undefined)
812+
return;
813+
814+
g.warn([
815+
'',
816+
'The user model %j is attached to an application that does not specify',
817+
'whether other sessions should be invalidated when a password or',
818+
'an email has changed. Session invalidation is important for security',
819+
'reasons as it allows users to recover from various account breach',
820+
'situations.',
821+
'',
822+
'We recommend turning this feature on by setting',
823+
'"{{logoutSessionsOnSensitiveChanges}}" to {{true}} in',
824+
'{{server/config.json}} (unless you have implemented your own solution',
825+
'for token invalidation).',
826+
'',
827+
'We also recommend enabling "{{injectOptionsFromRemoteContext}}" in',
828+
'%s\'s settings (typically via common/models/*.json file).',
829+
'This setting is required for the invalidation algorithm to keep ',
830+
'the current session valid.',
831+
''
832+
].join('\n'), UserModel.modelName, UserModel.modelName);
833+
});
834+
810835
return UserModel;
811836
};
812837

@@ -832,6 +857,8 @@ module.exports = function(User) {
832857

833858
// Delete old sessions once email is updated
834859
User.observe('before save', function beforeEmailUpdate(ctx, next) {
860+
if (!ctx.Model.app.get('logoutSessionsOnSensitiveChanges')) return next();
861+
835862
var emailChanged;
836863
if (ctx.isNewInstance) return next();
837864
if (!ctx.where && !ctx.instance) return next();
@@ -872,6 +899,8 @@ module.exports = function(User) {
872899
});
873900

874901
User.observe('after save', function afterEmailUpdate(ctx, next) {
902+
if (!ctx.Model.app.get('logoutSessionsOnSensitiveChanges')) return next();
903+
875904
if (!ctx.instance && !ctx.data) return next();
876905
if (!ctx.hookState.originalUserData) return next();
877906

test/access-token.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ function createTestApp(testToken, settings, done) {
590590
}, settings.token);
591591

592592
var app = loopback();
593+
app.set('logoutSessionsOnSensitiveChanges', true);
593594

594595
app.use(cookieParser('secret'));
595596
app.use(loopback.token(tokenSettings));
@@ -652,6 +653,7 @@ function createTestApp(testToken, settings, done) {
652653

653654
function givenLocalTokenModel() {
654655
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
656+
app.set('logoutSessionsOnSensitiveChanges', true);
655657
app.dataSource('db', { connector: 'memory' });
656658

657659
var User = app.registry.getModel('User');

test/app.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -718,6 +718,7 @@ describe('app', function() {
718718

719719
beforeEach(function() {
720720
app = loopback();
721+
app.set('logoutSessionsOnSensitiveChanges', true);
721722
app.dataSource('db', {
722723
connector: 'memory'
723724
});
@@ -922,6 +923,7 @@ describe('app', function() {
922923
var AUTH_MODELS = ['User', 'ACL', 'AccessToken', 'Role', 'RoleMapping'];
923924
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
924925
require('../lib/builtin-models')(app.registry);
926+
app.set('logoutSessionsOnSensitiveChanges', true);
925927
var db = app.dataSource('db', { connector: 'memory' });
926928

927929
app.enableAuth({ dataSource: 'db' });
@@ -937,6 +939,7 @@ describe('app', function() {
937939

938940
it('detects already configured subclass of a required model', function() {
939941
var app = loopback({ localRegistry: true, loadBuiltinModels: true });
942+
app.set('logoutSessionsOnSensitiveChanges', true);
940943
var db = app.dataSource('db', { connector: 'memory' });
941944
var Customer = app.registry.createModel('Customer', {}, { base: 'User' });
942945
app.model(Customer, { dataSource: 'db' });
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"port": 3000,
33
"host": "0.0.0.0",
4+
"logoutSessionsOnSensitiveChanges": true,
45
"legacyExplorer": false
5-
}
6+
}

test/fixtures/shared-methods/model-config-defined-false/server/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"disableStackTrace": false
2424
}
2525
},
26+
"logoutSessionsOnSensitiveChanges": true,
2627
"legacyExplorer": false
2728
}
2829

test/fixtures/simple-integration-app/server/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
"limit": "8kb"
1212
}
1313
},
14+
"logoutSessionsOnSensitiveChanges": true,
1415
"legacyExplorer": false
15-
}
16+
}

test/fixtures/user-integration-app/server/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
"limit": "8kb"
1212
}
1313
},
14+
"logoutSessionsOnSensitiveChanges": true,
1415
"legacyExplorer": false
15-
}
16+
}

test/model.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,6 +856,7 @@ describe.onServer('Remote Methods', function() {
856856

857857
function setupAppAndRequest() {
858858
app = loopback({localRegistry: true, loadBuiltinModels: true});
859+
app.set('logoutSessionsOnSensitiveChanges', true);
859860

860861
app.dataSource('db', {connector: 'memory'});
861862

test/replication.rest.test.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ describe('Replication over REST', function() {
462462

463463
function setupServer(done) {
464464
serverApp = loopback();
465+
serverApp.set('logoutSessionsOnSensitiveChanges', true);
465466
serverApp.enableAuth();
466467

467468
serverApp.dataSource('db', { connector: 'memory' });
@@ -514,6 +515,7 @@ describe('Replication over REST', function() {
514515

515516
function setupClient() {
516517
clientApp = loopback();
518+
clientApp.set('logoutSessionsOnSensitiveChanges', true);
517519
clientApp.dataSource('db', { connector: 'memory' });
518520
clientApp.dataSource('remote', {
519521
connector: 'remote',

test/rest.middleware.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ describe('loopback.rest', function() {
1313
// override the global app object provided by test/support.js
1414
// and create a local one that does not share state with other tests
1515
app = loopback({ localRegistry: true, loadBuiltinModels: true });
16+
app.set('logoutSessionsOnSensitiveChanges', true);
1617
var db = app.dataSource('db', { connector: 'memory' });
1718
MyModel = app.registry.createModel('MyModel');
1819
MyModel.attachTo(db);

0 commit comments

Comments
 (0)