adapt-authoring-server 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,15 @@
7
7
  "description": "API endpoint does not exist",
8
8
  "statusCode": 404
9
9
  },
10
+ "METHOD_NOT_ALLOWED": {
11
+ "data": {
12
+ "endpoint": "The endpoint",
13
+ "method": "The HTTP method",
14
+ "allowedMethods": "The allowed HTTP methods"
15
+ },
16
+ "description": "HTTP method not allowed for this endpoint",
17
+ "statusCode": 405
18
+ },
10
19
  "NO_NET_CONN": {
11
20
  "data": {
12
21
  "hostname": "The hostname we were trying to reach"
@@ -66,6 +66,7 @@ class ServerModule extends AbstractModule {
66
66
  this.api.init()
67
67
  this.root.init()
68
68
  // add not-found handlers
69
+ this.api.expressRouter.use(ServerUtils.methodNotAllowedHandler(this.api))
69
70
  this.api.expressRouter.use(ServerUtils.apiNotFoundHandler.bind(this))
70
71
  this.root.expressRouter.use(ServerUtils.rootNotFoundHandler.bind(this))
71
72
  // add generic error handling
@@ -15,6 +15,45 @@ class ServerUtils {
15
15
  next(App.instance.errors.ENDPOINT_NOT_FOUND.setData({ endpoint: req.originalUrl, method: req.method }))
16
16
  }
17
17
 
18
+ /**
19
+ * Middleware for handling 405 Method Not Allowed errors
20
+ * Checks if the requested path exists with a different HTTP method
21
+ * @param {Router} router The router to check routes against
22
+ * @return {Function} Middleware function
23
+ */
24
+ static methodNotAllowedHandler (router) {
25
+ const routePatterns = []
26
+ const routeMap = ServerUtils.getAllRoutes(router)
27
+
28
+ for (const [path, methods] of routeMap.entries()) {
29
+ const pathPattern = path
30
+ .replace(/\/:([^/?]+)\?/g, '(?:/([^/]+))?')
31
+ .replace(/:([^/]+)/g, '([^/]+)')
32
+ const regex = new RegExp(`^${pathPattern}$`)
33
+ routePatterns.push({ regex, methods })
34
+ }
35
+
36
+ return (req, res, next) => {
37
+ const requestMethod = req.method.toUpperCase()
38
+
39
+ for (const { regex, methods } of routePatterns) {
40
+ if (regex.test(req.path)) {
41
+ if (!methods.has(requestMethod)) {
42
+ const allowedMethods = Array.from(methods).sort().join(', ')
43
+ return next(App.instance.errors.METHOD_NOT_ALLOWED.setData({
44
+ endpoint: req.originalUrl,
45
+ method: req.method,
46
+ allowedMethods
47
+ }))
48
+ }
49
+ return next()
50
+ }
51
+ }
52
+
53
+ next()
54
+ }
55
+ }
56
+
18
57
  /**
19
58
  * Generic error handling middleware for the API router
20
59
  * @param {Error} error
@@ -45,6 +84,31 @@ class ServerUtils {
45
84
  return (req, res) => res.json(topRouter.map)
46
85
  }
47
86
 
87
+ /**
88
+ * Collects all registered routes with their methods across the router hierarchy
89
+ * @param {Router} router The router to traverse
90
+ * @return {Map<string, Set<string>>} Map of route paths to sets of allowed methods
91
+ */
92
+ static getAllRoutes (router) {
93
+ const routeMap = new Map()
94
+
95
+ router.flattenRouters().forEach(r => {
96
+ r.routes.forEach(route => {
97
+ const fullPath = `${r.path !== '/' ? r.path : ''}${route.route}`
98
+
99
+ if (!routeMap.has(fullPath)) {
100
+ routeMap.set(fullPath, new Set())
101
+ }
102
+
103
+ Object.keys(route.handlers).forEach(method => {
104
+ routeMap.get(fullPath).add(method.toUpperCase())
105
+ })
106
+ })
107
+ })
108
+
109
+ return routeMap
110
+ }
111
+
48
112
  /**
49
113
  * Generates a map for a given router
50
114
  * @param {Router} topRouter
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-server",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Provides an Express application routing and more",
5
5
  "homepage": "https://github.com/adapt-security/adapt-authoring-server",
6
6
  "license": "GPL-3.0",