express-generator-typescript 2.2.0 → 2.2.2

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.
Files changed (27) hide show
  1. package/lib/express-generator-typescript.js +15 -11
  2. package/lib/project-files/package.json +1 -8
  3. package/lib/project-files/spec/support/login-agent.ts +4 -6
  4. package/lib/project-files/spec/tests/auth.spec.ts +32 -37
  5. package/lib/project-files/spec/tests/users.spec.ts +59 -60
  6. package/lib/project-files/spec/types/supertest/index.d.ts +1 -1
  7. package/lib/project-files/src/configurations/HttpStatusCodes.ts +386 -0
  8. package/lib/project-files/src/declarations/classes.ts +13 -0
  9. package/lib/project-files/src/declarations/functions.ts +11 -0
  10. package/lib/project-files/src/declarations/types.ts +3 -25
  11. package/lib/project-files/src/index.ts +1 -1
  12. package/lib/project-files/src/models/User.ts +1 -1
  13. package/lib/project-files/src/repos/mock-orm.ts +1 -1
  14. package/lib/project-files/src/repos/user-repo.ts +2 -2
  15. package/lib/project-files/src/routes/api.ts +1 -1
  16. package/lib/project-files/src/routes/auth-routes.ts +6 -9
  17. package/lib/project-files/src/routes/middlware/adminMw.ts +28 -29
  18. package/lib/project-files/src/routes/middlware/validate.ts +29 -25
  19. package/lib/project-files/src/routes/shared/types.ts +22 -0
  20. package/lib/project-files/src/routes/user-routes.ts +8 -11
  21. package/lib/project-files/src/server.ts +11 -17
  22. package/lib/project-files/src/services/auth-service.ts +25 -7
  23. package/lib/project-files/src/services/user-service.ts +19 -5
  24. package/lib/project-files/src/util/pwd-util.ts +39 -0
  25. package/lib/project-files/tsconfig.json +2 -20
  26. package/package.json +1 -1
  27. package/lib/project-files/src/declarations/errors.ts +0 -54
@@ -36,13 +36,19 @@
36
36
 
37
37
  import { Request, Response, NextFunction, RequestHandler } from 'express';
38
38
 
39
- import { ParamInvalidError, ValidatorFnError } from 'src/declarations/errors';
40
- import { TAll } from 'src/declarations/types';
39
+ import HttpStatusCodes from '@src/configurations/HttpStatusCodes';
40
+ import { TAll } from '@src/declarations/types';
41
+
42
+
43
+ // **** Variables **** //
44
+
45
+ export const paramInvalidErr = 'One or more of the required params was ' +
46
+ 'missing or invalid.';
41
47
 
42
48
 
43
49
  // **** Types **** //
44
50
 
45
- type TLoopFn = (req: Request) => void;
51
+ type TLoopFn = (req: Request) => boolean;
46
52
  type TValidatorFn = ((arg: TAll) => boolean);
47
53
  type TReqObjProps = 'body' | 'query' | 'params';
48
54
 
@@ -69,7 +75,7 @@ function validate(...params: Array<string | TParamArr>): RequestHandler {
69
75
  const loopFns: TLoopFn[] = [];
70
76
  for (const param of params) {
71
77
  const { paramName, type, reqObjProp } = getParamFields(param);
72
- let loopFn: TLoopFn = () => { return; };
78
+ let loopFn: TLoopFn = () => false;
73
79
  if (type === 'string') {
74
80
  loopFn = checkStr(reqObjProp, paramName);
75
81
  } else if (type === 'number') {
@@ -85,11 +91,15 @@ function validate(...params: Array<string | TParamArr>): RequestHandler {
85
91
  loopFns.push(loopFn);
86
92
  }
87
93
  // Return middlware function
88
- return (req: Request, _: Response, next: NextFunction) => {
94
+ return (req: Request, res: Response, next: NextFunction) => {
89
95
  for (const loopFn of loopFns) {
90
- loopFn(req);
96
+ if (!loopFn(req)) {
97
+ return res
98
+ .status(HttpStatusCodes.BAD_REQUEST)
99
+ .json({ error: paramInvalidErr });
100
+ }
91
101
  }
92
- next();
102
+ return next();
93
103
  };
94
104
  }
95
105
 
@@ -139,9 +149,7 @@ function getParamFields(param: string | TParamArr): IParamFields {
139
149
  */
140
150
  function checkStr(reqObjProp: TReqObjProps, paramName: string): TLoopFn {
141
151
  return getLoopFn(reqObjProp, paramName, (toCheck) => {
142
- if (typeof toCheck !== 'string') {
143
- throw new ParamInvalidError();
144
- }
152
+ return (typeof toCheck === 'string');
145
153
  });
146
154
  }
147
155
 
@@ -151,11 +159,11 @@ function checkStr(reqObjProp: TReqObjProps, paramName: string): TLoopFn {
151
159
  function checkNum(reqObjProp: TReqObjProps, paramName: string): TLoopFn {
152
160
  return getLoopFn(reqObjProp, paramName, (toCheck) => {
153
161
  if (reqObjProp === 'query' || reqObjProp === 'params') {
154
- if (isNaN(toCheck as number)) {
155
- throw new ParamInvalidError();
156
- }
162
+ return !isNaN(toCheck as number);
157
163
  } else if (reqObjProp === 'body' && typeof toCheck !== 'number') {
158
- throw new ParamInvalidError();
164
+ return false;
165
+ } else {
166
+ return true;
159
167
  }
160
168
  });
161
169
  }
@@ -166,11 +174,11 @@ function checkNum(reqObjProp: TReqObjProps, paramName: string): TLoopFn {
166
174
  function checkBool(reqObjProp: TReqObjProps, paramName: string): TLoopFn {
167
175
  return getLoopFn(reqObjProp, paramName, (toCheck) => {
168
176
  if (reqObjProp === 'query' || reqObjProp === 'params') {
169
- if (!isBool(toCheck as boolean)) {
170
- throw new ParamInvalidError();
171
- }
177
+ return isBool(toCheck as boolean);
172
178
  } else if (reqObjProp === 'body' && typeof toCheck !== 'boolean') {
173
- throw new ParamInvalidError();
179
+ return false;
180
+ } else {
181
+ return true;
174
182
  }
175
183
  });
176
184
  }
@@ -198,9 +206,7 @@ function checkValidatorFn(
198
206
  validatorFn: TValidatorFn,
199
207
  ) {
200
208
  return getLoopFn(reqObjProp, paramName, (toCheck) => {
201
- if (!validatorFn(toCheck)) {
202
- throw new ValidatorFnError(validatorFn.name);
203
- }
209
+ return validatorFn(toCheck);
204
210
  });
205
211
  }
206
212
 
@@ -213,9 +219,7 @@ function checkDefault(
213
219
  type: string,
214
220
  ): TLoopFn {
215
221
  return getLoopFn(reqObjProp, paramName, (toCheck) => {
216
- if (typeof toCheck !== type) {
217
- throw new ParamInvalidError();
218
- }
222
+ return (toCheck === type);
219
223
  });
220
224
  }
221
225
 
@@ -225,7 +229,7 @@ function checkDefault(
225
229
  function getLoopFn(
226
230
  reqObjProp: TReqObjProps,
227
231
  paramName: string,
228
- cb: (toCheck: TAll) => void,
232
+ cb: (toCheck: TAll) => boolean,
229
233
  ): TLoopFn {
230
234
  return (req: Request) => {
231
235
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
@@ -0,0 +1,22 @@
1
+ import * as e from 'express';
2
+ import { Query } from 'express-serve-static-core';
3
+
4
+ import { ISessionUser } from '@src/routes/middlware/adminMw';
5
+
6
+
7
+ // **** Express **** //
8
+
9
+ export interface IReq<T = void> extends e.Request {
10
+ body: T;
11
+ }
12
+
13
+ export interface IReqQuery<T extends Query, U = void> extends e.Request {
14
+ query: T;
15
+ body: U;
16
+ }
17
+
18
+ export interface IRes extends e.Response {
19
+ locals: {
20
+ sessionUser: ISessionUser;
21
+ };
22
+ }
@@ -1,15 +1,12 @@
1
- import StatusCodes from 'http-status-codes';
1
+ import HttpStatusCodes from '@src/configurations/HttpStatusCodes';
2
2
 
3
- import userService from '@services/user-service';
4
- import { IUser } from '@models/User';
5
- import { IReq, IRes } from 'src/declarations/types';
3
+ import userService from '@src/services/user-service';
4
+ import { IUser } from '@src/models/User';
5
+ import { IReq, IRes } from './shared/types';
6
6
 
7
7
 
8
8
  // **** Variables **** //
9
9
 
10
- // Misc
11
- const { CREATED, OK } = StatusCodes;
12
-
13
10
  // Paths
14
11
  const paths = {
15
12
  basePath: '/users',
@@ -27,7 +24,7 @@ const paths = {
27
24
  */
28
25
  async function getAll(_: IReq, res: IRes) {
29
26
  const users = await userService.getAll();
30
- return res.status(OK).json({ users });
27
+ return res.status(HttpStatusCodes.OK).json({ users });
31
28
  }
32
29
 
33
30
  /**
@@ -36,7 +33,7 @@ async function getAll(_: IReq, res: IRes) {
36
33
  async function add(req: IReq<{user: IUser}>, res: IRes) {
37
34
  const { user } = req.body;
38
35
  await userService.addOne(user);
39
- return res.status(CREATED).end();
36
+ return res.status(HttpStatusCodes.CREATED).end();
40
37
  }
41
38
 
42
39
  /**
@@ -45,7 +42,7 @@ async function add(req: IReq<{user: IUser}>, res: IRes) {
45
42
  async function update(req: IReq<{user: IUser}>, res: IRes) {
46
43
  const { user } = req.body;
47
44
  await userService.updateOne(user);
48
- return res.status(OK).end();
45
+ return res.status(HttpStatusCodes.OK).end();
49
46
  }
50
47
 
51
48
  /**
@@ -54,7 +51,7 @@ async function update(req: IReq<{user: IUser}>, res: IRes) {
54
51
  async function _delete(req: IReq, res: IRes) {
55
52
  const id = +req.params.id;
56
53
  await userService.delete(id);
57
- return res.status(OK).end();
54
+ return res.status(HttpStatusCodes.OK).end();
58
55
  }
59
56
 
60
57
 
@@ -2,17 +2,16 @@ import cookieParser from 'cookie-parser';
2
2
  import morgan from 'morgan';
3
3
  import path from 'path';
4
4
  import helmet from 'helmet';
5
- import StatusCodes from 'http-status-codes';
6
5
  import express, { Request, Response, NextFunction } from 'express';
7
6
 
8
7
  import 'express-async-errors';
9
8
 
10
9
  import BaseRouter from './routes/api';
11
10
  import logger from 'jet-logger';
12
- import EnvVars from 'src/configurations/EnvVars';
13
- import { CustomError } from 'src/declarations/errors';
14
-
15
- import { NodeEnvs } from 'src/declarations/enums';
11
+ import EnvVars from '@src/configurations/EnvVars';
12
+ import HttpStatusCodes from '@src/configurations/HttpStatusCodes';
13
+ import { NodeEnvs } from '@src/declarations/enums';
14
+ import { RouteError } from '@src/declarations/classes';
16
15
 
17
16
 
18
17
  // **** Init express **** //
@@ -44,23 +43,18 @@ app.use('/api', BaseRouter);
44
43
 
45
44
  // Setup error handler
46
45
  app.use((
47
- err: Error | CustomError,
46
+ err: Error,
48
47
  _: Request,
49
48
  res: Response,
50
49
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
51
- __: NextFunction,
50
+ next: NextFunction,
52
51
  ) => {
53
52
  logger.err(err, true);
54
- // Status
55
- const status = (
56
- err instanceof CustomError
57
- ? err.HttpStatus
58
- : StatusCodes.BAD_REQUEST
59
- );
60
- // Return
61
- return res.status(status).json({
62
- error: err.message,
63
- });
53
+ let status = HttpStatusCodes.BAD_REQUEST;
54
+ if (err instanceof RouteError) {
55
+ status = err.status;
56
+ }
57
+ return res.status(status).json({ error: err.message });
64
58
  });
65
59
 
66
60
 
@@ -1,8 +1,18 @@
1
- import bcrypt from 'bcrypt';
1
+ import userRepo from '@src/repos/user-repo';
2
+ import jwtUtil from '@src/util/jwt-util';
3
+ import pwdUtil from '@src/util/pwd-util';
4
+ import HttpStatusCodes from '@src/configurations/HttpStatusCodes';
5
+ import { RouteError } from '@src/declarations/classes';
6
+ import { tick } from '@src/declarations/functions';
2
7
 
3
- import userRepo from '@repos/user-repo';
4
- import jwtUtil from '@util/jwt-util';
5
- import { UnauthorizedError } from 'src/declarations/errors';
8
+
9
+ // **** Variables **** //
10
+
11
+ // Errors
12
+ export const errors = {
13
+ unauth: 'Unauthorized',
14
+ emailNotFound: (email: string) => `User with email "${email}" not found`,
15
+ } as const;
6
16
 
7
17
 
8
18
  // **** Functions **** //
@@ -14,13 +24,21 @@ async function getJwt(email: string, password: string): Promise<string> {
14
24
  // Fetch user
15
25
  const user = await userRepo.getOne(email);
16
26
  if (!user) {
17
- throw new UnauthorizedError();
27
+ throw new RouteError(
28
+ HttpStatusCodes.UNAUTHORIZED,
29
+ errors.emailNotFound(email),
30
+ );
18
31
  }
19
32
  // Check password
20
33
  const hash = (user.pwdHash ?? '');
21
- const pwdPassed = await bcrypt.compare(password, hash);
34
+ const pwdPassed = await pwdUtil.compare(password, hash);
22
35
  if (!pwdPassed) {
23
- throw new UnauthorizedError();
36
+ // If password failed, wait 500ms this will increase security
37
+ await tick(500);
38
+ throw new RouteError(
39
+ HttpStatusCodes.UNAUTHORIZED,
40
+ errors.unauth,
41
+ );
24
42
  }
25
43
  // Setup Admin Cookie
26
44
  return jwtUtil.sign({
@@ -1,6 +1,12 @@
1
- import userRepo from '@repos/user-repo';
2
- import { IUser } from '@models/User';
3
- import { UserNotFoundError } from 'src/declarations/errors';
1
+ import userRepo from '@src/repos/user-repo';
2
+ import { IUser } from '@src/models/User';
3
+ import { RouteError } from '@src/declarations/classes';
4
+ import HttpStatusCodes from '@src/configurations/HttpStatusCodes';
5
+
6
+
7
+ // **** Variables **** //
8
+
9
+ export const userNotFoundErr = 'User not found';
4
10
 
5
11
 
6
12
  // **** Functions **** //
@@ -25,8 +31,12 @@ function addOne(user: IUser): Promise<void> {
25
31
  async function updateOne(user: IUser): Promise<void> {
26
32
  const persists = await userRepo.persists(user.id);
27
33
  if (!persists) {
28
- throw new UserNotFoundError();
34
+ throw new RouteError(
35
+ HttpStatusCodes.NOT_FOUND,
36
+ userNotFoundErr,
37
+ );
29
38
  }
39
+ // Return user
30
40
  return userRepo.update(user);
31
41
  }
32
42
 
@@ -36,8 +46,12 @@ async function updateOne(user: IUser): Promise<void> {
36
46
  async function _delete(id: number): Promise<void> {
37
47
  const persists = await userRepo.persists(id);
38
48
  if (!persists) {
39
- throw new UserNotFoundError();
49
+ throw new RouteError(
50
+ HttpStatusCodes.NOT_FOUND,
51
+ userNotFoundErr,
52
+ );
40
53
  }
54
+ // Delete user
41
55
  return userRepo.delete(id);
42
56
  }
43
57
 
@@ -0,0 +1,39 @@
1
+ import bcrypt from 'bcrypt';
2
+
3
+
4
+ // **** Variables **** //
5
+
6
+ const saltRounds = 12;
7
+
8
+
9
+ // **** Functions **** //
10
+
11
+ /**
12
+ * Get a hash from the password.
13
+ */
14
+ function getHash(pwd: string): Promise<string> {
15
+ return bcrypt.hash(pwd, saltRounds);
16
+ }
17
+
18
+ /**
19
+ * Useful for testing.
20
+ */
21
+ function hashSync(pwd: string): string {
22
+ return bcrypt.hashSync(pwd, saltRounds);
23
+ }
24
+
25
+ /**
26
+ * See if a password passes the hash.
27
+ */
28
+ function compare(pwd: string, hash: string): Promise<boolean> {
29
+ return bcrypt.compare(pwd, hash);
30
+ }
31
+
32
+
33
+ // **** Export Default **** //
34
+
35
+ export default {
36
+ getHash,
37
+ hashSync,
38
+ compare,
39
+ } as const;
@@ -66,27 +66,9 @@
66
66
  "skipLibCheck": true, /* Skip type checking of declaration files. */
67
67
  "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
68
68
  "paths": {
69
- "@repos/*": [
70
- "src/repos/*"
69
+ "@src/*": [
70
+ "src/*"
71
71
  ],
72
- "@models/*": [
73
- "src/models/*"
74
- ],
75
- "@shared/*": [
76
- "src/declarations/*"
77
- ],
78
- "@server": [
79
- "src/server"
80
- ],
81
- "@services/*": [
82
- "src/services/*"
83
- ],
84
- "@routes/*": [
85
- "src/routes/*"
86
- ],
87
- "@util/*": [
88
- "src/util/*"
89
- ]
90
72
  },
91
73
  "useUnknownInCatchVariables": false
92
74
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "express-generator-typescript",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
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,54 +0,0 @@
1
- import HttpStatusCodes from 'http-status-codes';
2
-
3
-
4
- export abstract class CustomError extends Error {
5
-
6
- public readonly HttpStatus = HttpStatusCodes.BAD_REQUEST;
7
-
8
- constructor(msg: string, httpStatus: number) {
9
- super(msg);
10
- this.HttpStatus = httpStatus;
11
- }
12
- }
13
-
14
- export class ParamInvalidError extends CustomError {
15
-
16
- public static readonly Msg = 'One or more of the required was missing ' +
17
- 'or invalid.';
18
- public static readonly HttpStatus = HttpStatusCodes.BAD_REQUEST;
19
-
20
- constructor() {
21
- super(ParamInvalidError.Msg, ParamInvalidError.HttpStatus);
22
- }
23
- }
24
-
25
- export class ValidatorFnError extends CustomError {
26
-
27
- public static readonly Msg = 'Validator function failed. function name: ';
28
- public static readonly HttpStatus = HttpStatusCodes.BAD_REQUEST;
29
-
30
- constructor(fnName: string) {
31
- super(ValidatorFnError.Msg + fnName, ValidatorFnError.HttpStatus);
32
- }
33
- }
34
-
35
- export class UserNotFoundError extends CustomError {
36
-
37
- public static readonly Msg = 'A user with the given id does not exists ' +
38
- 'in the database.';
39
- public static readonly HttpStatus = HttpStatusCodes.NOT_FOUND;
40
-
41
- constructor() {
42
- super(UserNotFoundError.Msg, UserNotFoundError.HttpStatus);
43
- }
44
- }
45
-
46
- export class UnauthorizedError extends CustomError {
47
-
48
- public static readonly Msg = 'Login failed';
49
- public static readonly HttpStatus = HttpStatusCodes.UNAUTHORIZED;
50
-
51
- constructor() {
52
- super(UnauthorizedError.Msg, UnauthorizedError.HttpStatus);
53
- }
54
- }