DTP Base provides a scalable and secure Node.js application development harness ready for production service.
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.
 
 
 
 

78 lines
2.2 KiB

// csrf-token.js
// Copyright (C) 2024 DTP Technologies, LLC
// All Rights Reserved
'use strict';
import dayjs from 'dayjs';
import mongoose from 'mongoose';
import { v4 as uuidv4 } from 'uuid';
const CsrfToken = mongoose.model('CsrfToken');
import { SiteService, SiteError } from '../../lib/site-lib.js';
export default class CsrfTokenService extends SiteService {
static get name ( ) { return 'CsrfTokenService'; }
static get slug ( ) { return 'csrfToken'; }
constructor (dtp) {
super(dtp, CsrfTokenService);
}
middleware (options) {
options = Object.assign({ allowReuse: false }, options);
return async (req, res, next) => {
const requestToken = req.body[`csrf-token-${options.name}`];
if (!requestToken) {
this.log.error('missing CSRF token', { options });
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 (!options.allowReuse && token.claimed) {
return next(new SiteError(403, 'This request cannot be accepted. Please re-load the form and try again.'));
}
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'));
}
}
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: dayjs(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;
}
}