From 3202d51e35f413d86630c4a23bb4b1375a277de1 Mon Sep 17 00:00:00 2001 From: Diwakar Cherukumilli Date: Wed, 29 Jun 2016 17:31:24 -0500 Subject: [PATCH] Adds ability to prevent login with unverified emails --- README.md | 5 + spec/ValidationAndPasswordsReset.spec.js | 123 +++++++++++++++++++++++ src/Config.js | 1 + src/ParseServer.js | 2 + src/Routers/UsersRouter.js | 5 + src/cli/cli-definitions.js | 5 + 6 files changed, 141 insertions(+) diff --git a/README.md b/README.md index c2d73d9510..8cb2602006 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,11 @@ var server = ParseServer({ ...otherOptions, // Enable email verification verifyUserEmails: true, + + // set preventLoginWithUnverifiedEmail to false to allow user to login without verifying their email + // set preventLoginWithUnverifiedEmail to true to prevent user from login if their email is not verified + preventLoginWithUnverifiedEmail: false, // defaults to false + // The public URL of your app. // This will appear in the link that is used to verify email addresses and reset passwords. // Set the mount path as it is in serverURL diff --git a/spec/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js index c7b33fb1cc..7781d486f8 100644 --- a/spec/ValidationAndPasswordsReset.spec.js +++ b/spec/ValidationAndPasswordsReset.spec.js @@ -238,6 +238,129 @@ describe("Custom Pages, Email Verification, Password Reset", () => { }); }); + it_exclude_dbs(['postgres'])('prevents user from login if email is not verified but preventLoginWithUnverifiedEmail is set to true', done => { + reconfigureServer({ + appName: 'test', + publicServerURL: 'http://localhost:1337/1', + verifyUserEmails: true, + preventLoginWithUnverifiedEmail: true, + emailAdapter: MockEmailAdapterWithOptions({ + fromAddress: 'parse@example.com', + apiKey: 'k', + domain: 'd', + }), + }) + .then(() => { + let user = new Parse.User(); + user.setPassword("asdf"); + user.setUsername("zxcv"); + user.set("email", "testInvalidConfig@parse.com"); + user.signUp(null) + .then(user => Parse.User.logIn("zxcv", "asdf")) + .then(result => { + fail('login should have failed'); + done(); + }, error => { + expect(error.message).toEqual('User email is not verified.') + done(); + }); + }) + .catch(error => { + fail(JSON.stringify(error)); + done(); + }); + }); + + it_exclude_dbs(['postgres'])('allows user to login only after user clicks on the link to confirm email address if preventLoginWithUnverifiedEmail is set to true', done => { + var user = new Parse.User(); + var sendEmailOptions; + var emailAdapter = { + sendVerificationEmail: options => { + sendEmailOptions = options; + }, + sendPasswordResetEmail: () => Promise.resolve(), + sendMail: () => {} + } + reconfigureServer({ + appName: 'emailing app', + verifyUserEmails: true, + preventLoginWithUnverifiedEmail: true, + emailAdapter: emailAdapter, + publicServerURL: "http://localhost:8378/1" + }) + .then(() => { + user.setPassword("other-password"); + user.setUsername("user"); + user.set('email', 'user@parse.com'); + return user.signUp(); + }).then(() => { + expect(sendEmailOptions).not.toBeUndefined(); + request.get(sendEmailOptions.link, { + followRedirect: false, + }, (error, response, body) => { + expect(response.statusCode).toEqual(302); + expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=user'); + user.fetch() + .then(() => { + expect(user.get('emailVerified')).toEqual(true); + + Parse.User.logIn("user", "other-password") + .then(user => { + expect(typeof user).toBe('object'); + expect(user.get('emailVerified')).toBe(true); + done(); + }, error => { + fail('login should have succeeded'); + done(); + }); + }, (err) => { + console.error(err); + fail("this should not fail"); + done(); + }).catch((err) => + { + console.error(err); + fail(err); + done(); + }) + }); + }); + }); + + it_exclude_dbs(['postgres'])('allows user to login if email is not verified but preventLoginWithUnverifiedEmail is set to false', done => { + reconfigureServer({ + appName: 'test', + publicServerURL: 'http://localhost:1337/1', + verifyUserEmails: true, + preventLoginWithUnverifiedEmail: false, + emailAdapter: MockEmailAdapterWithOptions({ + fromAddress: 'parse@example.com', + apiKey: 'k', + domain: 'd', + }), + }) + .then(() => { + let user = new Parse.User(); + user.setPassword("asdf"); + user.setUsername("zxcv"); + user.set("email", "testInvalidConfig@parse.com"); + user.signUp(null) + .then(user => Parse.User.logIn("zxcv", "asdf")) + .then(user => { + expect(typeof user).toBe('object'); + expect(user.get('emailVerified')).toBe(false); + done(); + }, error => { + fail('login should have succeeded'); + done(); + }); + }) + .catch(error => { + fail(JSON.stringify(error)); + done(); + }); + }); + it_exclude_dbs(['postgres'])('fails if you include an emailAdapter, set a publicServerURL, but have no appName and send a password reset email', done => { reconfigureServer({ appName: undefined, diff --git a/src/Config.js b/src/Config.js index 23ac9fef11..731cc7aa2f 100644 --- a/src/Config.js +++ b/src/Config.js @@ -36,6 +36,7 @@ export class Config { this.serverURL = cacheInfo.serverURL; this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL); this.verifyUserEmails = cacheInfo.verifyUserEmails; + this.preventLoginWithUnverifiedEmail = cacheInfo.preventLoginWithUnverifiedEmail; this.appName = cacheInfo.appName; this.cacheController = cacheInfo.cacheController; diff --git a/src/ParseServer.js b/src/ParseServer.js index 5c8380018c..9a65111603 100644 --- a/src/ParseServer.js +++ b/src/ParseServer.js @@ -117,6 +117,7 @@ class ParseServer { serverURL = requiredParameter('You must provide a serverURL!'), maxUploadSize = '20mb', verifyUserEmails = false, + preventLoginWithUnverifiedEmail = false, cacheAdapter, emailAdapter, publicServerURL, @@ -231,6 +232,7 @@ class ParseServer { hooksController: hooksController, userController: userController, verifyUserEmails: verifyUserEmails, + preventLoginWithUnverifiedEmail: preventLoginWithUnverifiedEmail, allowClientClassCreation: allowClientClassCreation, authDataManager: authDataManager(oauth, enableAnonymousUsers), appName: appName, diff --git a/src/Routers/UsersRouter.js b/src/Routers/UsersRouter.js index 3e14d4f925..dfddd0c18a 100644 --- a/src/Routers/UsersRouter.js +++ b/src/Routers/UsersRouter.js @@ -83,6 +83,11 @@ export class UsersRouter extends ClassesRouter { throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.'); } user = results[0]; + + if (req.config.verifyUserEmails && req.config.preventLoginWithUnverifiedEmail && !user.emailVerified) { + throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.'); + } + return passwordCrypto.compare(req.body.password, user.password); }).then((correct) => { diff --git a/src/cli/cli-definitions.js b/src/cli/cli-definitions.js index 83d263f162..ff4a3340ed 100644 --- a/src/cli/cli-definitions.js +++ b/src/cli/cli-definitions.js @@ -146,6 +146,11 @@ export default { help: "Enable (or disable) user email validation, defaults to false", action: booleanParser }, + "preventLoginWithUnverifiedEmail": { + env: "PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL", + help: "Prevent user from login if email is not verified and PARSE_SERVER_VERIFY_USER_EMAILS is true, defaults to false", + action: booleanParser + }, "appName": { env: "PARSE_SERVER_APP_NAME", help: "Sets the app name"