UNPKG

10.1 kBJavaScriptView Raw
1'use strict';
2/* globals module, require */
3
4var winston = module.parent.require('winston'),
5 async = module.parent.require('async'),
6 nconf = module.parent.require('nconf'),
7
8 Meta = module.parent.require('./meta'),
9 User = module.parent.require('./user'),
10 Posts = module.parent.require('./posts'),
11 Topics = module.parent.require('./topics'),
12 hostEmailer = module.parent.require('./emailer'),
13 Privileges = module.parent.require('./privileges'),
14 SocketHelpers = module.parent.require('./socket.io/helpers'),
15
16 mandrill,
17 Emailer = {
18 settings: {}
19 };
20
21Emailer.init = function(data, callback) {
22
23 var render = function(req, res) {
24 res.render('admin/plugins/emailer-mandrill', {
25 url: nconf.get('url')
26 });
27 };
28
29 Meta.settings.get('mandrill', function(err, settings) {
30 Emailer.settings = Object.freeze(settings);
31 Emailer.receiptRegex = new RegExp('^reply-([\\d]+)@' + Emailer.settings.receive_domain + '$');
32
33 if (!err && settings && settings.apiKey) {
34 mandrill = require('node-mandrill')(settings.apiKey || 'Replace Me');
35 } else {
36 winston.error('[plugins/emailer-mandrill] API key not set!');
37 }
38
39 data.router.get('/admin/plugins/emailer-mandrill', data.middleware.admin.buildHeader, render);
40 data.router.get('/api/admin/plugins/emailer-mandrill', render);
41 data.router.head('/emailer-mandrill/reply', function(req, res) {
42 res.sendStatus(200);
43 });
44 data.router.post('/emailer-mandrill/reply', Emailer.receive);
45
46 if (typeof callback === 'function') {
47 callback();
48 }
49 });
50};
51
52Emailer.send = function(data, callback) {
53 if (mandrill) {
54 var headers = {};
55
56 if (data.pid && Emailer.settings.hasOwnProperty('receive_domain')) {
57 headers['Reply-To'] = 'reply-' + data.pid + '@' + Emailer.settings.receive_domain;
58 }
59 async.waterfall([
60 function(next) {
61 if (data.fromUid) {
62 next(null, data.fromUid);
63 } else if (data.pid) {
64 Posts.getPostField(data.pid, 'uid', next);
65 } else {
66 next(null, false);
67 }
68 },
69 function(uid, next) {
70 if (uid === false) { return next(null, {}); }
71 User.getSettings(uid, function(err, settings) {
72 if (err) {
73 return next(err);
74 }
75
76 if (settings.showemail) {
77 User.getUserFields(parseInt(uid, 10), ['email', 'username'], function(err, userData) {
78 next(null, userData);
79 });
80 } else {
81 User.getUserFields(parseInt(uid, 10), ['username'], function(err, userData) {
82 next(null, userData);
83 });
84 }
85 })
86 },
87 function(userData, next) {
88 mandrill('/messages/send', {
89 message: {
90 to: [{email: data.to, name: data.toName}],
91 subject: data.subject,
92 from_email: userData.email || data.from,
93 from_name: data.from_name || userData.username || undefined,
94 html: data.html,
95 text: data.plaintext,
96 auto_text: !data.plaintext,
97 headers: headers
98 }
99 }, next);
100 }
101 ], function (err) {
102 if (!err) {
103 winston.verbose('[emailer.mandrill] Sent `' + data.template + '` email to uid ' + data.uid);
104 } else {
105 winston.warn('[emailer.mandrill] Unable to send `' + data.template + '` email to uid ' + data.uid + '!!');
106 winston.warn('[emailer.mandrill] Error Stringified:' + JSON.stringify(err));
107 }
108 callback(err, data);
109 });
110 } else {
111 winston.warn('[plugins/emailer-mandrill] API key not set, not sending email as Mandrill object is not instantiated.');
112 callback(new Error('[[error:mandril-api-key-not-set]]'));
113 }
114};
115
116Emailer.receive = function(req, res) {
117 var events;
118
119 try {
120 events = JSON.parse(req.body.mandrill_events);
121 } catch (err) {
122 winston.error('[emailer.mandrill] Error parsing response JSON from Mandrill API "Receive" webhook');
123 return res.sendStatus(400);
124 }
125
126 winston.debug('POST from Mandrill contained ' + events.length + ' items');
127
128 async.eachLimit(events, 5, function(eventObj, next) {
129 async.waterfall([
130 async.apply(Emailer.verifyEvent, eventObj),
131 Emailer.resolveUserOrGuest,
132 Emailer.processEvent,
133 Emailer.notifyUsers
134 ], function(err) {
135 Emailer.handleError(err, eventObj);
136 next(); // Don't block the continued handling of received events!
137 });
138 }, function() {
139 res.sendStatus(200);
140 });
141};
142
143Emailer.verifyEvent = function(eventObj, next) {
144 // This method verifies that
145 // - the "replyTo pid" is present in the "to" field
146 // - the pid belongs to a valid tid
147 // This method also saves `pid` and `tid` into eventObj, and returns it.
148 // Returns false if any of the above fail/do not match.
149 var pid, match;
150 eventObj.msg.to.forEach(function(recipient) {
151 match = recipient[0].match(Emailer.receiptRegex);
152 if (match && match[1]) { pid = match[1]; }
153 });
154
155 if (pid) {
156 eventObj.pid = pid;
157
158 Posts.getPostField(pid, 'tid', function(err, tid) {
159 if (!err && tid) {
160 eventObj.tid = tid;
161 next(null, eventObj);
162 } else {
163 if (!tid) { winston.warn('[emailer.mandrill.verifyEvent] Could not retrieve tid'); }
164 next(new Error('invalid-data'));
165 }
166 });
167 } else {
168 winston.warn('[emailer.mandrill.verifyEvent] Could not locate post id');
169 next(new Error('invalid-data'));
170 }
171};
172
173Emailer.resolveUserOrGuest = function(eventObj, callback) {
174 // This method takes the event object, reads the sender email and resolves it to a uid
175 // if the email is set in the system. If not, and guest posting is enabled, the email
176 // is treated as a guest instead.
177 User.getUidByEmail(eventObj.msg.from_email, function(err, uid) {
178 if (uid) {
179 eventObj.uid = uid;
180 callback(null, eventObj);
181 } else {
182 // See if guests can post to the category in question
183 async.waterfall([
184 async.apply(Topics.getTopicField, eventObj.tid, 'cid'),
185 function(cid, next) {
186 Privileges.categories.groupPrivileges(cid, 'guests', next);
187 }
188 ], function(err, privileges) {
189 if (privileges['groups:topics:reply']) {
190 eventObj.uid = 0;
191
192 if (parseInt(Meta.config.allowGuestHandles, 10) === 1) {
193 if (eventObj.msg.from_name && eventObj.msg.from_name.length) {
194 eventObj.handle = eventObj.msg.from_name;
195 } else {
196 eventObj.handle = eventObj.msg.from_email;
197 }
198 }
199
200 callback(null, eventObj);
201 } else {
202 // Guests can't post here
203 winston.verbose('[emailer.mandrill] Received reply by guest to pid ' + eventObj.pid + ', but guests are not allowed to post here.');
204 callback(new Error('[[error:no-privileges]]'));
205 }
206 });
207 }
208 });
209};
210
211Emailer.processEvent = function(eventObj, callback) {
212 winston.verbose('[emailer.mandrill] Processing incoming email reply by uid ' + eventObj.uid + ' to pid ' + eventObj.pid);
213 Topics.reply({
214 uid: eventObj.uid,
215 toPid: eventObj.pid,
216 tid: eventObj.tid,
217 content: eventObj.msg.text,
218 handle: (eventObj.uid === 0 && eventObj.hasOwnProperty('handle') ? eventObj.handle : undefined)
219 }, callback);
220};
221
222Emailer.notifyUsers = function(postData, next) {
223 var result = {
224 posts: [postData],
225 privileges: {
226 'topics:reply': true
227 },
228 'reputation:disabled': parseInt(Meta.config['reputation:disabled'], 10) === 1,
229 'downvote:disabled': parseInt(Meta.config['downvote:disabled'], 10) === 1,
230 };
231
232 SocketHelpers.notifyOnlineUsers(parseInt(postData.uid, 10), result);
233 next();
234};
235
236Emailer.handleError = function(err, eventObj) {
237 if (err) {
238 switch(err.message) {
239 case '[[error:no-privileges]]':
240 case 'invalid-data':
241 // Bounce a return back to sender
242 hostEmailer.sendToEmail('bounce', eventObj.msg.from_email, Meta.config.defaultLang || 'en_GB', {
243 site_title: Meta.config.title || 'NodeBB',
244 subject: 'Re: ' + eventObj.msg.subject,
245 messageBody: eventObj.msg.html
246 }, function(err) {
247 if (err) {
248 winston.error('[emailer.mandrill] Unable to bounce email back to sender! ' + err.message);
249 } else {
250 winston.verbose('[emailer.mandrill] Bounced email back to sender (' + eventObj.msg.from_email + ')');
251 }
252 });
253 break;
254 }
255 }
256};
257
258Emailer.admin = {
259 menu: function(custom_header, callback) {
260 custom_header.plugins.push({
261 'route': '/plugins/emailer-mandrill',
262 'icon': 'fa-envelope-o',
263 'name': 'Emailer (Mandrill)'
264 });
265
266 callback(null, custom_header);
267 }
268};
269
270module.exports = Emailer;