-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Use Mailgun as default implementation for reset password and email verification. #250
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
Changes from all commits
0b5528f
b92c784
ad105bf
d10f60f
8069d05
961ff69
1b733dc
137021b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,11 +8,11 @@ var rack = require('hat').rack(); | |
|
||
var Auth = require('./Auth'); | ||
var cache = require('./cache'); | ||
var Config = require('./Config'); | ||
var passwordCrypto = require('./password'); | ||
var facebook = require('./facebook'); | ||
var Parse = require('parse/node'); | ||
var triggers = require('./triggers'); | ||
var MailAdapterStore = require('./mail/MailAdapterStore') | ||
|
||
// query and data are both provided in REST API format. So data | ||
// types are encoded by plain old objects. | ||
|
@@ -350,13 +350,16 @@ RestWrite.prototype.transformUser = function() { | |
email: this.data.email, | ||
objectId: {'$ne': this.objectId()} | ||
}, {limit: 1}).then((results) => { | ||
if (results.length > 0) { | ||
throw new Parse.Error(Parse.Error.EMAIL_TAKEN, | ||
'Account already exists for this email ' + | ||
'address'); | ||
} | ||
return Promise.resolve(); | ||
}); | ||
if (results.length > 0) { | ||
throw new Parse.Error(Parse.Error.EMAIL_TAKEN, | ||
'Account already exists for this email ' + | ||
'address'); | ||
} | ||
this.data.emailVerified = false; | ||
this.data.perishableToken = rack(); | ||
this.data.emailVerifyToken = rack(); | ||
return Promise.resolve(); | ||
}); | ||
}); | ||
}; | ||
|
||
|
@@ -654,16 +657,34 @@ RestWrite.prototype.runDatabaseOperation = function() { | |
} | ||
} | ||
|
||
function sendEmailVerification () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this the right place to define the Maybe we abstract this to something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the That's a good call, I didn't even consider a user not wanting to add email verification, I'll update that. |
||
var emailSender = MailAdapterStore.getMailService(this.config.applicationId); | ||
if (!emailSender && this.config.verifyEmails) { | ||
throw new Error("Verify emails option was set, but not email sending configuration was sent to parse"); | ||
} | ||
var hasUserEmail = typeof this.data.email !== 'undefined' && this.className === "_User" | ||
var canSendEmail = emailSender && this.config.verifyEmails; | ||
if ( hasUserEmail && canSendEmail) { | ||
var link = this.config.mount + "/verify_email?token=" + encodeURIComponent(this.data.emailVerifyToken) + "&username=" + encodeURIComponent(this.data.email); | ||
var email = emailSender.getVerificationEmail(this.data.email, link); | ||
emailSender.sendMail(this.data.email, email.subject, email.text, email.html); | ||
} | ||
} | ||
|
||
if (this.query) { | ||
// Run an update | ||
return this.config.database.update( | ||
this.className, this.query, this.data, options).then((resp) => { | ||
this.response = resp; | ||
this.response.updatedAt = this.updatedAt; | ||
}); | ||
sendEmailVerification.call(this); | ||
this.response = resp; | ||
this.response.updatedAt = this.updatedAt; | ||
}); | ||
} else { | ||
// Run a create | ||
return this.config.database.create(this.className, this.data, options) | ||
.then(()=> { | ||
sendEmailVerification.call(this); | ||
}) | ||
.then(() => { | ||
var resp = { | ||
objectId: this.data.objectId, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
var defaultResetEmail = | ||
"Hi,\n\n" + | ||
"You requested a password reset.\n\n" + | ||
"" + | ||
"Click here to reset it:\n" + | ||
"<%LINK_GOES_HERE%>"; | ||
|
||
var defaultVerify = | ||
"Hi,\n\n" + | ||
"You are being asked to confirm the e-mail address <%EMAIL_GOES_HERE%>\n\n" + | ||
"" + | ||
"Click here to confirm it:\n" + | ||
"<%LINK_GOES_HERE%>"; | ||
|
||
|
||
function MailAdapter() { | ||
} | ||
|
||
MailAdapter.prototype.sendMail = function(to, subject, text) { | ||
throw new Error("Send mail must be overridden") | ||
}; | ||
|
||
MailAdapter.prototype.getResetPasswordEmail = function(to, resetLink) { | ||
return { | ||
subject: 'Password Reset Request', | ||
text: defaultResetEmail.replace("<%LINK_GOES_HERE%>", resetLink) | ||
} | ||
}; | ||
|
||
MailAdapter.prototype.getVerificationEmail = function(to, verifyLink) { | ||
return { | ||
subject: 'Please verify your e-mail', | ||
text: defaultVerify.replace("<%EMAIL_GOES_HERE%>", to).replace("<%LINK_GOES_HERE%>", verifyLink) | ||
} | ||
}; | ||
|
||
|
||
module.exports = MailAdapter; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
// Mail Adapter | ||
// | ||
// Allows you to send email using a third party API such as Mailgun. | ||
// | ||
// To send messages: | ||
// var service = MailAdapter.getMailService(appId); | ||
// if(service !== null) service.sendMail('user@domain.com', 'Hello User!', 'Thanks for signing up!'); | ||
// | ||
// Each adapter requires:- | ||
// * validateConfig(config) -> returns a set of configuration values for each service. Different services have different config requirements | ||
// * sendMail(to, subject, text) -> sends a message using the configured service | ||
|
||
var MailgunAdapter = require('./MailgunAdapter'); | ||
|
||
var adapter = MailgunAdapter; | ||
var mailConfigs = {}; | ||
var mailServices = {}; | ||
|
||
function configureDefaultAdapter(appId, mailApiConfig) { | ||
|
||
// Perform a type check on mailApiConfig to ensure it's a dictionary/object | ||
if(typeof mailApiConfig === 'object') { | ||
|
||
// Ensure mailApiConfig has a least a service defined, if not — default to mailgun | ||
if(typeof mailApiConfig.service === 'undefined' || mailApiConfig.service === '') { | ||
mailApiConfig.service = 'mailgun'; // use mailgun as the default adapter | ||
} | ||
|
||
// Set the mail service as configured | ||
if(mailApiConfig.service === '' || mailApiConfig.service === 'mailgun') { | ||
adapter = MailgunAdapter; | ||
mailApiConfig = MailgunAdapter.validateConfig(mailApiConfig); | ||
} else { | ||
// Handle other mail adapters here... (such as mandrill, postmark, etc | ||
} | ||
|
||
} else { | ||
// Unexpected type, should be an object/dictionary. | ||
console.log('Error: Unexpected `mailApiConfig` in MailAdapter.'); | ||
mailApiConfig = MailgunAdapter.validateConfig({}); // Just get some empty values | ||
return false; | ||
} | ||
|
||
mailConfigs[appId] = mailApiConfig; | ||
return true; | ||
} | ||
|
||
function setAdapter(appId, mailAdapter) { | ||
mailServices[appId] = mailAdapter; | ||
} | ||
|
||
function clearMailService(appId) { | ||
delete mailConfigs[appId]; | ||
delete mailServices[appId]; | ||
} | ||
|
||
function getMailService(appId) { | ||
if (mailServices[appId]) { | ||
return mailServices[appId]; | ||
} | ||
|
||
if(mailConfigs[appId]) { | ||
mailServices[appId] = new adapter(appId, mailConfigs[appId]); | ||
return mailServices[appId]; | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
module.exports = { | ||
mailConfigs: mailConfigs, | ||
mailServices: mailServices, | ||
configureDefaultAdapter: configureDefaultAdapter, | ||
setAdapter: setAdapter, | ||
getMailService: getMailService, | ||
clearMailService: clearMailService | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
var MailAdapter = require('./MailAdapter'); | ||
// options can contain: | ||
function MailgunAdapter(appId, mailApiConfig) { | ||
this.appId = appId; | ||
this.apiConfig = mailApiConfig; | ||
MailAdapter.call(this); | ||
} | ||
|
||
MailgunAdapter.prototype = Object.create(MailAdapter); | ||
|
||
// Connects to the database. Returns a promise that resolves when the | ||
// connection is successful. | ||
// this.db will be populated with a Mongo "Db" object when the | ||
// promise resolves successfully. | ||
MailgunAdapter.sendMail = function(to, subject, text, html) { | ||
|
||
var mailgun = require('mailgun-js')({apiKey: this.apiConfig.apiKey, domain: this.apiConfig.domain}); | ||
|
||
var data = { | ||
from: this.apiConfig.fromAddress, | ||
to: to, | ||
subject: subject, | ||
text: text, | ||
html: html | ||
}; | ||
|
||
return new Promise((resolve, reject) => { | ||
mailgun.messages().send(data, (err, body) => { | ||
if (typeof err !== 'undefined') { | ||
// console.log("Mailgun Error", err); | ||
return reject(err); | ||
} | ||
// console.log(body); | ||
resolve(body); | ||
}); | ||
}); | ||
}; | ||
|
||
MailgunAdapter.validateConfig = function(config) { | ||
var cfg = {apiKey:'', domain:'', fromAddress:''}; | ||
var helperMessage = "When creating an instance of ParseServer, you should have a mailConfig section like this: mailConfig: { service:'mailgun', apiKey:'MAILGUN_KEY_HERE', domain:'MAILGUN_DOMAIN_HERE', fromAddress:'MAILGUN_FROM_ADDRESS_HERE' }"; | ||
if(typeof config.apiKey === 'undefined' || config.apiKey === '') { | ||
console.error('Error: You need to define a MailGun `apiKey` when configuring ParseServer. ' + helperMessage); | ||
} else { | ||
cfg.apiKey = config.apiKey; | ||
} | ||
if(typeof config.domain === 'undefined' || config.domain === '') { | ||
console.error('Error: You need to define a MailGun `domain` when configuring ParseServer. ' + helperMessage); | ||
} else { | ||
cfg.domain = config.domain; | ||
} | ||
if(typeof config.fromAddress === 'undefined' || config.fromAddress === '') { | ||
console.error('Error: You need to define a MailGun `fromAddress` when configuring ParseServer. ' + helperMessage); | ||
} else { | ||
cfg.fromAddress = config.fromAddress; | ||
} | ||
return cfg; | ||
}; | ||
|
||
module.exports = MailgunAdapter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you add those in the default _User schema? to prevent accidental overrides.