express-generator-typescript 2.1.9 → 2.1.11
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.
- package/lib/project-files/spec/index.ts +1 -1
- package/lib/project-files/spec/{loadEnv.ts → load-env.ts} +3 -1
- package/lib/project-files/spec/support/login-agent.ts +1 -1
- package/lib/project-files/spec/tests/auth.spec.ts +1 -1
- package/lib/project-files/spec/tests/users.spec.ts +1 -1
- package/lib/project-files/spec/types/supertest/index.d.ts +1 -1
- package/lib/project-files/src/models/{user-model.ts → User.ts} +0 -0
- package/lib/project-files/src/repos/mock-orm.ts +1 -1
- package/lib/project-files/src/repos/user-repo.ts +1 -1
- package/lib/project-files/src/routes/api.ts +3 -2
- package/lib/project-files/src/routes/middlware/adminMw.ts +69 -0
- package/lib/project-files/src/routes/middlware/validate.ts +240 -0
- package/lib/project-files/src/routes/user-routes.ts +1 -1
- package/lib/project-files/src/services/user-service.ts +1 -1
- package/lib/project-files/src/shared/types.ts +1 -1
- package/lib/project-files/tsconfig.json +1 -1
- package/package.json +1 -1
- package/lib/project-files/src/routes/middleware.ts +0 -187
|
@@ -6,7 +6,7 @@ import logger from 'jet-logger';
|
|
|
6
6
|
import app from '@server';
|
|
7
7
|
import userRepo from '@repos/user-repo';
|
|
8
8
|
import envVars from '@shared/env-vars';
|
|
9
|
-
import User, { UserRoles } from '@models/
|
|
9
|
+
import User, { UserRoles } from '@models/User';
|
|
10
10
|
import authRoutes from '@routes/auth-routes';
|
|
11
11
|
import { pwdSaltRounds } from 'spec/support/login-agent';
|
|
12
12
|
import { UnauthorizedError } from '@shared/errors';
|
|
@@ -5,7 +5,7 @@ import logger from 'jet-logger';
|
|
|
5
5
|
|
|
6
6
|
import app from '@server';
|
|
7
7
|
import userRepo from '@repos/user-repo';
|
|
8
|
-
import User, { IUser } from '@models/
|
|
8
|
+
import User, { IUser } from '@models/User';
|
|
9
9
|
import userRoutes from '@routes/user-routes';
|
|
10
10
|
|
|
11
11
|
import {
|
|
File without changes
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import adminMw from './middlware/adminMw';
|
|
4
|
+
import validate from './middlware/validate';
|
|
5
|
+
import User from '@models/User';
|
|
5
6
|
import authRoutes from './auth-routes';
|
|
6
7
|
import userRoutes from './user-routes';
|
|
7
8
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware to verify user logged in and is an an admin.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import StatusCodes from 'http-status-codes';
|
|
6
|
+
import { Request, Response, NextFunction } from 'express';
|
|
7
|
+
import { JwtPayload } from 'jsonwebtoken';
|
|
8
|
+
|
|
9
|
+
import envVars from '@shared/env-vars';
|
|
10
|
+
import jwtUtil from '@util/jwt-util';
|
|
11
|
+
import { IUser, UserRoles } from '@models/User';
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
// **** Variables **** //
|
|
15
|
+
|
|
16
|
+
const { UNAUTHORIZED } = StatusCodes;
|
|
17
|
+
const jwtNotPresentErr = 'JWT not present in signed cookie.';
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
// **** Types **** //
|
|
21
|
+
|
|
22
|
+
export interface ISessionUser extends JwtPayload {
|
|
23
|
+
id: number;
|
|
24
|
+
email: string;
|
|
25
|
+
name: string;
|
|
26
|
+
role: IUser['role'];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
// **** Functions **** //
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* See note at beginning of file.
|
|
34
|
+
*/
|
|
35
|
+
async function adminMw(
|
|
36
|
+
req: Request,
|
|
37
|
+
res: Response,
|
|
38
|
+
next: NextFunction,
|
|
39
|
+
) {
|
|
40
|
+
try {
|
|
41
|
+
// Extract the token
|
|
42
|
+
const cookieName = envVars.cookieProps.key,
|
|
43
|
+
jwt = req.signedCookies[cookieName];
|
|
44
|
+
if (!jwt) {
|
|
45
|
+
throw Error(jwtNotPresentErr);
|
|
46
|
+
}
|
|
47
|
+
// Make sure user role is an admin
|
|
48
|
+
const clientData = await jwtUtil.decode<ISessionUser>(jwt);
|
|
49
|
+
if (typeof clientData === 'object' && clientData.role === UserRoles.Admin) {
|
|
50
|
+
res.locals.sessionUser = clientData;
|
|
51
|
+
next();
|
|
52
|
+
} else {
|
|
53
|
+
throw Error(jwtNotPresentErr);
|
|
54
|
+
}
|
|
55
|
+
} catch (err: unknown) {
|
|
56
|
+
let error;
|
|
57
|
+
if (typeof err === 'string') {
|
|
58
|
+
error = err;
|
|
59
|
+
} else if (err instanceof Error) {
|
|
60
|
+
error = err.message;
|
|
61
|
+
}
|
|
62
|
+
return res.status(UNAUTHORIZED).json({ error });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
// **** Export Default **** //
|
|
68
|
+
|
|
69
|
+
export default adminMw;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This function returns a middleware-function that validates the parameters
|
|
3
|
+
* provided. Each argument can be a string or array.
|
|
4
|
+
*
|
|
5
|
+
* If the argument is an array the format is:
|
|
6
|
+
* ['paramName', '(optional) type or validator function (default is string)',
|
|
7
|
+
* '(optional) property on "req" to extract the value from (default is body')]
|
|
8
|
+
*
|
|
9
|
+
* If a string, 'validate' makes sure the param is of type string on req.body.
|
|
10
|
+
* For the array, 'validate' makes sure the parameter is of the specified type
|
|
11
|
+
* or that the parameter satifies the validator function. The validator
|
|
12
|
+
* function must return 'true' or 'false'.
|
|
13
|
+
*
|
|
14
|
+
* **NOTE**: For numbers, strings which evalute to (!isNaN('string') => true)
|
|
15
|
+
* are valid for req.query and req.params. But on req.body a number should be
|
|
16
|
+
* 'typeof toCheck === "number"'
|
|
17
|
+
*
|
|
18
|
+
* **NOTE**: For booleans, on req.body must be true or false, on req.query or
|
|
19
|
+
* req.params must be 'true' or 'false'.
|
|
20
|
+
*
|
|
21
|
+
* Sample argument: ['id', 'number'], id is the incoming variable, 'number'
|
|
22
|
+
* is the type, and body is where to extract 'id' from.
|
|
23
|
+
*
|
|
24
|
+
* Example 1: validate('email', ['user', 'object'], ['id', 'number', 'params'])
|
|
25
|
+
* will check that 'email' is a string in req.body, that user is of type
|
|
26
|
+
* object in req.body, and that 'id' is a boolean in req.params.
|
|
27
|
+
* Example 2: validate('password') will check that 'password' is a string on
|
|
28
|
+
* req.body.
|
|
29
|
+
* Example 3: validate(['isAdmin', 'boolean']) will check that 'isAdmin' is a
|
|
30
|
+
* boolean on req.body
|
|
31
|
+
* Example 4: validate(['user', isInstanceOfUser]) will check that 'user'
|
|
32
|
+
* satifies the 'isInstanceOfUser()' function.
|
|
33
|
+
* Example 5: validate(['email']) will check that 'email' is a string in
|
|
34
|
+
* req.body,
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
38
|
+
|
|
39
|
+
import { ParamInvalidError, ValidatorFnError } from '@shared/errors';
|
|
40
|
+
import { TAll } from '@shared/types';
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
// **** Types **** //
|
|
44
|
+
|
|
45
|
+
type TLoopFn = (req: Request) => void;
|
|
46
|
+
type TValidatorFn = ((arg: TAll) => boolean);
|
|
47
|
+
type TReqObjProps = 'body' | 'query' | 'params';
|
|
48
|
+
|
|
49
|
+
type TParamArr = {
|
|
50
|
+
0: string,
|
|
51
|
+
1?: string | TValidatorFn,
|
|
52
|
+
2?: TReqObjProps,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface IParamFields {
|
|
56
|
+
paramName: string;
|
|
57
|
+
type: string;
|
|
58
|
+
reqObjProp: TReqObjProps;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
// **** Functions **** //
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* See comments at beginning of file on usage.
|
|
66
|
+
*/
|
|
67
|
+
function validate(...params: Array<string | TParamArr>): RequestHandler {
|
|
68
|
+
// Setup loop-function array
|
|
69
|
+
const loopFns: TLoopFn[] = [];
|
|
70
|
+
for (const param of params) {
|
|
71
|
+
const { paramName, type, reqObjProp } = getParamFields(param);
|
|
72
|
+
let loopFn: TLoopFn = () => { return; };
|
|
73
|
+
if (type === 'string') {
|
|
74
|
+
loopFn = checkStr(reqObjProp, paramName);
|
|
75
|
+
} else if (type === 'number') {
|
|
76
|
+
loopFn = checkNum(reqObjProp, paramName);
|
|
77
|
+
} else if (type === 'boolean') {
|
|
78
|
+
loopFn = checkBool(reqObjProp, paramName);
|
|
79
|
+
} else if (type === 'function') {
|
|
80
|
+
const fn = param[1] as TValidatorFn;
|
|
81
|
+
loopFn = checkValidatorFn(reqObjProp, paramName, fn);
|
|
82
|
+
} else {
|
|
83
|
+
loopFn = checkDefault(reqObjProp, paramName, type);
|
|
84
|
+
}
|
|
85
|
+
loopFns.push(loopFn);
|
|
86
|
+
}
|
|
87
|
+
// Return middlware function
|
|
88
|
+
return (req: Request, _: Response, next: NextFunction) => {
|
|
89
|
+
for (const loopFn of loopFns) {
|
|
90
|
+
loopFn(req);
|
|
91
|
+
}
|
|
92
|
+
next();
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get the param name, type to check against, and the request object property
|
|
98
|
+
* as well as validating the format of the param.
|
|
99
|
+
*/
|
|
100
|
+
function getParamFields(param: string | TParamArr): IParamFields {
|
|
101
|
+
let paramName = '';
|
|
102
|
+
let type = 'string';
|
|
103
|
+
let reqObjProp: TReqObjProps = 'body';
|
|
104
|
+
// Param is string
|
|
105
|
+
if (typeof param === 'string') {
|
|
106
|
+
paramName = param;
|
|
107
|
+
// Param is array
|
|
108
|
+
} else if (param instanceof Array) {
|
|
109
|
+
// Get the param name
|
|
110
|
+
paramName = param[0];
|
|
111
|
+
if (typeof paramName !== 'string') {
|
|
112
|
+
throw Error('param name must be a string');
|
|
113
|
+
}
|
|
114
|
+
// Get the type
|
|
115
|
+
if (typeof param[1] === 'string') {
|
|
116
|
+
type = param[1];
|
|
117
|
+
} else if (typeof param[1] === 'function') {
|
|
118
|
+
type = 'function';
|
|
119
|
+
} else {
|
|
120
|
+
throw Error('param[1] must be a string or a validator function');
|
|
121
|
+
}
|
|
122
|
+
// Get the request object property
|
|
123
|
+
const prop = (param.length >= 3 ? param[2] : 'body');
|
|
124
|
+
if (prop !== 'body' && prop !== 'params' && prop !== 'query') {
|
|
125
|
+
throw Error('param[2] must be "body", "query", or "params"');
|
|
126
|
+
} else {
|
|
127
|
+
reqObjProp = prop;
|
|
128
|
+
}
|
|
129
|
+
// Throw error if not string or array
|
|
130
|
+
} else {
|
|
131
|
+
throw Error('"validate()" argument must be a string or array');
|
|
132
|
+
}
|
|
133
|
+
// Return
|
|
134
|
+
return { paramName, type, reqObjProp };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check param is a valid string.
|
|
139
|
+
*/
|
|
140
|
+
function checkStr(reqObjProp: TReqObjProps, paramName: string): TLoopFn {
|
|
141
|
+
return getLoopFn(reqObjProp, paramName, (toCheck) => {
|
|
142
|
+
if (typeof toCheck !== 'string') {
|
|
143
|
+
throw new ParamInvalidError();
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Check param is a valid number. See notes at beginning of file.
|
|
150
|
+
*/
|
|
151
|
+
function checkNum(reqObjProp: TReqObjProps, paramName: string): TLoopFn {
|
|
152
|
+
return getLoopFn(reqObjProp, paramName, (toCheck) => {
|
|
153
|
+
if (reqObjProp === 'query' || reqObjProp === 'params') {
|
|
154
|
+
if (isNaN(toCheck as number)) {
|
|
155
|
+
throw new ParamInvalidError();
|
|
156
|
+
}
|
|
157
|
+
} else if (reqObjProp === 'body' && typeof toCheck !== 'number') {
|
|
158
|
+
throw new ParamInvalidError();
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Check param is a valid boolean. See notes at beginning of file.
|
|
165
|
+
*/
|
|
166
|
+
function checkBool(reqObjProp: TReqObjProps, paramName: string): TLoopFn {
|
|
167
|
+
return getLoopFn(reqObjProp, paramName, (toCheck) => {
|
|
168
|
+
if (reqObjProp === 'query' || reqObjProp === 'params') {
|
|
169
|
+
if (!isBool(toCheck as boolean)) {
|
|
170
|
+
throw new ParamInvalidError();
|
|
171
|
+
}
|
|
172
|
+
} else if (reqObjProp === 'body' && typeof toCheck !== 'boolean') {
|
|
173
|
+
throw new ParamInvalidError();
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Checks if arg is a boolean, or a string that is 'true', or 'false'.
|
|
180
|
+
*/
|
|
181
|
+
function isBool(arg: string | boolean): boolean {
|
|
182
|
+
if (typeof arg === 'boolean') {
|
|
183
|
+
return true;
|
|
184
|
+
} else if (typeof arg === 'string') {
|
|
185
|
+
arg = arg.toLowerCase();
|
|
186
|
+
return (arg === 'true' || arg === 'false');
|
|
187
|
+
} else {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Make sure the parameter satifies the supplied validator function.
|
|
194
|
+
*/
|
|
195
|
+
function checkValidatorFn(
|
|
196
|
+
reqObjProp: TReqObjProps,
|
|
197
|
+
paramName: string,
|
|
198
|
+
validatorFn: TValidatorFn,
|
|
199
|
+
) {
|
|
200
|
+
return getLoopFn(reqObjProp, paramName, (toCheck) => {
|
|
201
|
+
if (!validatorFn(toCheck)) {
|
|
202
|
+
throw new ValidatorFnError(validatorFn.name);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* For all other type make sure the param equals the type.
|
|
209
|
+
*/
|
|
210
|
+
function checkDefault(
|
|
211
|
+
reqObjProp: TReqObjProps,
|
|
212
|
+
paramName: string,
|
|
213
|
+
type: string,
|
|
214
|
+
): TLoopFn {
|
|
215
|
+
return getLoopFn(reqObjProp, paramName, (toCheck) => {
|
|
216
|
+
if (typeof toCheck !== type) {
|
|
217
|
+
throw new ParamInvalidError();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Extract the value to check and get the loop function.
|
|
224
|
+
*/
|
|
225
|
+
function getLoopFn(
|
|
226
|
+
reqObjProp: TReqObjProps,
|
|
227
|
+
paramName: string,
|
|
228
|
+
cb: (toCheck: TAll) => void,
|
|
229
|
+
): TLoopFn {
|
|
230
|
+
return (req: Request) => {
|
|
231
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
232
|
+
const toCheck = req[reqObjProp][paramName] as TAll;
|
|
233
|
+
return cb(toCheck);
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
// **** Export Default **** //
|
|
239
|
+
|
|
240
|
+
export default validate;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "express-generator-typescript",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.11",
|
|
4
4
|
"description": "Generate new Express applications similar to express-generate which but sets it up to use TypeScript instead",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"express-generator-typescript": "node bin/cli.js",
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import StatusCodes from 'http-status-codes';
|
|
2
|
-
import { Request, Response, NextFunction, RequestHandler } from 'express';
|
|
3
|
-
import { JwtPayload } from 'jsonwebtoken';
|
|
4
|
-
|
|
5
|
-
import envVars from '@shared/env-vars';
|
|
6
|
-
import jwtUtil from '@util/jwt-util';
|
|
7
|
-
import { IUser, UserRoles } from '@models/user-model';
|
|
8
|
-
import { ParamInvalidError, ValidatorFnError } from '@shared/errors';
|
|
9
|
-
import { TAll } from '@shared/types';
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// **** Variables **** //
|
|
13
|
-
|
|
14
|
-
const { UNAUTHORIZED } = StatusCodes;
|
|
15
|
-
const jwtNotPresentErr = 'JWT not present in signed cookie.';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// **** Types **** //
|
|
19
|
-
|
|
20
|
-
type TValidatorFn = ((arg: TAll) => boolean);
|
|
21
|
-
type TParamArr = {
|
|
22
|
-
0: string,
|
|
23
|
-
1?: string | TValidatorFn,
|
|
24
|
-
2?: 'body' | 'query' | 'params',
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface ISessionUser extends JwtPayload {
|
|
28
|
-
id: number;
|
|
29
|
-
email: string;
|
|
30
|
-
name: string;
|
|
31
|
-
role: IUser['role'];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// **** Functions **** //
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Middleware to verify user logged in and is an an admin.
|
|
39
|
-
*/
|
|
40
|
-
export async function adminMw(
|
|
41
|
-
req: Request,
|
|
42
|
-
res: Response,
|
|
43
|
-
next: NextFunction,
|
|
44
|
-
) {
|
|
45
|
-
try {
|
|
46
|
-
// Extract the token
|
|
47
|
-
const cookieName = envVars.cookieProps.key,
|
|
48
|
-
jwt = req.signedCookies[cookieName];
|
|
49
|
-
if (!jwt) {
|
|
50
|
-
throw Error(jwtNotPresentErr);
|
|
51
|
-
}
|
|
52
|
-
// Make sure user role is an admin
|
|
53
|
-
const clientData = await jwtUtil.decode<ISessionUser>(jwt);
|
|
54
|
-
if (typeof clientData === 'object' && clientData.role === UserRoles.Admin) {
|
|
55
|
-
res.locals.sessionUser = clientData;
|
|
56
|
-
next();
|
|
57
|
-
} else {
|
|
58
|
-
throw Error(jwtNotPresentErr);
|
|
59
|
-
}
|
|
60
|
-
} catch (err: unknown) {
|
|
61
|
-
let error;
|
|
62
|
-
if (typeof err === 'string') {
|
|
63
|
-
error = err;
|
|
64
|
-
} else if (err instanceof Error) {
|
|
65
|
-
error = err.message;
|
|
66
|
-
}
|
|
67
|
-
return res.status(UNAUTHORIZED).json({ error });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* This function returns a middleware-function that validates the parameters
|
|
73
|
-
* provided. Each argument can be a string or array.
|
|
74
|
-
*
|
|
75
|
-
* If the argument is an array the format is:
|
|
76
|
-
* ['paramName', '(optional) type or validator function (default is string)',
|
|
77
|
-
* '(optional) property on "req" to extract the value from (default is body')]
|
|
78
|
-
*
|
|
79
|
-
* If a string, vld makes sure the param is of type string on req.body. For
|
|
80
|
-
* the array, vld makes sure parameter is of the specified type or that the
|
|
81
|
-
* parameter satifies the validator function. The validator function must
|
|
82
|
-
* return 'true' or 'false'.
|
|
83
|
-
*
|
|
84
|
-
* **NOTE**: For numbers, strings which evalute to (!isNaN('string') => true)
|
|
85
|
-
* are valid for req.query and req.params. But on req.body a number should be
|
|
86
|
-
* 'typeof toCheck === "number"'
|
|
87
|
-
*
|
|
88
|
-
* **NOTE**: For booleans, on req.body must be true or false, on req.query or
|
|
89
|
-
* req.params must be 'true' or 'false'.
|
|
90
|
-
*
|
|
91
|
-
* Sample argument: ['id', 'number'], id is the incoming variable, 'number'
|
|
92
|
-
* is the type, and body is where to extract 'id' from.
|
|
93
|
-
*
|
|
94
|
-
* Example 1: validate('email', ['user', 'object'], ['id', 'number', 'params'])
|
|
95
|
-
* will check that 'email' is a string in req.body, that user is of type
|
|
96
|
-
* object in req.body, and that 'id' is a boolean in req.params.
|
|
97
|
-
* Example 2: validate('password') will check that 'password' is a string on
|
|
98
|
-
* req.body.
|
|
99
|
-
* Example 3: validate(['isAdmin', 'boolean']) will check that 'isAdmin' is a
|
|
100
|
-
* boolean on req.body
|
|
101
|
-
* Example 4: validate(['user', isInstanceOfUser]) will check that 'user'
|
|
102
|
-
* satifies the 'isInstanceOfUser()' function.
|
|
103
|
-
* Example 5: validate(['email']) will check that 'email' is a string in
|
|
104
|
-
* req.body,
|
|
105
|
-
*/
|
|
106
|
-
export function validate(...params: Array<string | TParamArr>): RequestHandler {
|
|
107
|
-
return (req: Request, _: Response, next: NextFunction) => {
|
|
108
|
-
for (const param of params) {
|
|
109
|
-
// Check params
|
|
110
|
-
if (typeof param !== 'string' && !(param instanceof Array)) {
|
|
111
|
-
throw Error('"validate()" arg must be a string or array');
|
|
112
|
-
}
|
|
113
|
-
// If param is a string, make sure param is of type string on req.body
|
|
114
|
-
if (typeof param === 'string') {
|
|
115
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
116
|
-
if (typeof req.body[param] !== 'string') {
|
|
117
|
-
throw new ParamInvalidError();
|
|
118
|
-
}
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
// Get the value to validate from the express Request object.
|
|
122
|
-
const paramName = param[0];
|
|
123
|
-
const reqObjProp = (param.length >= 3 ? param[2] : 'body');
|
|
124
|
-
// Validate request object property
|
|
125
|
-
if (
|
|
126
|
-
reqObjProp !== 'body' &&
|
|
127
|
-
reqObjProp !== 'params' &&
|
|
128
|
-
reqObjProp !== 'query'
|
|
129
|
-
) {
|
|
130
|
-
throw Error('param[2] must be "body", "query", or "params"');
|
|
131
|
-
}
|
|
132
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
133
|
-
const toValidate = req[reqObjProp][paramName] as TAll;
|
|
134
|
-
// If type is not present, check it's a string by default
|
|
135
|
-
if (param.length === 1) {
|
|
136
|
-
if (typeof toValidate !== 'string') {
|
|
137
|
-
throw new ParamInvalidError();
|
|
138
|
-
}
|
|
139
|
-
// If a validator function is given, validate it with that
|
|
140
|
-
} else if (typeof param[1] === 'function') {
|
|
141
|
-
const fn = param[1];
|
|
142
|
-
if (!fn(toValidate)) {
|
|
143
|
-
throw new ValidatorFnError(fn.name);
|
|
144
|
-
} else {
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
// If type is a number, see note above.
|
|
148
|
-
} else if (param[1] === 'number') {
|
|
149
|
-
if (reqObjProp === 'query' || reqObjProp === 'params') {
|
|
150
|
-
if (isNaN(toValidate as number)) {
|
|
151
|
-
throw new ParamInvalidError();
|
|
152
|
-
}
|
|
153
|
-
} else if (reqObjProp === 'body' && typeof toValidate !== 'number') {
|
|
154
|
-
throw new ParamInvalidError();
|
|
155
|
-
}
|
|
156
|
-
// If type is a boolean, see note above.
|
|
157
|
-
} else if (param[1] === 'boolean') {
|
|
158
|
-
if (reqObjProp === 'query' || reqObjProp === 'params') {
|
|
159
|
-
if (!isBool(toValidate as boolean)) {
|
|
160
|
-
throw new ParamInvalidError();
|
|
161
|
-
}
|
|
162
|
-
} else if (reqObjProp === 'body' && typeof toValidate !== 'boolean') {
|
|
163
|
-
throw new ParamInvalidError();
|
|
164
|
-
}
|
|
165
|
-
// Check the param is the provided type
|
|
166
|
-
} else if (typeof toValidate !== param[1]) {
|
|
167
|
-
throw new ParamInvalidError();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
// Call next() if no errors thrown
|
|
171
|
-
next();
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Checks if arg is a boolean, or a string that is 'true', or 'false'.
|
|
177
|
-
*/
|
|
178
|
-
function isBool(arg: string | boolean): boolean {
|
|
179
|
-
if (typeof arg === 'boolean') {
|
|
180
|
-
return true;
|
|
181
|
-
} else if (typeof arg === 'string') {
|
|
182
|
-
arg = arg.toLowerCase();
|
|
183
|
-
return (arg === 'true' || arg === 'false');
|
|
184
|
-
} else {
|
|
185
|
-
return false;
|
|
186
|
-
}
|
|
187
|
-
}
|