// csrf-token.js // Copyright (C) 2021 Digital Telepresence, LLC // License: Apache-2.0 'use strict'; const moment = require('moment'); const mongoose = require('mongoose'); const uuidv4 = require('uuid').v4; const CsrfToken = mongoose.model('CsrfToken'); const { SiteService } = require('../../lib/site-lib'); class CsrfTokenService extends SiteService { constructor (dtp) { super(dtp, module.exports); } middleware (options) { return async (req, res, next) => { const requestToken = req.body[`csrf-token-${options.name}`]; if (!requestToken) { return next(new Error('Must include valid CSRF token')); } const token = await CsrfToken.findOne({ token: requestToken }); if (!token) { return next(new Error('CSRF request token is invalid')); } if (token.ip !== req.ip) { return next(new Error('CSRF request token client mismatch')); } if (token.user) { if (!req.user) { return next(new Error('Must be logged in')); } if (!token.user.equals(req.user._id)) { return next(new Error('CSRF request token user mismatch')); } } this.log.info('claiming CSRF token', { requestToken, ip: req.ip, }); await CsrfToken.updateOne( { _id: token._id }, { $set: { claimed: new Date() } }, ); return next(); }; } async create (req, options) { options = Object.assign({ expiresMinutes: 30, }, options); const now = new Date(); let csrfToken = await CsrfToken.create({ created: now, expires: moment(now).add(options.expiresMinutes, 'minute').toDate(), user: req.user ? req.user._id : null, ip: req.ip, token: uuidv4(), }); csrfToken = csrfToken.toObject(); csrfToken.name = `csrf-token-${options.name}`; return csrfToken; } } module.exports = { slug: 'csrf-token', name: 'csrfToken', create: (dtp) => { return new CsrfTokenService(dtp); }, };