mbkauthe 1.3.4 → 1.4.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.
package/docs/db.md CHANGED
@@ -28,12 +28,12 @@ Ensure your `user_github` table exists with these columns:
28
28
  ```sql
29
29
  CREATE TABLE user_github (
30
30
  id SERIAL PRIMARY KEY,
31
- user_name VARCHAR(255) REFERENCES "Users"("UserName"),
32
- github_id VARCHAR(255) UNIQUE NOT NULL,
31
+ user_name VARCHAR(50) REFERENCES "Users"("UserName"),
32
+ github_id VARCHAR(255) UNIQUE,
33
33
  github_username VARCHAR(255),
34
- access_token TEXT,
35
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
36
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
34
+ access_token VARCHAR(255),
35
+ created_at TimeStamp WITH TIME ZONE DEFAULT NOW(),
36
+ updated_at TimeStamp WITH TIME ZONE DEFAULT NOW()
37
37
  );
38
38
  ```
39
39
 
@@ -149,18 +149,32 @@ The GitHub login feature is now fully integrated into your mbkauthe system and r
149
149
  - `AllowedApps`(JSONB):
150
150
 
151
151
  - **Schema:**
152
- ```sql
153
- CREATE TABLE "Users" (
154
- id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
155
- "UserName" TEXT NOT NULL,
156
- "Password" TEXT NOT NULL,
157
- "Role" TEXT CHECK("Role" IN ('SuperAdmin', 'NormalUser', 'Guest')) NOT NULL DEFAULT 'NormalUser'::text,
158
- "Active" BOOLEAN NOT NULL DEFAULT true,
159
- "HaveMailAccount" BOOLEAN NOT NULL DEFAULT false,
160
- "SessionId" TEXT,
161
- "AllowedApps" JSONB DEFAULT '["mbkauthe"]'::jsonb
162
- );
163
- ```
152
+ ```sql
153
+ CREATE TYPE role AS ENUM ('SuperAdmin', 'NormalUser', 'Guest');
154
+
155
+ CREATE TABLE "Users" (
156
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
157
+ "UserName" VARCHAR(50) NOT NULL UNIQUE,
158
+ "Password" VARCHAR(61) NOT NULL, -- For bcrypt hash
159
+ "Role" role DEFAULT 'NormalUser' NOT NULL,
160
+ "Active" BOOLEAN DEFAULT FALSE,
161
+ "HaveMailAccount" BOOLEAN DEFAULT FALSE,
162
+ "AllowedApps" JSONB DEFAULT '["mbkauthe", "portal"]',
163
+ "SessionId" VARCHAR(213),
164
+ "IsOnline" BOOLEAN DEFAULT FALSE,
165
+ "created_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
166
+ "updated_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
167
+ "last_login" TIMESTAMP WITH TIME ZONE
168
+ );
169
+
170
+ -- Add indexes for Users table
171
+ CREATE INDEX IF NOT EXISTS idx_users_session_id ON "Users" ("SessionId")
172
+ CREATE INDEX idx_users_username ON "Users" USING BTREE ("UserName");
173
+ CREATE INDEX idx_users_role ON "Users" USING BTREE ("Role");
174
+ CREATE INDEX idx_users_active ON "Users" USING BTREE ("Active");
175
+ CREATE INDEX idx_users_isonline ON "Users" USING BTREE ("IsOnline");
176
+ CREATE INDEX idx_users_last_login ON "Users" USING BTREE (last_login);
177
+ ```
164
178
 
165
179
  ### Session Table
166
180
 
@@ -171,13 +185,20 @@ The GitHub login feature is now fully integrated into your mbkauthe system and r
171
185
  - `expire` (TIMESTAMP): Expiration timestamp for the session.
172
186
 
173
187
  - **Schema:**
174
- ```sql
175
- CREATE TABLE session (
176
- sid VARCHAR PRIMARY KEY,
177
- sess JSON NOT NULL,
178
- expire TIMESTAMP NOT NULL
179
- );
180
- ```
188
+ ```sql
189
+ CREATE TABLE "session" (
190
+ sid VARCHAR(33) PRIMARY KEY NOT NULL,
191
+ sess JSONB NOT NULL,
192
+ expire TimeStamp WITH TIME ZONE Not Null,
193
+ "UserName" VARCHAR(50) REFERENCES "Users"("UserName"),
194
+ last_activity TimeStamp WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
195
+ );
196
+
197
+ -- Add indexes for session table
198
+ CREATE INDEX idx_session_expire ON "session" USING BTREE (expire);
199
+ CREATE INDEX idx_session_username ON "session" USING BTREE ("UserName");
200
+ CREATE INDEX idx_session_last_activity ON "session" USING BTREE (last_activity);
201
+ ```
181
202
 
182
203
  ### Two-Factor Authentication Table
183
204
 
@@ -188,13 +209,15 @@ The GitHub login feature is now fully integrated into your mbkauthe system and r
188
209
  - `TwoFASecret` (TEXT): The secret key used for two-factor authentication.
189
210
 
190
211
  - **Schema:**
191
- ```sql
192
- CREATE TABLE "TwoFA" (
193
- "UserName" TEXT NOT NULL PRIMARY KEY,
194
- "TwoFAStatus" TEXT NOT NULL DEFAULT false,
195
- "TwoFASecret" TEXT NOT NULL
196
- );
197
- ```
212
+ ```sql
213
+ CREATE TABLE "TwoFA" (
214
+ "UserName" VARCHAR(50) primary key REFERENCES "Users"("UserName"),
215
+ "TwoFAStatus" boolean NOT NULL,
216
+ "TwoFASecret" TEXT
217
+ );
218
+
219
+ CREATE INDEX IF NOT EXISTS idx_twofa_username ON "TwoFA" ("UserName")
220
+ ```
198
221
 
199
222
  ### Query to Add a User
200
223
 
package/index.js CHANGED
@@ -34,10 +34,21 @@ if (mbkautheVar.COOKIE_EXPIRE_TIME !== undefined) {
34
34
  }
35
35
 
36
36
  const app = express();
37
- if (process.env.test === "true") {
38
- console.log("[mbkauthe] Test mode is enabled. Starting server in test mode.");
37
+ if (process.env.test === "dev") {
38
+ console.log("[mbkauthe] Dev mode is enabled. Starting server in dev mode.");
39
39
  const port = 5555;
40
40
  app.use(router);
41
+ app.use((req, res) => {
42
+ console.log(`Path not found: ${req.method} ${req.url}`);
43
+ return res.status(404).render("Error/dError.handlebars", {
44
+ layout: false,
45
+ code: 404,
46
+ error: "Not Found",
47
+ message: "The requested page was not found.",
48
+ pagename: "Home",
49
+ page: "/mbkauthe/login",
50
+ });
51
+ });
41
52
  app.listen(port, () => {
42
53
  console.log(`[mbkauthe] Server running on http://localhost:${port}`);
43
54
  });
package/lib/main.js CHANGED
@@ -31,6 +31,10 @@ router.use(express.json());
31
31
  router.use(express.urlencoded({ extended: true }));
32
32
  router.use(cookieParser());
33
33
 
34
+ router.get('/mbkauthe/main.js', (req, res) => {
35
+ res.sendFile(path.join(process.cwd(), 'public', 'main.js'));
36
+ });
37
+
34
38
  // CSRF protection middleware
35
39
  const csrfProtection = csurf({ cookie: false });
36
40
 
@@ -55,6 +59,18 @@ const LoginLimit = rateLimit({
55
59
  }
56
60
  });
57
61
 
62
+ const LogoutLimit = rateLimit({
63
+ windowMs: 1 * 60 * 1000,
64
+ max: 10,
65
+ message: { success: false, message: "Too many logout attempts, please try again later" }
66
+ });
67
+
68
+ const TwoFALimit = rateLimit({
69
+ windowMs: 1 * 60 * 1000,
70
+ max: 5,
71
+ message: { success: false, message: "Too many 2FA attempts, please try again later" }
72
+ });
73
+
58
74
  const sessionConfig = {
59
75
  store: new PgSession({
60
76
  pool: dblogin,
@@ -79,11 +95,19 @@ const sessionConfig = {
79
95
  router.use(session(sessionConfig));
80
96
 
81
97
  router.use(async (req, res, next) => {
98
+ // Only restore session if not already present and sessionId cookie exists
82
99
  if (!req.session.user && req.cookies.sessionId) {
83
100
  try {
84
101
  const sessionId = req.cookies.sessionId;
85
- const query = `SELECT * FROM "Users" WHERE "SessionId" = $1`;
86
- const result = await dblogin.query(query, [sessionId]);
102
+
103
+ // Validate sessionId format (should be 64 hex characters)
104
+ if (typeof sessionId !== 'string' || !/^[a-f0-9]{64}$/i.test(sessionId)) {
105
+ console.warn("[mbkauthe] Invalid sessionId format detected");
106
+ return next();
107
+ }
108
+
109
+ const query = `SELECT id, "UserName", "Active", "Role", "SessionId", "AllowedApps" FROM "Users" WHERE "SessionId" = $1 AND "Active" = true`;
110
+ const result = await dblogin.query({ name: 'get-user-by-sessionid', text: query, values: [sessionId] });
87
111
 
88
112
  if (result.rows.length > 0) {
89
113
  const user = result.rows[0];
@@ -91,7 +115,10 @@ router.use(async (req, res, next) => {
91
115
  id: user.id,
92
116
  username: user.UserName,
93
117
  UserName: user.UserName,
118
+ role: user.Role,
119
+ Role: user.Role,
94
120
  sessionId,
121
+ allowedApps: user.AllowedApps,
95
122
  };
96
123
  }
97
124
  } catch (err) {
@@ -110,9 +137,18 @@ const getCookieOptions = () => ({
110
137
  httpOnly: true
111
138
  });
112
139
 
140
+ const getClearCookieOptions = () => ({
141
+ domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
142
+ secure: mbkautheVar.IS_DEPLOYED === 'true' ? 'auto' : false,
143
+ sameSite: 'lax',
144
+ path: '/',
145
+ httpOnly: true
146
+ });
147
+
113
148
  async function completeLoginProcess(req, res, user, redirectUrl = null) {
114
149
  try {
115
- const sessionId = crypto.randomBytes(256).toString("hex");
150
+ // smaller session id is sufficient and faster to generate/serialize
151
+ const sessionId = crypto.randomBytes(32).toString("hex");
116
152
  console.log(`[mbkauthe] Generated session ID for username: ${user.username}`);
117
153
 
118
154
  // Delete old session record for this user
@@ -138,17 +174,11 @@ async function completeLoginProcess(req, res, user, redirectUrl = null) {
138
174
 
139
175
  req.session.save(async (err) => {
140
176
  if (err) {
141
- console.log("[mbkauthe] Session save error:", err);
177
+ console.error("[mbkauthe] Session save error:", err);
142
178
  return res.status(500).json({ success: false, message: "Internal Server Error" });
143
179
  }
144
- try {
145
- await dblogin.query(
146
- 'UPDATE "session" SET username = $1 WHERE sid = $2',
147
- [user.username, req.sessionID]
148
- );
149
- } catch (e) {
150
- console.log("[mbkauthe] Failed to update username in session table:", e);
151
- }
180
+ // avoid writing back into the session table here to reduce DB writes;
181
+ // the pg session store will already persist the session data.
152
182
 
153
183
  const cookieOptions = getCookieOptions();
154
184
  res.cookie("sessionId", sessionId, cookieOptions);
@@ -167,7 +197,7 @@ async function completeLoginProcess(req, res, user, redirectUrl = null) {
167
197
  res.status(200).json(responsePayload);
168
198
  });
169
199
  } catch (err) {
170
- console.log("[mbkauthe] Error during login completion:", err);
200
+ console.error("[mbkauthe] Error during login completion:", err);
171
201
  res.status(500).json({ success: false, message: "Internal Server Error" });
172
202
  }
173
203
  }
@@ -175,8 +205,11 @@ async function completeLoginProcess(req, res, user, redirectUrl = null) {
175
205
  router.use(async (req, res, next) => {
176
206
  if (req.session && req.session.user) {
177
207
  const cookieOptions = getCookieOptions();
178
- res.cookie("username", req.session.user.username, { ...cookieOptions, httpOnly: false });
179
- res.cookie("sessionId", req.session.user.sessionId, cookieOptions);
208
+ // Only set cookies if they're missing or different
209
+ if (req.cookies.sessionId !== req.session.user.sessionId) {
210
+ res.cookie("username", req.session.user.username, { ...cookieOptions, httpOnly: false });
211
+ res.cookie("sessionId", req.session.user.sessionId, cookieOptions);
212
+ }
180
213
  }
181
214
  next();
182
215
  });
@@ -192,7 +225,7 @@ router.post("/mbkauthe/api/terminateAllSessions", authenticate(mbkautheVar.Main_
192
225
  return res.status(500).json({ success: false, message: "Failed to terminate sessions" });
193
226
  }
194
227
 
195
- const cookieOptions = getCookieOptions();
228
+ const cookieOptions = getClearCookieOptions();
196
229
  res.clearCookie("mbkauthe.sid", cookieOptions);
197
230
  res.clearCookie("sessionId", cookieOptions);
198
231
  res.clearCookie("username", cookieOptions);
@@ -204,7 +237,7 @@ router.post("/mbkauthe/api/terminateAllSessions", authenticate(mbkautheVar.Main_
204
237
  });
205
238
  });
206
239
  } catch (err) {
207
- console.log("[mbkauthe] Database query error during session termination:", err);
240
+ console.error("[mbkauthe] Database query error during session termination:", err);
208
241
  res.status(500).json({ success: false, message: "Internal Server Error" });
209
242
  }
210
243
  });
@@ -213,8 +246,8 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
213
246
  console.log("[mbkauthe] Login request received");
214
247
 
215
248
  const { username, password } = req.body;
216
- console.log(`[mbkauthe] Login attempt for username: ${username}`);
217
249
 
250
+ // Input validation
218
251
  if (!username || !password) {
219
252
  console.log("[mbkauthe] Missing username or password");
220
253
  return res.status(400).json({
@@ -223,17 +256,45 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
223
256
  });
224
257
  }
225
258
 
259
+ // Validate username format and length
260
+ if (typeof username !== 'string' || username.trim().length === 0 || username.length > 255) {
261
+ console.warn("[mbkauthe] Invalid username format");
262
+ return res.status(400).json({
263
+ success: false,
264
+ message: "Invalid username format",
265
+ });
266
+ }
267
+
268
+ // Validate password length
269
+ if (typeof password !== 'string' || password.length < 8 || password.length > 255) {
270
+ console.warn("[mbkauthe] Invalid password length");
271
+ return res.status(400).json({
272
+ success: false,
273
+ message: "Password must be at least 8 characters long",
274
+ });
275
+ }
276
+
277
+ console.log(`[mbkauthe] Login attempt for username: ${username.trim()}`);
278
+
279
+ const trimmedUsername = username.trim();
280
+
226
281
  try {
227
- const userQuery = `SELECT * FROM "Users" WHERE "UserName" = $1`;
228
- const userResult = await dblogin.query(userQuery, [username]);
282
+ const userQuery = `SELECT id, "UserName", "Password", "Active", "Role", "AllowedApps" FROM "Users" WHERE "UserName" = $1`;
283
+ const userResult = await dblogin.query({ name: 'get-user-by-username', text: userQuery, values: [trimmedUsername] });
229
284
 
230
285
  if (userResult.rows.length === 0) {
231
- console.log(`[mbkauthe] Username does not exist: ${username}`);
286
+ console.log(`[mbkauthe] Username does not exist: ${trimmedUsername}`);
232
287
  return res.status(404).json({ success: false, message: "Incorrect Username Or Password" });
233
288
  }
234
289
 
235
290
  const user = userResult.rows[0];
236
291
 
292
+ // Validate user has password field
293
+ if (!user.Password) {
294
+ console.error("[mbkauthe] User account has no password set");
295
+ return res.status(500).json({ success: false, message: "Internal Server Error" });
296
+ }
297
+
237
298
  if (mbkautheVar.EncryptedPassword === "true") {
238
299
  try {
239
300
  const result = await bcrypt.compare(password, user.Password);
@@ -248,13 +309,13 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
248
309
  }
249
310
  } else {
250
311
  if (user.Password !== password) {
251
- console.log(`[mbkauthe] Incorrect password for username: ${username}`);
312
+ console.log(`[mbkauthe] Incorrect password for username: ${trimmedUsername}`);
252
313
  return res.status(401).json({ success: false, errorCode: 603, message: "Incorrect Username Or Password" });
253
314
  }
254
315
  }
255
316
 
256
317
  if (!user.Active) {
257
- console.log(`[mbkauthe] Inactive account for username: ${username}`);
318
+ console.log(`[mbkauthe] Inactive account for username: ${trimmedUsername}`);
258
319
  return res.status(403).json({ success: false, message: "Account is inactive" });
259
320
  }
260
321
 
@@ -268,7 +329,7 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
268
329
 
269
330
  if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLocaleLowerCase() === "true") {
270
331
  const query = `SELECT "TwoFAStatus" FROM "TwoFA" WHERE "UserName" = $1`;
271
- const twoFAResult = await dblogin.query(query, [username]);
332
+ const twoFAResult = await dblogin.query({ name: 'get-2fa-status', text: query, values: [trimmedUsername] });
272
333
 
273
334
  if (twoFAResult.rows.length > 0 && twoFAResult.rows[0].TwoFAStatus) {
274
335
  // 2FA is enabled, prompt for token on a separate page
@@ -276,9 +337,9 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
276
337
  id: user.id,
277
338
  username: user.UserName,
278
339
  role: user.Role,
279
- Role: user.role,
340
+ Role: user.Role,
280
341
  };
281
- console.log(`[mbkauthe] 2FA required for user: ${username}`);
342
+ console.log(`[mbkauthe] 2FA required for user: ${trimmedUsername}`);
282
343
  return res.json({ success: true, twoFactorRequired: true });
283
344
  }
284
345
  }
@@ -288,12 +349,12 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
288
349
  id: user.id,
289
350
  username: user.UserName,
290
351
  role: user.Role,
291
- Role: user.role,
352
+ Role: user.Role,
292
353
  };
293
354
  await completeLoginProcess(req, res, userForSession);
294
355
 
295
356
  } catch (err) {
296
- console.log("[mbkauthe] Error during login process:", err);
357
+ console.error("[mbkauthe] Error during login process:", err);
297
358
  res.status(500).json({ success: false, message: "Internal Server Error" });
298
359
  }
299
360
  });
@@ -309,7 +370,7 @@ router.get("/mbkauthe/2fa", csrfProtection, (req, res) => {
309
370
  });
310
371
  });
311
372
 
312
- router.post("/mbkauthe/api/verify-2fa", async (req, res) => {
373
+ router.post("/mbkauthe/api/verify-2fa", TwoFALimit, csrfProtection, async (req, res) => {
313
374
  if (!req.session.preAuthUser) {
314
375
  return res.status(401).json({ success: false, message: "Not authorized. Please login first." });
315
376
  }
@@ -317,10 +378,17 @@ router.post("/mbkauthe/api/verify-2fa", async (req, res) => {
317
378
  const { token } = req.body;
318
379
  const { username, id, role } = req.session.preAuthUser;
319
380
 
320
- if (!token) {
381
+ // Validate 2FA token
382
+ if (!token || typeof token !== 'string') {
321
383
  return res.status(400).json({ success: false, message: "2FA token is required" });
322
384
  }
323
385
 
386
+ // Validate token format (should be 6 digits)
387
+ const sanitizedToken = token.trim();
388
+ if (!/^\d{6}$/.test(sanitizedToken)) {
389
+ return res.status(400).json({ success: false, message: "Invalid 2FA token format" });
390
+ }
391
+
324
392
  try {
325
393
  const query = `SELECT "TwoFASecret" FROM "TwoFA" WHERE "UserName" = $1`;
326
394
  const twoFAResult = await dblogin.query(query, [username]);
@@ -333,7 +401,7 @@ router.post("/mbkauthe/api/verify-2fa", async (req, res) => {
333
401
  const tokenValidates = speakeasy.totp.verify({
334
402
  secret: sharedSecret,
335
403
  encoding: "base32",
336
- token: token,
404
+ token: sanitizedToken,
337
405
  window: 1,
338
406
  });
339
407
 
@@ -348,12 +416,12 @@ router.post("/mbkauthe/api/verify-2fa", async (req, res) => {
348
416
  await completeLoginProcess(req, res, userForSession, redirectUrl);
349
417
 
350
418
  } catch (err) {
351
- console.log("[mbkauthe] Error during 2FA verification:", err);
419
+ console.error("[mbkauthe] Error during 2FA verification:", err);
352
420
  res.status(500).json({ success: false, message: "Internal Server Error" });
353
421
  }
354
422
  });
355
423
 
356
- router.post("/mbkauthe/api/logout", async (req, res) => {
424
+ router.post("/mbkauthe/api/logout", LogoutLimit, csrfProtection, async (req, res) => {
357
425
  if (req.session.user) {
358
426
  try {
359
427
  const { id, username } = req.session.user;
@@ -366,11 +434,11 @@ router.post("/mbkauthe/api/logout", async (req, res) => {
366
434
 
367
435
  req.session.destroy((err) => {
368
436
  if (err) {
369
- console.log("[mbkauthe] Error destroying session:", err);
437
+ console.error("[mbkauthe] Error destroying session:", err);
370
438
  return res.status(500).json({ success: false, message: "Logout failed" });
371
439
  }
372
440
 
373
- const cookieOptions = getCookieOptions();
441
+ const cookieOptions = getClearCookieOptions();
374
442
  res.clearCookie("mbkauthe.sid", cookieOptions);
375
443
  res.clearCookie("sessionId", cookieOptions);
376
444
  res.clearCookie("username", cookieOptions);
@@ -379,7 +447,7 @@ router.post("/mbkauthe/api/logout", async (req, res) => {
379
447
  res.status(200).json({ success: true, message: "Logout successful" });
380
448
  });
381
449
  } catch (err) {
382
- console.log("[mbkauthe] Database query error during logout:", err);
450
+ console.error("[mbkauthe] Database query error during logout:", err);
383
451
  res.status(500).json({ success: false, message: "Internal Server Error" });
384
452
  }
385
453
  } else {
@@ -404,7 +472,7 @@ async function getLatestVersion() {
404
472
  try {
405
473
  const response = await fetch('https://raw.githubusercontent.com/MIbnEKhalid/mbkauthe/main/package.json');
406
474
  if (!response.ok) {
407
- console.Error(`GitHub API responded with status ${response.status}`);
475
+ console.error(`GitHub API responded with status ${response.status}`);
408
476
  return "0.0.0";
409
477
  }
410
478
  const latestPackageJson = await response.json();
@@ -561,7 +629,7 @@ router.get('/mbkauthe/api/github/login/callback',
561
629
  };
562
630
 
563
631
  // Generate session and complete login
564
- const sessionId = crypto.randomBytes(256).toString("hex");
632
+ const sessionId = crypto.randomBytes(32).toString("hex");
565
633
  console.log(`[mbkauthe] GitHub login: Generated session ID for username: ${user.UserName}`);
566
634
 
567
635
  // Delete old session record for this user
package/lib/pool.js CHANGED
@@ -15,15 +15,15 @@ if (!mbkautheVar) {
15
15
  }
16
16
  const requiredKeys = ["APP_NAME", "SESSION_SECRET_KEY", "IS_DEPLOYED", "LOGIN_DB", "MBKAUTH_TWO_FA_ENABLE", "DOMAIN"];
17
17
  requiredKeys.forEach(key => {
18
- if (!mbkautheVar[key]) {
19
- throw new Error(`mbkautheVar.${key} is required`);
20
- }
18
+ if (!mbkautheVar[key]) {
19
+ throw new Error(`mbkautheVar.${key} is required`);
20
+ }
21
21
  });
22
22
  if (mbkautheVar.COOKIE_EXPIRE_TIME !== undefined) {
23
- const expireTime = parseFloat(mbkautheVar.COOKIE_EXPIRE_TIME);
24
- if (isNaN(expireTime) || expireTime <= 0) {
25
- throw new Error("mbkautheVar.COOKIE_EXPIRE_TIME must be a valid positive number");
26
- }
23
+ const expireTime = parseFloat(mbkautheVar.COOKIE_EXPIRE_TIME);
24
+ if (isNaN(expireTime) || expireTime <= 0) {
25
+ throw new Error("mbkautheVar.COOKIE_EXPIRE_TIME must be a valid positive number");
26
+ }
27
27
  }
28
28
 
29
29
  const poolConfig = {
@@ -32,6 +32,13 @@ const poolConfig = {
32
32
  rejectUnauthorized: true,
33
33
  },
34
34
 
35
+ // Connection pool tuning for serverless/ephemeral environments (Vercel)
36
+ // - keep max small to avoid exhausting DB connections
37
+ // - reduce idle time so connections are returned sooner
38
+ // - set a short connection timeout to fail fast
39
+ max: 10,
40
+ idleTimeoutMillis: 50000,
41
+ connectionTimeoutMillis: 5000,
35
42
  };
36
43
 
37
44
  export const dblogin = new Pool(poolConfig);