feature: User Lockout (#4749)

* Allows masterKey to lock _User object and prevent login with email / password

* Ensure the authData based auth can be locked out as well when accounts is masterKey only
This commit is contained in:
Florent Vilmart
2018-05-16 15:40:02 -04:00
committed by GitHub
parent bfd0c4bf2f
commit ad244d6654
3 changed files with 75 additions and 2 deletions

View File

@@ -213,6 +213,71 @@ describe('Parse.User testing', () => {
}) })
}); });
it('should let masterKey lockout user', (done) => {
const user = new Parse.User();
const ACL = new Parse.ACL();
ACL.setPublicReadAccess(false);
ACL.setPublicWriteAccess(false);
user.setUsername('asdf');
user.setPassword('zxcv');
user.setACL(ACL);
user.signUp().then(() => {
return Parse.User.logIn("asdf", "zxcv");
}).then((user) => {
equal(user.get("username"), "asdf");
// Lock the user down
const ACL = new Parse.ACL();
user.setACL(ACL);
return user.save(null, { useMasterKey: true });
}).then(() => {
expect(user.getACL().getPublicReadAccess()).toBe(false);
return Parse.User.logIn("asdf", "zxcv");
}).then(done.fail).catch((err) => {
expect(err.message).toBe('Invalid username/password.');
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
done();
});
});
it('should be let masterKey lock user out with authData', (done) => {
let objectId;
let sessionToken;
rp.post({
url: 'http://localhost:8378/1/classes/_User',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
json: { key: "value", authData: {anonymous: {id: '00000000-0000-0000-0000-000000000001'}}}
}).then((body) => {
objectId = body.objectId;
sessionToken = body.sessionToken;
expect(sessionToken).toBeDefined();
expect(objectId).toBeDefined();
const user = new Parse.User();
user.id = objectId;
const ACL = new Parse.ACL();
user.setACL(ACL);
return user.save(null, { useMasterKey: true });
}).then(() => {
// update the user
const options = {
url: `http://localhost:8378/1/classes/_User/`,
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-REST-API-Key': 'rest',
},
json: { key: "otherValue", authData: {anonymous: {id: '00000000-0000-0000-0000-000000000001'}}}
}
return rp.post(options);
}).then((res) => {
// Because the user is locked out, this should behave as creating a new user
expect(res.objectId).not.toEqual(objectId);
}).then(done)
.catch(done.fail);
});
it("user login with files", (done) => { it("user login with files", (done) => {
const file = new Parse.File("yolo.txt", [1,2,3], "text/plain"); const file = new Parse.File("yolo.txt", [1,2,3], "text/plain");
file.save().then((file) => { file.save().then((file) => {

View File

@@ -282,7 +282,9 @@ RestWrite.prototype.findUsersWithAuthData = function(authData) {
RestWrite.prototype.handleAuthData = function(authData) { RestWrite.prototype.handleAuthData = function(authData) {
let results; let results;
return this.findUsersWithAuthData(authData).then((r) => { return this.findUsersWithAuthData(authData).then((r) => {
results = r; results = r.filter((user) => {
return !this.auth.isMaster && user.ACL && Object.keys(user.ACL).length > 0;
});
if (results.length > 1) { if (results.length > 1) {
// More than 1 user with the passed id's // More than 1 user with the passed id's
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED, throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
@@ -980,7 +982,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
if (this.query) { if (this.query) {
// Force the user to not lockout // Force the user to not lockout
// Matched with parse.com // Matched with parse.com
if (this.className === '_User' && this.data.ACL) { if (this.className === '_User' && this.data.ACL && this.auth.isMaster !== true) {
this.data.ACL[this.query.objectId] = { read: true, write: true }; this.data.ACL[this.query.objectId] = { read: true, write: true };
} }
// update password timestamp if user password is being changed // update password timestamp if user password is being changed

View File

@@ -114,6 +114,12 @@ export class UsersRouter extends ClassesRouter {
if (!isValidPassword) { if (!isValidPassword) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
} }
// Ensure the user isn't locked out
// A locked out user won't be able to login
// To lock a user out, just set the ACL to `masterKey` only ({}).
if (!req.auth.isMaster && (!user.ACL || Object.keys(user.ACL).length == 0)) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
}
if (req.config.verifyUserEmails && req.config.preventLoginWithUnverifiedEmail && !user.emailVerified) { if (req.config.verifyUserEmails && req.config.preventLoginWithUnverifiedEmail && !user.emailVerified) {
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.'); throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.');
} }