You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
119 lines
3.5 KiB
119 lines
3.5 KiB
// 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);
|
|
}
|
|
}
|
|
}
|