exp-node-server 1.1.7 → 2.0.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.
Files changed (181) hide show
  1. package/README.md +82 -20
  2. package/dist/cli.js +525 -0
  3. package/package.json +29 -9
  4. package/templates/mongoose/.dockerignore +10 -0
  5. package/templates/mongoose/.env.example +56 -7
  6. package/templates/mongoose/.prettierignore +3 -0
  7. package/templates/mongoose/.prettierrc +7 -0
  8. package/templates/mongoose/Dockerfile +33 -0
  9. package/templates/mongoose/_gitignore +32 -0
  10. package/templates/mongoose/docker-compose.yml +21 -0
  11. package/templates/mongoose/eslint.config.js +35 -0
  12. package/templates/mongoose/package-lock.json +6377 -0
  13. package/templates/mongoose/package.json +49 -10
  14. package/templates/mongoose/readme.md +85 -0
  15. package/templates/mongoose/src/app/configs/index.ts +178 -26
  16. package/templates/mongoose/src/app/errors/zodError.ts +16 -16
  17. package/templates/mongoose/src/app/lib/db.ts +18 -0
  18. package/templates/mongoose/src/app/lib/logger.ts +12 -0
  19. package/templates/mongoose/src/app/middlewares/auth.ts +48 -44
  20. package/templates/mongoose/src/app/middlewares/global_error_handler.ts +59 -65
  21. package/templates/mongoose/src/app/middlewares/not_found_api.ts +7 -7
  22. package/templates/mongoose/src/app/middlewares/rate_limiter.ts +153 -0
  23. package/templates/mongoose/src/app/middlewares/request_validator.ts +11 -10
  24. package/templates/mongoose/src/app/middlewares/uploader.ts +17 -13
  25. package/templates/mongoose/src/app/modules/account/account.controller.ts +156 -0
  26. package/templates/mongoose/src/app/modules/account/account.interface.ts +65 -0
  27. package/templates/mongoose/src/app/modules/account/account.route.ts +101 -0
  28. package/templates/mongoose/src/app/modules/account/account.schema.ts +97 -0
  29. package/templates/mongoose/src/app/modules/account/account.service.ts +422 -0
  30. package/templates/mongoose/src/app/modules/account/account.swagger.ts +278 -0
  31. package/templates/mongoose/src/app/modules/account/account.validation.ts +63 -0
  32. package/templates/mongoose/src/app/modules/profile/profile.controller.ts +17 -0
  33. package/templates/mongoose/src/app/modules/profile/profile.interface.ts +7 -0
  34. package/templates/mongoose/src/app/modules/profile/profile.route.ts +28 -0
  35. package/templates/mongoose/src/app/modules/profile/profile.schema.ts +13 -0
  36. package/templates/mongoose/src/app/modules/profile/profile.service.ts +28 -0
  37. package/templates/mongoose/src/app/modules/profile/profile.swagger.ts +34 -0
  38. package/templates/mongoose/src/app/modules/profile/profile.validation.ts +8 -0
  39. package/templates/mongoose/src/app/passport-js/index.ts +87 -0
  40. package/templates/mongoose/src/app/passport-js/oauth-state.ts +150 -0
  41. package/templates/mongoose/src/app/passport-js/providers.ts +258 -0
  42. package/templates/mongoose/src/app/passport-js/social-account.ts +141 -0
  43. package/templates/mongoose/src/app/queues/agenda.ts +24 -0
  44. package/templates/mongoose/src/app/queues/email.queue.ts +53 -0
  45. package/templates/mongoose/src/app/queues/index.ts +13 -0
  46. package/templates/mongoose/src/app/templates/otpEmailTemplate.ts +185 -0
  47. package/templates/mongoose/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
  48. package/templates/mongoose/src/app/types/error.d.ts +7 -7
  49. package/templates/mongoose/src/app/types/express.d.ts +11 -0
  50. package/templates/mongoose/src/app/types/passport-oauth1.d.ts +51 -0
  51. package/templates/mongoose/src/app/utils/JWT.ts +38 -18
  52. package/templates/mongoose/src/app/utils/app_error.ts +10 -10
  53. package/templates/mongoose/src/app/utils/auth_cookie.ts +27 -0
  54. package/templates/mongoose/src/app/utils/catch_async.ts +8 -8
  55. package/templates/mongoose/src/app/utils/cloudinary.ts +26 -59
  56. package/templates/mongoose/src/app/utils/mail_sender.ts +26 -92
  57. package/templates/mongoose/src/app/utils/manage_response.ts +19 -19
  58. package/templates/mongoose/src/app/utils/otp.ts +20 -0
  59. package/templates/mongoose/src/app/utils/session.ts +55 -0
  60. package/templates/mongoose/src/app.ts +73 -42
  61. package/templates/mongoose/src/routes.ts +13 -13
  62. package/templates/mongoose/src/server.ts +38 -18
  63. package/templates/mongoose/src/swaggerOptions.ts +23 -23
  64. package/templates/mongoose/tests/app.test.ts +97 -0
  65. package/templates/mongoose/tests/global-setup.ts +15 -0
  66. package/templates/mongoose/tests/helpers/fixtures.ts +42 -0
  67. package/templates/mongoose/tests/routes/account.test.ts +614 -0
  68. package/templates/mongoose/tests/routes/profile.test.ts +95 -0
  69. package/templates/mongoose/tests/routes/social.test.ts +310 -0
  70. package/templates/mongoose/tests/setup.ts +25 -0
  71. package/templates/mongoose/tsconfig.json +16 -20
  72. package/templates/mongoose/vitest.config.ts +43 -0
  73. package/templates/prisma/.dockerignore +2 -0
  74. package/templates/prisma/.env.example +62 -6
  75. package/templates/prisma/.prettierignore +3 -0
  76. package/templates/prisma/.prettierrc +7 -0
  77. package/templates/prisma/.projectrc.json +1 -1
  78. package/templates/prisma/Dockerfile +21 -7
  79. package/templates/prisma/_gitignore +35 -0
  80. package/templates/prisma/docker-compose.yml +8 -7
  81. package/templates/prisma/eslint.config.js +35 -0
  82. package/templates/prisma/package-lock.json +7327 -0
  83. package/templates/prisma/package.json +47 -9
  84. package/templates/prisma/prisma/schema/account.schema.prisma +92 -10
  85. package/templates/prisma/prisma/schema/profile.schema.prisma +8 -3
  86. package/templates/prisma/prisma/schema/schema.prisma +1 -2
  87. package/templates/prisma/prisma.config.ts +5 -5
  88. package/templates/prisma/readme.md +81 -0
  89. package/templates/prisma/src/app/configs/index.ts +175 -26
  90. package/templates/prisma/src/app/errors/zodError.ts +16 -16
  91. package/templates/prisma/src/app/lib/logger.ts +12 -0
  92. package/templates/prisma/src/app/lib/prisma.ts +4 -6
  93. package/templates/prisma/src/app/middlewares/auth.ts +37 -16
  94. package/templates/prisma/src/app/middlewares/global_error_handler.ts +42 -35
  95. package/templates/prisma/src/app/middlewares/not_found_api.ts +7 -7
  96. package/templates/prisma/src/app/middlewares/rate_limiter.ts +124 -0
  97. package/templates/prisma/src/app/middlewares/request_validator.ts +4 -3
  98. package/templates/prisma/src/app/middlewares/uploader.ts +17 -13
  99. package/templates/prisma/src/app/modules/account/account.controller.ts +74 -34
  100. package/templates/prisma/src/app/modules/account/account.route.ts +68 -22
  101. package/templates/prisma/src/app/modules/account/account.service.ts +329 -309
  102. package/templates/prisma/src/app/modules/account/account.swagger.ts +167 -76
  103. package/templates/prisma/src/app/modules/account/account.validation.ts +41 -19
  104. package/templates/prisma/src/app/modules/profile/profile.controller.ts +4 -4
  105. package/templates/prisma/src/app/modules/profile/profile.route.ts +18 -12
  106. package/templates/prisma/src/app/modules/profile/profile.service.ts +11 -18
  107. package/templates/prisma/src/app/modules/profile/profile.swagger.ts +11 -11
  108. package/templates/prisma/src/app/modules/profile/profile.validation.ts +2 -2
  109. package/templates/prisma/src/app/passport-js/index.ts +87 -0
  110. package/templates/prisma/src/app/passport-js/oauth-state.ts +150 -0
  111. package/templates/prisma/src/app/passport-js/providers.ts +263 -0
  112. package/templates/prisma/src/app/passport-js/social-account.ts +147 -0
  113. package/templates/prisma/src/app/queues/boss.ts +8 -0
  114. package/templates/prisma/src/app/queues/email.queue.ts +56 -0
  115. package/templates/prisma/src/app/queues/index.ts +14 -0
  116. package/templates/prisma/src/app/queues/maintenance.queue.ts +30 -0
  117. package/templates/prisma/src/app/templates/otpEmailTemplate.ts +185 -0
  118. package/templates/prisma/src/app/templates/resetPasswordEmailTemplate.ts +157 -0
  119. package/templates/prisma/src/app/types/error.d.ts +7 -7
  120. package/templates/prisma/src/app/types/express.d.ts +11 -0
  121. package/templates/prisma/src/app/types/passport-oauth1.d.ts +51 -0
  122. package/templates/prisma/src/app/utils/JWT.ts +36 -17
  123. package/templates/prisma/src/app/utils/app_error.ts +10 -10
  124. package/templates/prisma/src/app/utils/auth_cookie.ts +27 -0
  125. package/templates/prisma/src/app/utils/catch_async.ts +8 -8
  126. package/templates/prisma/src/app/utils/cloudinary.ts +26 -59
  127. package/templates/prisma/src/app/utils/mail_sender.ts +26 -92
  128. package/templates/prisma/src/app/utils/manage_response.ts +19 -19
  129. package/templates/prisma/src/app/utils/otp.ts +20 -0
  130. package/templates/prisma/src/app/utils/session.ts +57 -0
  131. package/templates/prisma/src/app.ts +73 -42
  132. package/templates/prisma/src/routes.ts +12 -10
  133. package/templates/prisma/src/server.ts +37 -19
  134. package/templates/prisma/src/swaggerOptions.ts +22 -26
  135. package/templates/prisma/tests/app.test.ts +86 -0
  136. package/templates/prisma/tests/helpers/fixtures.ts +35 -0
  137. package/templates/prisma/tests/helpers/prisma.ts +32 -0
  138. package/templates/prisma/tests/routes/account.test.ts +627 -0
  139. package/templates/prisma/tests/routes/profile.test.ts +91 -0
  140. package/templates/prisma/tests/routes/social.test.ts +317 -0
  141. package/templates/prisma/tests/setup.ts +10 -0
  142. package/templates/prisma/tsconfig.json +16 -23
  143. package/templates/prisma/vitest.config.ts +38 -0
  144. package/bin/cli.js +0 -41
  145. package/src/createProject.js +0 -61
  146. package/src/generateModule.js +0 -93
  147. package/src/utils/getProjectType.js +0 -13
  148. package/src/utils/mongoose/controllerGenerator.js +0 -70
  149. package/src/utils/mongoose/routeGenerator.js +0 -27
  150. package/src/utils/mongoose/schemaGenerator.js +0 -11
  151. package/src/utils/mongoose/serviceGenerator.js +0 -49
  152. package/src/utils/mongoose/swaggerGenerator.js +0 -112
  153. package/src/utils/mongoose/validationGenerator.js +0 -13
  154. package/src/utils/prisma/prismaSchemaGenerator.js +0 -10
  155. package/src/utils/prisma/prismaServiceGenerator.js +0 -47
  156. package/src/utils/routeInjector.js +0 -40
  157. package/src/utils/swaggerInjector.js +0 -52
  158. package/src/utils/validateModuleName.js +0 -17
  159. package/src/utils/validateProjectName.js +0 -15
  160. package/src/utils.js +0 -26
  161. package/templates/mongoose/src/app/errors/castError.ts +0 -23
  162. package/templates/mongoose/src/app/errors/duplicateError.ts +0 -26
  163. package/templates/mongoose/src/app/errors/validationError.ts +0 -25
  164. package/templates/mongoose/src/app/modules/auth/auth.controller.ts +0 -127
  165. package/templates/mongoose/src/app/modules/auth/auth.interface.ts +0 -24
  166. package/templates/mongoose/src/app/modules/auth/auth.route.ts +0 -46
  167. package/templates/mongoose/src/app/modules/auth/auth.schema.ts +0 -19
  168. package/templates/mongoose/src/app/modules/auth/auth.service.ts +0 -324
  169. package/templates/mongoose/src/app/modules/auth/auth.swagger.ts +0 -214
  170. package/templates/mongoose/src/app/modules/auth/auth.validation.ts +0 -37
  171. package/templates/mongoose/src/app/modules/user/user.controller.ts +0 -17
  172. package/templates/mongoose/src/app/modules/user/user.interface.ts +0 -15
  173. package/templates/mongoose/src/app/modules/user/user.route.ts +0 -20
  174. package/templates/mongoose/src/app/modules/user/user.schema.ts +0 -22
  175. package/templates/mongoose/src/app/modules/user/user.service.ts +0 -22
  176. package/templates/mongoose/src/app/modules/user/user.swagger.ts +0 -75
  177. package/templates/mongoose/src/app/modules/user/user.validation.ts +0 -18
  178. package/templates/mongoose/src/app/types/index.d.ts +0 -12
  179. package/templates/mongoose/src/app/utils/isAccountExist.ts +0 -31
  180. package/templates/prisma/src/app/types/index.d.ts +0 -12
  181. package/templates/prisma/src/app/utils/otpGenerator.ts +0 -29
@@ -1,50 +1,57 @@
1
- import { ErrorRequestHandler } from "express";
2
- import { ZodError } from "zod";
3
- import { configs } from "../configs";
4
- import handleZodError from "../errors/zodError";
5
- import { TErrorSources } from "../types/error";
6
- import { AppError } from "../utils/app_error";
1
+ import { Prisma } from '@prisma/client';
2
+ import type { ErrorRequestHandler } from 'express';
3
+ import multer from 'multer';
4
+ import { ZodError } from 'zod';
5
+ import { configs } from '../configs/index.js';
6
+ import handleZodError from '../errors/zodError.js';
7
+ import { logger } from '../lib/logger.js';
8
+ import type { TErrorSources } from '../types/error.js';
9
+ import { AppError } from '../utils/app_error.js';
7
10
 
8
- const globalErrorHandler: ErrorRequestHandler = (err, req, res, next) => {
11
+ const globalErrorHandler: ErrorRequestHandler = (err, req, res, _next) => {
9
12
  let statusCode = 500;
10
- let message = "Something went wrong!";
11
- let errorSources: TErrorSources = [
12
- {
13
- path: "",
14
- message: "Something went wrong",
15
- },
16
- ];
13
+ let message = 'Something went wrong!';
14
+ let errorSources: TErrorSources = [];
17
15
 
18
16
  if (err instanceof ZodError) {
19
- const simplifiedError = handleZodError(err);
20
- statusCode = simplifiedError?.statusCode;
21
- message = simplifiedError?.message;
22
- errorSources = simplifiedError?.errorSources;
17
+ ({ statusCode, message, errorSources } = handleZodError(err));
23
18
  } else if (err instanceof AppError) {
24
- statusCode = err?.statusCode;
19
+ statusCode = err.statusCode;
25
20
  message = err.message;
26
- errorSources = [
27
- {
28
- path: "",
29
- message: err?.message,
30
- },
31
- ];
32
- } else if (err instanceof Error) {
21
+ } else if (err instanceof multer.MulterError) {
22
+ statusCode = 400;
33
23
  message = err.message;
34
- errorSources = [
35
- {
36
- path: "",
37
- message: err?.message,
38
- },
39
- ];
24
+ } else if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2002') {
25
+ statusCode = 409;
26
+ message = 'Resource already exists';
27
+ } else if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2025') {
28
+ statusCode = 404;
29
+ message = 'Resource not found';
30
+ } else if (err?.status === 401 || err?.name === 'AuthenticationError') {
31
+ // passport authentication failures
32
+ statusCode = 401;
33
+ message = 'Authentication failed';
34
+ } else if (err instanceof SyntaxError && 'body' in err) {
35
+ // malformed JSON body
36
+ statusCode = 400;
37
+ message = 'Invalid JSON body';
38
+ } else if (err?.expose && typeof err.status === 'number' && err.status < 500) {
39
+ // client errors from body-parser & co (body too large, unsupported charset, ...)
40
+ statusCode = err.status;
41
+ message = err.message;
42
+ }
43
+
44
+ if (statusCode >= 500) {
45
+ logger.error({ err, method: req.method, url: req.originalUrl }, 'Unhandled error');
46
+ // don't leak internals (SQL, file paths, ...) to clients in production
47
+ if (!configs.is_production && err instanceof Error) message = err.message;
40
48
  }
41
49
 
42
50
  res.status(statusCode).json({
43
51
  success: false,
44
52
  message,
45
- errorSources,
46
- err,
47
- stack: configs.env === "development" ? err?.stack : null,
53
+ errorSources: errorSources.length ? errorSources : [{ path: '', message }],
54
+ ...(configs.is_production ? {} : { stack: err?.stack }),
48
55
  });
49
56
  };
50
57
 
@@ -1,10 +1,10 @@
1
1
  import { Request, Response, NextFunction } from 'express';
2
2
 
3
- const notFound = (req: Request, res: Response, next: NextFunction) => {
4
- res.status(404).json({
5
- message: 'Sorry Route is not found!! 😴😴😴',
6
- success: false,
7
- error: '',
8
- });
3
+ const notFound = (req: Request, res: Response, _next: NextFunction) => {
4
+ res.status(404).json({
5
+ message: 'Sorry Route is not found!! 😴😴😴',
6
+ success: false,
7
+ error: '',
8
+ });
9
9
  };
10
- export default notFound;
10
+ export default notFound;
@@ -0,0 +1,124 @@
1
+ import type { Request } from 'express';
2
+ import rateLimit, {
3
+ type IncrementResponse,
4
+ ipKeyGenerator,
5
+ type Options,
6
+ type Store,
7
+ } from 'express-rate-limit';
8
+ import { configs } from '../configs/index.js';
9
+ import { prisma } from '../lib/prisma.js';
10
+
11
+ /**
12
+ * Keeps hit counters in the `RateLimit` table, so limits are shared by every app
13
+ * instance and survive restarts (no Redis needed). Expired rows are removed by the
14
+ * maintenance job (see queues/maintenance.queue.ts).
15
+ */
16
+ class PostgresStore implements Store {
17
+ windowMs = 60_000;
18
+ // `prefix` keeps counters of different limiters apart in the same table
19
+ constructor(public prefix: string) {}
20
+
21
+ init(options: Options) {
22
+ this.windowMs = options.windowMs;
23
+ }
24
+
25
+ async increment(key: string): Promise<IncrementResponse> {
26
+ // one atomic upsert: start a new window if the old one is over, else count the hit
27
+ const [row] = await prisma.$queryRaw<{ hits: number; resetAt: Date }[]>`
28
+ INSERT INTO "RateLimit" ("key", "hits", "resetAt")
29
+ VALUES (${this.prefix + key}, 1, now() + ${this.windowMs}::int * interval '1 millisecond')
30
+ ON CONFLICT ("key") DO UPDATE SET
31
+ "hits" = CASE WHEN "RateLimit"."resetAt" <= now() THEN 1 ELSE "RateLimit"."hits" + 1 END,
32
+ "resetAt" = CASE WHEN "RateLimit"."resetAt" <= now() THEN EXCLUDED."resetAt" ELSE "RateLimit"."resetAt" END
33
+ RETURNING "hits", "resetAt"`;
34
+ return { totalHits: row!.hits, resetTime: row!.resetAt };
35
+ }
36
+
37
+ async decrement(key: string) {
38
+ await prisma.rateLimit.updateMany({
39
+ where: { key: this.prefix + key, hits: { gt: 0 } },
40
+ data: { hits: { decrement: 1 } },
41
+ });
42
+ }
43
+
44
+ async resetKey(key: string) {
45
+ await prisma.rateLimit.deleteMany({ where: { key: this.prefix + key } });
46
+ }
47
+ }
48
+
49
+ const message = { success: false, message: 'Too many attempts, please try again later.' };
50
+
51
+ /** Client IP, with IPv6 addresses grouped by subnet so they can't rotate around the limit. */
52
+ const ip = (req: Request) => ipKeyGenerator(req.ip ?? '');
53
+
54
+ /** Email from the (not yet validated) body, normalised like the validators do. */
55
+ const email = (req: Request) =>
56
+ typeof req.body?.email === 'string' ? req.body.email.trim().toLowerCase() : '';
57
+
58
+ type LimiterOptions = {
59
+ windowMs: number;
60
+ limit: number;
61
+ keyGenerator?: (req: Request) => string;
62
+ };
63
+
64
+ /** A limiter backed by Postgres (in-memory under tests). `name` must be unique. */
65
+ const createLimiter = (name: string, { windowMs, limit, keyGenerator = ip }: LimiterOptions) =>
66
+ rateLimit({
67
+ windowMs,
68
+ limit,
69
+ keyGenerator,
70
+ standardHeaders: 'draft-8',
71
+ legacyHeaders: false,
72
+ message,
73
+ store: configs.is_test ? undefined : new PostgresStore(`${name}:`),
74
+ });
75
+
76
+ const MINUTE = 60 * 1000;
77
+
78
+ /**
79
+ * Coarse limit for the whole API. Kept in memory (per instance) so normal traffic
80
+ * doesn't pay for a database round trip; the auth limiters below are the strict ones.
81
+ */
82
+ export const apiRateLimiter = rateLimit({
83
+ windowMs: MINUTE,
84
+ limit: 300,
85
+ standardHeaders: 'draft-8',
86
+ legacyHeaders: false,
87
+ message,
88
+ });
89
+
90
+ export const rateLimiters = {
91
+ /** Account creation, per IP. */
92
+ signUp: createLimiter('sign-up', { windowMs: 60 * MINUTE, limit: 10 }),
93
+
94
+ /** Password guessing: per IP+email for one account, and per IP across many accounts. */
95
+ signIn: createLimiter('sign-in', {
96
+ windowMs: 15 * MINUTE,
97
+ limit: 10,
98
+ keyGenerator: (req) => `${ip(req)}:${email(req)}`,
99
+ }),
100
+ signInIp: createLimiter('sign-in-ip', { windowMs: 15 * MINUTE, limit: 50 }),
101
+
102
+ /** OTP / link / reset token guessing, per IP. */
103
+ verify: createLimiter('verify', { windowMs: 15 * MINUTE, limit: 20 }),
104
+
105
+ /**
106
+ * Endpoints that send an email. Limited per recipient so nobody can flood an
107
+ * inbox from many IPs, and per IP so one client can't spray many addresses.
108
+ */
109
+ emailPerAddress: createLimiter('email-address', {
110
+ windowMs: 15 * MINUTE,
111
+ limit: 3,
112
+ keyGenerator: email,
113
+ }),
114
+ emailPerIp: createLimiter('email-ip', { windowMs: 15 * MINUTE, limit: 10 }),
115
+
116
+ /** Password changes for signed-in users, per IP. */
117
+ changePassword: createLimiter('change-password', { windowMs: 15 * MINUTE, limit: 10 }),
118
+
119
+ /** Token refreshes, per IP (tokens are unguessable; this only caps abuse). */
120
+ refresh: createLimiter('refresh', { windowMs: 15 * MINUTE, limit: 300 }),
121
+
122
+ /** Starting a social login and exchanging its code, per IP. */
123
+ social: createLimiter('social', { windowMs: 15 * MINUTE, limit: 30 }),
124
+ };
@@ -1,7 +1,8 @@
1
- import { NextFunction, Request, Response } from "express";
1
+ import type { NextFunction, Request, Response } from 'express';
2
+ import type { ZodType } from 'zod';
2
3
 
3
- const RequestValidator = (schema: any) => {
4
- return async (req: Request, res: Response, next: NextFunction) => {
4
+ const RequestValidator = (schema: ZodType) => {
5
+ return async (req: Request, _res: Response, next: NextFunction) => {
5
6
  try {
6
7
  req.body = await schema.parseAsync(req.body);
7
8
  next();
@@ -1,16 +1,20 @@
1
- import multer from "multer";
2
- import path from "path";
1
+ import multer from 'multer';
2
+ import { AppError } from '../utils/app_error.js';
3
3
 
4
- const storage = multer.diskStorage({
5
- destination: function (req, file, cb) {
6
- cb(null, path.join(process.cwd(), "uploads"))
7
- },
8
- filename: function (req, file, cb) {
4
+ const ALLOWED_IMAGE_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
5
+ const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
9
6
 
10
- cb(null, file.originalname)
11
- }
12
- })
7
+ /**
8
+ * Keeps the (size limited) file in memory and streams it to Cloudinary, so nothing
9
+ * is ever left behind on disk when validation or the upload fails.
10
+ */
11
+ const uploader = multer({
12
+ storage: multer.memoryStorage(),
13
+ limits: { fileSize: MAX_FILE_SIZE, files: 1, fields: 10 },
14
+ fileFilter: (_req, file, cb) => {
15
+ if (ALLOWED_IMAGE_TYPES.includes(file.mimetype)) return cb(null, true);
16
+ cb(new AppError(`Only ${ALLOWED_IMAGE_TYPES.join(', ')} files are allowed`, 400));
17
+ },
18
+ });
13
19
 
14
- const uploader = multer({ storage: storage })
15
-
16
- export default uploader;
20
+ export default uploader;
@@ -1,14 +1,14 @@
1
- import { configs } from "../../configs";
2
- import catchAsync from "../../utils/catch_async";
3
- import manageResponse from "../../utils/manage_response";
4
- import { account_services } from "./account.service";
1
+ import catchAsync from '../../utils/catch_async.js';
2
+ import manageResponse from '../../utils/manage_response.js';
3
+ import { clearSessionCookies, createSession } from '../../utils/session.js';
4
+ import { account_services } from './account.service.js';
5
5
 
6
6
  const create_account = catchAsync(async (req, res) => {
7
7
  const result = await account_services.create_account_into_db(req);
8
8
  manageResponse(res, {
9
- statusCode: 200,
9
+ statusCode: 201,
10
10
  success: true,
11
- message: "Account created successfully",
11
+ message: 'Account created successfully',
12
12
  data: result,
13
13
  });
14
14
  });
@@ -18,7 +18,7 @@ const verify_account_using_otp = catchAsync(async (req, res) => {
18
18
  manageResponse(res, {
19
19
  statusCode: 200,
20
20
  success: true,
21
- message: "Otp verification successfull",
21
+ message: 'Otp verification successful',
22
22
  data: result,
23
23
  });
24
24
  });
@@ -28,68 +28,61 @@ const verify_account_using_link = catchAsync(async (req, res) => {
28
28
  manageResponse(res, {
29
29
  statusCode: 200,
30
30
  success: true,
31
- message: "Account verification successfull",
31
+ message: 'Account verification successful',
32
32
  data: result,
33
33
  });
34
34
  });
35
35
 
36
36
  const login_user = catchAsync(async (req, res) => {
37
- const result = await account_services.login_user_into_db(req);
38
-
39
- // set access token into cookie
40
- res.cookie("access_token", result, {
41
- secure: configs.env === "production",
42
- httpOnly: true,
43
- });
44
-
37
+ const user = await account_services.login_user_into_db(req);
38
+ const tokens = await createSession(res, user);
45
39
  manageResponse(res, {
46
40
  statusCode: 200,
47
41
  success: true,
48
- message: "User logged in successfully",
49
- data: {
50
- accessToken: result,
51
- },
42
+ message: 'User logged in successfully',
43
+ data: tokens,
52
44
  });
53
45
  });
46
+
54
47
  const get_user_account = catchAsync(async (req, res) => {
55
48
  const result = await account_services.get_user_account_from_db(req);
56
49
 
57
50
  manageResponse(res, {
58
51
  statusCode: 200,
59
52
  success: true,
60
- message: "Account fetched successfully",
53
+ message: 'Account fetched successfully',
61
54
  data: result,
62
55
  });
63
56
  });
64
57
 
65
58
  const change_password = catchAsync(async (req, res) => {
66
- const result = await account_services.change_password_into_db(req);
59
+ const user = await account_services.change_password_into_db(req);
60
+ // every session was revoked, so give this one a fresh start
61
+ const tokens = await createSession(res, user);
67
62
  manageResponse(res, {
68
63
  statusCode: 200,
69
64
  success: true,
70
- message: "Password Change successfully",
71
- data: result,
65
+ message: 'Password changed successfully',
66
+ data: tokens,
72
67
  });
73
68
  });
74
69
 
75
70
  const resend_otp_and_verification_link = catchAsync(async (req, res) => {
76
- const result =
77
- await account_services.resend_otp_and_verification_link_from_db(req);
71
+ const result = await account_services.resend_otp_and_verification_link_from_db(req);
78
72
  manageResponse(res, {
79
73
  statusCode: 200,
80
74
  success: true,
81
- message: "OTP reset successfully",
75
+ message: 'If the account exists and is not verified, a new OTP has been sent',
82
76
  data: result,
83
77
  });
84
78
  });
85
79
 
86
- const forget_password_genereate_reset_token = catchAsync(async (req, res) => {
87
- const result =
88
- await account_services.forget_password_genereate_reset_token_from_db(req);
80
+ const forget_password_generate_reset_token = catchAsync(async (req, res) => {
81
+ const result = await account_services.forget_password_generate_reset_token_from_db(req);
89
82
  manageResponse(res, {
90
83
  statusCode: 200,
91
84
  success: true,
92
- message: "Password reset successfully",
85
+ message: 'If an account exists for this email, a password reset link has been sent',
93
86
  data: result,
94
87
  });
95
88
  });
@@ -99,10 +92,53 @@ const reset_password_using_token = catchAsync(async (req, res) => {
99
92
  manageResponse(res, {
100
93
  statusCode: 200,
101
94
  success: true,
102
- message: "Password reset successfully",
95
+ message: 'Password reset successfully',
103
96
  data: result,
104
97
  });
105
98
  });
99
+
100
+ const refresh_token = catchAsync(async (req, res) => {
101
+ const { user, familyId } = await account_services.refresh_session_from_db(req);
102
+ const tokens = await createSession(res, user, familyId);
103
+ manageResponse(res, {
104
+ statusCode: 200,
105
+ success: true,
106
+ message: 'Tokens refreshed successfully',
107
+ data: tokens,
108
+ });
109
+ });
110
+
111
+ const sign_out = catchAsync(async (req, res) => {
112
+ await account_services.sign_out_from_db(req);
113
+ clearSessionCookies(res);
114
+ manageResponse(res, {
115
+ statusCode: 200,
116
+ success: true,
117
+ message: 'Signed out successfully',
118
+ });
119
+ });
120
+
121
+ const sign_out_everywhere = catchAsync(async (req, res) => {
122
+ await account_services.sign_out_everywhere_from_db(req);
123
+ clearSessionCookies(res);
124
+ manageResponse(res, {
125
+ statusCode: 200,
126
+ success: true,
127
+ message: 'Signed out from all devices',
128
+ });
129
+ });
130
+
131
+ const exchange_social_login_code = catchAsync(async (req, res) => {
132
+ const user = await account_services.exchange_social_login_code_from_db(req);
133
+ const tokens = await createSession(res, user);
134
+ manageResponse(res, {
135
+ statusCode: 200,
136
+ success: true,
137
+ message: 'User logged in successfully',
138
+ data: tokens,
139
+ });
140
+ });
141
+
106
142
  export const account_controller = {
107
143
  create_account,
108
144
  login_user,
@@ -111,6 +147,10 @@ export const account_controller = {
111
147
  verify_account_using_otp,
112
148
  resend_otp_and_verification_link,
113
149
  verify_account_using_link,
114
- forget_password_genereate_reset_token,
115
- reset_password_using_token
150
+ forget_password_generate_reset_token,
151
+ reset_password_using_token,
152
+ refresh_token,
153
+ sign_out,
154
+ sign_out_everywhere,
155
+ exchange_social_login_code,
116
156
  };
@@ -1,55 +1,101 @@
1
- import { Router } from "express";
2
- import auth from "../../middlewares/auth";
3
- import RequestValidator from "../../middlewares/request_validator";
4
- import { account_controller } from "./account.controller";
5
- import { account_validation } from "./account.validation";
1
+ import { Router } from 'express';
2
+ import auth from '../../middlewares/auth.js';
3
+ import { rateLimiters } from '../../middlewares/rate_limiter.js';
4
+ import RequestValidator from '../../middlewares/request_validator.js';
5
+ import { socialAuthRouter } from '../../passport-js/index.js';
6
+ import { account_controller } from './account.controller.js';
7
+ import { account_validation } from './account.validation.js';
6
8
 
7
9
  const accountRouter = Router();
8
10
 
9
11
  accountRouter.post(
10
- "/sign-up",
12
+ '/sign-up',
13
+ rateLimiters.signUp,
14
+ rateLimiters.emailPerAddress,
11
15
  RequestValidator(account_validation.sign_up),
12
16
  account_controller.create_account,
13
17
  );
18
+
14
19
  accountRouter.post(
15
- "/sign-in",
16
- RequestValidator(account_validation.sing_in),
20
+ '/sign-in',
21
+ rateLimiters.signInIp,
22
+ rateLimiters.signIn,
23
+ RequestValidator(account_validation.sign_in),
17
24
  account_controller.login_user,
18
25
  );
26
+
27
+ accountRouter.post(
28
+ '/refresh-token',
29
+ rateLimiters.refresh,
30
+ RequestValidator(account_validation.refresh_token),
31
+ account_controller.refresh_token,
32
+ );
33
+
34
+ accountRouter.post(
35
+ '/sign-out',
36
+ RequestValidator(account_validation.refresh_token),
37
+ account_controller.sign_out,
38
+ );
39
+
40
+ accountRouter.post('/sign-out-all', auth(), account_controller.sign_out_everywhere);
41
+
19
42
  accountRouter.put(
20
- "/verify-otp",
43
+ '/verify-otp',
44
+ rateLimiters.verify,
21
45
  RequestValidator(account_validation.verify_otp),
22
46
  account_controller.verify_account_using_otp,
23
47
  );
48
+
24
49
  accountRouter.put(
25
- "/verify-link",
50
+ '/verify-link',
51
+ rateLimiters.verify,
26
52
  RequestValidator(account_validation.verify_link),
27
53
  account_controller.verify_account_using_link,
28
54
  );
29
- accountRouter.get(
30
- "/me",
31
- auth("USER", "ADMIN"),
32
- account_controller.get_user_account,
33
- );
55
+
56
+ accountRouter.get('/me', auth(), account_controller.get_user_account);
57
+
34
58
  accountRouter.put(
35
- "/change-password",
36
- auth("USER", "ADMIN"),
59
+ '/change-password',
60
+ rateLimiters.changePassword,
61
+ auth(),
37
62
  RequestValidator(account_validation.change_password),
38
63
  account_controller.change_password,
39
64
  );
65
+
40
66
  accountRouter.put(
41
- "/resend-otp",
67
+ '/resend-otp',
68
+ rateLimiters.emailPerIp,
69
+ rateLimiters.emailPerAddress,
42
70
  RequestValidator(account_validation.resend_otp),
43
71
  account_controller.resend_otp_and_verification_link,
44
72
  );
73
+
45
74
  accountRouter.put(
46
- "/forget-password",
47
- RequestValidator(account_validation.resend_otp),
48
- account_controller.forget_password_genereate_reset_token,
75
+ '/forget-password',
76
+ rateLimiters.emailPerIp,
77
+ rateLimiters.emailPerAddress,
78
+ RequestValidator(account_validation.forget_password),
79
+ account_controller.forget_password_generate_reset_token,
49
80
  );
81
+
50
82
  accountRouter.put(
51
- "/reset-password",
83
+ '/reset-password',
84
+ rateLimiters.verify,
52
85
  RequestValidator(account_validation.reset_pass),
53
86
  account_controller.reset_password_using_token,
54
87
  );
88
+
89
+ // Social login: the front-end trades the `?code=` from the redirect for a token
90
+ accountRouter.post(
91
+ '/social/exchange',
92
+ rateLimiters.social,
93
+ RequestValidator(account_validation.social_exchange),
94
+ account_controller.exchange_social_login_code,
95
+ );
96
+
97
+ // GET /social/providers, /social/<provider> and /social/<provider>/callback
98
+ // for every provider enabled in .env (see passport-js/providers.ts)
99
+ accountRouter.use('/social', socialAuthRouter);
100
+
55
101
  export default accountRouter;