diff --git a/spec/VerifyUserPassword.spec.js b/spec/VerifyUserPassword.spec.js index eef2485815..3d15a25e15 100644 --- a/spec/VerifyUserPassword.spec.js +++ b/spec/VerifyUserPassword.spec.js @@ -585,4 +585,83 @@ describe('Verify User Password', () => { done(); }); }); + + it('verify password of user with unverified email with master key and ignoreEmailVerification=true', async () => { + await reconfigureServer({ + publicServerURL: 'http://localhost:8378/', + appName: 'emailVerify', + verifyUserEmails: true, + preventLoginWithUnverifiedEmail: true, + emailAdapter: MockEmailAdapterWithOptions({ + fromAddress: 'parse@example.com', + apiKey: 'k', + domain: 'd', + }), + }); + + const user = new Parse.User(); + user.setUsername('user'); + user.setPassword('pass'); + user.setEmail('test@example.com'); + await user.signUp(); + + const { data: res } = await request({ + method: 'POST', + url: Parse.serverURL + '/verifyPassword', + headers: { + 'X-Parse-Master-Key': Parse.masterKey, + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + 'Content-Type': 'application/json', + }, + body: { + username: 'user', + password: 'pass', + ignoreEmailVerification: true, + }, + json: true, + }); + expect(res.objectId).toBe(user.id); + expect(Object.prototype.hasOwnProperty.call(res, 'sessionToken')).toEqual(false); + expect(Object.prototype.hasOwnProperty.call(res, 'password')).toEqual(false); + }); + + it('fails to verify password of user with unverified email with master key and ignoreEmailVerification=false', async () => { + await reconfigureServer({ + publicServerURL: 'http://localhost:8378/', + appName: 'emailVerify', + verifyUserEmails: true, + preventLoginWithUnverifiedEmail: true, + emailAdapter: MockEmailAdapterWithOptions({ + fromAddress: 'parse@example.com', + apiKey: 'k', + domain: 'd', + }), + }); + + const user = new Parse.User(); + user.setUsername('user'); + user.setPassword('pass'); + user.setEmail('test@example.com'); + await user.signUp(); + + const res = await request({ + method: 'POST', + url: Parse.serverURL + '/verifyPassword', + headers: { + 'X-Parse-Master-Key': Parse.masterKey, + 'X-Parse-Application-Id': Parse.applicationId, + 'X-Parse-REST-API-Key': 'rest', + 'Content-Type': 'application/json', + }, + body: { + username: 'user', + password: 'pass', + ignoreEmailVerification: false, + }, + json: true, + }).catch(e => e); + expect(res.status).toBe(400); + expect(res.text).toMatch(/User email is not verified/); + }); }); diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index e6f0c7db3a..8af8dc5434 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -75,7 +75,7 @@ export class UsersRouter extends ClassesRouter { ) { payload = req.query; } - const { username, email, password } = payload; + const { username, email, password, ignoreEmailVerification } = payload; // TODO: use the right error codes / descriptions. if (!username && !email) { @@ -144,13 +144,18 @@ export class UsersRouter extends ClassesRouter { installationId: req.auth.installationId, object: Parse.User.fromJSON(Object.assign({ className: '_User' }, user)), }; - // Get verification conditions which can be booleans or functions; the purpose of this async/await - // structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the - // conditional statement below, as a developer may decide to execute expensive operations in them - const verifyUserEmails = async () => req.config.verifyUserEmails === true || (typeof req.config.verifyUserEmails === 'function' && await Promise.resolve(req.config.verifyUserEmails(request)) === true); - const preventLoginWithUnverifiedEmail = async () => req.config.preventLoginWithUnverifiedEmail === true || (typeof req.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(req.config.preventLoginWithUnverifiedEmail(request)) === true); - if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail() && !user.emailVerified) { - throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.'); + + // If request doesn't use master or maintenance key with ignoring email verification + if (!((req.auth.isMaster || req.auth.isMaintenance) && ignoreEmailVerification)) { + + // Get verification conditions which can be booleans or functions; the purpose of this async/await + // structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the + // conditional statement below, as a developer may decide to execute expensive operations in them + const verifyUserEmails = async () => req.config.verifyUserEmails === true || (typeof req.config.verifyUserEmails === 'function' && await Promise.resolve(req.config.verifyUserEmails(request)) === true); + const preventLoginWithUnverifiedEmail = async () => req.config.preventLoginWithUnverifiedEmail === true || (typeof req.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(req.config.preventLoginWithUnverifiedEmail(request)) === true); + if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail() && !user.emailVerified) { + throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.'); + } } this._sanitizeAuthData(user); @@ -658,6 +663,9 @@ export class UsersRouter extends ClassesRouter { this.route('GET', '/verifyPassword', req => { return this.handleVerifyPassword(req); }); + this.route('POST', '/verifyPassword', req => { + return this.handleVerifyPassword(req); + }); this.route('POST', '/challenge', req => { return this.handleChallenge(req); });