express-generator-typescript 2.1.9 → 2.1.10

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.
@@ -1,4 +1,4 @@
1
- import './loadEnv';
1
+ import './load-env';
2
2
  import find from 'find';
3
3
  import Jasmine from 'jasmine';
4
4
  import commandLineArgs from 'command-line-args';
@@ -1,8 +1,10 @@
1
1
  import dotenv from 'dotenv';
2
2
 
3
3
 
4
+ // **** Load test env file **** //
5
+
4
6
  const result2 = dotenv.config({
5
- path: `./src/pre-start/env/test.env`,
7
+ path: `./env/test.env`,
6
8
  });
7
9
 
8
10
  if (result2.error) {
@@ -1,6 +1,7 @@
1
1
  import { Router } from 'express';
2
2
 
3
- import { adminMw, validate } from './middleware';
3
+ import adminMw from './middlware/adminMw';
4
+ import validate from './middlware/validate';
4
5
  import User from '@models/user-model';
5
6
  import authRoutes from './auth-routes';
6
7
  import userRoutes from './user-routes';
@@ -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-model';
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;
@@ -1,7 +1,7 @@
1
1
  import * as e from 'express';
2
2
  import { Query } from 'express-serve-static-core';
3
3
 
4
- import { ISessionUser } from '@routes/middleware';
4
+ import { ISessionUser } from '@routes/middlware/adminMw';
5
5
 
6
6
 
7
7
  // **** Misc **** //
@@ -93,7 +93,7 @@
93
93
  "include": [
94
94
  "src/**/*.ts",
95
95
  "spec/**/*.ts"
96
- ],
96
+ , "src/routes/middlware" ],
97
97
  "exclude": [
98
98
  "src/public/"
99
99
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-generator-typescript",
3
- "version": "2.1.9",
3
+ "version": "2.1.10",
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
- }