Skip to content

Commit d33dd68

Browse files
drew-grossflovilmart
authored andcommitted
Add revokeSessionOnPasswordReset option. Closes #1584 (#1597)
* Add revokeSessionOnPasswordReset option * Fix nits
1 parent 0d09476 commit d33dd68

9 files changed

+96
-17
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ For the full list of available options, run `parse-server --help`.
161161

162162
* `appId` **(required)** - The application id to host with this server instance. You can use any arbitrary string. For migrated apps, this should match your hosted Parse app.
163163
* `masterKey` **(required)** - The master key to use for overriding ACL security. You can use any arbitrary string. Keep it secret! For migrated apps, this should match your hosted Parse app.
164-
* `databaseURI` **(required)** - The connection string for your database, i.e. `mongodb://user:[email protected]/dbname`. Be sure to [URL encode your password](https://app.zencoder.com/docs/guides/getting-started/special-characters-in-usernames-and-passwords) if your password has special charachters.
164+
* `databaseURI` **(required)** - The connection string for your database, i.e. `mongodb://user:[email protected]/dbname`. Be sure to [URL encode your password](https://app.zencoder.com/docs/guides/getting-started/special-characters-in-usernames-and-passwords) if your password has special charachters.
165165
* `port` - The default port is 1337, specify this parameter to use a different port.
166166
* `serverURL` - URL to your Parse Server (don't forget to specify http:// or https://). This URL will be used when making requests to Parse Server from Cloud Code.
167167
* `cloud` - The absolute path to your cloud code `main.js` file.
@@ -188,6 +188,7 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
188188
* `maxUploadSize` - Max file size for uploads. Defaults to 20 MB.
189189
* `loggerAdapter` - The default behavior/transport (File) can be changed by creating an adapter class (see [`LoggerAdapter.js`](https://github.com/ParsePlatform/parse-server/blob/master/src/Adapters/Logger/LoggerAdapter.js)).
190190
* `sessionLength` - The length of time in seconds that a session should be valid for. Defaults to 31536000 seconds (1 year).
191+
* `revokeSessionOnPasswordReset` - When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.
191192

192193
##### Email verification and password reset
193194

spec/ParseUser.spec.js

+65-5
Original file line numberDiff line numberDiff line change
@@ -2115,9 +2115,7 @@ describe('Parse.User testing', () => {
21152115
});
21162116
});
21172117

2118-
// Sometimes the authData still has null on that keys
2119-
// https://github.com/ParsePlatform/parse-server/issues/935
2120-
it('should cleanup null authData keys', (done) => {
2118+
it('should cleanup null authData keys (regression test for #935)', (done) => {
21212119
let database = new Config(Parse.applicationId).database;
21222120
database.create('_User', {
21232121
username: 'user',
@@ -2151,8 +2149,7 @@ describe('Parse.User testing', () => {
21512149
})
21522150
});
21532151

2154-
// https://github.com/ParsePlatform/parse-server/issues/1198
2155-
it('should cleanup null authData keys ParseUser update', (done) => {
2152+
it('should cleanup null authData keys ParseUser update (regression test for #1198)', (done) => {
21562153
Parse.Cloud.beforeSave('_User', (req, res) => {
21572154
req.object.set('foo', 'bar');
21582155
res.success();
@@ -2347,4 +2344,67 @@ describe('Parse.User testing', () => {
23472344
done();
23482345
});
23492346
});
2347+
2348+
it('should revoke sessions when converting anonymous user to "normal" user', done => {
2349+
request.post({
2350+
url: 'http://localhost:8378/1/classes/_User',
2351+
headers: {
2352+
'X-Parse-Application-Id': Parse.applicationId,
2353+
'X-Parse-REST-API-Key': 'rest',
2354+
},
2355+
json: {authData: {anonymous: {id: '00000000-0000-0000-0000-000000000001'}}}
2356+
}, (err, res, body) => {
2357+
Parse.User.become(body.sessionToken)
2358+
.then(user => {
2359+
let obj = new Parse.Object('TestObject');
2360+
obj.setACL(new Parse.ACL(user));
2361+
return obj.save()
2362+
.then(() => {
2363+
// Change password, revoking session
2364+
user.set('username', 'no longer anonymous');
2365+
user.set('password', 'password');
2366+
return user.save()
2367+
})
2368+
.then(() => obj.fetch())
2369+
.catch(error => {
2370+
expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND);
2371+
done();
2372+
});
2373+
})
2374+
});
2375+
});
2376+
2377+
it('should not revoke session tokens if the server is configures to not revoke session tokens', done => {
2378+
setServerConfiguration({
2379+
serverURL: 'http://localhost:8378/1',
2380+
appId: 'test',
2381+
masterKey: 'test',
2382+
cloud: './spec/cloud/main.js',
2383+
revokeSessionOnPasswordReset: false,
2384+
})
2385+
request.post({
2386+
url: 'http://localhost:8378/1/classes/_User',
2387+
headers: {
2388+
'X-Parse-Application-Id': Parse.applicationId,
2389+
'X-Parse-REST-API-Key': 'rest',
2390+
},
2391+
json: {authData: {anonymous: {id: '00000000-0000-0000-0000-000000000001'}}}
2392+
}, (err, res, body) => {
2393+
Parse.User.become(body.sessionToken)
2394+
.then(user => {
2395+
let obj = new Parse.Object('TestObject');
2396+
obj.setACL(new Parse.ACL(user));
2397+
return obj.save()
2398+
.then(() => {
2399+
// Change password, revoking session
2400+
user.set('username', 'no longer anonymous');
2401+
user.set('password', 'password');
2402+
return user.save()
2403+
})
2404+
.then(() => obj.fetch())
2405+
// fetch should succeed as we still have our session token
2406+
.then(done, fail);
2407+
})
2408+
});
2409+
})
23502410
});

spec/helper.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ var defaultConfiguration = {
4040
myoauth: {
4141
module: path.resolve(__dirname, "myoauth") // relative path as it's run from src
4242
}
43-
}
43+
},
4444
};
4545

4646
// Set up a default API server for testing with default configuration.
@@ -54,7 +54,7 @@ delete defaultConfiguration.cloud;
5454

5555
var currentConfiguration;
5656
// Allows testing specific configurations of Parse Server
57-
var setServerConfiguration = configuration => {
57+
const setServerConfiguration = configuration => {
5858
// the configuration hasn't changed
5959
if (configuration === currentConfiguration) {
6060
return;

spec/index.spec.js

+5
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,9 @@ describe('server', () => {
333333
})).toThrow('Session length must be a value greater than 0.');
334334
done();
335335
})
336+
337+
it('fails if you try to set revokeSessionOnPasswordReset to non-boolean', done => {
338+
expect(() => setServerConfiguration({ revokeSessionOnPasswordReset: 'non-bool' })).toThrow();
339+
done();
340+
});
336341
});

src/Config.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,20 @@ export class Config {
4949
this.liveQueryController = cacheInfo.liveQueryController;
5050
this.sessionLength = cacheInfo.sessionLength;
5151
this.generateSessionExpiresAt = this.generateSessionExpiresAt.bind(this);
52+
this.revokeSessionOnPasswordReset = cacheInfo.revokeSessionOnPasswordReset;
5253
}
5354

5455
static validate(options) {
55-
this.validateEmailConfiguration({verifyUserEmails: options.verifyUserEmails,
56-
appName: options.appName,
57-
publicServerURL: options.publicServerURL})
56+
this.validateEmailConfiguration({
57+
verifyUserEmails: options.verifyUserEmails,
58+
appName: options.appName,
59+
publicServerURL: options.publicServerURL
60+
})
61+
62+
if (typeof options.revokeSessionOnPasswordReset !== 'boolean') {
63+
throw 'revokeSessionOnPasswordReset must be a boolean value';
64+
}
65+
5866
if (options.publicServerURL) {
5967
if (!options.publicServerURL.startsWith("http://") && !options.publicServerURL.startsWith("https://")) {
6068
throw "publicServerURL should be a valid HTTPS URL starting with https://"

src/ParseServer.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ var batch = require('./batch'),
99
Parse = require('parse/node').Parse,
1010
path = require('path'),
1111
authDataManager = require('./authDataManager');
12-
12+
1313
if (!global._babelPolyfill) {
1414
require('babel-polyfill');
1515
}
@@ -115,6 +115,7 @@ class ParseServer {
115115
liveQuery = {},
116116
sessionLength = 31536000, // 1 Year in seconds
117117
verbose = false,
118+
revokeSessionOnPasswordReset = true,
118119
}) {
119120
// Initialize the node client SDK automatically
120121
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
@@ -186,7 +187,8 @@ class ParseServer {
186187
customPages: customPages,
187188
maxUploadSize: maxUploadSize,
188189
liveQueryController: liveQueryController,
189-
sessionLength : Number(sessionLength),
190+
sessionLength: Number(sessionLength),
191+
revokeSessionOnPasswordReset
190192
});
191193

192194
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability

src/RestWrite.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,7 @@ RestWrite.prototype.createSessionTokenIfNeeded = function() {
420420

421421
// Handles any followup logic
422422
RestWrite.prototype.handleFollowup = function() {
423-
424-
if (this.storage && this.storage['clearSessions']) {
423+
if (this.storage && this.storage['clearSessions'] && this.config.revokeSessionOnPasswordReset) {
425424
var sessionQuery = {
426425
user: {
427426
__type: 'Pointer',

src/cli/cli-definitions.js

+5
Original file line numberDiff line numberDiff line change
@@ -174,5 +174,10 @@ export default {
174174
"verbose": {
175175
env: "VERBOSE",
176176
help: "Set the logging to verbose"
177+
},
178+
"revokeSessionOnPasswordReset": {
179+
env: "PARSE_SERVER_REVOKE_SESSION_ON_PASSWORD_RESET",
180+
help: "When a user changes their password, either through the reset password email or while logged in, all sessions are revoked if this is true. Set to false if you don't want to revoke sessions.",
181+
action: booleanParser
177182
}
178183
};

src/rest.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
var Parse = require('parse/node').Parse;
1111
import cache from './cache';
12-
import Auth from './Auth';
12+
import Auth from './Auth';
1313

1414
var RestQuery = require('./RestQuery');
1515
var RestWrite = require('./RestWrite');
@@ -96,7 +96,6 @@ function create(config, auth, className, restObject) {
9696
// Usually, this is just updatedAt.
9797
function update(config, auth, className, objectId, restObject) {
9898
enforceRoleSecurity('update', className, auth);
99-
10099
return Promise.resolve().then(() => {
101100
if (triggers.getTrigger(className, triggers.Types.beforeSave, config.applicationId) ||
102101
triggers.getTrigger(className, triggers.Types.afterSave, config.applicationId) ||

0 commit comments

Comments
 (0)