feat: Add conditional email verification via dynamic Parse Server options verifyUserEmails, sendUserEmailVerification that now accept functions (#8425)

This commit is contained in:
Daniel
2023-06-20 20:10:25 +10:00
committed by GitHub
parent 3710da7379
commit 44acd6d9ed
11 changed files with 341 additions and 82 deletions

View File

@@ -255,7 +255,16 @@ function inject(t, list) {
props.push(t.objectProperty(t.stringLiteral('action'), action)); props.push(t.objectProperty(t.stringLiteral('action'), action));
} }
if (elt.defaultValue) { if (elt.defaultValue) {
const parsedValue = parseDefaultValue(elt, elt.defaultValue, t); let parsedValue = parseDefaultValue(elt, elt.defaultValue, t);
if (!parsedValue) {
for (const type of elt.typeAnnotation.types) {
elt.type = type.type;
parsedValue = parseDefaultValue(elt, elt.defaultValue, t);
if (parsedValue) {
break;
}
}
}
if (parsedValue) { if (parsedValue) {
props.push(t.objectProperty(t.stringLiteral('default'), parsedValue)); props.push(t.objectProperty(t.stringLiteral('default'), parsedValue));
} else { } else {

View File

@@ -288,6 +288,184 @@ describe('Email Verification Token Expiration: ', () => {
}); });
}); });
it('can conditionally send emails', async () => {
let sendEmailOptions;
const emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
};
const verifyUserEmails = {
method(req) {
expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip']);
return false;
},
};
const verifySpy = spyOn(verifyUserEmails, 'method').and.callThrough();
await reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: verifyUserEmails.method,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1',
});
const beforeSave = {
method(req) {
req.object.set('emailVerified', true);
},
};
const saveSpy = spyOn(beforeSave, 'method').and.callThrough();
const emailSpy = spyOn(emailAdapter, 'sendVerificationEmail').and.callThrough();
Parse.Cloud.beforeSave(Parse.User, beforeSave.method);
const user = new Parse.User();
user.setUsername('sets_email_verify_token_expires_at');
user.setPassword('expiringToken');
user.set('email', 'user@example.com');
await user.signUp();
const config = Config.get('test');
const results = await config.database.find(
'_User',
{
username: 'sets_email_verify_token_expires_at',
},
{},
Auth.maintenance(config)
);
expect(results.length).toBe(1);
const user_data = results[0];
expect(typeof user_data).toBe('object');
expect(user_data.emailVerified).toEqual(true);
expect(user_data._email_verify_token).toBeUndefined();
expect(user_data._email_verify_token_expires_at).toBeUndefined();
expect(emailSpy).not.toHaveBeenCalled();
expect(saveSpy).toHaveBeenCalled();
expect(sendEmailOptions).toBeUndefined();
expect(verifySpy).toHaveBeenCalled();
});
it('can conditionally send emails and allow conditional login', async () => {
let sendEmailOptions;
const emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
};
const verifyUserEmails = {
method(req) {
expect(Object.keys(req)).toEqual(['original', 'object', 'master', 'ip']);
if (req.object.get('username') === 'no_email') {
return false;
}
return true;
},
};
const verifySpy = spyOn(verifyUserEmails, 'method').and.callThrough();
await reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: verifyUserEmails.method,
preventLoginWithUnverifiedEmail: verifyUserEmails.method,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1',
});
const user = new Parse.User();
user.setUsername('no_email');
user.setPassword('expiringToken');
user.set('email', 'user@example.com');
await user.signUp();
expect(sendEmailOptions).toBeUndefined();
expect(user.getSessionToken()).toBeDefined();
expect(verifySpy).toHaveBeenCalledTimes(2);
const user2 = new Parse.User();
user2.setUsername('email');
user2.setPassword('expiringToken');
user2.set('email', 'user2@example.com');
await user2.signUp();
expect(user2.getSessionToken()).toBeUndefined();
expect(sendEmailOptions).toBeDefined();
expect(verifySpy).toHaveBeenCalledTimes(4);
});
it('can conditionally send user email verification', async () => {
const emailAdapter = {
sendVerificationEmail: () => {},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
};
const sendVerificationEmail = {
method(req) {
expect(req.user).toBeDefined();
expect(req.master).toBeDefined();
return false;
},
};
const sendSpy = spyOn(sendVerificationEmail, 'method').and.callThrough();
await reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1',
sendUserEmailVerification: sendVerificationEmail.method,
});
const emailSpy = spyOn(emailAdapter, 'sendVerificationEmail').and.callThrough();
const newUser = new Parse.User();
newUser.setUsername('unsets_email_verify_token_expires_at');
newUser.setPassword('expiringToken');
newUser.set('email', 'user@example.com');
await newUser.signUp();
await Parse.User.requestEmailVerification('user@example.com');
expect(sendSpy).toHaveBeenCalledTimes(2);
expect(emailSpy).toHaveBeenCalledTimes(0);
});
it('beforeSave options do not change existing behaviour', async () => {
let sendEmailOptions;
const emailAdapter = {
sendVerificationEmail: options => {
sendEmailOptions = options;
},
sendPasswordResetEmail: () => Promise.resolve(),
sendMail: () => {},
};
await reconfigureServer({
appName: 'emailVerifyToken',
verifyUserEmails: true,
emailAdapter: emailAdapter,
emailVerifyTokenValidityDuration: 5, // 5 seconds
publicServerURL: 'http://localhost:8378/1',
});
const emailSpy = spyOn(emailAdapter, 'sendVerificationEmail').and.callThrough();
const newUser = new Parse.User();
newUser.setUsername('unsets_email_verify_token_expires_at');
newUser.setPassword('expiringToken');
newUser.set('email', 'user@parse.com');
await newUser.signUp();
const response = await request({
url: sendEmailOptions.link,
followRedirects: false,
});
expect(response.status).toEqual(302);
const config = Config.get('test');
const results = await config.database.find('_User', {
username: 'unsets_email_verify_token_expires_at',
});
expect(results.length).toBe(1);
const user = results[0];
expect(typeof user).toBe('object');
expect(user.emailVerified).toEqual(true);
expect(typeof user._email_verify_token).toBe('undefined');
expect(typeof user._email_verify_token_expires_at).toBe('undefined');
expect(emailSpy).toHaveBeenCalled();
});
it('unsets the _email_verify_token_expires_at and _email_verify_token fields in the User class if email verification is successful', done => { it('unsets the _email_verify_token_expires_at and _email_verify_token fields in the User class if email verification is successful', done => {
const user = new Parse.User(); const user = new Parse.User();
let sendEmailOptions; let sendEmailOptions;

View File

@@ -1,4 +1,3 @@
const UserController = require('../lib/Controllers/UserController').UserController;
const emailAdapter = require('./support/MockEmailAdapter'); const emailAdapter = require('./support/MockEmailAdapter');
describe('UserController', () => { describe('UserController', () => {
@@ -11,11 +10,14 @@ describe('UserController', () => {
describe('sendVerificationEmail', () => { describe('sendVerificationEmail', () => {
describe('parseFrameURL not provided', () => { describe('parseFrameURL not provided', () => {
it('uses publicServerURL', async done => { it('uses publicServerURL', async done => {
await reconfigureServer({ const server = await reconfigureServer({
publicServerURL: 'http://www.example.com', publicServerURL: 'http://www.example.com',
customPages: { customPages: {
parseFrameURL: undefined, parseFrameURL: undefined,
}, },
verifyUserEmails: true,
emailAdapter,
appName: 'test',
}); });
emailAdapter.sendVerificationEmail = options => { emailAdapter.sendVerificationEmail = options => {
expect(options.link).toEqual( expect(options.link).toEqual(
@@ -24,20 +26,20 @@ describe('UserController', () => {
emailAdapter.sendVerificationEmail = () => Promise.resolve(); emailAdapter.sendVerificationEmail = () => Promise.resolve();
done(); done();
}; };
const userController = new UserController(emailAdapter, 'test', { server.config.userController.sendVerificationEmail(user);
verifyUserEmails: true,
});
userController.sendVerificationEmail(user);
}); });
}); });
describe('parseFrameURL provided', () => { describe('parseFrameURL provided', () => {
it('uses parseFrameURL and includes the destination in the link parameter', async done => { it('uses parseFrameURL and includes the destination in the link parameter', async done => {
await reconfigureServer({ const server = await reconfigureServer({
publicServerURL: 'http://www.example.com', publicServerURL: 'http://www.example.com',
customPages: { customPages: {
parseFrameURL: 'http://someother.example.com/handle-parse-iframe', parseFrameURL: 'http://someother.example.com/handle-parse-iframe',
}, },
verifyUserEmails: true,
emailAdapter,
appName: 'test',
}); });
emailAdapter.sendVerificationEmail = options => { emailAdapter.sendVerificationEmail = options => {
expect(options.link).toEqual( expect(options.link).toEqual(
@@ -46,10 +48,7 @@ describe('UserController', () => {
emailAdapter.sendVerificationEmail = () => Promise.resolve(); emailAdapter.sendVerificationEmail = () => Promise.resolve();
done(); done();
}; };
const userController = new UserController(emailAdapter, 'test', { server.config.userController.sendVerificationEmail(user);
verifyUserEmails: true,
});
userController.sendVerificationEmail(user);
}); });
}); });
}); });

View File

@@ -32,20 +32,33 @@ export class UserController extends AdaptableController {
} }
get shouldVerifyEmails() { get shouldVerifyEmails() {
return this.options.verifyUserEmails; return (this.config || this.options).verifyUserEmails;
} }
setEmailVerifyToken(user) { async setEmailVerifyToken(user, req, storage = {}) {
if (this.shouldVerifyEmails) { let shouldSendEmail = this.shouldVerifyEmails;
if (typeof shouldSendEmail === 'function') {
const response = await Promise.resolve(shouldSendEmail(req));
shouldSendEmail = response !== false;
}
if (!shouldSendEmail) {
return false;
}
storage.sendVerificationEmail = true;
user._email_verify_token = randomString(25); user._email_verify_token = randomString(25);
if (
!storage.fieldsChangedByTrigger ||
!storage.fieldsChangedByTrigger.includes('emailVerified')
) {
user.emailVerified = false; user.emailVerified = false;
}
if (this.config.emailVerifyTokenValidityDuration) { if (this.config.emailVerifyTokenValidityDuration) {
user._email_verify_token_expires_at = Parse._encode( user._email_verify_token_expires_at = Parse._encode(
this.config.generateEmailVerifyTokenExpiresAt() this.config.generateEmailVerifyTokenExpiresAt()
); );
} }
} return true;
} }
verifyEmail(username, token) { verifyEmail(username, token) {
@@ -131,27 +144,39 @@ export class UserController extends AdaptableController {
}); });
} }
sendVerificationEmail(user) { async sendVerificationEmail(user, req) {
if (!this.shouldVerifyEmails) { if (!this.shouldVerifyEmails) {
return; return;
} }
const token = encodeURIComponent(user._email_verify_token); const token = encodeURIComponent(user._email_verify_token);
// We may need to fetch the user in case of update email // We may need to fetch the user in case of update email
this.getUserIfNeeded(user).then(user => { const fetchedUser = await this.getUserIfNeeded(user);
let shouldSendEmail = this.config.sendUserEmailVerification;
if (typeof shouldSendEmail === 'function') {
const response = await Promise.resolve(
this.config.sendUserEmailVerification({
user: Parse.Object.fromJSON({ className: '_User', ...fetchedUser }),
master: req.auth?.isMaster,
})
);
shouldSendEmail = !!response;
}
if (!shouldSendEmail) {
return;
}
const username = encodeURIComponent(user.username); const username = encodeURIComponent(user.username);
const link = buildEmailLink(this.config.verifyEmailURL, username, token, this.config); const link = buildEmailLink(this.config.verifyEmailURL, username, token, this.config);
const options = { const options = {
appName: this.config.appName, appName: this.config.appName,
link: link, link: link,
user: inflate('_User', user), user: inflate('_User', fetchedUser),
}; };
if (this.adapter.sendVerificationEmail) { if (this.adapter.sendVerificationEmail) {
this.adapter.sendVerificationEmail(options); this.adapter.sendVerificationEmail(options);
} else { } else {
this.adapter.sendMail(this.defaultVerificationEmail(options)); this.adapter.sendMail(this.defaultVerificationEmail(options));
} }
});
} }
/** /**
@@ -160,7 +185,7 @@ export class UserController extends AdaptableController {
* @param user * @param user
* @returns {*} * @returns {*}
*/ */
regenerateEmailVerifyToken(user) { async regenerateEmailVerifyToken(user, master) {
const { _email_verify_token } = user; const { _email_verify_token } = user;
let { _email_verify_token_expires_at } = user; let { _email_verify_token_expires_at } = user;
if (_email_verify_token_expires_at && _email_verify_token_expires_at.__type === 'Date') { if (_email_verify_token_expires_at && _email_verify_token_expires_at.__type === 'Date') {
@@ -174,19 +199,22 @@ export class UserController extends AdaptableController {
) { ) {
return Promise.resolve(); return Promise.resolve();
} }
this.setEmailVerifyToken(user); const shouldSend = await this.setEmailVerifyToken(user, { user, master });
if (!shouldSend) {
return;
}
return this.config.database.update('_User', { username: user.username }, user); return this.config.database.update('_User', { username: user.username }, user);
} }
resendVerificationEmail(username) { async resendVerificationEmail(username, req) {
return this.getUserIfNeeded({ username: username }).then(aUser => { const aUser = await this.getUserIfNeeded({ username: username });
if (!aUser || aUser.emailVerified) { if (!aUser || aUser.emailVerified) {
throw undefined; throw undefined;
} }
return this.regenerateEmailVerifyToken(aUser).then(() => { const generate = await this.regenerateEmailVerifyToken(aUser, req.auth?.isMaster);
this.sendVerificationEmail(aUser); if (generate) {
}); this.sendVerificationEmail(aUser, req);
}); }
} }
setPasswordResetToken(email) { setPasswordResetToken(email) {

View File

@@ -496,6 +496,12 @@ module.exports.ParseServerOptions = {
action: parsers.objectParser, action: parsers.objectParser,
default: {}, default: {},
}, },
sendUserEmailVerification: {
env: 'PARSE_SERVER_SEND_USER_EMAIL_VERIFICATION',
help:
'Set to `false` to prevent sending of verification email. Supports a function with a return value of `true` or `false` for conditional email sending.<br><br>Default is `true`.<br>',
default: true,
},
serverCloseComplete: { serverCloseComplete: {
env: 'PARSE_SERVER_SERVER_CLOSE_COMPLETE', env: 'PARSE_SERVER_SERVER_CLOSE_COMPLETE',
help: 'Callback when server has closed', help: 'Callback when server has closed',
@@ -542,8 +548,7 @@ module.exports.ParseServerOptions = {
verifyUserEmails: { verifyUserEmails: {
env: 'PARSE_SERVER_VERIFY_USER_EMAILS', env: 'PARSE_SERVER_VERIFY_USER_EMAILS',
help: help:
'Set to `true` to require users to verify their email address to complete the sign-up process.<br><br>Default is `false`.', 'Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification.<br><br>Default is `false`.',
action: parsers.booleanParser,
default: false, default: false,
}, },
webhookKey: { webhookKey: {

View File

@@ -89,6 +89,7 @@
* @property {Boolean} scheduledPush Configuration for push scheduling, defaults to false. * @property {Boolean} scheduledPush Configuration for push scheduling, defaults to false.
* @property {SchemaOptions} schema Defined schema * @property {SchemaOptions} schema Defined schema
* @property {SecurityOptions} security The security options to identify and report weak security settings. * @property {SecurityOptions} security The security options to identify and report weak security settings.
* @property {Boolean} sendUserEmailVerification Set to `false` to prevent sending of verification email. Supports a function with a return value of `true` or `false` for conditional email sending.<br><br>Default is `true`.<br>
* @property {Function} serverCloseComplete Callback when server has closed * @property {Function} serverCloseComplete Callback when server has closed
* @property {String} serverURL URL to your parse server with http:// or https://. * @property {String} serverURL URL to your parse server with http:// or https://.
* @property {Number} sessionLength Session duration, in seconds, defaults to 1 year * @property {Number} sessionLength Session duration, in seconds, defaults to 1 year
@@ -97,7 +98,7 @@
* @property {Any} trustProxy The trust proxy settings. It is important to understand the exact setup of the reverse proxy, since this setting will trust values provided in the Parse Server API request. See the <a href="https://expressjs.com/en/guide/behind-proxies.html">express trust proxy settings</a> documentation. Defaults to `false`. * @property {Any} trustProxy The trust proxy settings. It is important to understand the exact setup of the reverse proxy, since this setting will trust values provided in the Parse Server API request. See the <a href="https://expressjs.com/en/guide/behind-proxies.html">express trust proxy settings</a> documentation. Defaults to `false`.
* @property {String[]} userSensitiveFields Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields * @property {String[]} userSensitiveFields Personally identifiable information fields in the user table the should be removed for non-authorized users. Deprecated @see protectedFields
* @property {Boolean} verbose Set the logging to verbose * @property {Boolean} verbose Set the logging to verbose
* @property {Boolean} verifyUserEmails Set to `true` to require users to verify their email address to complete the sign-up process.<br><br>Default is `false`. * @property {Boolean} verifyUserEmails Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification.<br><br>Default is `false`.
* @property {String} webhookKey Key sent with outgoing webhook calls * @property {String} webhookKey Key sent with outgoing webhook calls
*/ */

View File

@@ -153,11 +153,11 @@ export interface ParseServerOptions {
/* Max file size for uploads, defaults to 20mb /* Max file size for uploads, defaults to 20mb
:DEFAULT: 20mb */ :DEFAULT: 20mb */
maxUploadSize: ?string; maxUploadSize: ?string;
/* Set to `true` to require users to verify their email address to complete the sign-up process. /* Set to `true` to require users to verify their email address to complete the sign-up process. Supports a function with a return value of `true` or `false` for conditional verification.
<br><br> <br><br>
Default is `false`. Default is `false`.
:DEFAULT: false */ :DEFAULT: false */
verifyUserEmails: ?boolean; verifyUserEmails: ?(boolean | void);
/* Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required. /* Set to `true` to prevent a user from logging in if the email has not yet been verified and email verification is required.
<br><br> <br><br>
Default is `false`. Default is `false`.
@@ -188,6 +188,12 @@ export interface ParseServerOptions {
Requires option `verifyUserEmails: true`. Requires option `verifyUserEmails: true`.
:DEFAULT: false */ :DEFAULT: false */
emailVerifyTokenReuseIfValid: ?boolean; emailVerifyTokenReuseIfValid: ?boolean;
/* Set to `false` to prevent sending of verification email. Supports a function with a return value of `true` or `false` for conditional email sending.
<br><br>
Default is `true`.
<br>
:DEFAULT: true */
sendUserEmailVerification: ?(boolean | void);
/* The account lockout policy for failed login attempts. */ /* The account lockout policy for failed login attempts. */
accountLockout: ?AccountLockoutOptions; accountLockout: ?AccountLockoutOptions;
/* The password policy for enforcing password related rules. */ /* The password policy for enforcing password related rules. */

View File

@@ -113,6 +113,9 @@ RestWrite.prototype.execute = function () {
.then(() => { .then(() => {
return this.validateAuthData(); return this.validateAuthData();
}) })
.then(() => {
return this.checkRestrictedFields();
})
.then(() => { .then(() => {
return this.runBeforeSaveTrigger(); return this.runBeforeSaveTrigger();
}) })
@@ -603,17 +606,23 @@ RestWrite.prototype.handleAuthData = async function (authData) {
} }
}; };
// The non-third-party parts of User transformation RestWrite.prototype.checkRestrictedFields = async function () {
RestWrite.prototype.transformUser = function () {
var promise = Promise.resolve();
if (this.className !== '_User') { if (this.className !== '_User') {
return promise; return;
} }
if (!this.auth.isMaintenance && !this.auth.isMaster && 'emailVerified' in this.data) { if (!this.auth.isMaintenance && !this.auth.isMaster && 'emailVerified' in this.data) {
const error = `Clients aren't allowed to manually update email verification.`; const error = `Clients aren't allowed to manually update email verification.`;
throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error); throw new Parse.Error(Parse.Error.OPERATION_FORBIDDEN, error);
} }
};
// The non-third-party parts of User transformation
RestWrite.prototype.transformUser = function () {
var promise = Promise.resolve();
if (this.className !== '_User') {
return promise;
}
// Do not cleanup session if objectId is not set // Do not cleanup session if objectId is not set
if (this.query && this.objectId()) { if (this.query && this.objectId()) {
@@ -751,8 +760,14 @@ RestWrite.prototype._validateEmail = function () {
Object.keys(this.data.authData)[0] === 'anonymous') Object.keys(this.data.authData)[0] === 'anonymous')
) { ) {
// We updated the email, send a new validation // We updated the email, send a new validation
this.storage['sendVerificationEmail'] = true; const { originalObject, updatedObject } = this.buildParseObjects();
this.config.userController.setEmailVerifyToken(this.data); const request = {
original: originalObject,
object: updatedObject,
master: this.auth.isMaster,
ip: this.config.ip,
};
return this.config.userController.setEmailVerifyToken(this.data, request, this.storage);
} }
}); });
}; };
@@ -864,7 +879,7 @@ RestWrite.prototype._validatePasswordHistory = function () {
return Promise.resolve(); return Promise.resolve();
}; };
RestWrite.prototype.createSessionTokenIfNeeded = function () { RestWrite.prototype.createSessionTokenIfNeeded = async function () {
if (this.className !== '_User') { if (this.className !== '_User') {
return; return;
} }
@@ -878,13 +893,31 @@ RestWrite.prototype.createSessionTokenIfNeeded = function () {
} }
if ( if (
!this.storage.authProvider && // signup call, with !this.storage.authProvider && // signup call, with
this.config.preventLoginWithUnverifiedEmail && // no login without verification this.config.preventLoginWithUnverifiedEmail === true && // no login without verification
this.config.verifyUserEmails this.config.verifyUserEmails
) { ) {
// verification is on // verification is on
this.storage.rejectSignup = true; this.storage.rejectSignup = true;
return; 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,
};
shouldPreventUnverifedLogin = await Promise.resolve(
this.config.preventLoginWithUnverifiedEmail(request)
);
}
if (shouldPreventUnverifedLogin === true) {
return;
}
}
return this.createSessionToken(); return this.createSessionToken();
}; };
@@ -1010,7 +1043,7 @@ RestWrite.prototype.handleFollowup = function () {
if (this.storage && this.storage['sendVerificationEmail']) { if (this.storage && this.storage['sendVerificationEmail']) {
delete this.storage['sendVerificationEmail']; delete this.storage['sendVerificationEmail'];
// Fire and forget! // Fire and forget!
this.config.userController.sendVerificationEmail(this.data); this.config.userController.sendVerificationEmail(this.data, { auth: this.auth });
return this.handleFollowup.bind(this); return this.handleFollowup.bind(this);
} }
}; };

View File

@@ -125,7 +125,7 @@ export class PagesRouter extends PromiseRouter {
const userController = config.userController; const userController = config.userController;
return userController.resendVerificationEmail(username).then( return userController.resendVerificationEmail(username, req).then(
() => { () => {
return this.goToPage(req, pages.emailVerificationSendSuccess); return this.goToPage(req, pages.emailVerificationSendSuccess);
}, },

View File

@@ -63,7 +63,7 @@ export class PublicAPIRouter extends PromiseRouter {
const userController = config.userController; const userController = config.userController;
return userController.resendVerificationEmail(username).then( return userController.resendVerificationEmail(username, req).then(
() => { () => {
return Promise.resolve({ return Promise.resolve({
status: 302, status: 302,

View File

@@ -447,7 +447,7 @@ export class UsersRouter extends ClassesRouter {
} }
} }
handleVerificationEmailRequest(req) { async handleVerificationEmailRequest(req) {
this._throwOnBadEmailConfig(req); this._throwOnBadEmailConfig(req);
const { email } = req.body; const { email } = req.body;
@@ -461,7 +461,7 @@ export class UsersRouter extends ClassesRouter {
); );
} }
return req.config.database.find('_User', { email: email }).then(results => { const results = await req.config.database.find('_User', { email: email });
if (!results.length || results.length < 1) { if (!results.length || results.length < 1) {
throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `No user found with email ${email}`); throw new Parse.Error(Parse.Error.EMAIL_NOT_FOUND, `No user found with email ${email}`);
} }
@@ -475,11 +475,11 @@ export class UsersRouter extends ClassesRouter {
} }
const userController = req.config.userController; const userController = req.config.userController;
return userController.regenerateEmailVerifyToken(user).then(() => { const send = await userController.regenerateEmailVerifyToken(user, req.auth.isMaster);
userController.sendVerificationEmail(user); if (send) {
userController.sendVerificationEmail(user, req);
}
return { response: {} }; return { response: {} };
});
});
} }
async handleChallenge(req) { async handleChallenge(req) {