// email.js // Copyright (C) 2024 Digital Telepresence, LLC // All Rights Reserved 'use strict'; import path from 'node:path'; import express from 'express'; import SvgCaptcha from 'svg-captcha'; import { SiteController, SiteError} from '../../lib/site-lib.js'; export default class EmailController extends SiteController { static get name ( ) { return 'EmailController'; } static get slug ( ) { return 'email'; } constructor (dtp) { super(dtp, EmailController); } async start ( ) { const { jobQueue: jobQueueService, limiter: limiterService } = this.dtp.services; SvgCaptcha.loadFont(path.join(this.dtp.config.root, 'client', 'fonts', 'green-nature.ttf')); this.emailJobQueue = jobQueueService.getJobQueue('email', { attempts: 3 }); const router = express.Router(); this.dtp.app.use('/email', router); router.post( '/verify', limiterService.create(limiterService.config.email.postEmailVerify), this.postEmailVerify.bind(this), ); router.get( '/verify', limiterService.create(limiterService.config.email.getEmailVerify), this.getEmailVerify.bind(this), ); router.get( '/opt-out', limiterService.create(limiterService.config.email.getEmailOptOut), this.getEmailOptOut.bind(this), ); return router; } async postEmailVerify (req, res, next) { const { email: emailService } = this.dtp.services; try { // if the session doesn't *have* an emailVerify captcha challenge, they // didn't start by requesting the form (and are most likely automated) if (!req.session.dtp || !req.session.dtp.captcha || !req.session.dtp.captcha.emailVerify) { throw new SiteError(403, 'Invalid input'); } // If the captcha text entered does not exactly match the text stored in // the session, reject the request. if (req.body.captcha.trim() !== req.session.dtp.captcha.emailVerify) { throw new SiteError(403, 'The captcha text entered does not match'); } await emailService.verifyToken(req.body.token); res.render('email/verify-success'); } catch (error) { this.log.error('failed to verify email', { error }); return next(error); } } async getEmailOptOut (req, res, next) { const { user: userService } = this.dtp.services; try { await userService.emailOptOut(req.query.u, req.query.c); res.render('email/opt-out-success'); } catch (error) { this.log.error('failed to opt-out from email', { userId: req.query.t, category: req.query.c, error, }); return next(error); } } async getEmailVerify (req, res, next) { const { email: emailService } = this.dtp.services; try { res.locals.token = await emailService.getVerificationToken(req.query.t); if (!res.locals.token) { throw new SiteError(404, 'Verification token not found'); } res.locals.captcha = SvgCaptcha.create({ size: Math.round(Math.random() * 2) + 4, width: 280, height: 80, noise: Math.floor(Math.random() * 2) + 1, // background: '#d8d8d8', color: false, }); req.session.dtp = req.session.dtp || { }; req.session.dtp.captcha = req.session.dtp.captcha || { }; req.session.dtp.captcha.emailVerify = res.locals.captcha.text; res.render('email/verify-form'); } catch (error) { this.log.error('failed to verify email', { error }); return next(error); } } }