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
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;
|
|
}
|
|
}
|