Skip to content

Adds ability to prevent login with unverified emails #2175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
123 changes: 123 additions & 0 deletions spec/ValidationAndPasswordsReset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: '[email protected]',
apiKey: 'k',
domain: 'd',
}),
})
.then(() => {
let user = new Parse.User();
user.setPassword("asdf");
user.setUsername("zxcv");
user.set("email", "[email protected]");
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', '[email protected]');
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: '[email protected]',
apiKey: 'k',
domain: 'd',
}),
})
.then(() => {
let user = new Parse.User();
user.setPassword("asdf");
user.setUsername("zxcv");
user.set("email", "[email protected]");
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,
Expand Down
1 change: 1 addition & 0 deletions src/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/ParseServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class ParseServer {
serverURL = requiredParameter('You must provide a serverURL!'),
maxUploadSize = '20mb',
verifyUserEmails = false,
preventLoginWithUnverifiedEmail = false,
cacheAdapter,
emailAdapter,
publicServerURL,
Expand Down Expand Up @@ -231,6 +232,7 @@ class ParseServer {
hooksController: hooksController,
userController: userController,
verifyUserEmails: verifyUserEmails,
preventLoginWithUnverifiedEmail: preventLoginWithUnverifiedEmail,
allowClientClassCreation: allowClientClassCreation,
authDataManager: authDataManager(oauth, enableAnonymousUsers),
appName: appName,
Expand Down
5 changes: 5 additions & 0 deletions src/Routers/UsersRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {

Expand Down
5 changes: 5 additions & 0 deletions src/cli/cli-definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down