forwardemail / free-email-forwarding
Showing 1 of 8 files from the diff.
Other files ignored by Codecov
package.json has changed.
ecosystem.json has changed.
.env.schema has changed.
LICENSE has changed.
NOTICE has changed.
test/test.js has changed.
.env.defaults has changed.

@@ -116,7 +116,6 @@
Loading
116 116
const RETRY_CODES = _.keys(CODES_TO_RESPONSE_CODES);
117 117
const transporterConfig = {
118 118
  debug: !env.IS_SILENT,
119 -
  logger,
120 119
  direct: true,
121 120
  opportunisticTLS: true,
122 121
  // this can be overridden now
@@ -124,9 +123,9 @@
Loading
124 123
  tls: {
125 124
    rejectUnauthorized: env.NODE_ENV !== 'test'
126 125
  },
127 -
  connectionTimeout: ms('10s'),
128 -
  greetingTimeout: ms('10s'),
129 -
  socketTimeout: ms('10s')
126 +
  connectionTimeout: ms('1m'),
127 +
  greetingTimeout: ms('1m'),
128 +
  socketTimeout: ms('1m')
130 129
};
131 130
132 131
class ForwardEmail {
@@ -286,7 +285,7 @@
Loading
286 285
    // initialize redis
287 286
    const client = new Redis(
288 287
      this.config.redis,
289 -
      logger,
288 +
      this.config.logger,
290 289
      this.config.redisMonitor
291 290
    );
292 291
@@ -306,7 +305,7 @@
Loading
306 305
    // <https://github.com/nodemailer/smtp-server/issues/135>
307 306
    this.server.address = this.server.server.address.bind(this.server.server);
308 307
    this.server.on('error', err => {
309 -
      logger.error(err);
308 +
      this.config.logger.error(err);
310 309
    });
311 310
312 311
    dns.setServers(this.config.dns);
@@ -339,9 +338,10 @@
Loading
339 338
    );
340 339
  }
341 340
342 -
  async listen(port) {
341 +
  async listen(port, ...args) {
343 342
    await util.promisify(this.server.listen).bind(this.server)(
344 -
      port || this.config.port
343 +
      port || this.config.port,
344 +
      ...args
345 345
    );
346 346
  }
347 347
@@ -351,57 +351,56 @@
Loading
351 351
352 352
  processRecipient(options) {
353 353
    const { recipient, name, from, raw } = options;
354 -
    const { address, addresses, port } = recipient;
355 -
    return Promise.all(
356 -
      addresses.map(({ to, host }) => {
357 -
        return this.processAddress(address, {
358 -
          host,
359 -
          name,
360 -
          envelope: {
361 -
            from,
362 -
            to
363 -
          },
364 -
          raw,
365 -
          port
366 -
        });
367 -
      })
368 -
    );
354 +
    return this.processAddress(recipient.replacements, {
355 +
      host: recipient.host,
356 +
      name,
357 +
      envelope: {
358 +
        from,
359 +
        to: recipient.to.join(', ')
360 +
      },
361 +
      raw,
362 +
      port: recipient.port
363 +
    });
369 364
  }
370 365
371 -
  async processAddress(address, options) {
366 +
  async processAddress(replacements, options) {
372 367
    try {
373 368
      const info = await this.sendEmail(options);
374 -
      logger.log(info);
369 +
      this.config.logger.log(info);
375 370
      return info;
376 371
    } catch (err) {
372 +
      this.config.logger.error(err);
377 373
      // here we do some magic so that we push an error message
378 374
      // that has the end-recipient's email masked with the
379 375
      // original to address that we were trying to send to
380 -
      err.message = err.message.replace(
381 -
        new RegExp(options.envelope.to, 'gi'),
382 -
        address
383 -
      );
384 -
      logger.error(err);
376 +
      for (const address of Object.keys(replacements)) {
377 +
        err.message = err.message.replace(
378 +
          new RegExp(address, 'gi'),
379 +
          replacements[address]
380 +
        );
381 +
      }
382 +
385 383
      return {
386 384
        accepted: [],
387 -
        rejected: [address],
385 +
        // TODO: in future handle this `options.port`
386 +
        // and also handle it in `13) send email`
387 +
        rejected: [options.host],
388 388
        rejectedErrors: [err]
389 389
      };
390 390
    }
391 391
  }
392 392
393 -
  // TODO: eventually we can combine multiple recipients
394 -
  // that have the same MX records in the same envelope `to`
393 +
  // we have already combined multiple recipients with same host+port mx combo
395 394
  sendEmail(options) {
396 395
    const { host, name, envelope, raw, port } = options;
397 396
    const transporter = nodemailer.createTransport({
398 397
      ...transporterConfig,
399 398
      ...this.config.ssl,
400 399
      port: parseInt(port, 10),
400 +
      logger: this.config.logger,
401 401
      host,
402 402
      name
403 403
    });
404 -
    logger.debug('sendEmail', { envelope, port, host, name, raw });
405 404
    return transporter.sendMail({ envelope, raw });
406 405
  }
407 406
@@ -463,7 +462,7 @@
Loading
463 462
      (Array.isArray(this.config.dnsbl.domains) &&
464 463
        this.config.dnsbl.domains.length === 0)
465 464
    ) {
466 -
      logger.warn('No DNS blacklists were provided');
465 +
      this.config.logger.warn('No DNS blacklists were provided');
467 466
      return false;
468 467
    }
469 468
@@ -538,10 +537,10 @@
Loading
538 537
      const message = await this.checkBlacklists(session.remoteAddress);
539 538
      if (!message) return fn();
540 539
      const err = new CustomError(message, 554);
541 -
      logger.error(err);
540 +
      this.config.logger.error(err);
542 541
      fn(err);
543 542
    } catch (err) {
544 -
      logger.error(err);
543 +
      this.config.logger.error(err);
545 544
      fn();
546 545
    }
547 546
  }
@@ -604,7 +603,8 @@
Loading
604 603
      // 9) X rewrite message ID and lookup multiple recipients
605 604
      // 10) X add our own DKIM signature
606 605
      // 11) X set from address using SRS
607 -
      // 12) X send email
606 +
      // 12) X normalize recipients by host and without "+" symbols
607 +
      // 13) X send email
608 608
      //
609 609
      try {
610 610
        //
@@ -709,7 +709,7 @@
Loading
709 709
        try {
710 710
          spamScore = await computeSpamScoreAsync(originalRaw);
711 711
        } catch (err) {
712 -
          logger.error(err);
712 +
          this.config.logger.error(err);
713 713
        }
714 714
715 715
        if (spamScore >= this.config.spamScoreThreshold)
@@ -979,7 +979,7 @@
Loading
979 979
980 980
            /* eslint-enable max-depth */
981 981
          } catch (err) {
982 -
            logger.error(err);
982 +
            this.config.logger.error(err);
983 983
          }
984 984
        }
985 985
@@ -1031,12 +1031,15 @@
Loading
1031 1031
                  body.port !== '25'
1032 1032
                ) {
1033 1033
                  port = body.port;
1034 -
                  logger.debug(`Custom port for ${to.address} detected`, {
1035 -
                    port
1036 -
                  });
1034 +
                  this.config.logger.debug(
1035 +
                    `Custom port for ${to.address} detected`,
1036 +
                    {
1037 +
                      port
1038 +
                    }
1039 +
                  );
1037 1040
                }
1038 1041
              } catch (err) {
1039 -
                logger.error(err);
1042 +
                this.config.logger.error(err);
1040 1043
              }
1041 1044
1042 1045
              // if we already rewrote headers no need to continue
@@ -1073,7 +1076,7 @@
Loading
1073 1076
1074 1077
              return { address: to.address, addresses, port };
1075 1078
            } catch (err) {
1076 -
              logger.error(err);
1079 +
              this.config.logger.error(err);
1077 1080
              bounces.push({
1078 1081
                address: to.address,
1079 1082
                err
@@ -1115,7 +1118,7 @@
Loading
1115 1118
                  } catch (err) {
1116 1119
                    // e.g. if the MX servers don't exist for recipient
1117 1120
                    // then obviously there should be an error
1118 -
                    logger.error(err);
1121 +
                    this.config.logger.error(err);
1119 1122
                    errors.push({
1120 1123
                      address,
1121 1124
                      err
@@ -1130,7 +1133,7 @@
Loading
1130 1133
                errors.map(error => `${error.address}: ${error.err.message}`)
1131 1134
              );
1132 1135
            } catch (err) {
1133 -
              logger.error(err);
1136 +
              this.config.logger.error(err);
1134 1137
              bounces.push({
1135 1138
                address: recipient.address,
1136 1139
                err
@@ -1171,43 +1174,65 @@
Loading
1171 1174
          this.config.srsDomain
1172 1175
        );
1173 1176
1174 -
        logger.debug('recipients', { recipients });
1177 +
        //
1178 +
        // 12) normalize recipients by host and without "+" symbols
1179 +
        //
1180 +
        const normalized = [];
1181 +
1182 +
        for (const recipient of recipients) {
1183 +
          // if it's ignored then don't bother
1184 +
          if (recipient.ignored) continue;
1185 +
          for (const address of recipient.addresses) {
1186 +
            // get normalized form without `+` symbol
1187 +
            const normal = `${this.parseUsername(
1188 +
              address.to
1189 +
            )}@${this.parseDomain(address.to, false)}`;
1190 +
            const match = normalized.find(
1191 +
              r => r.host === address.host && r.port === recipient.port
1192 +
            );
1193 +
            if (match) {
1194 +
              if (!match.to.includes(normal)) match.to.push(normal);
1195 +
              if (!match.replacements[recipient.address])
1196 +
                match.replacements[recipient.address] = normal;
1197 +
            } else {
1198 +
              const replacements = {};
1199 +
              replacements[recipient.address] = normal;
1200 +
              normalized.push({
1201 +
                host: address.host,
1202 +
                port: recipient.port,
1203 +
                to: [normal],
1204 +
                replacements
1205 +
              });
1206 +
            }
1207 +
          }
1208 +
        }
1175 1209
1176 1210
        //
1177 -
        // 12) send email
1211 +
        // 13) send email
1178 1212
        //
1179 1213
        try {
1180 1214
          const accepted = [];
1181 -
          await Promise.all(
1182 -
            recipients.map(async recipient => {
1183 -
              // return early if recipient is ignored
1184 -
              if (recipient.ignored) return;
1185 -
              const results = await this.processRecipient({
1186 -
                recipient,
1187 -
                name,
1188 -
                from,
1189 -
                raw
1215 +
          const mapper = async recipient => {
1216 +
            const result = await this.processRecipient({
1217 +
              recipient,
1218 +
              name,
1219 +
              from,
1220 +
              raw
1221 +
            });
1222 +
            if (result.accepted.length > 0) accepted.push(recipient.address);
1223 +
            if (result.rejected.length === 0) return;
1224 +
            for (let x = 0; x < result.rejected.length; x++) {
1225 +
              const err = result.rejectedErrors[x];
1226 +
              bounces.push({
1227 +
                // TODO: in future handle this port: recipient.port
1228 +
                // and also handle it in `async processAddress(replacements, opts)`
1229 +
                host: recipient.host,
1230 +
                err
1190 1231
              });
1191 -
              for (const element of results) {
1192 -
                // TODO: a@a.com -> b@b.com + c@c.com when c@c.com fails
1193 -
                // it will still say a@a.com is successful
1194 -
                // but it will be confusing because the b@b.com will be
1195 -
                // masked to a@a.com and the end user will see that there
1196 -
                // was both a success and a failure for the same address
1197 -
                // (perhaps we indicate this user has email forwarded?)
1198 -
                if (element.accepted.length > 0)
1199 -
                  accepted.push(recipient.address);
1200 -
                if (element.rejected.length === 0) continue;
1201 -
                for (let x = 0; x < element.rejected.length; x++) {
1202 -
                  const err = element.rejectedErrors[x];
1203 -
                  bounces.push({
1204 -
                    address: recipient.address,
1205 -
                    err
1206 -
                  });
1207 -
                }
1208 -
              }
1209 -
            })
1210 -
          );
1232 +
            }
1233 +
          };
1234 +
1235 +
          await Promise.all(normalized.map(mapper));
1211 1236
1212 1237
          if (bounces.length === 0) return fn();
1213 1238
@@ -1237,14 +1262,16 @@
Loading
1237 1262
1238 1263
          for (const element of bounces) {
1239 1264
            messages.push(
1240 -
              `Error for ${element.address} of "${element.err.message}"`
1265 +
              `Error for ${element.host || element.address} of "${
1266 +
                element.err.message
1267 +
              }"`
1241 1268
            );
1242 1269
          }
1243 1270
1244 1271
          // join the messages together and make them unique
1245 1272
          const err = new CustomError(_.uniq(messages).join(', '), code);
1246 1273
1247 -
          logger.error(err);
1274 +
          this.config.logger.error(err);
1248 1275
1249 1276
          fn(err);
1250 1277
        } catch (err) {
@@ -1263,9 +1290,8 @@
Loading
1263 1290
      }
1264 1291
1265 1292
      err.message += ` - if you need help please forward this email to ${this.config.email} or visit ${this.config.website}`;
1266 -
      const log = { session };
1267 -
      if (originalRaw) log.email = originalRaw.toString();
1268 -
      logger.error(err, log);
1293 +
      // if (originalRaw) meta.email = originalRaw.toString();
1294 +
      this.config.logger.error(err, { session });
1269 1295
      fn(err);
1270 1296
    });
1271 1297
@@ -1322,7 +1348,7 @@
Loading
1322 1348
        return this.getDMARC(`${parsedDomain.domain}.${parsedDomain.tld}`);
1323 1349
      }
1324 1350
1325 -
      logger.error(err);
1351 +
      this.config.logger.error(err);
1326 1352
      return false;
1327 1353
    }
1328 1354
  }
@@ -1332,7 +1358,7 @@
Loading
1332 1358
      const pass = await dkimVerify(raw, index);
1333 1359
      return pass;
1334 1360
    } catch (err) {
1335 -
      logger.error(err);
1361 +
      this.config.logger.error(err);
1336 1362
      err.message = `Your email contained an invalid DKIM signature. For more information visit https://en.wikipedia.org/wiki/DomainKeys_Identified_Mail. You can also reach out to us for help analyzing this issue.  Original error message: ${err.message}`;
1337 1363
      err.responseCode = 421;
1338 1364
      throw err;
@@ -1378,7 +1404,7 @@
Loading
1378 1404
        }
1379 1405
1380 1406
        if (limit.remaining) {
1381 -
          logger.info(
1407 +
          this.config.logger.info(
1382 1408
            `Rate limit for ${email} is now ${limit.remaining - 1}/${
1383 1409
              limit.total
1384 1410
            }`
@@ -1429,7 +1455,7 @@
Loading
1429 1455
        throw new Error(`Invalid SRS reversed address for ${address}`);
1430 1456
      return reversed;
1431 1457
    } catch (err) {
1432 -
      logger.error(err);
1458 +
      this.config.logger.error(err);
1433 1459
      return address;
1434 1460
    }
1435 1461
  }
@@ -1502,7 +1528,7 @@
Loading
1502 1528
          }
1503 1529
        }
1504 1530
      } catch (err) {
1505 -
        logger.error(err);
1531 +
        this.config.logger.error(err);
1506 1532
      }
1507 1533
    }
1508 1534
@@ -1641,7 +1667,7 @@
Loading
1641 1667
          forwardingAddresses.push(element);
1642 1668
        }
1643 1669
      } catch (err) {
1644 -
        logger.error(err);
1670 +
        this.config.logger.error(err);
1645 1671
      }
1646 1672
    }
1647 1673
Files Coverage
helpers 81.37%
index.js 41.23%
Project Totals (7 files) 47.65%
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading