UNPKG

hapi-405-routes

Version:

Allows 405 'Method Not Allowed' responses for hapi routes

108 lines (94 loc) 3.75 kB
'use strict'; const Joi = require('joi'); const Boom = require('boom'); var internals = {}; internals.schema = Joi.object().keys({ methodsToSupport: Joi.array().optional(), log: Joi.boolean().optional(), setAllowHeader: Joi.boolean().optional(), allowHeadWithGet: Joi.boolean().optional() }).optional(); var settings; const add405routes = { register: function (server, options, next) { const validOptions = Joi.validate(options, internals.schema); settings = validOptions.value; const routes = server.table()[0].table; var newRoutes = add405Routes(routes, settings.methodsToSupport, server.realm.modifiers.route.prefix); log('Adding ' + newRoutes.length + ' new \"Method Not Allowed\" routes'); server.route(newRoutes); next(); } }; function determineMethods(methodsToSupport) { // compared methods from server.table will be lowercase return methodsToSupport ? methodsToSupport.map(method => { return method.toLowerCase(); }) : ['get', 'post', 'delete', 'put', 'patch', 'options', 'trace']; } function add405Routes (routes, methodsToSupport, prefix) { log('Adding 405 responses for routes without methods: ' + determineMethods(methodsToSupport)); var routes405 = []; var routePaths = {}; // find which methods each route path currently supports routes.forEach((route) => { const currPath = route.path; const currMethod = route.method; // if this path has not yet been seen add all desired methods to not supported if (!routePaths[currPath]) { routePaths[currPath] = { unsupportedMethods: determineMethods(methodsToSupport), supportedMethods: [] } } // if this method is supported, remove it from the list of unsupported methods for this route routePaths[currPath].unsupportedMethods.splice(routePaths[currPath].unsupportedMethods.indexOf(currMethod), 1); routePaths[currPath].supportedMethods.push(currMethod); log(currPath + ' supports ' + currMethod); }); // add 405s to each route for every method not supported Object.keys(routePaths).forEach(route => { if (routePaths[route].unsupportedMethods.length > 0) { routes405.push(build405Route(route, routePaths[route], prefix)); } }); return routes405; } function log() { if (settings.log) { Array.prototype.slice.call(arguments).forEach(message => { console.log('| ' + message); }); } } function determineAllowHeader(supported) { const indexOfGet = supported.indexOf('get'); const headNotIncluded = supported.indexOf('head') === -1; if (settings.allowHeadWithGet && indexOfGet >= 0 && headNotIncluded) { supported.splice(indexOfGet+1, 0, 'head'); } return supported.join(', ').toUpperCase(); } function build405Route (routePath, routeMethods, prefix) { log(routePath, '\tadding 405s for: ' + routeMethods.unsupportedMethods, ''); return { path: prefix ? new RegExp('^\\' + prefix + '(.*)$').exec(routePath)[1] : routePath, method: routeMethods.unsupportedMethods, config: { description: 'Method Not Allowed Route', tags: ['methodNotAllowed'] }, handler: function (request, reply) { var err = Boom.methodNotAllowed(); if (settings.setAllowHeader) { err.output.headers['allow'] = determineAllowHeader(routeMethods.supportedMethods); } reply(err); } }; } add405routes.register.attributes = { name: '405Routes', version: '1.0.0' }; module.exports = add405routes.register;