import Controller from "../../Controller"; import {Request, Response} from "express"; import MagicLinkWebSocketListener from "./MagicLinkWebSocketListener"; import {BadRequestError, NotFoundHttpError} from "../../HttpError"; import Throttler from "../../Throttler"; import Mail, {MailTemplate} from "../../Mail"; import MagicLink from "../models/MagicLink"; import config from "config"; import ModelFactory from "../../db/ModelFactory"; export default abstract class MagicLinkController extends Controller { public static async sendMagicLink(sessionID: string, actionType: string, original_url: string, email: string, mailTemplate: MailTemplate, data: object): Promise { Throttler.throttle('magic_link', 2, MagicLink.validityPeriod(), sessionID, 0, 0); Throttler.throttle('magic_link', 1, MagicLink.validityPeriod(), email, 0, 0); const link = await MagicLink.bySessionID(sessionID, actionType) || ModelFactory.get(MagicLink).make({ 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: `${config.get('base_url')}${Controller.route('magic_link', undefined, { id: link.id, token: token, })}`, })).send(email); } protected readonly magicLinkWebsocketPath: string; protected constructor(magicLinkWebsocketListener: MagicLinkWebSocketListener) { super(); this.magicLinkWebsocketPath = magicLinkWebsocketListener.path(); } public getRoutesPrefix(): string { return '/magic'; } public routes(): void { this.get('/lobby', this.getLobby, 'magic_link_lobby'); this.get('/link', this.getMagicLink, 'magic_link'); } protected 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: link.getActionType(), validUntil: link.getExpirationDate().getTime(), websocketUrl: config.get('public_websocket_url') + this.magicLinkWebsocketPath, }); } protected 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', { magicLink: magicLink, err: err, success: success && err === null, }); } protected abstract async performAction(magicLink: MagicLink, req: Request, res: Response): Promise; }