// csrf-token.js
// Copyright (C) 2022 DTP Technologies, 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,
      });
      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); },
};