import nodemailer from "nodemailer"; import fs from 'fs/promises'; import _ from 'lodash'; import logging from "./logging.js"; import { promises } from "dns"; const log = logging.create("lib/email.js"); // TODO: move this out to an easy to access/change location let configuration = {}; const configure_transporter = async () => { if(process.env.DANGER_ADMIN || process.env.DEBUG) { configuration = { streamTransport: true, debug: true, logger: log, newline: 'windows' } } else { configuration = JSON.parse(await fs.readFile("./secrets/email.json")); console.log("EMAIL CONFIG", configuration); } return nodemailer.createTransport(configuration); } // TODO: trash hacks to get the config out for the mail testing tool export const get_config = () => configuration; export const transporter = await configure_transporter(); export const load_templates = async (name) => { const html = await fs.readFile(`emails/${name}.html`); const txt = await fs.readFile(`emails/${name}.txt`); return { html: _.template(html), txt: _.template(txt) } } export const debug_templates = async (templates, name) => { if(process.env.DEBUG) { log.debug(`Writing debug email for ${name} to debug/emails/${name}.(html|txt)`); await fs.mkdir("debug/emails", { recursive: true}); log.debug(`Emails written to debug/emails/${name}.(html|txt)`); await fs.writeFile(`debug/emails/${name}.html`, templates.html); await fs.writeFile(`debug/emails/${name}.txt`, templates.text); } } /* This never rejects the promise it makes but instead returns * any errors it receives or undefined if none. */ export const send_email = async (data) => { const result = new Promise((resolve, reject) => { transporter.sendMail(data, (err, info) => { try { if(err) { log.error(err); resolve(err); } else { if(process.env.DEBUG) { log.debug(info.envelope); log.debug(info.messageId); // I only do this when I'm lazy // info.message.pipe(process.stdout); } resolve(undefined); } } catch(error) { resolve(error); } }); }); return result; } const add_reverse_error = (result, error) => { if(result.reverse_errors === undefined) { result.reverse_errors = []; } let res_error = { error: {...error} }; res_error.message = error.message; result.reverse_errors.push(res_error); } export const dns_check = async (hostname) => { let result = { ip4: {}, ip6: {} }; const res = new promises.Resolver(); res.setServers(['8.8.8.8']); try { result.ip4.host = await res.resolve4(hostname); } catch(error) { // the errors in dns are stupid. You only get .message if you call it. result.ip4.error = {...error}; result.ip4.error.message = error.message; } // no point doing reverse DNS if no IP4 address if(result.ip4.host) { result.ip4.reverse = []; for(let ip4 of result.ip4.host) { try { let host = await res.reverse(ip4); result.ip4.reverse.push(host); } catch(error) { add_reverse_error(res.ip4, error); } } } try { result.ip6.host = await res.resolve6(hostname); } catch(error) { result.ip6.error = {...error}; result.ip6.error.message = error.message; } // no point in doing reverse DNS if no address if(result.ip6.host) { result.ip6.reverse = []; for(let ip6 of result.ip6.host) { try { let host = await res.reverse(ip6); result.ip6.reverse.push(host); } catch(error) { add_reverse_error(result.ip6, error); } } } try { result.mx = await res.resolveMx(hostname); } catch(error) { result.mx_error = {...error}; result.mx_error.message = error.message; } try { result.spf = await res.resolveTxt(hostname); } catch(error) { result.spf_error = {...error}; result.spf_error.message = error.message; } try { result.dmarc = await res.resolveTxt(`_dmarc.${hostname}`); } catch(error) { result.dmarc_error = {...error}; result.dmarc_error.message = error.message; } return result; }