Skip to content
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

fix: Conditional email verification not working in some cases if verifyUserEmails, preventLoginWithUnverifiedEmail set to functions #8838

Merged
2 changes: 1 addition & 1 deletion spec/EmailVerificationToken.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ describe('Email Verification Token Expiration: ', () => {
await user2.signUp();
expect(user2.getSessionToken()).toBeUndefined();
expect(sendEmailOptions).toBeDefined();
expect(verifySpy).toHaveBeenCalledTimes(4);
expect(verifySpy).toHaveBeenCalledTimes(5);
});

it('can conditionally send user email verification', async () => {
Expand Down
25 changes: 25 additions & 0 deletions spec/ValidationAndPasswordsReset.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,31 @@ describe('Custom Pages, Email Verification, Password Reset', () => {
});
});

it('prevents user from signup and login if email is not verified and preventLoginWithUnverifiedEmail is set to function returning true', async () => {
await reconfigureServer({
appName: 'test',
publicServerURL: 'http://localhost:1337/1',
verifyUserEmails: async () => true,
preventLoginWithUnverifiedEmail: async () => true,
preventSignupWithUnverifiedEmail: true,
emailAdapter: MockEmailAdapterWithOptions({
fromAddress: '[email protected]',
apiKey: 'k',
domain: 'd',
}),
});

const user = new Parse.User();
user.setPassword('asdf');
user.setUsername('zxcv');
user.set('email', '[email protected]');
const signupRes = await user.signUp(null).catch(e => e);
expect(signupRes.message).toEqual('User email is not verified.');

const loginRes = await Parse.User.logIn('zxcv', 'asdf').catch(e => e);
expect(loginRes.message).toEqual('User email is not verified.');
});

it('allows user to login only after user clicks on the link to confirm email address if preventLoginWithUnverifiedEmail is set to true', async () => {
let sendEmailOptions;
const emailAdapter = {
Expand Down
9 changes: 4 additions & 5 deletions src/Controllers/UserController.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,10 @@ export class UserController extends AdaptableController {
}

async setEmailVerifyToken(user, req, storage = {}) {
let shouldSendEmail = this.shouldVerifyEmails;
if (typeof shouldSendEmail === 'function') {
const response = await Promise.resolve(shouldSendEmail(req));
shouldSendEmail = response !== false;
}
const shouldSendEmail =
this.shouldVerifyEmails === true ||
(typeof this.shouldVerifyEmails === 'function' &&
(await Promise.resolve(this.shouldVerifyEmails(req))) === true);
if (!shouldSendEmail) {
return false;
}
Expand Down
44 changes: 19 additions & 25 deletions src/RestWrite.js
Original file line number Diff line number Diff line change
Expand Up @@ -930,31 +930,25 @@ RestWrite.prototype.createSessionTokenIfNeeded = async function () {
if (this.auth.user && this.data.authData) {
return;
}
if (
!this.storage.authProvider && // signup call, with
this.config.preventLoginWithUnverifiedEmail === true && // no login without verification
this.config.verifyUserEmails
) {
// verification is on
this.storage.rejectSignup = true;
return;
}
if (!this.storage.authProvider && this.config.verifyUserEmails) {
let shouldPreventUnverifedLogin = this.config.preventLoginWithUnverifiedEmail;
if (typeof this.config.preventLoginWithUnverifiedEmail === 'function') {
const { originalObject, updatedObject } = this.buildParseObjects();
const request = {
original: originalObject,
object: updatedObject,
master: this.auth.isMaster,
ip: this.config.ip,
installationId: this.auth.installationId,
};
shouldPreventUnverifedLogin = await Promise.resolve(
this.config.preventLoginWithUnverifiedEmail(request)
);
}
if (shouldPreventUnverifedLogin === true) {
// If sign-up call
if (!this.storage.authProvider) {
// Create request object for verification functions
const { originalObject, updatedObject } = this.buildParseObjects();
const request = {
original: originalObject,
object: updatedObject,
master: this.auth.isMaster,
ip: this.config.ip,
installationId: this.auth.installationId,
};
// 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 () => this.config.verifyUserEmails === true || (typeof this.config.verifyUserEmails === 'function' && await Promise.resolve(this.config.verifyUserEmails(request)) === true);
const preventLoginWithUnverifiedEmail = async () => this.config.preventLoginWithUnverifiedEmail === true || (typeof this.config.preventLoginWithUnverifiedEmail === 'function' && await Promise.resolve(this.config.preventLoginWithUnverifiedEmail(request)) === true);
// If verification is required
if (await verifyUserEmails() && await preventLoginWithUnverifiedEmail()) {
this.storage.rejectSignup = true;
return;
}
}
Expand Down
19 changes: 13 additions & 6 deletions src/Routers/UsersRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class UsersRouter extends ClassesRouter {
const accountLockoutPolicy = new AccountLockout(user, req.config);
return accountLockoutPolicy.handleLoginAttempt(isValidPassword);
})
.then(() => {
.then(async () => {
if (!isValidPassword) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
}
Expand All @@ -137,11 +137,18 @@ export class UsersRouter extends ClassesRouter {
if (!req.auth.isMaster && user.ACL && Object.keys(user.ACL).length == 0) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Invalid username/password.');
}
if (
req.config.verifyUserEmails &&
req.config.preventLoginWithUnverifiedEmail &&
!user.emailVerified
) {
// Create request object for verification functions
const request = {
master: req.auth.isMaster,
ip: req.config.ip,
installationId: req.auth.installationId,
};
// 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.');
}

Expand Down
Loading