Skip to content

Commit 08c63f3

Browse files
cherukumilliflovilmart
authored andcommitted
Adds ability to prevent login with unverified emails (#2175)
1 parent b641712 commit 08c63f3

File tree

6 files changed

+141
-0
lines changed

6 files changed

+141
-0
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ var server = ParseServer({
209209
...otherOptions,
210210
// Enable email verification
211211
verifyUserEmails: true,
212+
213+
// set preventLoginWithUnverifiedEmail to false to allow user to login without verifying their email
214+
// set preventLoginWithUnverifiedEmail to true to prevent user from login if their email is not verified
215+
preventLoginWithUnverifiedEmail: false, // defaults to false
216+
212217
// The public URL of your app.
213218
// This will appear in the link that is used to verify email addresses and reset passwords.
214219
// Set the mount path as it is in serverURL

spec/ValidationAndPasswordsReset.spec.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,129 @@ describe("Custom Pages, Email Verification, Password Reset", () => {
238238
});
239239
});
240240

241+
it_exclude_dbs(['postgres'])('prevents user from login if email is not verified but preventLoginWithUnverifiedEmail is set to true', done => {
242+
reconfigureServer({
243+
appName: 'test',
244+
publicServerURL: 'http://localhost:1337/1',
245+
verifyUserEmails: true,
246+
preventLoginWithUnverifiedEmail: true,
247+
emailAdapter: MockEmailAdapterWithOptions({
248+
fromAddress: '[email protected]',
249+
apiKey: 'k',
250+
domain: 'd',
251+
}),
252+
})
253+
.then(() => {
254+
let user = new Parse.User();
255+
user.setPassword("asdf");
256+
user.setUsername("zxcv");
257+
user.set("email", "[email protected]");
258+
user.signUp(null)
259+
.then(user => Parse.User.logIn("zxcv", "asdf"))
260+
.then(result => {
261+
fail('login should have failed');
262+
done();
263+
}, error => {
264+
expect(error.message).toEqual('User email is not verified.')
265+
done();
266+
});
267+
})
268+
.catch(error => {
269+
fail(JSON.stringify(error));
270+
done();
271+
});
272+
});
273+
274+
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 => {
275+
var user = new Parse.User();
276+
var sendEmailOptions;
277+
var emailAdapter = {
278+
sendVerificationEmail: options => {
279+
sendEmailOptions = options;
280+
},
281+
sendPasswordResetEmail: () => Promise.resolve(),
282+
sendMail: () => {}
283+
}
284+
reconfigureServer({
285+
appName: 'emailing app',
286+
verifyUserEmails: true,
287+
preventLoginWithUnverifiedEmail: true,
288+
emailAdapter: emailAdapter,
289+
publicServerURL: "http://localhost:8378/1"
290+
})
291+
.then(() => {
292+
user.setPassword("other-password");
293+
user.setUsername("user");
294+
user.set('email', '[email protected]');
295+
return user.signUp();
296+
}).then(() => {
297+
expect(sendEmailOptions).not.toBeUndefined();
298+
request.get(sendEmailOptions.link, {
299+
followRedirect: false,
300+
}, (error, response, body) => {
301+
expect(response.statusCode).toEqual(302);
302+
expect(response.body).toEqual('Found. Redirecting to http://localhost:8378/1/apps/verify_email_success.html?username=user');
303+
user.fetch()
304+
.then(() => {
305+
expect(user.get('emailVerified')).toEqual(true);
306+
307+
Parse.User.logIn("user", "other-password")
308+
.then(user => {
309+
expect(typeof user).toBe('object');
310+
expect(user.get('emailVerified')).toBe(true);
311+
done();
312+
}, error => {
313+
fail('login should have succeeded');
314+
done();
315+
});
316+
}, (err) => {
317+
console.error(err);
318+
fail("this should not fail");
319+
done();
320+
}).catch((err) =>
321+
{
322+
console.error(err);
323+
fail(err);
324+
done();
325+
})
326+
});
327+
});
328+
});
329+
330+
it_exclude_dbs(['postgres'])('allows user to login if email is not verified but preventLoginWithUnverifiedEmail is set to false', done => {
331+
reconfigureServer({
332+
appName: 'test',
333+
publicServerURL: 'http://localhost:1337/1',
334+
verifyUserEmails: true,
335+
preventLoginWithUnverifiedEmail: false,
336+
emailAdapter: MockEmailAdapterWithOptions({
337+
fromAddress: '[email protected]',
338+
apiKey: 'k',
339+
domain: 'd',
340+
}),
341+
})
342+
.then(() => {
343+
let user = new Parse.User();
344+
user.setPassword("asdf");
345+
user.setUsername("zxcv");
346+
user.set("email", "[email protected]");
347+
user.signUp(null)
348+
.then(user => Parse.User.logIn("zxcv", "asdf"))
349+
.then(user => {
350+
expect(typeof user).toBe('object');
351+
expect(user.get('emailVerified')).toBe(false);
352+
done();
353+
}, error => {
354+
fail('login should have succeeded');
355+
done();
356+
});
357+
})
358+
.catch(error => {
359+
fail(JSON.stringify(error));
360+
done();
361+
});
362+
});
363+
241364
it_exclude_dbs(['postgres'])('fails if you include an emailAdapter, set a publicServerURL, but have no appName and send a password reset email', done => {
242365
reconfigureServer({
243366
appName: undefined,

src/Config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export class Config {
3636
this.serverURL = cacheInfo.serverURL;
3737
this.publicServerURL = removeTrailingSlash(cacheInfo.publicServerURL);
3838
this.verifyUserEmails = cacheInfo.verifyUserEmails;
39+
this.preventLoginWithUnverifiedEmail = cacheInfo.preventLoginWithUnverifiedEmail;
3940
this.appName = cacheInfo.appName;
4041

4142
this.cacheController = cacheInfo.cacheController;

src/ParseServer.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class ParseServer {
117117
serverURL = requiredParameter('You must provide a serverURL!'),
118118
maxUploadSize = '20mb',
119119
verifyUserEmails = false,
120+
preventLoginWithUnverifiedEmail = false,
120121
cacheAdapter,
121122
emailAdapter,
122123
publicServerURL,
@@ -231,6 +232,7 @@ class ParseServer {
231232
hooksController: hooksController,
232233
userController: userController,
233234
verifyUserEmails: verifyUserEmails,
235+
preventLoginWithUnverifiedEmail: preventLoginWithUnverifiedEmail,
234236
allowClientClassCreation: allowClientClassCreation,
235237
authDataManager: authDataManager(oauth, enableAnonymousUsers),
236238
appName: appName,

src/Routers/UsersRouter.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ export class UsersRouter extends ClassesRouter {
8383
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
8484
}
8585
user = results[0];
86+
87+
if (req.config.verifyUserEmails && req.config.preventLoginWithUnverifiedEmail && !user.emailVerified) {
88+
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.');
89+
}
90+
8691
return passwordCrypto.compare(req.body.password, user.password);
8792
}).then((correct) => {
8893

src/cli/cli-definitions.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ export default {
146146
help: "Enable (or disable) user email validation, defaults to false",
147147
action: booleanParser
148148
},
149+
"preventLoginWithUnverifiedEmail": {
150+
env: "PARSE_SERVER_PREVENT_LOGIN_WITH_UNVERIFIED_EMAIL",
151+
help: "Prevent user from login if email is not verified and PARSE_SERVER_VERIFY_USER_EMAILS is true, defaults to false",
152+
action: booleanParser
153+
},
149154
"appName": {
150155
env: "PARSE_SERVER_APP_NAME",
151156
help: "Sets the app name"

0 commit comments

Comments
 (0)