Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add revokeSessionOnPasswordReset option. Closes #1584 #1597

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ For the full list of available options, run `parse-server --help`.

* `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.
* `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.
* `databaseURI` **(required)** - The connection string for your database, i.e. `mongodb://user:pass@host.com/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.
* `databaseURI` **(required)** - The connection string for your database, i.e. `mongodb://user:pass@host.com/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.
* `port` - The default port is 1337, specify this parameter to use a different port.
* `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.
* `cloud` - The absolute path to your cloud code `main.js` file.
Expand All @@ -188,6 +188,7 @@ The client keys used with Parse are no longer necessary with Parse Server. If yo
* `maxUploadSize` - Max file size for uploads. Defaults to 20 MB.
* `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)).
* `sessionLength` - The length of time in seconds that a session should be valid for. Defaults to 31536000 seconds (1 year).
* `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.

##### Email verification and password reset

Expand Down
70 changes: 65 additions & 5 deletions spec/ParseUser.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2115,9 +2115,7 @@ describe('Parse.User testing', () => {
});
});

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

// https://github.com/ParsePlatform/parse-server/issues/1198
it('should cleanup null authData keys ParseUser update', (done) => {
it('should cleanup null authData keys ParseUser update (regression test for #1198)', (done) => {
Parse.Cloud.beforeSave('_User', (req, res) => {
req.object.set('foo', 'bar');
res.success();
Expand Down Expand Up @@ -2347,4 +2344,67 @@ describe('Parse.User testing', () => {
done();
});
});

it('should revoke sessions when converting anonymous user to "normal" user', done => {
request.post({
url: 'http://localhost:8378/1/classes/_User',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
json: {authData: {anonymous: {id: '00000000-0000-0000-0000-000000000001'}}}
}, (err, res, body) => {
Parse.User.become(body.sessionToken)
.then(user => {
let obj = new Parse.Object('TestObject');
obj.setACL(new Parse.ACL(user));
return obj.save()
.then(() => {
// Change password, revoking session
user.set('username', 'no longer anonymous');
user.set('password', 'password');
return user.save()
})
.then(() => obj.fetch())
.catch(error => {
expect(error.code).toEqual(Parse.Error.OBJECT_NOT_FOUND);
done();
});
})
});
});

it('should not revoke session tokens if the server is configures to not revoke session tokens', done => {
setServerConfiguration({
serverURL: 'http://localhost:8378/1',
appId: 'test',
masterKey: 'test',
cloud: './spec/cloud/main.js',
revokeSessionOnPasswordReset: false,
})
request.post({
url: 'http://localhost:8378/1/classes/_User',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
json: {authData: {anonymous: {id: '00000000-0000-0000-0000-000000000001'}}}
}, (err, res, body) => {
Parse.User.become(body.sessionToken)
.then(user => {
let obj = new Parse.Object('TestObject');
obj.setACL(new Parse.ACL(user));
return obj.save()
.then(() => {
// Change password, revoking session
user.set('username', 'no longer anonymous');
user.set('password', 'password');
return user.save()
})
.then(() => obj.fetch())
// fetch should succeed as we still have our session token
.then(done, fail);
})
});
})
});
4 changes: 2 additions & 2 deletions spec/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ var defaultConfiguration = {
myoauth: {
module: path.resolve(__dirname, "myoauth") // relative path as it's run from src
}
}
},
};

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

var currentConfiguration;
// Allows testing specific configurations of Parse Server
var setServerConfiguration = configuration => {
const setServerConfiguration = configuration => {
// the configuration hasn't changed
if (configuration === currentConfiguration) {
return;
Expand Down
5 changes: 5 additions & 0 deletions spec/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,4 +333,9 @@ describe('server', () => {
})).toThrow('Session length must be a value greater than 0.');
done();
})

it('fails if you try to set revokeSessionOnPasswordReset to non-boolean', done => {
expect(() => setServerConfiguration({ revokeSessionOnPasswordReset: 'non-bool' })).toThrow();
done();
});
});
14 changes: 11 additions & 3 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,20 @@ export class Config {
this.liveQueryController = cacheInfo.liveQueryController;
this.sessionLength = cacheInfo.sessionLength;
this.generateSessionExpiresAt = this.generateSessionExpiresAt.bind(this);
this.revokeSessionOnPasswordReset = cacheInfo.revokeSessionOnPasswordReset;
}

static validate(options) {
this.validateEmailConfiguration({verifyUserEmails: options.verifyUserEmails,
appName: options.appName,
publicServerURL: options.publicServerURL})
this.validateEmailConfiguration({
verifyUserEmails: options.verifyUserEmails,
appName: options.appName,
publicServerURL: options.publicServerURL
})

if (typeof options.revokeSessionOnPasswordReset !== 'boolean') {
throw 'revokeSessionOnPasswordReset must be a boolean value';
}

if (options.publicServerURL) {
if (!options.publicServerURL.startsWith("http://") && !options.publicServerURL.startsWith("https://")) {
throw "publicServerURL should be a valid HTTPS URL starting with https://"
Expand Down
6 changes: 4 additions & 2 deletions src/ParseServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ var batch = require('./batch'),
Parse = require('parse/node').Parse,
path = require('path'),
authDataManager = require('./authDataManager');

if (!global._babelPolyfill) {
require('babel-polyfill');
}
Expand Down Expand Up @@ -115,6 +115,7 @@ class ParseServer {
liveQuery = {},
sessionLength = 31536000, // 1 Year in seconds
verbose = false,
revokeSessionOnPasswordReset = true,
}) {
// Initialize the node client SDK automatically
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
Expand Down Expand Up @@ -186,7 +187,8 @@ class ParseServer {
customPages: customPages,
maxUploadSize: maxUploadSize,
liveQueryController: liveQueryController,
sessionLength : Number(sessionLength),
sessionLength: Number(sessionLength),
revokeSessionOnPasswordReset
});

// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
Expand Down
3 changes: 1 addition & 2 deletions src/RestWrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -420,8 +420,7 @@ RestWrite.prototype.createSessionTokenIfNeeded = function() {

// Handles any followup logic
RestWrite.prototype.handleFollowup = function() {

if (this.storage && this.storage['clearSessions']) {
if (this.storage && this.storage['clearSessions'] && this.config.revokeSessionOnPasswordReset) {
var sessionQuery = {
user: {
__type: 'Pointer',
Expand Down
5 changes: 5 additions & 0 deletions src/cli/cli-definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,5 +174,10 @@ export default {
"verbose": {
env: "VERBOSE",
help: "Set the logging to verbose"
},
"revokeSessionOnPasswordReset": {
env: "PARSE_SERVER_REVOKE_SESSION_ON_PASSWORD_RESET",
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.",
action: booleanParser
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 nice touch you thought about it :)

}
};
3 changes: 1 addition & 2 deletions src/rest.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

var Parse = require('parse/node').Parse;
import cache from './cache';
import Auth from './Auth';
import Auth from './Auth';

var RestQuery = require('./RestQuery');
var RestWrite = require('./RestWrite');
Expand Down Expand Up @@ -96,7 +96,6 @@ function create(config, auth, className, restObject) {
// Usually, this is just updatedAt.
function update(config, auth, className, objectId, restObject) {
enforceRoleSecurity('update', className, auth);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why that removal? Checked after?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accident. Good catch. Somewhat worrying that it didn't cause any tests to fail though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uhm.. You're right :(

Copy link
Contributor

@flovilmart flovilmart Apr 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But, the enforceRoleSecurity is a no-op when className != _Installation and action is 'update'


return Promise.resolve().then(() => {
if (triggers.getTrigger(className, triggers.Types.beforeSave, config.applicationId) ||
triggers.getTrigger(className, triggers.Types.afterSave, config.applicationId) ||
Expand Down