import Controller from "../../Controller"; import {Request, Response} from "express"; import MagicLinkWebSocketListener from "./MagicLinkWebSocketListener"; import {BadRequestError, NotFoundHttpError} from "../../HttpError"; import querystring from "querystring"; import Throttler from "../../Throttler"; import Mail, {MailTemplate} from "../../Mail"; import MagicLink from "../models/MagicLink"; import config from "config"; export default abstract class MagicLinkController extends Controller { public static async sendMagicLink(sessionID: string, actionType: string, original_url: string, email: string, mailTemplate: MailTemplate, data: object, req: Request, res: Response): Promise { Throttler.throttle('magic_link', 2, MagicLink.validityPeriod(), sessionID, 0, 0); Throttler.throttle('magic_link', 1, MagicLink.validityPeriod(), email, 0, 0); let link = await MagicLink.bySessionID(sessionID, actionType); if (!link) { link = new MagicLink({ session_id: sessionID, action_type: actionType, original_url: original_url, }); } const token = await link.generateToken(email); await link.save(); // Send email await new Mail(mailTemplate, Object.assign(data, { link: `${req.protocol}://${req.get('host') + Controller.route('magic_link')}?${querystring.stringify({ id: link.id, token: token, })}`, })).send(email); res.redirect(Controller.route('magic_link_lobby')); } protected readonly magicLinkWebsocketPath: string; protected constructor(magicLinkWebsocketListener: MagicLinkWebSocketListener) { super(); this.magicLinkWebsocketPath = magicLinkWebsocketListener.path(); } getRoutesPrefix(): string { return '/magic'; } routes(): void { this.get('/lobby', this.getLobby, 'magic_link_lobby'); this.get('/link', this.getMagicLink, 'magic_link'); } private async getLobby(req: Request, res: Response): Promise { const link = await MagicLink.bySessionID(req.sessionID!); if (!link) { throw new NotFoundHttpError('magic link', req.url); } if (!await link.isValid()) { req.flash('error', 'This magic link has expired. Please try again.'); res.redirect(link.getOriginalURL()); return; } if (await link.isAuthorized()) { await this.performAction(link, req, res); return; } res.render('magic_link_lobby', { email: link.getEmail(), type: await link.getActionType(), validUntil: link.getExpirationDate().getTime(), websocketUrl: config.get('public_websocket_url') + this.magicLinkWebsocketPath, }); } private async getMagicLink(req: Request, res: Response): Promise { const id = parseInt(req.query.id); const token = req.query.token; if (!id || !token) throw new BadRequestError('Need parameters id, token.', 'Please try again.', req.originalUrl); let success = true; let err; const magicLink = await MagicLink.getById(id); if (!magicLink) { res.status(404); err = `Couldn't find this magic link. Perhaps it has already expired.`; success = false; } else if (!await magicLink.isAuthorized()) { err = await magicLink.verifyToken(token); if (err === null) { // Validation success, authenticate the user magicLink.authorize(); await magicLink.save(); MagicLinkWebSocketListener.refreshMagicLink(magicLink.getSessionID()); } } res.render('magic_link', { actionType: await magicLink?.getActionType(), actionMessage: await magicLink?.getActionMessage(), err: err, success: success && err === null, }); } protected abstract async performAction(magicLink: MagicLink, req: Request, res: Response): Promise; }