Skip to content

Commit 8e7a6b1

Browse files
authored
fix: Conditional email verification not working in some cases if verifyUserEmails, preventLoginWithUnverifiedEmail set to functions (#8838)
1 parent f9dde4a commit 8e7a6b1

File tree

5 files changed

+62
-37
lines changed

5 files changed

+62
-37
lines changed

spec/EmailVerificationToken.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ describe('Email Verification Token Expiration: ', () => {
389389
await user2.signUp();
390390
expect(user2.getSessionToken()).toBeUndefined();
391391
expect(sendEmailOptions).toBeDefined();
392-
expect(verifySpy).toHaveBeenCalledTimes(4);
392+
expect(verifySpy).toHaveBeenCalledTimes(5);
393393
});
394394

395395
it('can conditionally send user email verification', async () => {

spec/ValidationAndPasswordsReset.spec.js

+25
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,31 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
242242
});
243243
});
244244

245+
it('prevents user from signup and login if email is not verified and preventLoginWithUnverifiedEmail is set to function returning true', async () => {
246+
await reconfigureServer({
247+
appName: 'test',
248+
publicServerURL: 'http://localhost:1337/1',
249+
verifyUserEmails: async () => true,
250+
preventLoginWithUnverifiedEmail: async () => true,
251+
preventSignupWithUnverifiedEmail: true,
252+
emailAdapter: MockEmailAdapterWithOptions({
253+
fromAddress: '[email protected]',
254+
apiKey: 'k',
255+
domain: 'd',
256+
}),
257+
});
258+
259+
const user = new Parse.User();
260+
user.setPassword('asdf');
261+
user.setUsername('zxcv');
262+
user.set('email', '[email protected]');
263+
const signupRes = await user.signUp(null).catch(e => e);
264+
expect(signupRes.message).toEqual('User email is not verified.');
265+
266+
const loginRes = await Parse.User.logIn('zxcv', 'asdf').catch(e => e);
267+
expect(loginRes.message).toEqual('User email is not verified.');
268+
});
269+
245270
it('allows user to login only after user clicks on the link to confirm email address if preventLoginWithUnverifiedEmail is set to true', async () => {
246271
let sendEmailOptions;
247272
const emailAdapter = {

src/Controllers/UserController.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,10 @@ export class UserController extends AdaptableController {
3636
}
3737

3838
async setEmailVerifyToken(user, req, storage = {}) {
39-
let shouldSendEmail = this.shouldVerifyEmails;
40-
if (typeof shouldSendEmail === 'function') {
41-
const response = await Promise.resolve(shouldSendEmail(req));
42-
shouldSendEmail = response !== false;
43-
}
39+
const shouldSendEmail =
40+
this.shouldVerifyEmails === true ||
41+
(typeof this.shouldVerifyEmails === 'function' &&
42+
(await Promise.resolve(this.shouldVerifyEmails(req))) === true);
4443
if (!shouldSendEmail) {
4544
return false;
4645
}

src/RestWrite.js

+19-25
Original file line numberDiff line numberDiff line change
@@ -930,31 +930,25 @@ RestWrite.prototype.createSessionTokenIfNeeded = async function () {
930930
if (this.auth.user && this.data.authData) {
931931
return;
932932
}
933-
if (
934-
!this.storage.authProvider && // signup call, with
935-
this.config.preventLoginWithUnverifiedEmail === true && // no login without verification
936-
this.config.verifyUserEmails
937-
) {
938-
// verification is on
939-
this.storage.rejectSignup = true;
940-
return;
941-
}
942-
if (!this.storage.authProvider && this.config.verifyUserEmails) {
943-
let shouldPreventUnverifedLogin = this.config.preventLoginWithUnverifiedEmail;
944-
if (typeof this.config.preventLoginWithUnverifiedEmail === 'function') {
945-
const { originalObject, updatedObject } = this.buildParseObjects();
946-
const request = {
947-
original: originalObject,
948-
object: updatedObject,
949-
master: this.auth.isMaster,
950-
ip: this.config.ip,
951-
installationId: this.auth.installationId,
952-
};
953-
shouldPreventUnverifedLogin = await Promise.resolve(
954-
this.config.preventLoginWithUnverifiedEmail(request)
955-
);
956-
}
957-
if (shouldPreventUnverifedLogin === true) {
933+
// If sign-up call
934+
if (!this.storage.authProvider) {
935+
// Create request object for verification functions
936+
const { originalObject, updatedObject } = this.buildParseObjects();
937+
const request = {
938+
original: originalObject,
939+
object: updatedObject,
940+
master: this.auth.isMaster,
941+
ip: this.config.ip,
942+
installationId: this.auth.installationId,
943+
};
944+
// Get verification conditions which can be booleans or functions; the purpose of this async/await
945+
// structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the
946+
// conditional statement below, as a developer may decide to execute expensive operations in them
947+
const verifyUserEmails = async () => this.config.verifyUserEmails === true || (typeof this.config.verifyUserEmails === 'function' && await Promise.resolve(this.config.verifyUserEmails(request)) === true);
948+
const preventLoginWithUnverifiedEmail = async () => this.config.preventLoginWithUnverifiedEmail === true || (typeof this.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(this.config.preventLoginWithUnverifiedEmail(request)) === true);
949+
// If verification is required
950+
if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail()) {
951+
this.storage.rejectSignup = true;
958952
return;
959953
}
960954
}

src/Routers/UsersRouter.js

+13-6
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export class UsersRouter extends ClassesRouter {
126126
const accountLockoutPolicy = new AccountLockout(user, req.config);
127127
return accountLockoutPolicy.handleLoginAttempt(isValidPassword);
128128
})
129-
.then(() => {
129+
.then(async () => {
130130
if (!isValidPassword) {
131131
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
132132
}
@@ -137,11 +137,18 @@ export class UsersRouter extends ClassesRouter {
137137
if (!req.auth.isMaster && user.ACL && Object.keys(user.ACL).length == 0) {
138138
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
139139
}
140-
if (
141-
req.config.verifyUserEmails &&
142-
req.config.preventLoginWithUnverifiedEmail &&
143-
!user.emailVerified
144-
) {
140+
// Create request object for verification functions
141+
const request = {
142+
master: req.auth.isMaster,
143+
ip: req.config.ip,
144+
installationId: req.auth.installationId,
145+
};
146+
// Get verification conditions which can be booleans or functions; the purpose of this async/await
147+
// structure is to avoid unnecessarily executing subsequent functions if previous ones fail in the
148+
// conditional statement below, as a developer may decide to execute expensive operations in them
149+
const verifyUserEmails = async () => req.config.verifyUserEmails === true || (typeof req.config.verifyUserEmails === 'function' && await Promise.resolve(req.config.verifyUserEmails(request)) === true);
150+
const preventLoginWithUnverifiedEmail = async () => req.config.preventLoginWithUnverifiedEmail === true || (typeof req.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(req.config.preventLoginWithUnverifiedEmail(request)) === true);
151+
if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail() && !user.emailVerified) {
145152
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, 'User email is not verified.');
146153
}
147154

0 commit comments

Comments
 (0)