Strider-CD/strider

View on GitHub
apps/strider/lib/middleware.js

Summary

Maintainability
F
1 wk
Test Coverage
require('./logging');
const auth = require('./auth');
const utils = require('./utils');
const common = require('./common');
const Project = require('./models').Project;
const User = require('./models').User;

module.exports = {
  bodySetter: bodySetter,
  requireBody: requireBody,
  custom404: custom404,

  anonProject: anonProject,
  project: project,
  projectPlugin: projectPlugin,
  projectProvider: projectProvider,
  csrfErrorHandler: csrfErrorHandler,

  // Legacy aliases - don't use these:
  require_auth: auth.requireUserOr401,
  require_auth_browser: auth.requireUser,
  require_admin: auth.requireAdminOr401,
  require_project_admin: auth.requireProjectAdmin,
};

// Custom middleware to save unparsed request body to req.content
function bodySetter(req, res, next) {
  if (req._post_body) {
    return next();
  }

  req.post_body = req.post_body || '';

  if ('POST' !== req.method) {
    return next();
  }

  req._post_body = true;

  req.on('data', function (chunk) {
    req.post_body += chunk;
  });

  next();
}

// Require the specified req.body parameters, or else return a 400 with a descriptive JSON body
function requireBody(paramsList) {
  return function (req, res, next) {
    const errors = [];
    let status = 'ok';

    for (let i = 0; i < paramsList.length; i++) {
      const val = req.body[paramsList[i]];

      if (!val) {
        errors.push(`required parameter \`${paramsList[i]}\` not found.`);
        status = 'error';
      }
    }

    if (errors.length === 0) {
      next();
    } else {
      return res.status(400).json({
        errors: errors,
        status: status,
      });
    }
  };
}

function custom404(req, res, next) {
  if (req.method !== 'GET') {
    return next();
  }

  res.statusCode = 404;
  res.render('doesnotexist.html');
}

// Create helper function to get or set the provifer config. Expects req.project
// `req.providerConfig` function
//   providerConfig() -> return the config
//   providerConfig(config, next(err)). save the config
//
// For hosted providers, the following function is also available
//   accountConfig() -> return the account confi
//   acountConfig(config, next(err)). save the account config
function projectProvider(req, res, next) {
  const project = req.project;

  req.providerConfig = function (config, next) {
    if (arguments.length === 0) {
      return project.provider.config;
    }

    project.provider.config = config;
    project.markModified('provider');
    project.save(next);
  };
  // make this conditional?
  if (project.provider.account) {
    const account = project.creator.account(
      project.provider.id,
      project.provider.account
    );

    req.accountConfig = function (config, next) {
      if (arguments.length === 0) {
        return account.config;
      }

      account.config = config;
      project.creator.markModified('accounts');
      project.creator.save(next);
    };
  }

  next();
}

// Get plugin config. Expects req.project
// Sets `req.pluginConfig` function
//   pluginConfig() -> return the config
//   pluginConfig(config, next(err)). save the config
function projectPlugin(req, res, next) {
  let pluginid;
  // if only 3 args, then get pluginid from params ":plugin"
  if (arguments.length === 4) {
    pluginid = req;
    req = res;
    res = next;
    next = arguments[3];
  } else {
    pluginid = req.params.plugin;
  }

  const branch = req.project.branch(req.query.branch);
  let plugin = null;

  if (!branch) {
    return res.status(404).send('Specified branch not found for the project');
  }
  // if it's just mirroring master
  if (branch.mirror_master) {
    return res.status(400).send('Branch not individually configurable');
  }

  for (let i = 0; i < branch.plugins.length; i++) {
    if (branch.plugins[i].id === pluginid) {
      plugin = branch.plugins[i];
      break;
    }
  }

  if (plugin === null) {
    return res.status(404).send('Plugin not enabled for the specified project');
  }

  req.pluginConfig = function (config, next) {
    if (arguments.length === 0) {
      return plugin.config;
    }

    plugin.config = config;
    req.project.markModified('branches');
    req.project.save(function (err) {
      next(err, config);
    });
  };

  req.userConfig = function (config, next) {
    if (!req.user.isProjectCreator) {
      if (arguments.length === 0) {
        return false;
      }

      return next(
        new Error(
          'Current user is not the creator - cannot set the creator config'
        )
      );
    }

    if (arguments.length === 0) {
      return req.project.creator.jobplugins[pluginid];
    }

    const schema = common.userConfigs.job && common.userConfigs.job[pluginid];

    if (!schema) {
      return next(
        new Error(`Plugin ${pluginid} doesn't define any user config`)
      );
    }

    config = utils.validateAgainstSchema(config, schema);
    // TODO: validation
    req.project.creator.jobplugins[pluginid] = config;
    req.project.creator.markModified('jobplugins');

    req.project.creator.save(function (err) {
      next(err, config);
    });
  };

  next();
}

// just link project but doesn't fail if there's no auth
function anonProject(req, res, next) {
  let name = `${req.params.org}/${req.params.repo}`;

  name = name.toLowerCase();

  Project.findOne({ name: name })
    .populate('creator')
    .exec(function (err, project) {
      if (err) {
        return res.status(500).send({
          error: 'Failed to find project',
          info: err,
        });
      }

      if (!project) {
        return res.status(404).send('Project not found');
      }

      if (!project.creator) {
        return res
          .status(400)
          .send('Project malformed; project creator user is missing.');
      }

      req.project = project;
      req.accessLevel = User.projectAccessLevel(req.user, project);

      if (req.user && project.creator) {
        req.user.isProjectCreator =
          project.creator._id.toString() === req.user._id.toString();
      }

      next();
    });
}

// getProject Middleware
// assumes two url parameters, :org and :repo, and req.user
// checks user access level, and sets the following properties on the
// request object.
//
// req.project = the project
// req.accessLevel = -1 for no access, 0 for public, 1 for normal, 2 for admin
//
// Errors:
//   404: not found
//   401: not public and you don't have access
//   500: something strange happened w/ the DB lookup
function project(req, res, next) {
  if (req.params.org === 'auth') {
    return next();
  }

  anonProject(req, res, function () {
    if (req.accessLevel > -1) {
      return next();
    }

    if (!req.user) {
      req.session.return_to = req.url;
      return res.redirect('/login?ember=true');
    }

    res.status(401).send('Not authorized');
  });
}

function csrfErrorHandler(err, req, res, next) {
  if (err.code !== 'EBADCSRFTOKEN')
    return next(err);
  res.status(403).send({ status: 'forbidden', errors: [{ message: 'invalid _csrf token' }] });
}