* Add revokeSessionOnPasswordReset option * Fix nits
This commit is contained in:
@@ -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.
|
* `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)).
|
* `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).
|
* `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
|
##### Email verification and password reset
|
||||||
|
|
||||||
|
|||||||
@@ -2115,9 +2115,7 @@ describe('Parse.User testing', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sometimes the authData still has null on that keys
|
it('should cleanup null authData keys (regression test for #935)', (done) => {
|
||||||
// https://github.com/ParsePlatform/parse-server/issues/935
|
|
||||||
it('should cleanup null authData keys', (done) => {
|
|
||||||
let database = new Config(Parse.applicationId).database;
|
let database = new Config(Parse.applicationId).database;
|
||||||
database.create('_User', {
|
database.create('_User', {
|
||||||
username: 'user',
|
username: 'user',
|
||||||
@@ -2151,8 +2149,7 @@ describe('Parse.User testing', () => {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://github.com/ParsePlatform/parse-server/issues/1198
|
it('should cleanup null authData keys ParseUser update (regression test for #1198)', (done) => {
|
||||||
it('should cleanup null authData keys ParseUser update', (done) => {
|
|
||||||
Parse.Cloud.beforeSave('_User', (req, res) => {
|
Parse.Cloud.beforeSave('_User', (req, res) => {
|
||||||
req.object.set('foo', 'bar');
|
req.object.set('foo', 'bar');
|
||||||
res.success();
|
res.success();
|
||||||
@@ -2347,4 +2344,67 @@ describe('Parse.User testing', () => {
|
|||||||
done();
|
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);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ var defaultConfiguration = {
|
|||||||
myoauth: {
|
myoauth: {
|
||||||
module: path.resolve(__dirname, "myoauth") // relative path as it's run from src
|
module: path.resolve(__dirname, "myoauth") // relative path as it's run from src
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set up a default API server for testing with default configuration.
|
// Set up a default API server for testing with default configuration.
|
||||||
@@ -54,7 +54,7 @@ delete defaultConfiguration.cloud;
|
|||||||
|
|
||||||
var currentConfiguration;
|
var currentConfiguration;
|
||||||
// Allows testing specific configurations of Parse Server
|
// Allows testing specific configurations of Parse Server
|
||||||
var setServerConfiguration = configuration => {
|
const setServerConfiguration = configuration => {
|
||||||
// the configuration hasn't changed
|
// the configuration hasn't changed
|
||||||
if (configuration === currentConfiguration) {
|
if (configuration === currentConfiguration) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -333,4 +333,9 @@ describe('server', () => {
|
|||||||
})).toThrow('Session length must be a value greater than 0.');
|
})).toThrow('Session length must be a value greater than 0.');
|
||||||
done();
|
done();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('fails if you try to set revokeSessionOnPasswordReset to non-boolean', done => {
|
||||||
|
expect(() => setServerConfiguration({ revokeSessionOnPasswordReset: 'non-bool' })).toThrow();
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,12 +49,20 @@ export class Config {
|
|||||||
this.liveQueryController = cacheInfo.liveQueryController;
|
this.liveQueryController = cacheInfo.liveQueryController;
|
||||||
this.sessionLength = cacheInfo.sessionLength;
|
this.sessionLength = cacheInfo.sessionLength;
|
||||||
this.generateSessionExpiresAt = this.generateSessionExpiresAt.bind(this);
|
this.generateSessionExpiresAt = this.generateSessionExpiresAt.bind(this);
|
||||||
|
this.revokeSessionOnPasswordReset = cacheInfo.revokeSessionOnPasswordReset;
|
||||||
}
|
}
|
||||||
|
|
||||||
static validate(options) {
|
static validate(options) {
|
||||||
this.validateEmailConfiguration({verifyUserEmails: options.verifyUserEmails,
|
this.validateEmailConfiguration({
|
||||||
appName: options.appName,
|
verifyUserEmails: options.verifyUserEmails,
|
||||||
publicServerURL: options.publicServerURL})
|
appName: options.appName,
|
||||||
|
publicServerURL: options.publicServerURL
|
||||||
|
})
|
||||||
|
|
||||||
|
if (typeof options.revokeSessionOnPasswordReset !== 'boolean') {
|
||||||
|
throw 'revokeSessionOnPasswordReset must be a boolean value';
|
||||||
|
}
|
||||||
|
|
||||||
if (options.publicServerURL) {
|
if (options.publicServerURL) {
|
||||||
if (!options.publicServerURL.startsWith("http://") && !options.publicServerURL.startsWith("https://")) {
|
if (!options.publicServerURL.startsWith("http://") && !options.publicServerURL.startsWith("https://")) {
|
||||||
throw "publicServerURL should be a valid HTTPS URL starting with https://"
|
throw "publicServerURL should be a valid HTTPS URL starting with https://"
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ class ParseServer {
|
|||||||
liveQuery = {},
|
liveQuery = {},
|
||||||
sessionLength = 31536000, // 1 Year in seconds
|
sessionLength = 31536000, // 1 Year in seconds
|
||||||
verbose = false,
|
verbose = false,
|
||||||
|
revokeSessionOnPasswordReset = true,
|
||||||
}) {
|
}) {
|
||||||
// Initialize the node client SDK automatically
|
// Initialize the node client SDK automatically
|
||||||
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
|
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
|
||||||
@@ -186,7 +187,8 @@ class ParseServer {
|
|||||||
customPages: customPages,
|
customPages: customPages,
|
||||||
maxUploadSize: maxUploadSize,
|
maxUploadSize: maxUploadSize,
|
||||||
liveQueryController: liveQueryController,
|
liveQueryController: liveQueryController,
|
||||||
sessionLength : Number(sessionLength),
|
sessionLength: Number(sessionLength),
|
||||||
|
revokeSessionOnPasswordReset
|
||||||
});
|
});
|
||||||
|
|
||||||
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
|
// To maintain compatibility. TODO: Remove in some version that breaks backwards compatability
|
||||||
|
|||||||
@@ -420,8 +420,7 @@ RestWrite.prototype.createSessionTokenIfNeeded = function() {
|
|||||||
|
|
||||||
// Handles any followup logic
|
// Handles any followup logic
|
||||||
RestWrite.prototype.handleFollowup = function() {
|
RestWrite.prototype.handleFollowup = function() {
|
||||||
|
if (this.storage && this.storage['clearSessions'] && this.config.revokeSessionOnPasswordReset) {
|
||||||
if (this.storage && this.storage['clearSessions']) {
|
|
||||||
var sessionQuery = {
|
var sessionQuery = {
|
||||||
user: {
|
user: {
|
||||||
__type: 'Pointer',
|
__type: 'Pointer',
|
||||||
|
|||||||
@@ -174,5 +174,10 @@ export default {
|
|||||||
"verbose": {
|
"verbose": {
|
||||||
env: "VERBOSE",
|
env: "VERBOSE",
|
||||||
help: "Set the logging to 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
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
var Parse = require('parse/node').Parse;
|
var Parse = require('parse/node').Parse;
|
||||||
import cache from './cache';
|
import cache from './cache';
|
||||||
import Auth from './Auth';
|
import Auth from './Auth';
|
||||||
|
|
||||||
var RestQuery = require('./RestQuery');
|
var RestQuery = require('./RestQuery');
|
||||||
var RestWrite = require('./RestWrite');
|
var RestWrite = require('./RestWrite');
|
||||||
@@ -96,7 +96,6 @@ function create(config, auth, className, restObject) {
|
|||||||
// Usually, this is just updatedAt.
|
// Usually, this is just updatedAt.
|
||||||
function update(config, auth, className, objectId, restObject) {
|
function update(config, auth, className, objectId, restObject) {
|
||||||
enforceRoleSecurity('update', className, auth);
|
enforceRoleSecurity('update', className, auth);
|
||||||
|
|
||||||
return Promise.resolve().then(() => {
|
return Promise.resolve().then(() => {
|
||||||
if (triggers.getTrigger(className, triggers.Types.beforeSave, config.applicationId) ||
|
if (triggers.getTrigger(className, triggers.Types.beforeSave, config.applicationId) ||
|
||||||
triggers.getTrigger(className, triggers.Types.afterSave, config.applicationId) ||
|
triggers.getTrigger(className, triggers.Types.afterSave, config.applicationId) ||
|
||||||
|
|||||||
Reference in New Issue
Block a user