From b8295d651d72536c7ae5404e2eec9b54a4ae18c3 Mon Sep 17 00:00:00 2001 From: cubap Date: Tue, 25 Aug 2026 11:34:48 -0500 Subject: [PATCH] fix: include accurate Allow headers on 405 responses --- rest.js | 9 ++++++++- routes/__tests__/route_wrappers.test.js | 13 ++++++++++++- routes/_gog_fragments_from_manuscript.js | 4 ++-- routes/_gog_glosses_from_manuscript.js | 4 ++-- routes/_gog_id.js | 4 ++-- routes/bulkCreate.js | 3 +-- routes/bulkUpdate.js | 3 +-- routes/create.js | 3 +-- routes/delete.js | 4 ++-- routes/history.js | 4 ++-- routes/id.js | 6 ++---- routes/overwrite.js | 3 +-- routes/patchSet.js | 3 +-- routes/patchUnset.js | 3 +-- routes/patchUpdate.js | 3 +-- routes/putUpdate.js | 3 +-- routes/query.js | 3 +-- routes/release.js | 4 ++-- routes/search.js | 6 ++---- routes/since.js | 4 ++-- utils.js | 2 -- 21 files changed, 47 insertions(+), 44 deletions(-) diff --git a/rest.js b/rest.js index 927e38fa..32e3c4c3 100644 --- a/rest.js +++ b/rest.js @@ -37,12 +37,19 @@ const createPatchOverrideMiddleware = (message) => { return (req, res, next) => { if (!checkPatchOverrideSupport(req, res)) { res.statusMessage = message + res.set("Allow", "PATCH,POST") return res.status(405).end() } next() } } +const sendMethodNotAllowed = (res, message, allowedMethods) => { + res.statusMessage = message + res.set("Allow", allowedMethods) + return res.status(405).end() +} + /** * Detects multiple MIME types smuggled into a single Content-Type header. * The following are the cases that should result in a 415 (not a 500) @@ -230,4 +237,4 @@ It may not have completed at all, and most likely did not complete successfully. res.status(error.status).send(error.message) } -export default { checkPatchOverrideSupport, createPatchOverrideMiddleware, verifyJsonContentType, verifyEitherContentType, messenger } +export default { checkPatchOverrideSupport, createPatchOverrideMiddleware, sendMethodNotAllowed, verifyJsonContentType, verifyEitherContentType, messenger } diff --git a/routes/__tests__/route_wrappers.test.js b/routes/__tests__/route_wrappers.test.js index c0bb1300..1fb452a6 100644 --- a/routes/__tests__/route_wrappers.test.js +++ b/routes/__tests__/route_wrappers.test.js @@ -88,6 +88,7 @@ function assertInvalidOverride(router) { }) assert.strictEqual(res.statusCode, 405) + assert.strictEqual(res.headers.Allow, 'PATCH,POST') assert.strictEqual(res.ended, true) assert.deepStrictEqual(nextCalls, []) } @@ -105,13 +106,23 @@ function assertValidOverride(router) { assert.strictEqual(nextCalls[0], undefined) } +function getAllowedMethods(route) { + const methods = Object.keys(route.methods) + .filter(method => method !== '_all') + .map(method => method.toUpperCase()) + if (methods.includes('GET') && !methods.includes('HEAD')) methods.push('HEAD') + return methods.join(',') +} + function assertUnsupportedMethodOnPath(router, path) { - const fallbackLayer = getRoute(router, path).stack.at(-1) + const route = getRoute(router, path) + const fallbackLayer = route.stack.at(-1) assert.ok(fallbackLayer, `Expected fallback .all() layer for '${path}'`) const { res, nextCalls } = invokeLayer(fallbackLayer) assert.strictEqual(res.statusCode, 405) + assert.strictEqual(res.headers.Allow, getAllowedMethods(route)) assert.strictEqual(res.ended, true) assert.deepStrictEqual(nextCalls, []) } diff --git a/routes/_gog_fragments_from_manuscript.js b/routes/_gog_fragments_from_manuscript.js index 109bd9dd..d8a6dedf 100644 --- a/routes/_gog_fragments_from_manuscript.js +++ b/routes/_gog_fragments_from_manuscript.js @@ -3,12 +3,12 @@ const router = express.Router() //This controller will handle all MongoDB interactions. import controller from '../db-controller.js' import auth from '../auth/index.js' +import rest from '../rest.js' router.route('/') .post(auth.checkJwt, controller._gog_fragments_from_manuscript) .all((req, res, next) => { - res.statusMessage = 'Improper request method. Please use POST.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method. Please use POST.', 'POST') }) export default router diff --git a/routes/_gog_glosses_from_manuscript.js b/routes/_gog_glosses_from_manuscript.js index 4d80970a..bbc945e7 100644 --- a/routes/_gog_glosses_from_manuscript.js +++ b/routes/_gog_glosses_from_manuscript.js @@ -3,12 +3,12 @@ const router = express.Router() //This controller will handle all MongoDB interactions. import controller from '../db-controller.js' import auth from '../auth/index.js' +import rest from '../rest.js' router.route('/') .post(auth.checkJwt, controller._gog_glosses_from_manuscript) .all((req, res, next) => { - res.statusMessage = 'Improper request method. Please use POST.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method. Please use POST.', 'POST') }) export default router \ No newline at end of file diff --git a/routes/_gog_id.js b/routes/_gog_id.js index e287ec3d..54785e18 100644 --- a/routes/_gog_id.js +++ b/routes/_gog_id.js @@ -1,13 +1,13 @@ import express from 'express' const router = express.Router() import controller from '../db-controller.js' +import rest from '../rest.js' // GoG-namespaced, stable, browser-cacheable URL returning the object with its targeting Annotations merged in. router.route('/:_id') .get(controller.expandedId) .all((req, res, next) => { - res.statusMessage = 'Improper request method, please use GET.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method, please use GET.', 'GET,HEAD') }) export default router diff --git a/routes/bulkCreate.js b/routes/bulkCreate.js index 3b5286ae..3532d4af 100644 --- a/routes/bulkCreate.js +++ b/routes/bulkCreate.js @@ -10,8 +10,7 @@ import rest from '../rest.js' router.route('/') .post(auth.checkJwt, rest.verifyJsonContentType, controller.bulkCreate) .all((req, res, next) => { - res.statusMessage = 'Improper request method for creating, please use POST.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for creating, please use POST.', 'POST') }) export default router diff --git a/routes/bulkUpdate.js b/routes/bulkUpdate.js index c22dcfb8..71ff32ec 100644 --- a/routes/bulkUpdate.js +++ b/routes/bulkUpdate.js @@ -10,8 +10,7 @@ import rest from '../rest.js' router.route('/') .put(auth.checkJwt, rest.verifyJsonContentType, controller.bulkUpdate) .all((req, res, next) => { - res.statusMessage = 'Improper request method for creating, please use PUT.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for creating, please use PUT.', 'PUT') }) export default router diff --git a/routes/create.js b/routes/create.js index 9a879e58..29537b2f 100644 --- a/routes/create.js +++ b/routes/create.js @@ -9,8 +9,7 @@ import rest from '../rest.js' router.route('/') .post(auth.checkJwt, rest.verifyJsonContentType, controller.create) .all((req, res, next) => { - res.statusMessage = 'Improper request method for creating, please use POST.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for creating, please use POST.', 'POST') }) export default router diff --git a/routes/delete.js b/routes/delete.js index f2f63565..1acecb72 100644 --- a/routes/delete.js +++ b/routes/delete.js @@ -3,12 +3,12 @@ const router = express.Router() //This controller will handle all MongoDB interactions. import { deleteObj } from '../controllers/delete.js' import auth from '../auth/index.js' +import rest from '../rest.js' router.route('/:_id') .delete(auth.checkJwt, deleteObj) .all((req, res, next) => { - res.statusMessage = 'Improper request method for deleting, please use DELETE.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for deleting, please use DELETE.', 'DELETE') }) export default router diff --git a/routes/history.js b/routes/history.js index 44943e96..45e95c7a 100644 --- a/routes/history.js +++ b/routes/history.js @@ -2,12 +2,12 @@ import express from 'express' const router = express.Router() //This controller will handle all MongoDB interactions. import controller from '../db-controller.js' +import rest from '../rest.js' router.route('/:_id') .get(controller.history) .all((req, res, next) => { - res.statusMessage = 'Improper request method, please use GET.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method, please use GET.', 'GET,HEAD') }) export default router diff --git a/routes/id.js b/routes/id.js index 3e2069a1..e69b8e3d 100644 --- a/routes/id.js +++ b/routes/id.js @@ -8,15 +8,13 @@ router.route('/:_id/expanded') .get(controller.idExpanded) .post(rest.verifyJsonContentType, controller.idExpanded) .all((req, res, next) => { - res.statusMessage = 'Improper request method, please use GET or POST.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method, please use GET or POST.', 'GET,POST,HEAD') }) router.route('/:_id') .get(controller.id) .all((req, res, next) => { - res.statusMessage = 'Improper request method, please use GET.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method, please use GET.', 'GET,HEAD') }) export default router diff --git a/routes/overwrite.js b/routes/overwrite.js index 657a4232..38360137 100644 --- a/routes/overwrite.js +++ b/routes/overwrite.js @@ -9,8 +9,7 @@ import rest from '../rest.js' router.route('/') .put(auth.checkJwt, rest.verifyJsonContentType, controller.overwrite) .all((req, res, next) => { - res.statusMessage = 'Improper request method for overwriting, please use PUT to overwrite this object.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for overwriting, please use PUT to overwrite this object.', 'PUT') }) export default router diff --git a/routes/patchSet.js b/routes/patchSet.js index c5c41bb8..5c4082fb 100644 --- a/routes/patchSet.js +++ b/routes/patchSet.js @@ -11,8 +11,7 @@ router.route('/') .patch(auth.checkJwt, rest.verifyJsonContentType, controller.patchSet) .post(auth.checkJwt, rest.verifyJsonContentType, checkPatchOverride, controller.patchSet) .all((req, res, next) => { - res.statusMessage = 'Improper request method for updating, please use PATCH to add new keys to this object.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for updating, please use PATCH to add new keys to this object.', 'PATCH,POST') }) export default router diff --git a/routes/patchUnset.js b/routes/patchUnset.js index da9ced82..60e41a07 100644 --- a/routes/patchUnset.js +++ b/routes/patchUnset.js @@ -11,8 +11,7 @@ router.route('/') .patch(auth.checkJwt, rest.verifyJsonContentType, controller.patchUnset) .post(auth.checkJwt, rest.verifyJsonContentType, checkPatchOverride, controller.patchUnset) .all((req, res, next) => { - res.statusMessage = 'Improper request method for updating, please use PATCH to remove keys from this object.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for updating, please use PATCH to remove keys from this object.', 'PATCH,POST') }) export default router diff --git a/routes/patchUpdate.js b/routes/patchUpdate.js index 2c496bf2..0647633c 100644 --- a/routes/patchUpdate.js +++ b/routes/patchUpdate.js @@ -12,8 +12,7 @@ router.route('/') .patch(auth.checkJwt, rest.verifyJsonContentType, controller.patchUpdate) .post(auth.checkJwt, rest.verifyJsonContentType, checkPatchOverride, controller.patchUpdate) .all((req, res, next) => { - res.statusMessage = 'Improper request method for updating, please use PATCH to alter existing keys on this object.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for updating, please use PATCH to alter existing keys on this object.', 'PATCH,POST') }) export default router diff --git a/routes/putUpdate.js b/routes/putUpdate.js index cca93137..e86a722c 100644 --- a/routes/putUpdate.js +++ b/routes/putUpdate.js @@ -9,8 +9,7 @@ import rest from '../rest.js' router.route('/') .put(auth.checkJwt, rest.verifyJsonContentType, controller.putUpdate) .all((req, res, next) => { - res.statusMessage = 'Improper request method for updating, please use PUT to update this object.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for updating, please use PUT to update this object.', 'PUT') }) export default router diff --git a/routes/query.js b/routes/query.js index b9882b82..281770c8 100644 --- a/routes/query.js +++ b/routes/query.js @@ -8,8 +8,7 @@ router.route('/') .post(rest.verifyJsonContentType, controller.query) .head(controller.queryHeadRequest) .all((req, res, next) => { - res.statusMessage = 'Improper request method for requesting objects with matching properties. Please use POST.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for requesting objects with matching properties. Please use POST.', 'POST,HEAD') }) export default router diff --git a/routes/release.js b/routes/release.js index 3a90ac15..c53244d6 100644 --- a/routes/release.js +++ b/routes/release.js @@ -4,12 +4,12 @@ const router = express.Router() //This controller will handle all MongoDB interactions. import controller from '../db-controller.js' import auth from '../auth/index.js' +import rest from '../rest.js' router.route('/:_id') .patch(auth.checkJwt, controller.release) .all((req, res, next) => { - res.statusMessage = 'Improper request method for releasing, please use PATCH to release this object.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for releasing, please use PATCH to release this object.', 'PATCH') }) export default router diff --git a/routes/search.js b/routes/search.js index e9c3bd51..e0e21a39 100644 --- a/routes/search.js +++ b/routes/search.js @@ -6,15 +6,13 @@ import rest from '../rest.js' router.route('/') .post(rest.verifyEitherContentType, controller.searchAsWords) .all((req, res, next) => { - res.statusMessage = 'Improper request method for search. Please use POST.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for search. Please use POST.', 'POST') }) router.route('/phrase') .post(rest.verifyEitherContentType, controller.searchAsPhrase) .all((req, res, next) => { - res.statusMessage = 'Improper request method for search. Please use POST.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method for search. Please use POST.', 'POST') }) // Note that there are more search functions available in the controller, such as controller.searchFuzzily diff --git a/routes/since.js b/routes/since.js index 4c545df0..ed52d803 100644 --- a/routes/since.js +++ b/routes/since.js @@ -2,12 +2,12 @@ import express from 'express' const router = express.Router() //This controller will handle all MongoDB interactions. import controller from '../db-controller.js' +import rest from '../rest.js' router.route('/:_id') .get(controller.since) .all((req, res, next) => { - res.statusMessage = 'Improper request method, please use GET.' - res.status(405).end() + rest.sendMethodNotAllowed(res, 'Improper request method, please use GET.', 'GET,HEAD') }) export default router diff --git a/utils.js b/utils.js index 9143fe82..8a77075e 100644 --- a/utils.js +++ b/utils.js @@ -133,7 +133,6 @@ const configureWebAnnoHeadersFor = function(obj){ else{ headers["Link"] = "; rel=\"type\"" } - headers["Allow"] = "GET,OPTIONS,HEAD,PUT,PATCH,DELETE,POST" return headers } @@ -158,7 +157,6 @@ const configureLDHeadersFor = function(obj){ headers["Content-Type"] = "application/json;charset=utf-8;" } */ - headers["Allow"] = "GET,OPTIONS,HEAD,PUT,PATCH,DELETE,POST" headers["Content-Type"] = 'application/ld+json;charset=utf-8;profile="http://www.w3.org/ns/anno.jsonld"' headers["Link"] = '; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' return headers