This repository was archived by the owner on Mar 6, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 197
/
Copy pathrequest-validator.js
215 lines (185 loc) · 7.53 KB
/
request-validator.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
"use strict";
module.exports = requestValidator;
const util = require("./helpers/util");
const ono = require("@jsdevtools/ono");
const _ = require("lodash");
/**
* Validates the HTTP request against the Swagger API.
* An error is sent downstream if the request is invalid for any reason.
*
* @param {MiddlewareContext} context
* @returns {function[]}
*/
function requestValidator (context) {
return [http500, http401, http404, http405, http406, http413, http415];
/**
* Throws an HTTP 500 error if the Swagger API is invalid.
* Calling {@link Middleware#init} again with a valid Swagger API will clear the error.
*/
function http500 (req, res, next) {
if (context.error) {
context.error.status = 500;
throw context.error;
}
next();
}
}
/**
* If the Swagger API requires security for the request, and the request doesn't contain the necessary security info,
* then an HTTP 401 (Unauthorized) is thrown, and the WWW-Authenticate header is set.
* NOTE: This does not perform any authentication or authorization. It simply verifies that authentication info is present.
*/
function http401 (req, res, next) {
if (util.isSwaggerRequest(req) && req.swagger.security.length > 0) {
let securityTypes = [];
util.debug("Validating security requirements");
// Loop through each Security Requirement (if ANY of them are met, then the request is valid)
let isValid = req.swagger.security.some((requirement) => {
let securityDefinitions = _.map(requirement, (scopes, name) => {
return req.swagger.api.securityDefinitions[name];
});
// Loop through each Security Definition (if ALL of them are met, then the request is valid)
return securityDefinitions.every((securityDef) => {
if (securityTypes.indexOf(securityDef.type) === -1) {
securityTypes.push(securityDef.type);
}
if (securityDef.type === "basic") {
return _.startsWith(req.header("Authorization"), "Basic ");
}
else if (securityDef.type === "apiKey" && securityDef.in === "header") {
return req.header(securityDef.name) !== undefined;
}
else if (securityDef.type === "apiKey" && securityDef.in === "query") {
return req.query[securityDef.name] !== undefined;
}
else {
// For any other type of security, just assume it's valid.
// TODO: Is there a way to validate OAuth2 here?
return true;
}
});
});
if (!isValid) {
securityTypes = securityTypes.join(", ");
util.debug(
"The client didn't provide authentication information for any of the required authentication types (%s). " +
"Returning HTTP 401 (Unauthorized)", securityTypes
);
res.set("WWW-Authenticate", 'Basic realm="' + (req.hostname || "server") + '"');
throw ono({ status: 401 }, "%s %s requires authentication (%s)", req.method, req.path, securityTypes);
}
}
next();
}
/**
* If the request is under the Swagger API's basePath, but no matching Path was found,
* then an HTTP 404 (Not Found) error is thrown
*/
function http404 (req, res, next) {
if (req.swagger && req.swagger.api && !req.swagger.path) {
util.debug(
'Client requested path "%s", which is not defined in the Swagger API. Returning HTTP 404 (Not Found)',
req.path
);
throw ono({ status: 404 }, "Resource not found: %s", req.path);
}
next();
}
/**
* If the Swagger Path was matched, but the HTTP method doesn't match any of the Swagger Operations,
* then an HTTP 405 (Method Not Allowed) error is thrown, and the "Allow" header is set to the list of valid methods
*/
function http405 (req, res, next) {
if (req.swagger && req.swagger.path && !req.swagger.operation) {
util.debug(
'Client attempted a %s operation on "%s", which is not allowed by the Swagger API. ' +
"Returning HTTP 405 (Method Not Allowed)",
req.method, req.path
);
// Let the client know which methods are allowed
let allowedList = util.getAllowedMethods(req.swagger.path);
res.set("Allow", allowedList);
throw ono({ status: 405 }, "%s does not allow %s. \nAllowed methods: %s",
req.path, req.method, allowedList || "NONE");
}
next();
}
/**
* If the Swagger API specifies the MIME types that this operation produces,
* and the HTTP Accept header does not match any of those MIME types, then an HTTP 406 (Not Acceptable) is thrown.
*/
function http406 (req, res, next) {
if (util.isSwaggerRequest(req)) {
// Get the MIME types that this operation produces
let produces = req.swagger.operation.produces || req.swagger.api.produces || [];
if (produces.length > 0) {
util.debug("Validating Accept header (%s)", req.get("Accept"));
if (!req.accepts(produces)) {
let accepts = req.accepts();
util.debug(
'The %s operation on "%s" only produces %j content, but the client requested %j. ' +
"Returning HTTP 406 (Not Acceptable)",
req.method, req.path, produces, accepts
);
throw ono({ status: 406 }, "%s %s cannot produce any of the requested formats (%s). \nSupported formats: %s",
req.method, req.path, accepts.join(", "), produces.join(", "));
}
}
}
next();
}
/**
* Throws an HTTP 413 (Request Entity Too Large) if the HTTP request includes
* body content that is not allowed by the Swagger API.
*/
function http413 (req, res, next) {
if (util.isSwaggerRequest(req)) {
// Determine if the request allows body content
let bodyAllowed = req.swagger.params.some((param) => {
return param.in === "body" || param.in === "formData";
});
if (!bodyAllowed) {
// NOTE: We used to also check the Transfer-Encoding header, but that fails in Node 0.10.x
// TODO: Once we drop support for Node 0.10.x, add a Transfer-Encoding check (via typeIs.hasBody())
let length = req.get("Content-Length");
util.debug("Validating Content-Length header (%d)", length);
// NOTE: Even a zero-byte file attachment will have a Content-Length > 0
if (length > 0) {
util.debug(
'The HTTP request contains body content, but the %s operation on "%s" does not allow a request body. ' +
"Returning HTTP 413 (Request Entity Too Large)",
req.method, req.path
);
throw ono({ status: 413 }, "%s %s does not allow body content", req.method, req.path);
}
}
}
next();
}
/**
* Validates the HTTP Content-Type header against the Swagger API's "consumes" MIME types,
* and throws an HTTP 415 (Unsupported Media Type) if there's a conflict.
*/
function http415 (req, res, next) {
if (util.isSwaggerRequest(req)) {
// Only validate the Content-Type if there's body content
if (!_.isEmpty(req.body)) {
// Get the MIME types that this operation consumes
let consumes = req.swagger.operation.consumes || req.swagger.api.consumes || [];
if (consumes.length > 0) {
util.debug("Validating Content-Type header (%s)", req.get("Content-Type"));
if (!req.is(consumes)) {
let contentType = req.header("Content-Type");
util.debug(
'Client attempted to send %s data to the %s operation on "%s", which is not allowed by the Swagger API. ' +
"Returning HTTP 415 (Unsupported Media Type)",
contentType, req.method, req.path
);
throw ono({ status: 415 }, '%s %s does not allow Content-Type "%s". \nAllowed Content-Types: %s',
req.method, req.path, contentType, consumes.join(", "));
}
}
}
}
next();
}