jcc-express-mvc 1.8.8 → 1.8.9

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.

Potentially problematic release.


This version of jcc-express-mvc might be problematic. Click here for more details.

package/index.d.ts CHANGED
@@ -12,25 +12,28 @@ export {
12
12
  verifyHash,
13
13
  jwtSign,
14
14
  jwtVerify,
15
+ jwtTokenType,
16
+ assertProductionJwtSecret,
15
17
  saveImage,
16
18
  asyncHandler,
17
19
  cloudinaryUpload,
18
20
  rootPath,
19
21
  } from "./lib/util";
22
+ export { loginRateLimit, registerRateLimit } from "./lib/Auth/loginRateLimit";
20
23
  export declare const guest: (
21
24
  req: import("./lib/Interface").AppRequest,
22
25
  res: import("./lib/Interface").AppResponse,
23
- next: import("./lib/Interface").AppNext
26
+ next: import("./lib/Interface").AppNext,
24
27
  ) => any;
25
28
  export declare const apiAuth: (
26
29
  req: import("./lib/Interface").AppRequest,
27
30
  res: import("./lib/Interface").AppResponse,
28
- next: import("./lib/Interface").AppNext
31
+ next: import("./lib/Interface").AppNext,
29
32
  ) => Promise<import("./lib/Interface").AppResponse | undefined>;
30
33
  export declare const auth: (
31
34
  req: import("./lib/Interface").AppRequest,
32
35
  res: import("./lib/Interface").AppResponse,
33
- next: import("./lib/Interface").AppNext
36
+ next: import("./lib/Interface").AppNext,
34
37
  ) => Promise<void>;
35
38
  export declare const httpContext: AppHttpContext;
36
39
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1,13 +1,18 @@
1
1
  "use strict";
2
+
2
3
  Object.defineProperty(exports, "__esModule", { value: true });
3
4
  exports.httpContext =
4
5
  exports.auth =
5
6
  exports.apiAuth =
6
7
  exports.guest =
8
+ exports.registerRateLimit =
9
+ exports.loginRateLimit =
7
10
  exports.rootPath =
8
11
  exports.cloudinaryUpload =
9
12
  exports.asyncHandler =
10
13
  exports.saveImage =
14
+ exports.assertProductionJwtSecret =
15
+ exports.jwtTokenType =
11
16
  exports.jwtVerify =
12
17
  exports.jwtSign =
13
18
  exports.verifyHash =
@@ -55,6 +60,18 @@ Object.defineProperty(exports, "jwtVerify", {
55
60
  return util_1.jwtVerify;
56
61
  },
57
62
  });
63
+ Object.defineProperty(exports, "jwtTokenType", {
64
+ enumerable: true,
65
+ get: function () {
66
+ return util_1.jwtTokenType;
67
+ },
68
+ });
69
+ Object.defineProperty(exports, "assertProductionJwtSecret", {
70
+ enumerable: true,
71
+ get: function () {
72
+ return util_1.assertProductionJwtSecret;
73
+ },
74
+ });
58
75
  Object.defineProperty(exports, "saveImage", {
59
76
  enumerable: true,
60
77
  get: function () {
@@ -79,6 +96,19 @@ Object.defineProperty(exports, "rootPath", {
79
96
  return util_1.rootPath;
80
97
  },
81
98
  });
99
+ var loginRateLimit_1 = require("./lib/Auth/loginRateLimit");
100
+ Object.defineProperty(exports, "loginRateLimit", {
101
+ enumerable: true,
102
+ get: function () {
103
+ return loginRateLimit_1.loginRateLimit;
104
+ },
105
+ });
106
+ Object.defineProperty(exports, "registerRateLimit", {
107
+ enumerable: true,
108
+ get: function () {
109
+ return loginRateLimit_1.registerRateLimit;
110
+ },
111
+ });
82
112
  exports.guest = AuthMiddleware_1.authMiddleware.guest;
83
113
  exports.apiAuth = AuthMiddleware_1.authMiddleware.apiAuth;
84
114
  exports.auth = AuthMiddleware_1.authMiddleware.auth;
@@ -1 +1 @@
1
- {"version":3,"file":"AuthMiddleware.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/Auth/AuthMiddleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhE,cAAM,cAAc;IAClB,qCAAqC;IACxB,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO;IAuBxD,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO;IAuB3D,KAAK,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO;CAgB9D;AAED,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
1
+ {"version":3,"file":"AuthMiddleware.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/Auth/AuthMiddleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAyBhE,cAAM,cAAc;IAClB,qCAAqC;IACxB,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO;IA+BxD,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO;IAiC3D,KAAK,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO;CAgB9D;AAED,eAAO,MAAM,cAAc,gBAAuB,CAAC"}
@@ -4,6 +4,18 @@ exports.authMiddleware = void 0;
4
4
  const Config_1 = require("../Config/Config");
5
5
  const util_1 = require("../util");
6
6
  const { User } = (0, util_1.getModel)("User");
7
+ const authCookieClear = () => ({
8
+ path: "/",
9
+ httpOnly: true,
10
+ secure: Config_1.config.get("APP_ENV") === "production",
11
+ sameSite: "lax",
12
+ });
13
+ function accessTokenRejected(payload) {
14
+ if (Config_1.config.get("JWT_REQUIRE_TYPED_TOKENS", "").toLowerCase() !== "true") {
15
+ return false;
16
+ }
17
+ return (0, util_1.jwtTokenType)(payload) === "legacy";
18
+ }
7
19
  class AuthMiddleware {
8
20
  /** Middleware: API authentication */
9
21
  async apiAuth(req, res, next) {
@@ -11,14 +23,22 @@ class AuthMiddleware {
11
23
  if (!token)
12
24
  return res.status(401).json({ message: "Not authorized" });
13
25
  try {
14
- const id = (0, util_1.jwtVerify)(token);
15
- const user = Config_1.config.get("DB_ORM") === "mongodb"
26
+ const payload = (0, util_1.jwtVerify)(token);
27
+ if ((0, util_1.jwtTokenType)(payload) === "refresh") {
28
+ return res.status(401).json({ message: "Not authorized" });
29
+ }
30
+ if (accessTokenRejected(payload)) {
31
+ return res.status(401).json({ message: "Not authorized" });
32
+ }
33
+ const id = (0, util_1.jwtSubjectId)(payload);
34
+ const user = Config_1.config.get("DB_ORM") === "mongodb" ||
35
+ Config_1.config.get("DB_ORM") === "mongoose"
16
36
  ? await User.findById(id)
17
37
  : await User.where("id", id).first();
18
38
  if (!user)
19
39
  return res.status(401).json({ message: "Not authorized" });
20
40
  req.user = user;
21
- req.id = id;
41
+ req.id = String(id);
22
42
  next();
23
43
  }
24
44
  catch (err) {
@@ -31,10 +51,21 @@ class AuthMiddleware {
31
51
  return res.redirect(`/login?redirect=${req.url || "/"}`);
32
52
  try {
33
53
  const payload = (0, util_1.jwtVerify)(token);
34
- const user = await (0, util_1.findUserById)(User, payload.id);
54
+ if ((0, util_1.jwtTokenType)(payload) === "refresh") {
55
+ res.clearCookie("auth_token", authCookieClear());
56
+ res.clearCookie("refresh_token", authCookieClear());
57
+ return res.redirect(`/login?redirect=${req.url || "/"}`);
58
+ }
59
+ if (accessTokenRejected(payload)) {
60
+ res.clearCookie("auth_token", authCookieClear());
61
+ res.clearCookie("refresh_token", authCookieClear());
62
+ return res.redirect(`/login?redirect=${req.url || "/"}`);
63
+ }
64
+ const id = (0, util_1.jwtSubjectId)(payload);
65
+ const user = await (0, util_1.findUserById)(User, id);
35
66
  if (!user) {
36
- res.clearCookie("auth_token");
37
- res.clearCookie("refresh_token");
67
+ res.clearCookie("auth_token", authCookieClear());
68
+ res.clearCookie("refresh_token", authCookieClear());
38
69
  return res.redirect(`/login?redirect=${req.url || "/"}`);
39
70
  }
40
71
  req.user = user;
@@ -42,8 +73,8 @@ class AuthMiddleware {
42
73
  next();
43
74
  }
44
75
  catch (err) {
45
- res.clearCookie("auth_token");
46
- res.clearCookie("refresh_token");
76
+ res.clearCookie("auth_token", authCookieClear());
77
+ res.clearCookie("refresh_token", authCookieClear());
47
78
  return res.redirect(`/login?redirect=${req.url || "/"}`);
48
79
  }
49
80
  }
@@ -55,8 +86,8 @@ class AuthMiddleware {
55
86
  return res.redirect(303, req.previousUrls[1]);
56
87
  }
57
88
  else {
58
- res.clearCookie("auth_token");
59
- res.clearCookie("refresh_token");
89
+ res.clearCookie("auth_token", authCookieClear());
90
+ res.clearCookie("refresh_token", authCookieClear());
60
91
  return res.redirect(303, req.url);
61
92
  }
62
93
  }
@@ -1,13 +1,17 @@
1
1
  import { AppRequest, AppResponse, AppNext } from "../Interface";
2
+ import { type IRefreshTokenStore } from "./refreshTokenStore";
2
3
  export declare class Authentication {
4
+ private static refreshStore;
5
+ /** Use a shared store (e.g. Redis) when running multiple app instances. */
6
+ static setRefreshTokenStore(store: IRefreshTokenStore): void;
3
7
  /** Get user lookup field (email, phone, username) */
4
8
  private static getCredentials;
5
9
  /** Fetch user from DB (MongoDB, Sequelize, or JCC ORM) */
6
10
  private static getUser;
7
- /** Generate and attach tokens to cookies */
11
+ /** Generate and attach tokens to cookies (refresh is rotated server-side via `jti`). */
8
12
  private static setTokens;
9
13
  /** Handle user login attempt */
10
- static attempt: (req: AppRequest, res: AppResponse, next: AppNext, redirect?: string) => Promise<void | AppResponse>;
14
+ static attempt: (next: AppNext, redirect?: string) => Promise<void | AppResponse>;
11
15
  /** Refresh token middleware */
12
16
  static refreshToken(req: AppRequest, res: AppResponse, next: AppNext): Promise<AppResponse | undefined>;
13
17
  /** Logout handler */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/Auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAehE,qBAAa,cAAc;IACzB,qDAAqD;IACrD,OAAO,CAAC,MAAM,CAAC,cAAc;IAc7B,0DAA0D;mBACrC,OAAO;IAoB5B,4CAA4C;IAC5C,OAAO,CAAC,MAAM,CAAC,SAAS;IAuBxB,gCAAgC;IAChC,MAAM,CAAC,OAAO,GACZ,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,MAAM,OAAO,EACb,WAAU,MAAgB,iCAuB1B;IAEF,+BAA+B;WAClB,YAAY,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO;IAmB1E,qBAAqB;IACrB,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW;CAKhD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/Auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAchE,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,qBAAqB,CAAC;AA0C7B,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAC,YAAY,CAAgD;IAE3E,2EAA2E;IAC3E,MAAM,CAAC,oBAAoB,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI;IAI5D,qDAAqD;IACrD,OAAO,CAAC,MAAM,CAAC,cAAc;IAc7B,0DAA0D;mBACrC,OAAO;IAoB5B,wFAAwF;IACxF,OAAO,CAAC,MAAM,CAAC,SAAS;IA0BxB,gCAAgC;IAChC,MAAM,CAAC,OAAO,GAGZ,MAAM,OAAO,EACb,WAAU,MAAgB,iCAgC1B;IAEF,+BAA+B;WAClB,YAAY,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO;IAqC1E,qBAAqB;IACrB,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW;CAmBhD"}
package/lib/Auth/index.js CHANGED
@@ -6,8 +6,45 @@ const util_1 = require("../util");
6
6
  const Config_1 = require("../Config/Config");
7
7
  const ValidationException_v2_1 = require("../Error/ValidationException-v2");
8
8
  const Jcc_eloquent_1 = require("../Jcc-eloquent");
9
+ const refreshTokenStore_1 = require("./refreshTokenStore");
9
10
  const { User } = (0, util_1.getModel)("User");
11
+ const REFRESH_TTL_MS = 7 * 24 * 60 * 60 * 1000;
12
+ const ACCESS_MAX_AGE_MS = 60 * 60 * 1000;
13
+ function authCookieBase() {
14
+ return {
15
+ httpOnly: true,
16
+ secure: Config_1.config.get("APP_ENV") === "production",
17
+ sameSite: "lax",
18
+ path: "/",
19
+ };
20
+ }
21
+ function clearAuthCookies(res) {
22
+ const base = authCookieBase();
23
+ res.clearCookie("auth_token", base);
24
+ res.clearCookie("refresh_token", base);
25
+ }
26
+ /** Avoid open redirects: only same-origin relative paths. */
27
+ function safeInternalRedirect(url, fallback) {
28
+ if (!url || typeof url !== "string")
29
+ return fallback;
30
+ const t = url.trim();
31
+ if (t.startsWith("/") && !t.startsWith("//") && !t.includes("\\"))
32
+ return t;
33
+ return fallback;
34
+ }
35
+ function publicUserFields(user) {
36
+ const id = user.id ?? user._id;
37
+ return {
38
+ id: id != null ? String(id) : undefined,
39
+ name: user.name,
40
+ email: user.email,
41
+ };
42
+ }
10
43
  class Authentication {
44
+ /** Use a shared store (e.g. Redis) when running multiple app instances. */
45
+ static setRefreshTokenStore(store) {
46
+ _a.refreshStore = store;
47
+ }
11
48
  /** Get user lookup field (email, phone, username) */
12
49
  static getCredentials(data) {
13
50
  const query = {};
@@ -29,7 +66,7 @@ class Authentication {
29
66
  return { user: null, field: "email" };
30
67
  let user = null;
31
68
  const orm = Config_1.config.get("DB_ORM");
32
- if (orm === "mongodb") {
69
+ if (orm === "mongodb" || orm === "mongoose") {
33
70
  user = await User.findOne(field).select("+password");
34
71
  }
35
72
  else if (orm === "sequelize") {
@@ -43,22 +80,21 @@ class Authentication {
43
80
  }
44
81
  return { user, field: Object.keys(field)[0] || "email" };
45
82
  }
46
- /** Generate and attach tokens to cookies */
83
+ /** Generate and attach tokens to cookies (refresh is rotated server-side via `jti`). */
47
84
  static setTokens(res, userId) {
48
- const accessToken = (0, util_1.jwtSign)(userId.toString(), { expiresIn: "1h" });
49
- const refreshToken = (0, util_1.jwtSign)(userId.toString(), { expiresIn: "7d" });
50
- const cookieOptions = {
51
- httpOnly: true,
52
- secure: Config_1.config.get("APP_ENV") === "production",
53
- sameSite: "lax",
54
- };
85
+ const id = String(userId);
86
+ const jti = _a.refreshStore.generateJti();
87
+ _a.refreshStore.register(jti, id, REFRESH_TTL_MS);
88
+ const accessToken = (0, util_1.jwtSign)(id, { expiresIn: "1h" });
89
+ const refreshToken = (0, util_1.jwtSign)({ id, typ: "refresh", jti }, { expiresIn: "7d" });
90
+ const cookieOptions = authCookieBase();
55
91
  res.cookie("auth_token", accessToken, {
56
92
  ...cookieOptions,
57
- maxAge: 1000 * 60 * 60, // 1 hour
93
+ maxAge: ACCESS_MAX_AGE_MS,
58
94
  });
59
95
  res.cookie("refresh_token", refreshToken, {
60
96
  ...cookieOptions,
61
- maxAge: 1000 * 60 * 60 * 24 * 7, // 7 days
97
+ maxAge: REFRESH_TTL_MS,
62
98
  });
63
99
  return { accessToken, refreshToken };
64
100
  }
@@ -68,30 +104,64 @@ class Authentication {
68
104
  const refreshToken = req.cookies.refresh_token;
69
105
  if (!refreshToken)
70
106
  throw new Error("No refresh token");
71
- const userId = (0, util_1.jwtVerify)(refreshToken);
107
+ const payload = (0, util_1.jwtVerify)(refreshToken);
108
+ const kind = (0, util_1.jwtTokenType)(payload);
109
+ if (kind === "access") {
110
+ throw new Error("Invalid refresh token");
111
+ }
112
+ const jti = payload != null &&
113
+ typeof payload === "object" &&
114
+ typeof payload.jti === "string"
115
+ ? payload.jti
116
+ : "";
117
+ if (!jti) {
118
+ throw new Error("Invalid refresh token");
119
+ }
120
+ const session = _a.refreshStore.consume(jti);
121
+ const userId = (0, util_1.jwtSubjectId)(payload);
122
+ if (!session || session.userId !== String(userId)) {
123
+ throw new Error("Invalid refresh token");
124
+ }
72
125
  this.setTokens(res, userId);
73
- // Use universal finder
74
126
  req.user = await (0, util_1.findUserById)(User, userId);
75
127
  next();
76
128
  }
77
129
  catch (error) {
78
- res.clearCookie("auth_token");
79
- res.clearCookie("refresh_token");
130
+ clearAuthCookies(res);
80
131
  return res.status(401).json({ message: "Unauthorized" });
81
132
  }
82
133
  }
83
134
  /** Logout handler */
84
135
  static logout(req, res) {
85
- res.clearCookie("auth_token");
86
- res.clearCookie("refresh_token");
136
+ try {
137
+ const rt = req.cookies?.refresh_token;
138
+ if (rt) {
139
+ const payload = (0, util_1.jwtVerify)(rt);
140
+ if (payload != null &&
141
+ typeof payload === "object" &&
142
+ typeof payload.jti === "string") {
143
+ _a.refreshStore.revoke(payload.jti);
144
+ }
145
+ }
146
+ }
147
+ catch {
148
+ /* expired or malformed */
149
+ }
150
+ clearAuthCookies(res);
87
151
  return res.redirect("/login");
88
152
  }
89
153
  }
90
154
  exports.Authentication = Authentication;
91
155
  _a = Authentication;
156
+ Authentication.refreshStore = refreshTokenStore_1.defaultRefreshTokenStore;
92
157
  /** Handle user login attempt */
93
- Authentication.attempt = async (req, res, next, redirect = "/home") => {
158
+ Authentication.attempt = async (
159
+ // req: AppRequest,
160
+ // res: AppResponse,
161
+ next, redirect = "/home") => {
94
162
  try {
163
+ const req = request();
164
+ const res = response();
95
165
  const { user, field } = await _a.getUser(req.body);
96
166
  if (!user)
97
167
  throw new ValidationException_v2_1.ValidationException({ [field]: ["Invalid credentials"] });
@@ -100,9 +170,13 @@ Authentication.attempt = async (req, res, next, redirect = "/home") => {
100
170
  }
101
171
  const tokens = _a.setTokens(res, user.id || user._id);
102
172
  if (req.expectsJson() && !req.isInertia()) {
103
- return res.status(200).json({ tokens, user });
173
+ const plain = typeof user?.toObject === "function" ? user.toObject() : user;
174
+ return res.status(200).json({
175
+ tokens: { accessToken: tokens.accessToken },
176
+ user,
177
+ });
104
178
  }
105
- const redirectTo = req.query.redirect?.toString() || redirect;
179
+ const redirectTo = safeInternalRedirect(req.query.redirect?.toString(), redirect);
106
180
  return res.redirect(303, redirectTo);
107
181
  }
108
182
  catch (error) {
@@ -0,0 +1,6 @@
1
+ import type { RequestHandler } from "express";
2
+ /** Stricter limit for login attempts (per IP). */
3
+ export declare const loginRateLimit: RequestHandler;
4
+ /** Stricter limit for registration (per IP). */
5
+ export declare const registerRateLimit: RequestHandler;
6
+ //# sourceMappingURL=loginRateLimit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loginRateLimit.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/Auth/loginRateLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE9C,kDAAkD;AAClD,eAAO,MAAM,cAAc,EAAE,cAO3B,CAAC;AAEH,gDAAgD;AAChD,eAAO,MAAM,iBAAiB,EAAE,cAO9B,CAAC"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerRateLimit = exports.loginRateLimit = void 0;
7
+ const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
8
+ /** Stricter limit for login attempts (per IP). */
9
+ exports.loginRateLimit = (0, express_rate_limit_1.default)({
10
+ windowMs: 15 * 60 * 1000,
11
+ max: 20,
12
+ message: { message: "Too many login attempts. Try again later." },
13
+ standardHeaders: true,
14
+ legacyHeaders: false,
15
+ validate: { trustProxy: false },
16
+ });
17
+ /** Stricter limit for registration (per IP). */
18
+ exports.registerRateLimit = (0, express_rate_limit_1.default)({
19
+ windowMs: 60 * 60 * 1000,
20
+ max: 10,
21
+ message: { message: "Too many registration attempts. Try again later." },
22
+ standardHeaders: true,
23
+ legacyHeaders: false,
24
+ validate: { trustProxy: false },
25
+ });
@@ -0,0 +1,24 @@
1
+ export type RefreshSession = {
2
+ userId: string;
3
+ expiresAt: number;
4
+ };
5
+ /** Pluggable store for refresh-token `jti` rotation (swap for Redis in multi-instance). */
6
+ export interface IRefreshTokenStore {
7
+ generateJti(): string;
8
+ register(jti: string, userId: string, ttlMs: number): void;
9
+ /** Remove and return the session if `jti` is valid and not expired. */
10
+ consume(jti: string): RefreshSession | undefined;
11
+ revoke(jti: string): void;
12
+ revokeAllForUser(userId: string): void;
13
+ }
14
+ export declare class MemoryRefreshTokenStore implements IRefreshTokenStore {
15
+ private readonly byJti;
16
+ generateJti(): string;
17
+ register(jti: string, userId: string, ttlMs: number): void;
18
+ consume(jti: string): RefreshSession | undefined;
19
+ revoke(jti: string): void;
20
+ revokeAllForUser(userId: string): void;
21
+ private prune;
22
+ }
23
+ export declare const defaultRefreshTokenStore: MemoryRefreshTokenStore;
24
+ //# sourceMappingURL=refreshTokenStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refreshTokenStore.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/Auth/refreshTokenStore.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,2FAA2F;AAC3F,MAAM,WAAW,kBAAkB;IACjC,WAAW,IAAI,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3D,uEAAuE;IACvE,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;IACjD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC;AAED,qBAAa,uBAAwB,YAAW,kBAAkB;IAChE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAE3D,WAAW,IAAI,MAAM;IAIrB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ1D,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAQhD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAMtC,OAAO,CAAC,KAAK;CAMd;AAED,eAAO,MAAM,wBAAwB,yBAAgC,CAAC"}
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defaultRefreshTokenStore = exports.MemoryRefreshTokenStore = void 0;
4
+ const crypto_1 = require("crypto");
5
+ class MemoryRefreshTokenStore {
6
+ constructor() {
7
+ this.byJti = new Map();
8
+ }
9
+ generateJti() {
10
+ return (0, crypto_1.randomBytes)(32).toString("hex");
11
+ }
12
+ register(jti, userId, ttlMs) {
13
+ this.prune();
14
+ this.byJti.set(jti, {
15
+ userId,
16
+ expiresAt: Date.now() + ttlMs,
17
+ });
18
+ }
19
+ consume(jti) {
20
+ const row = this.byJti.get(jti);
21
+ if (!row)
22
+ return undefined;
23
+ this.byJti.delete(jti);
24
+ if (Date.now() > row.expiresAt)
25
+ return undefined;
26
+ return row;
27
+ }
28
+ revoke(jti) {
29
+ this.byJti.delete(jti);
30
+ }
31
+ revokeAllForUser(userId) {
32
+ for (const [k, v] of this.byJti) {
33
+ if (v.userId === userId)
34
+ this.byJti.delete(k);
35
+ }
36
+ }
37
+ prune() {
38
+ const now = Date.now();
39
+ for (const [k, v] of this.byJti) {
40
+ if (v.expiresAt < now)
41
+ this.byJti.delete(k);
42
+ }
43
+ }
44
+ }
45
+ exports.MemoryRefreshTokenStore = MemoryRefreshTokenStore;
46
+ exports.defaultRefreshTokenStore = new MemoryRefreshTokenStore();
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Generate a cryptographically strong secret (64 hex chars = 32 bytes).
3
+ * Default env key: JWT_SECRET (meets production length checks in this framework).
4
+ */
5
+ export declare function runKeyGenerate(firstArg?: string, secondArg?: string): void;
6
+ //# sourceMappingURL=KeyGenerateCommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"KeyGenerateCommand.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/Command-Line/KeyGenerateCommand.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,SAAK,EAAE,SAAS,SAAK,GAAG,IAAI,CAoClE"}
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runKeyGenerate = runKeyGenerate;
7
+ const crypto_1 = require("crypto");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const app_root_path_1 = __importDefault(require("app-root-path"));
11
+ const colors_1 = __importDefault(require("colors"));
12
+ function escapeRegex(s) {
13
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14
+ }
15
+ /**
16
+ * Generate a cryptographically strong secret (64 hex chars = 32 bytes).
17
+ * Default env key: JWT_SECRET (meets production length checks in this framework).
18
+ */
19
+ function runKeyGenerate(firstArg = "", secondArg = "") {
20
+ const showOnly = firstArg === "show" ||
21
+ secondArg === "show" ||
22
+ secondArg === "--show";
23
+ const keyName = firstArg && firstArg !== "show" ? firstArg : "JWT_SECRET";
24
+ const value = (0, crypto_1.randomBytes)(32).toString("hex");
25
+ if (showOnly) {
26
+ console.log(colors_1.default.green(`${keyName}=`) + value);
27
+ return;
28
+ }
29
+ const envPath = path_1.default.join(app_root_path_1.default.path, ".env");
30
+ if (!fs_1.default.existsSync(envPath)) {
31
+ console.log(colors_1.default.yellow(".env not found. Add this line to your environment file:\n"));
32
+ console.log(colors_1.default.cyan(`${keyName}=${value}\n`));
33
+ return;
34
+ }
35
+ let contents = fs_1.default.readFileSync(envPath, "utf8");
36
+ const re = new RegExp(`^${escapeRegex(keyName)}=.*$`, "m");
37
+ const line = `${keyName}=${value}`;
38
+ if (re.test(contents)) {
39
+ contents = contents.replace(re, line);
40
+ }
41
+ else {
42
+ contents = contents.replace(/\s*$/, "") + "\n" + line + "\n";
43
+ }
44
+ fs_1.default.writeFileSync(envPath, contents, "utf8");
45
+ console.log(colors_1.default.green(`✓ Set ${keyName} in .env`) +
46
+ colors_1.default.gray(` (${value.length} characters)`));
47
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"NodeArtisanCommand.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/Command-Line/NodeArtisanCommand.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoCpC,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAK1B;IACF,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,GAAG,CAA0B;IACrC,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,IAAI,CAAc;IAE1B,OAAO,KAAK,OAAO,GAElB;IAED,OAAO,KAAK,EAAE,GAEb;IAED,OAAO,KAAK,IAAI,GAEf;IAED,OAAO,KAAK,KAAK,GAEhB;IAED,OAAO,KAAK,QAAQ,GAEnB;IACD,OAAO,CAAC,GAAG;;IAWX,0FAA0F;IAC1F,OAAO,CAAC,kBAAkB;IA0B1B,OAAO,CAAC,aAAa;IAOrB,wFAAwF;IACxF,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,aAAa;IAkKrB,OAAO,CAAC,QAAQ;IAwHhB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;CA4BpD"}
1
+ {"version":3,"file":"NodeArtisanCommand.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/Command-Line/NodeArtisanCommand.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqCpC,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAK1B;IACF,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,GAAG,CAA0B;IACrC,OAAO,CAAC,KAAK,CAA4B;IACzC,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,IAAI,CAAc;IAE1B,OAAO,KAAK,OAAO,GAElB;IAED,OAAO,KAAK,EAAE,GAEb;IAED,OAAO,KAAK,IAAI,GAEf;IAED,OAAO,KAAK,KAAK,GAEhB;IAED,OAAO,KAAK,QAAQ,GAEnB;IACD,OAAO,CAAC,GAAG;;IAWX,0FAA0F;IAC1F,OAAO,CAAC,kBAAkB;IA0B1B,OAAO,CAAC,aAAa;IAOrB,wFAAwF;IACxF,OAAO,CAAC,SAAS;IAkBjB,OAAO,CAAC,aAAa;IA6KrB,OAAO,CAAC,QAAQ;IA8HhB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;CA4BpD"}
@@ -18,6 +18,7 @@ const MakeCommand_1 = require("./MakeCommand");
18
18
  const RouteCommand_1 = require("./RouteCommand");
19
19
  const ScheduleCommand_1 = require("./ScheduleCommand");
20
20
  const InertiaCommand_1 = require("./InertiaCommand");
21
+ const KeyGenerateCommand_1 = require("./KeyGenerateCommand");
21
22
  const Tinker_1 = require("./NodeTinker/Tinker");
22
23
  const util_1 = require("../util");
23
24
  /** Parse key=value from secondArg (e.g. steps=2, class=UserSeeder) */
@@ -191,6 +192,10 @@ class ConsoleKernel {
191
192
  this.defineCommand("schedule:list").action(this.runAction(() => this.schedule.list()));
192
193
  // ─── build ──────────────────────────────────────────────────────────
193
194
  this.defineCommand("build").action(this.runAction(() => (0, buildCommand_1.BuildCommand)()));
195
+ // ─── key (JWT / app secrets) ─────────────────────────────────────────
196
+ this.defineCommand("key:generate")
197
+ .description("Generate a random secret (default: JWT_SECRET in .env). Use 'show' to print only.")
198
+ .action(this.runAction((firstArg, secondArg) => (0, KeyGenerateCommand_1.runKeyGenerate)(firstArg ?? "", secondArg ?? "")));
194
199
  // ─── custom user commands ────────────────────────────────────────────
195
200
  for (const { signature, description, CommandClass } of this
196
201
  .customCommands) {
@@ -235,7 +240,8 @@ class ConsoleKernel {
235
240
  console.log(line("route:list [middleware=]", "Display routes (optionally filtered)\n"));
236
241
  console.log(section("⚡ Development Tools:"));
237
242
  console.log(line("tinker", "Start the interactive Tinker REPL"));
238
- console.log(line("build", "Build the application for production\n"));
243
+ console.log(line("build", "Build the application for production"));
244
+ console.log(line("key:generate [name] [show]", "Random secret → .env (default JWT_SECRET); name=env key, show=print only") + "\n");
239
245
  console.log(section("⏰ Schedule Commands:"));
240
246
  console.log(line("schedule:run", "Run scheduled tasks (runs continuously)"));
241
247
  console.log(line("schedule:list", "List all scheduled tasks\n"));
@@ -1 +1 @@
1
- {"version":3,"file":"Models.d.ts","sourceRoot":"","sources":["../../../../jcc-express-mvc/lib/Command-Line/files/Models.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAEH,QAAA,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,MASnC,CAAC;AAiDF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"Models.d.ts","sourceRoot":"","sources":["../../../../jcc-express-mvc/lib/Command-Line/files/Models.ts"],"names":[],"mappings":"AACA;;;;GAIG;AAEH,QAAA,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,MASnC,CAAC;AAsDF,eAAe,WAAW,CAAC"}
@@ -31,17 +31,22 @@ const createJccModel = (name) => {
31
31
  };
32
32
  const createMongooseModel = (name) => {
33
33
  return `
34
- import mongoose, { HydratedDocument, InferSchemaType,Schema } from "mongoose";
34
+ import mongoose, { HydratedDocument, InferSchemaType, Model, Schema } from "mongoose";
35
35
 
36
+ const ${name}Schema = new Schema(
37
+ {
38
+ //
39
+ },
40
+ { timestamps: true }
41
+ );
36
42
 
37
- const ${name}Schema = new mongoose.Schema({
38
- //
39
- },{
40
- timestamps:true
41
- });
43
+ type ${name}Attrs = InferSchemaType<typeof ${name}Schema>;
44
+
45
+ export const ${name}: Model<${name}Attrs> =
46
+ (mongoose.models.${name} as Model<${name}Attrs>) ||
47
+ mongoose.model<${name}Attrs>("${name}", ${name}Schema);
42
48
 
43
- export const ${name} = mongoose.model("${name}", ${name}Schema);
44
- export type ${name} = HydratedDocument<InferSchemaType<typeof ${name}Schema>>;
49
+ export type ${name}Document = HydratedDocument<${name}Attrs>;
45
50
  `;
46
51
  };
47
52
  const createSequelizeModel = (name) => {
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/Global/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAUzD,eAAO,MAAM,aAAa,GAAI,KAAK,WAAW,SAoH7C,CAAC"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/Global/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAkBzD,eAAO,MAAM,aAAa,GAAI,KAAK,WAAW,SAqH7C,CAAC"}
@@ -8,6 +8,7 @@ const util_1 = require("../util");
8
8
  const Authorization_1 = require("../Authorization");
9
9
  const Date_1 = require("../Date");
10
10
  const globalHelpers = (app) => {
11
+ (0, util_1.assertProductionJwtSecret)();
11
12
  globalThis.app = app;
12
13
  //
13
14
  globalThis.env = (key, defaultValue) => {
@@ -1 +1 @@
1
- {"version":3,"file":"CustomValidation.d.ts","sourceRoot":"","sources":["../../../../jcc-express-mvc/lib/Validation/Validator/CustomValidation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAE/C,qBAAa,gBAAgB;IAI3B,MAAM,CAAC,QAAQ,CACb,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU;WASP,MAAM,CACjB,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU;IAapB,MAAM,CAAC,IAAI,CACT,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU;IAQpB,MAAM,CAAC,KAAK,CACV,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU;WAQP,MAAM,CACjB,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU;CAWrB;AAED,eAAO,MAAM,gBAAgB;;;;;;CAM5B,CAAC"}
1
+ {"version":3,"file":"CustomValidation.d.ts","sourceRoot":"","sources":["../../../../jcc-express-mvc/lib/Validation/Validator/CustomValidation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAE/C,qBAAa,gBAAgB;IAI3B,MAAM,CAAC,QAAQ,CACb,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU;WASP,MAAM,CACjB,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU;IAkBpB,MAAM,CAAC,IAAI,CACT,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU;IAQpB,MAAM,CAAC,KAAK,CACV,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU;WAQP,MAAM,CACjB,IAAI,EAAE,GAAG,EACT,KAAK,EAAE,SAAS,EAChB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,UAAU;CAerB;AAED,eAAO,MAAM,gBAAgB;;;;;;CAM5B,CAAC"}
@@ -16,7 +16,7 @@ class CustomValidation {
16
16
  if (!this.ruleValue || !attribute) {
17
17
  return passes(false, "Invalid unique rule definition.");
18
18
  }
19
- const result = await (0, helper_1.existsHelper)(attribute, value, this.ruleValue);
19
+ const result = await (0, helper_1.existsHelper)(this.attribute || attribute, value, this.ruleValue);
20
20
  if (result) {
21
21
  return passes(false, `This ${this.attribute} has already been taken`);
22
22
  }
@@ -38,7 +38,7 @@ class CustomValidation {
38
38
  if (!value) {
39
39
  return passes(false, "The field is required.");
40
40
  }
41
- const result = await (0, helper_1.existsHelper)(attribute, value, this.ruleValue);
41
+ const result = await (0, helper_1.existsHelper)(this.attribute || attribute, value, this.ruleValue);
42
42
  if (!result) {
43
43
  return passes(false, `The ${this.attribute} does not exist`);
44
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../../../jcc-express-mvc/lib/Validation/Validator/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGnC;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GACvB,WAAW,MAAM,EACjB,OAAO,SAAS,EAChB,WAAW,MAAM,iBAkBlB,CAAC"}
1
+ {"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../../../../jcc-express-mvc/lib/Validation/Validator/helper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAGnC;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GACvB,WAAW,MAAM,EACjB,OAAO,SAAS,EAChB,WAAW,MAAM,iBAmBlB,CAAC"}
@@ -15,6 +15,7 @@ const existsHelper = async (attribute, value, ruleValue) => {
15
15
  const modelInstance = (0, index_1.getModel)(Model);
16
16
  let result;
17
17
  if (process.env.DB_CONNECTION === "mongodb") {
18
+ console.log("modelInstance", modelInstance);
18
19
  result = await modelInstance[Model].findOne({
19
20
  [column || attribute]: value,
20
21
  });
@@ -75,6 +75,9 @@ export declare const verifyHash: (password: string, hashPassword: string) => Pro
75
75
  * @param {object} data - The data to be encoded in the token.
76
76
  * @returns {string} - Signed JWT token.
77
77
  */
78
+ export type JwtTokenKind = "access" | "refresh" | "legacy";
79
+ /** `typ` claim: access for API/session, refresh for refresh flow only. */
80
+ export declare const jwtTokenType: (payload: unknown) => JwtTokenKind;
78
81
  export declare const jwtSign: (data: string | Record<string, any>, options?: Record<string, any>) => string | any;
79
82
  /**
80
83
  * Function to verify a JWT token.
@@ -82,6 +85,12 @@ export declare const jwtSign: (data: string | Record<string, any>, options?: Rec
82
85
  * @returns {object} - Decoded JWT payload.
83
86
  */
84
87
  export declare const jwtVerify: (token: string) => string | any;
88
+ /** User id from JWT payload (jwt.sign stores subject as `{ id }` from jwtSign string helper). */
89
+ export declare const jwtSubjectId: (payload: unknown) => string | number;
90
+ /**
91
+ * In production, refuse weak or missing JWT_SECRET (call once at bootstrap).
92
+ */
93
+ export declare function assertProductionJwtSecret(): void;
85
94
  /**
86
95
  * Function to save an image file.
87
96
  * @param {object} req - The request object containing the file.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/util/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAW,UAAU,EAAe,MAAM,cAAc,CAAC;AAIhE,OAAO,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAC;AAKxC;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,KAAG,GAC0B,CAAC;AAEnE;;GAEG;AACH,eAAO,MAAM,UAAU,QAAO,IAG7B,CAAC;AAEF,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,SAMtC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,GACW,CAAC;AAEzD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,GACY,CAAC;AAE7D;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,KAAG,GACM,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,GACW,CAAC;AAEzD;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,GACW,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,KAAG,GAEvC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAO,KAAK,CAAC,GAAG,CAGxC,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAI,MAAM,MAAM,KAAG,GAM1C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,MAAM,CAGxD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,EAChB,cAAc,MAAM,KACnB,OAAO,CAAC,OAAO,CAEjB,CAAC;AAEF;;;;GAIG;AAEH,eAAO,MAAM,OAAO,GAClB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAClC,UAAU,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAC5B,MAAM,GAAG,GAWX,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,GAMlD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,GACpB,KAAK,GAAG,EACR,WAAW,MAAM,EACjB,SAAQ,MAAgB,KACvB,MAgBF,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAExC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACtB,MAAM,GAAG,KAAG,GAEsC,CAAC;AAEtD,eAAO,MAAM,YAAY,GAAI,WAAW,MAAM,KAAG,MAEhD,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,MAAM,GAAG,KAAG,GAezC,CAAC;AAEL,eAAO,MAAM,WAAW,GAAI,SAAS,MAAM,KAAG,GAQ7C,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAG1C,CAAC;AAOF,0EAA0E;AAC1E,eAAO,MAAM,YAAY,GAAU,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,MAAM,iBAkBpE,CAAC;AAEF,eAAO,MAAM,mBAAmB,GAC9B,KAAK,UAAU,EACf,YAAY,OAAO,KAAK,EACxB,UAAU,GAAG,iBAoDd,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,GAAG,GAAU,CAAC,EACzB,MAAM,CAAC,EACP,WAAW,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,KAC1B,OAAO,CAAC,CAAC,CASX,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,GAAG,KAAG,OAc5C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../jcc-express-mvc/lib/util/index.ts"],"names":[],"mappings":"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"}
package/lib/util/index.js CHANGED
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.isModelInstance = exports.tap = exports.resolveModelBinding = exports.findUserById = exports.pluralizeStr = exports.getJobClass = exports.cloudinaryUpload = exports.getModelFile = exports.asyncHandler = exports.capitalize = exports.saveImage = exports.jwtVerify = exports.jwtSign = exports.verifyHash = exports.bcrypt = exports.getProvider = exports.getProviders = exports.getRoute = exports.getRequest = exports.getMiddleware = exports.getModel = exports.getApiController = exports.getController = exports.loadRoute = exports.loadRoutes = exports.rootPath = void 0;
6
+ exports.isModelInstance = exports.tap = exports.resolveModelBinding = exports.findUserById = exports.pluralizeStr = exports.getJobClass = exports.cloudinaryUpload = exports.getModelFile = exports.asyncHandler = exports.capitalize = exports.saveImage = exports.jwtSubjectId = exports.jwtVerify = exports.jwtSign = exports.jwtTokenType = exports.verifyHash = exports.bcrypt = exports.getProvider = exports.getProviders = exports.getRoute = exports.getRequest = exports.getMiddleware = exports.getModel = exports.getApiController = exports.getController = exports.loadRoute = exports.loadRoutes = exports.rootPath = void 0;
7
+ exports.assertProductionJwtSecret = assertProductionJwtSecret;
7
8
  const bcryptjs_1 = __importDefault(require("bcryptjs"));
8
9
  const app_root_path_1 = __importDefault(require("app-root-path"));
9
10
  const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
@@ -128,17 +129,28 @@ const verifyHash = async (password, hashPassword) => {
128
129
  return bcryptjs_1.default.compare(`${password || ""}`, hashPassword);
129
130
  };
130
131
  exports.verifyHash = verifyHash;
131
- /**
132
- * Function to sign a JWT token.
133
- * @param {object} data - The data to be encoded in the token.
134
- * @returns {string} - Signed JWT token.
135
- */
132
+ /** `typ` claim: access for API/session, refresh for refresh flow only. */
133
+ const jwtTokenType = (payload) => {
134
+ if (payload == null || typeof payload !== "object")
135
+ return "legacy";
136
+ const typ = payload.typ;
137
+ if (typ === "refresh")
138
+ return "refresh";
139
+ if (typ === "access")
140
+ return "access";
141
+ return "legacy";
142
+ };
143
+ exports.jwtTokenType = jwtTokenType;
136
144
  const jwtSign = (data, options) => {
137
145
  try {
138
146
  const secret = Config_1.config.get("JWT_SECRET") || "app-generated-key";
139
- // Wrap string payload in an object
140
- const payload = typeof data === "string" ? { id: data } : data;
141
- return jsonwebtoken_1.default.sign(payload, secret, options);
147
+ const payload = typeof data === "string"
148
+ ? { id: data, typ: "access" }
149
+ : { typ: "access", ...data };
150
+ return jsonwebtoken_1.default.sign(payload, secret, {
151
+ algorithm: "HS256",
152
+ ...options,
153
+ });
142
154
  }
143
155
  catch (error) {
144
156
  throw new AppError_1.AppError(error?.message, error_1.JWT_SIGN_ERROR);
@@ -152,13 +164,43 @@ exports.jwtSign = jwtSign;
152
164
  */
153
165
  const jwtVerify = (token) => {
154
166
  try {
155
- return jsonwebtoken_1.default.verify(token, Config_1.config.get(Constants_1.JWT_SECRET, "app-generated-key") || "");
167
+ return jsonwebtoken_1.default.verify(token, Config_1.config.get(Constants_1.JWT_SECRET, "app-generated-key") || "", {
168
+ algorithms: ["HS256"],
169
+ clockTolerance: 30,
170
+ });
156
171
  }
157
172
  catch (error) {
158
173
  throw new AppError_1.AppError(error?.message, error_1.JWT_VERIFY_ERROR);
159
174
  }
160
175
  };
161
176
  exports.jwtVerify = jwtVerify;
177
+ /** User id from JWT payload (jwt.sign stores subject as `{ id }` from jwtSign string helper). */
178
+ const jwtSubjectId = (payload) => {
179
+ if (payload != null && typeof payload === "object" && "id" in payload) {
180
+ return payload.id;
181
+ }
182
+ if (typeof payload === "string" || typeof payload === "number") {
183
+ return payload;
184
+ }
185
+ throw new AppError_1.AppError("Invalid token payload", error_1.JWT_VERIFY_ERROR);
186
+ };
187
+ exports.jwtSubjectId = jwtSubjectId;
188
+ /**
189
+ * In production, refuse weak or missing JWT_SECRET (call once at bootstrap).
190
+ */
191
+ function assertProductionJwtSecret() {
192
+ const secret = (Config_1.config.get(Constants_1.JWT_SECRET, "") || "").trim();
193
+ const appEnv = (Config_1.config.get("APP_ENV", "") || "").trim().toLowerCase();
194
+ const isProduction = process.env.NODE_ENV === "production" || appEnv === "production";
195
+ if (!isProduction)
196
+ return;
197
+ if (secret.length < 32) {
198
+ throw new Error("[security] JWT_SECRET must be at least 32 characters in production.");
199
+ }
200
+ if (secret === "app-generated-key") {
201
+ throw new Error("[security] JWT_SECRET must not use the default placeholder in production.");
202
+ }
203
+ }
162
204
  /**
163
205
  * Function to save an image file.
164
206
  * @param {object} req - The request object containing the file.
@@ -246,7 +288,8 @@ exports.pluralizeStr = pluralizeStr;
246
288
  /** Universal user finder that supports MongoDB, Sequelize, and JCC ORM */
247
289
  const findUserById = async (User, userId) => {
248
290
  const orm = Config_1.config.get("DB_ORM");
249
- if (orm === "mongodb" && typeof User.findById === "function") {
291
+ if (orm === "mongodb" ||
292
+ (orm === "mongoose" && typeof User.findById === "function")) {
250
293
  return await User.findById(userId);
251
294
  }
252
295
  if (orm === "sequelize" && typeof User.findByPk === "function") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jcc-express-mvc",
3
- "version": "1.8.8",
3
+ "version": "1.8.9",
4
4
  "description": "express mvc structure",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",