Skip to content

Commit aef5bb9

Browse files
authored
feature: User Lockout (parse-community#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
1 parent 4af0935 commit aef5bb9

File tree

3 files changed

+75
-2
lines changed

3 files changed

+75
-2
lines changed

spec/ParseUser.spec.js

+65
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,71 @@ describe('Parse.User testing', () => {
213213
})
214214
});
215215

216+
it('should let masterKey lockout user', (done) => {
217+
const user = new Parse.User();
218+
const ACL = new Parse.ACL();
219+
ACL.setPublicReadAccess(false);
220+
ACL.setPublicWriteAccess(false);
221+
user.setUsername('asdf');
222+
user.setPassword('zxcv');
223+
user.setACL(ACL);
224+
user.signUp().then(() => {
225+
return Parse.User.logIn("asdf", "zxcv");
226+
}).then((user) => {
227+
equal(user.get("username"), "asdf");
228+
// Lock the user down
229+
const ACL = new Parse.ACL();
230+
user.setACL(ACL);
231+
return user.save(null, { useMasterKey: true });
232+
}).then(() => {
233+
expect(user.getACL().getPublicReadAccess()).toBe(false);
234+
return Parse.User.logIn("asdf", "zxcv");
235+
}).then(done.fail).catch((err) => {
236+
expect(err.message).toBe('Invalid username/password.');
237+
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
238+
done();
239+
});
240+
});
241+
242+
it('should be let masterKey lock user out with authData', (done) => {
243+
let objectId;
244+
let sessionToken;
245+
246+
rp.post({
247+
url: 'http://localhost:8378/1/classes/_User',
248+
headers: {
249+
'X-Parse-Application-Id': Parse.applicationId,
250+
'X-Parse-REST-API-Key': 'rest',
251+
},
252+
json: { key: "value", authData: {anonymous: {id: '00000000-0000-0000-0000-000000000001'}}}
253+
}).then((body) => {
254+
objectId = body.objectId;
255+
sessionToken = body.sessionToken;
256+
expect(sessionToken).toBeDefined();
257+
expect(objectId).toBeDefined();
258+
const user = new Parse.User();
259+
user.id = objectId;
260+
const ACL = new Parse.ACL();
261+
user.setACL(ACL);
262+
return user.save(null, { useMasterKey: true });
263+
}).then(() => {
264+
// update the user
265+
const options = {
266+
url: `http://localhost:8378/1/classes/_User/`,
267+
headers: {
268+
'X-Parse-Application-Id': Parse.applicationId,
269+
'X-Parse-REST-API-Key': 'rest',
270+
},
271+
json: { key: "otherValue", authData: {anonymous: {id: '00000000-0000-0000-0000-000000000001'}}}
272+
}
273+
return rp.post(options);
274+
}).then((res) => {
275+
// Because the user is locked out, this should behave as creating a new user
276+
expect(res.objectId).not.toEqual(objectId);
277+
}).then(done)
278+
.catch(done.fail);
279+
});
280+
216281
it("user login with files", (done) => {
217282
const file = new Parse.File("yolo.txt", [1,2,3], "text/plain");
218283
file.save().then((file) => {

src/RestWrite.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,9 @@ RestWrite.prototype.findUsersWithAuthData = function(authData) {
282282
RestWrite.prototype.handleAuthData = function(authData) {
283283
let results;
284284
return this.findUsersWithAuthData(authData).then((r) => {
285-
results = r;
285+
results = r.filter((user) => {
286+
return !this.auth.isMaster && user.ACL && Object.keys(user.ACL).length > 0;
287+
});
286288
if (results.length > 1) {
287289
// More than 1 user with the passed id's
288290
throw new Parse.Error(Parse.Error.ACCOUNT_ALREADY_LINKED,
@@ -980,7 +982,7 @@ RestWrite.prototype.runDatabaseOperation = function() {
980982
if (this.query) {
981983
// Force the user to not lockout
982984
// Matched with parse.com
983-
if (this.className === '_User' && this.data.ACL) {
985+
if (this.className === '_User' && this.data.ACL && this.auth.isMaster !== true) {
984986
this.data.ACL[this.query.objectId] = { read: true, write: true };
985987
}
986988
// update password timestamp if user password is being changed

src/Routers/UsersRouter.js

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

0 commit comments

Comments
 (0)