diff --git a/spec/ValidationAndPasswordsReset.spec.js b/spec/ValidationAndPasswordsReset.spec.js index 0519b887..91f7ddce 100644 --- a/spec/ValidationAndPasswordsReset.spec.js +++ b/spec/ValidationAndPasswordsReset.spec.js @@ -150,10 +150,67 @@ describe("Email Verification", () => { user.set("email", "cool_guy@parse.com"); return user.save(); }).then((user) => { - expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled(); return user.fetch(); }).then(() => { expect(user.get('emailVerified')).toEqual(false); + // Wait as on update emai, we need to fetch the username + setTimeout(function(){ + expect(emailAdapter.sendVerificationEmail).toHaveBeenCalled(); + done(); + }, 200); + }); + }, + error: function(userAgain, error) { + fail('Failed to save user'); + done(); + } + }); + }); + + it('does send with a simple adapter', done => { + var calls = 0; + var emailAdapter = { + sendMail: function(options){ + expect(options.to).toBe('cool_guy@parse.com'); + if (calls == 0) { + expect(options.subject).toEqual('Please verify your e-mail for My Cool App'); + expect(options.text.match(/verify_email/)).not.toBe(null); + } else if (calls == 1) { + expect(options.subject).toEqual('Password Reset for My Cool App'); + expect(options.text.match(/request_password_reset/)).not.toBe(null); + } + calls++; + return Promise.resolve(); + } + } + setServerConfiguration({ + serverURL: 'http://localhost:8378/1', + appId: 'test', + appName: 'My Cool App', + javascriptKey: 'test', + dotNetKey: 'windows', + clientKey: 'client', + restAPIKey: 'rest', + masterKey: 'test', + collectionPrefix: 'test_', + fileKey: 'test', + verifyUserEmails: true, + emailAdapter: emailAdapter, + }); + var user = new Parse.User(); + user.setPassword("asdf"); + user.setUsername("zxcv"); + user.set("email", "cool_guy@parse.com"); + user.signUp(null, { + success: function(user) { + expect(calls).toBe(1); + user.fetch() + .then((user) => { + return user.save(); + }).then((user) => { + return Parse.User.requestPasswordReset("cool_guy@parse.com"); + }).then(() => { + expect(calls).toBe(2); done(); }); }, diff --git a/src/Adapters/Email/MailAdapter.js b/src/Adapters/Email/MailAdapter.js index ab8f1571..82ea8b34 100644 --- a/src/Adapters/Email/MailAdapter.js +++ b/src/Adapters/Email/MailAdapter.js @@ -1,7 +1,23 @@ + +/* + Mail Adapter prototype + A MailAdapter should implement at least sendMail() + */ export class MailAdapter { - sendVerificationEmail(options) {} - sendPasswordResetEmail(options) {} + /* + * A method for sending mail + * @param options would have the parameters + * - to: the recipient + * - text: the raw text of the message + * - subject: the subject of the email + */ sendMail(options) {} + + /* You can implement those methods if you want + * to provide HTML templates etc... + */ + // sendVerificationEmail({ link, appName, user }) {} + // sendPasswordResetEmail({ link, appName, user }) {} } export default MailAdapter; diff --git a/src/Adapters/Email/SimpleMailgunAdapter.js b/src/Adapters/Email/SimpleMailgunAdapter.js index 6720962f..a90a43d7 100644 --- a/src/Adapters/Email/SimpleMailgunAdapter.js +++ b/src/Adapters/Email/SimpleMailgunAdapter.js @@ -25,31 +25,6 @@ let SimpleMailgunAdapter = mailgunOptions => { } return Object.freeze({ - sendVerificationEmail: ({ link, user, appName, }) => { - let verifyMessage = - "Hi,\n\n" + - "You are being asked to confirm the e-mail address " + user.email + " with " + appName + "\n\n" + - "" + - "Click here to confirm it:\n" + link; - return sendMail({ - to:user.email, - subject: 'Please verify your e-mail for ' + appName, - text: verifyMessage - }); - }, - - sendPasswordResetEmail: ({link,user, appName}) => { - let message = - "Hi,\n\n" + - "You requested to reset your password for " + appName + ".\n\n" + - "" + - "Click here to reset it:\n" + link; - return sendMail({ - to:user.email, - subject: 'Password Reset for ' + appName, - text: message - }); - }, sendMail: sendMail }); } diff --git a/src/Controllers/UserController.js b/src/Controllers/UserController.js index 786d118e..b707e124 100644 --- a/src/Controllers/UserController.js +++ b/src/Controllers/UserController.js @@ -5,6 +5,7 @@ import MailAdapter from '../Adapters/Email/MailAdapter'; var DatabaseAdapter = require('../DatabaseAdapter'); var RestWrite = require('../RestWrite'); +var RestQuery = require('../RestQuery'); var hash = require('../password').hash; var Auth = require('../Auth'); @@ -84,20 +85,47 @@ export class UserController extends AdaptableController { }); } + getUserIfNeeded(user) { + if (user.username && user.email) { + return Promise.resolve(user); + } + var where = {}; + if (user.username) { + where.username = user.username; + } + if (user.email) { + where.email = user.email; + } + + var query = new RestQuery(this.config, Auth.master(this.config), '_User', where); + return query.execute().then(function(result){ + if (result.results.length != 1) { + return Promise.reject(); + } + return result.results[0]; + }) + } + sendVerificationEmail(user) { if (!this.shouldVerifyEmails) { return; } - - const token = encodeURIComponent(user._email_verify_token); - const username = encodeURIComponent(user.username); - - let link = `${this.config.verifyEmailURL}?token=${token}&username=${username}`; - this.adapter.sendVerificationEmail({ - appName: this.config.appName, - link: link, - user: inflate('_User', user), + // We may need to fetch the user in case of update email + this.getUserIfNeeded(user).then((user) => { + const token = encodeURIComponent(user._email_verify_token); + const username = encodeURIComponent(user.username); + let link = `${this.config.verifyEmailURL}?token=${token}&username=${username}`; + let options = { + appName: this.config.appName, + link: link, + user: inflate('_User', user), + }; + if (this.adapter.sendVerificationEmail) { + this.adapter.sendVerificationEmail(options); + } else { + this.adapter.sendMail(this.defaultVerificationEmail(options)); + } }); } @@ -134,11 +162,23 @@ export class UserController extends AdaptableController { const token = encodeURIComponent(user._perishable_token); const username = encodeURIComponent(user.username); let link = `${this.config.requestResetPasswordURL}?token=${token}&username=${username}` - this.adapter.sendPasswordResetEmail({ - appName: this.config.appName, - link: link, - user: inflate('_User', user), - }); + + if (!user.username) { + console.log('No username...'); + } + + let options = { + appName: this.config.appName, + link: link, + user: inflate('_User', user), + }; + + if (this.adapter.sendPasswordResetEmail) { + this.adapter.sendPasswordResetEmail(options); + } else { + this.adapter.sendMail(this.defaultResetPasswordEmail(options)); + } + return Promise.resolve(user); }); } @@ -148,6 +188,26 @@ export class UserController extends AdaptableController { return updateUserPassword(username, token, password, this.config); }); } + + defaultVerificationEmail({link, user, appName, }) { + let text = "Hi,\n\n" + + "You are being asked to confirm the e-mail address " + user.email + " with " + appName + "\n\n" + + "" + + "Click here to confirm it:\n" + link; + let to = user.get("email"); + let subject = 'Please verify your e-mail for ' + appName; + return { text, to, subject }; + } + + defaultResetPasswordEmail({link, user, appName, }) { + let text = "Hi,\n\n" + + "You requested to reset your password for " + appName + ".\n\n" + + "" + + "Click here to reset it:\n" + link; + let to = user.get("email"); + let subject = 'Password Reset for ' + appName; + return { text, to, subject }; + } } // Mark this private