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 +6 -3
- package/index.js +30 -0
- package/lib/Auth/AuthMiddleware.d.ts.map +1 -1
- package/lib/Auth/AuthMiddleware.js +41 -10
- package/lib/Auth/index.d.ts +6 -2
- package/lib/Auth/index.d.ts.map +1 -1
- package/lib/Auth/index.js +94 -20
- package/lib/Auth/loginRateLimit.d.ts +6 -0
- package/lib/Auth/loginRateLimit.d.ts.map +1 -0
- package/lib/Auth/loginRateLimit.js +25 -0
- package/lib/Auth/refreshTokenStore.d.ts +24 -0
- package/lib/Auth/refreshTokenStore.d.ts.map +1 -0
- package/lib/Auth/refreshTokenStore.js +46 -0
- package/lib/Command-Line/KeyGenerateCommand.d.ts +6 -0
- package/lib/Command-Line/KeyGenerateCommand.d.ts.map +1 -0
- package/lib/Command-Line/KeyGenerateCommand.js +47 -0
- package/lib/Command-Line/NodeArtisanCommand.d.ts.map +1 -1
- package/lib/Command-Line/NodeArtisanCommand.js +7 -1
- package/lib/Command-Line/files/Models.d.ts.map +1 -1
- package/lib/Command-Line/files/Models.js +13 -8
- package/lib/Global/helpers.d.ts.map +1 -1
- package/lib/Global/helpers.js +1 -0
- package/lib/Validation/Validator/CustomValidation.d.ts.map +1 -1
- package/lib/Validation/Validator/CustomValidation.js +2 -2
- package/lib/Validation/Validator/helper.d.ts.map +1 -1
- package/lib/Validation/Validator/helper.js +1 -0
- package/lib/util/index.d.ts +9 -0
- package/lib/util/index.d.ts.map +1 -1
- package/lib/util/index.js +54 -11
- package/package.json +1 -1
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;
|
|
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
|
|
15
|
-
|
|
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
|
-
|
|
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
|
}
|
package/lib/Auth/index.d.ts
CHANGED
|
@@ -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: (
|
|
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 */
|
package/lib/Auth/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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:
|
|
93
|
+
maxAge: ACCESS_MAX_AGE_MS,
|
|
58
94
|
});
|
|
59
95
|
res.cookie("refresh_token", refreshToken, {
|
|
60
96
|
...cookieOptions,
|
|
61
|
-
maxAge:
|
|
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
|
|
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
|
|
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
|
-
|
|
86
|
-
|
|
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 (
|
|
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
|
-
|
|
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()
|
|
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;
|
|
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
|
|
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;
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
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
|
|
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;
|
|
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"}
|
package/lib/Global/helpers.js
CHANGED
|
@@ -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;
|
|
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,
|
|
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
|
});
|
package/lib/util/index.d.ts
CHANGED
|
@@ -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.
|
package/lib/util/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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"
|
|
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") {
|