Skip to content

Commit

Permalink
Use sendgrid directly, CORS_ORGINS config, fix auth.ctrl
Browse files Browse the repository at this point in the history
  • Loading branch information
Adam Buczynski committed Jun 28, 2016
1 parent c0f0090 commit 952d3e2
Show file tree
Hide file tree
Showing 20 changed files with 175 additions and 108 deletions.
5 changes: 0 additions & 5 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,7 +1,2 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text eol=lf

# Binary files
*.png binary
*.jpg binary
*.woff binary
5 changes: 0 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
# Common
.DS_Store
Thumbs.db
*.sublime-*
*.log
node_modules
coverage

# App
config/local.*
keys
6 changes: 1 addition & 5 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# Common
.DS_Store
Thumbs.db
*.sublime-*
*.log
node_modules
coverage

# App
config/local.*
keys
23 changes: 3 additions & 20 deletions app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ let config = require('./config');
/**
* Configuration
*/
const ENV = config.ENV;
const CORS_ORIGINS = config.CORS_ORIGINS;
const I18N_LOCALES = config.I18N_LOCALES;
const I18N_DEFAULT_LOCALE = config.I18N_DEFAULT_LOCALE;
const TOKEN_TYPES = config.TOKEN_TYPES;
const TOKEN_DEFAULT_ISSUER = config.TOKEN_DEFAULT_ISSUER;
const TOKEN_DEFAULT_AUDIENCE = config.TOKEN_DEFAULT_AUDIENCE;
const APP_DOMAIN = config.APP_DOMAIN;
const SERVER_LATENCY = config.SERVER_LATENCY;
const SERVER_LATENCY_MIN = config.SERVER_LATENCY_MIN;
const SERVER_LATENCY_MAX = config.SERVER_LATENCY_MAX;
Expand Down Expand Up @@ -62,23 +61,12 @@ module.exports = function() {
});
tokens.register(TOKEN_TYPES);

//Trust proxy (for Google Cloud forwarding of requests)
//Trust proxy (for Cloud hosted forwarding of requests)
app.set('trust_proxy', 1);

//Determine origin for CORS
let origin = [
new RegExp('[a-z0-9\-]+\.' + APP_DOMAIN.replace(/\./g, '\\\.'))
];

//Add dev origins
if (ENV === 'dev') {
origin.push(/localhost\:8080/);
origin.push(/192\.168\.1\.[0-9]+/);
}

//CORS
app.use(cors({
origin,
origin: CORS_ORIGINS,
credentials: true //NOTE: needed for cross domain cookies to work
}));

Expand Down Expand Up @@ -135,11 +123,6 @@ module.exports = function() {
.map(handler => require('./error/middleware/' + handler))
.forEach(handler => app.use(handler));

//NOTE: Prevent Express from using the default error handler
//See: https://github.com/expressjs/express/issues/3024
/* jshint -W098 */
app.use(function(err, req, res, next) {});

//Return express server instance
return app;
};
11 changes: 0 additions & 11 deletions app/auth/auth.ctrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ let moment = require('moment');
let NotAuthenticatedError = require('../error/type/auth/not-authenticated');
let NotAuthorizedError = require('../error/type/auth/not-authorized');
let UserSuspendedError = require('../error/type/auth/user-suspended');
let UserPendingError = require('../error/type/auth/user-pending');
let tokens = require('../services/tokens');
let config = require('../config');

Expand Down Expand Up @@ -96,11 +95,6 @@ module.exports = {
error = new UserSuspendedError();
}

//User pending approval?
else if (!user.isApproved) {
error = new UserPendingError();
}

//Check error
if (error) {
return next(error);
Expand Down Expand Up @@ -184,11 +178,6 @@ module.exports = {
error = new UserSuspendedError();
}

//User pending approval?
else if (!user.isApproved) {
error = new UserPendingError();
}

//Check error
if (error) {
return next(error);
Expand Down
2 changes: 1 addition & 1 deletion app/error/middleware/issue-on-github.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ module.exports = function(error, req, res, next) {

//User data
if (user) {
parts.push('Member: `' + user.id + '`');
parts.push('User: `' + user.id + '`');
}

//User agent
Expand Down
30 changes: 12 additions & 18 deletions app/locales/en.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,20 @@
{
"user": {
"mail": {
"verifyEmailAddress": {
"mail": {
"subject": "Verify your email address",
"instructions": "Please verify your email address by clicking on the following link:",
"action": "verify my email address"
}
"subject": "Verify your email address",
"instructions": "Please verify your email address by clicking on the following link:",
"action": "verify my email address"
},
"resetPassword": {
"mail": {
"subject": "Reset your password",
"instructions": "Reset your password by clicking on the following link:",
"action": "reset password",
"validityNotice": "This link is only valid for {{numHours}} hours.",
"ignoreNotice": "If you did not request to change your password, you can ignore this email."
}
"subject": "Reset your password",
"instructions": "Reset your password by clicking on the following link:",
"action": "reset password",
"validityNotice": "This link is only valid for {{numHours}} hours.",
"ignoreNotice": "If you did not request to change your password, you can ignore this email."
},
"passwordHasChanged": {
"mail": {
"subject": "Your password has been reset",
"confirmation": "Your password has been reset."
}
"subject": "Your password has been reset",
"confirmation": "Your password has been reset."
}
}
}
}
143 changes: 123 additions & 20 deletions app/services/mailer.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,108 @@
let fs = require('fs');
let path = require('path');
let Promise = require('bluebird');
let sendgrid = require('sendgrid');
let readFile = Promise.promisify(fs.readFile);
let nodemailer = require('nodemailer');
let sendgrid = require('nodemailer-sendgrid-transport');
let SendMailError = require('../error/type/server/send-mail');
let config = require('../config');

/**
* Constants
* Initialise sendgrid
*/
const SENDGRID_API_KEY = config.SENDGRID_API_KEY;
let sg = sendgrid.SendGrid(config.SENDGRID_API_KEY);

/**
* Create mailer
* Split name and email address
*/
let mailer = Promise.promisifyAll(
nodemailer.createTransport(
sendgrid({
auth: {
api_key: SENDGRID_API_KEY
function splitNameEmail(str) {

//If no email bracket indicator present, return as is
if (str.indexOf('<') === -1) {
return ['', str];
}

//Split into name and email
let [name, email] = str.split('<');

//Fix up
name = name.trim();
email = email.replace('>', '').trim();

//Return as array
return [name, email];
}

/**
* Convert plain data to sendgrid mail object
*/
function getMail(data) {

//Extract email/name
if (data.to && data.to.indexOf('<') !== -1) {
let [name, email] = splitNameEmail(data.to);
data.to = email;
data.toname = name;
}
if (data.from && data.from.indexOf('<') !== -1) {
let [name, email] = splitNameEmail(data.from);
data.from = email;
data.fromname = name;
}

//Get sendgrid classes
let Mail = sendgrid.mail.Mail;
let Email = sendgrid.mail.Email;
let Content = sendgrid.mail.Content;
let Personalization = sendgrid.mail.Personalization;

//Create new mail object
let mail = new Mail();

//Create recipients
let recipients = new Personalization();
recipients.addTo(new Email(data.to, data.toname));

//Set recipients
mail.addPersonalization(recipients);

//Set sender
mail.setFrom(new Email(data.from, data.fromname));

//Set subject
mail.setSubject(data.subject);

//Add content
mail.addContent(new Content('text/plain', data.text));
mail.addContent(new Content('text/html', data.html));

//Return it
return mail;
}

/**
* Send email (wrapped in promise)
*/
function sendMail(mail) {
return new Promise((resolve, reject) => {

//Build request
let request = sg.emptyRequest();
request.method = 'POST';
request.path = '/v3/mail/send';
request.body = mail.toJSON();

//Send request
sg.API(request, response => {
if (response && response.statusCode &&
response.statusCode >= 200 && response.statusCode <= 299) {
resolve(response);
}
})
)
);
reject(new SendMailError(
'Sendgrid response error ' + response.statusCode
));
});
});
}

/**
* Export mailer interface (wrapped in promise)
Expand All @@ -42,14 +121,21 @@ module.exports = {
return name + ' <' + email + '>';
},

/**
* Split name and email address
*/
splitNameEmail(identity) {
return splitNameEmail(identity);
},

/**
* Load an email (both plain text and html)
*/
load(email, data) {

//Get filenames
let text = path.resolve('./app/emails/' + email + '.txt');
let html = path.resolve('./app/emails/' + email + '.html');
let text = path.resolve('./app/' + email + '.txt');
let html = path.resolve('./app/' + email + '.html');

//Return promise
return Promise.all([
Expand All @@ -61,11 +147,28 @@ module.exports = {
/**
* Send mail
*/
send(email) {
return mailer.sendMailAsync(email)
.catch(error => {
throw new SendMailError(error);
});
send(data) {

//Array
if (Array.isArray(data)) {

//No emails
if (data.length === 0) {
return Promise.resolve();
}

//Multiple emails
let promises = data
.map(getMail)
.map(sendMail);

//Return all promises wrapped
return Promise.all(promises);
}

//Get and send the mail
let mail = getMail(data);
return sendMail(mail);
}
};

Expand Down
File renamed without changes.
6 changes: 3 additions & 3 deletions app/user/emails/password-has-changed.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ module.exports = function passwordHasChanged(user) {

//Create data for emails
let data = {
confirmation: locale.t('user.passwordHasChanged.mail.confirmation')
confirmation: locale.t('mail.passwordHasChanged.confirmation')
};

//Load
return mailer.load('password-has-changed', data)
return mailer.load('user/emails/password-has-changed', data)
.spread((text, html) => ({
to: user.email,
from: EMAIL_IDENTITY_NOREPLY,
subject: locale.t('user.passwordHasChanged.mail.subject'),
subject: locale.t('mail.passwordHasChanged.subject'),
text, html
}));
};
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 952d3e2

Please sign in to comment.