mbkauthe 4.7.2 → 4.8.1

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.
@@ -6,6 +6,44 @@ import { ErrorCodes, createErrorResponse } from "../utils/errors.js";
6
6
  import { hashApiToken } from "#config.js";
7
7
  import { canAccessMethod } from "#config.js";
8
8
 
9
+ const IS_DEV = process.env.env === 'dev' || process.env.test === 'dev' || process.env.NODE_ENV === 'development';
10
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11
+ const isUuid = (val) => typeof val === 'string' && UUID_RE.test(val);
12
+
13
+ const SQL_VALIDATE_APP_SESSION = `
14
+ SELECT s.expires_at, u."Active", u."Role"
15
+ FROM "Sessions" s
16
+ JOIN "Users" u ON s."UserName" = u."UserName"
17
+ WHERE s.id = $1
18
+ LIMIT 1
19
+ `;
20
+
21
+ /**
22
+ * Decide if the incoming request should return JSON errors instead of HTML.
23
+ * Non-browser clients (API calls / AJAX) should get JSON.
24
+ */
25
+ function isJsonRequest(req) {
26
+ if (!req || !req.headers) return false;
27
+ const accept = (req.headers.accept || "").toLowerCase();
28
+ const xRequestedWith = (req.headers["x-requested-with"] || "").toLowerCase();
29
+ const userAgent = (req.headers["user-agent"] || "").toLowerCase();
30
+ const url = (req.originalUrl || req.url || "").toLowerCase();
31
+ const path = (req.path || "").toLowerCase();
32
+
33
+ const isApiPath = url.startsWith("/mbkauthe/api/") || url.startsWith("/api/") || path.startsWith("/mbkauthe/api/") || path.startsWith("/api/");
34
+ const isAcceptJson = accept.includes("application/json") || accept.includes("json") || accept.includes("*/*");
35
+
36
+ const nonBrowserAgent = /curl|wget|httpie|python-requests|python|go-http-client|java\/|php|node-fetch|axios|postman|insomnia|okhttp/;
37
+ const browserAgent = /mozilla|applewebkit|chrome|safari|firefox|edg|msie|trident|opera/;
38
+
39
+ if (isApiPath || xRequestedWith === "xmlhttprequest") return true;
40
+ if (isAcceptJson && !accept.includes("text/html")) return true;
41
+
42
+ if (nonBrowserAgent.test(userAgent) && !browserAgent.test(userAgent)) return true;
43
+
44
+ return false;
45
+ }
46
+
9
47
  /**
10
48
  * Validates a Bearer token (API Token or Session UUID)
11
49
  * Returns a user object if valid, or null/error object
@@ -158,8 +196,13 @@ async function validateSession(req, res, next, strictTokenValidation = false) {
158
196
 
159
197
  // --- Fallback to Cookie Session ---
160
198
  if (!req.session.user) {
161
- console.log("[mbkauthe] User not authenticated");
162
- console.log("[mbkauthe]: ", req.session.user);
199
+ if (IS_DEV) {
200
+ console.log("[mbkauthe] User not authenticated");
201
+ console.log("[mbkauthe]: ", req.session.user);
202
+ }
203
+ if (isJsonRequest(req)) {
204
+ return res.status(401).json(createErrorResponse(401, ErrorCodes.SESSION_NOT_FOUND));
205
+ }
163
206
  return renderError(res, req, {
164
207
  code: 401,
165
208
  error: "Not Logged In",
@@ -170,13 +213,16 @@ async function validateSession(req, res, next, strictTokenValidation = false) {
170
213
  }
171
214
 
172
215
  try {
173
- const { id, sessionId, role, allowedApps } = req.session.user;
216
+ const { sessionId, role, allowedApps } = req.session.user;
174
217
 
175
218
  // Defensive checks for sessionId and allowedApps
176
- if (!sessionId) {
219
+ if (!sessionId || !isUuid(sessionId)) {
177
220
  console.warn(`[mbkauthe] Missing sessionId for user "${req.session.user.username}"`);
178
221
  req.session.destroy();
179
222
  clearSessionCookies(res);
223
+ if (isJsonRequest(req)) {
224
+ return res.status(401).json(createErrorResponse(401, ErrorCodes.SESSION_EXPIRED));
225
+ }
180
226
  return renderError(res, req, {
181
227
  code: 401,
182
228
  error: "Session Expired",
@@ -186,20 +232,16 @@ async function validateSession(req, res, next, strictTokenValidation = false) {
186
232
  });
187
233
  }
188
234
 
189
- // Normalize sessionId (DB id) for consistent comparison
190
- const normalizedSessionId = sessionId;
191
-
192
235
  // Validate session by DB primary key id and join to user
193
- const query = `SELECT s.id as sid, s.expires_at, u."Active", u."Role"
194
- FROM "Sessions" s
195
- JOIN "Users" u ON s."UserName" = u."UserName"
196
- WHERE s.id = $1 LIMIT 1`;
197
- const result = await dblogin.query({ name: 'validate-app-session', text: query, values: [normalizedSessionId] });
236
+ const result = await dblogin.query({ name: 'validate-app-session', text: SQL_VALIDATE_APP_SESSION, values: [sessionId] });
198
237
 
199
238
  if (result.rows.length === 0) {
200
239
  console.log(`[mbkauthe] Session not found for user "${req.session.user.username}"`);
201
240
  req.session.destroy();
202
241
  clearSessionCookies(res);
242
+ if (isJsonRequest(req)) {
243
+ return res.status(401).json(createErrorResponse(401, ErrorCodes.SESSION_EXPIRED));
244
+ }
203
245
  return renderError(res, req, {
204
246
  code: 401,
205
247
  error: "Session Expired",
@@ -212,11 +254,18 @@ async function validateSession(req, res, next, strictTokenValidation = false) {
212
254
  const sessionRow = result.rows[0];
213
255
 
214
256
  // Check expired
215
- if (sessionRow.expires_at && new Date(sessionRow.expires_at) <= new Date()) {
257
+ if (sessionRow.expires_at) {
258
+ const expiresMs = sessionRow.expires_at instanceof Date
259
+ ? sessionRow.expires_at.getTime()
260
+ : Date.parse(sessionRow.expires_at);
261
+ if (!Number.isNaN(expiresMs) && expiresMs <= Date.now()) {
216
262
  console.log(`[mbkauthe] Session invalidated (expired) for user "${req.session.user.username}"`);
217
263
  // destroy and clear cookies
218
264
  req.session.destroy();
219
265
  clearSessionCookies(res);
266
+ if (isJsonRequest(req)) {
267
+ return res.status(401).json(createErrorResponse(401, ErrorCodes.SESSION_EXPIRED));
268
+ }
220
269
  return renderError(res, req, {
221
270
  code: 401,
222
271
  error: "Session Expired",
@@ -225,12 +274,16 @@ async function validateSession(req, res, next, strictTokenValidation = false) {
225
274
  page: `/mbkauthe/login?redirect=${encodeURIComponent(req.originalUrl)}`,
226
275
  });
227
276
  }
277
+ }
228
278
 
229
279
 
230
280
  if (!sessionRow.Active) {
231
281
  console.log(`[mbkauthe] Account is inactive for user "${req.session.user.username}"`);
232
282
  req.session.destroy();
233
283
  clearSessionCookies(res);
284
+ if (isJsonRequest(req)) {
285
+ return res.status(401).json(createErrorResponse(401, ErrorCodes.ACCOUNT_INACTIVE));
286
+ }
234
287
  return renderError(res, req, {
235
288
  code: 401,
236
289
  error: "Account Inactive",
@@ -247,6 +300,9 @@ async function validateSession(req, res, next, strictTokenValidation = false) {
247
300
  console.warn(`[mbkauthe] User \"${req.session.user.username}\" is not authorized to use the application \"${mbkautheVar.APP_NAME}\"`);
248
301
  req.session.destroy();
249
302
  clearSessionCookies(res);
303
+ if (isJsonRequest(req)) {
304
+ return res.status(401).json(createErrorResponse(401, ErrorCodes.APP_NOT_AUTHORIZED));
305
+ }
250
306
  return renderError(res, req, {
251
307
  code: 401,
252
308
  error: "Unauthorized",
@@ -258,7 +314,7 @@ async function validateSession(req, res, next, strictTokenValidation = false) {
258
314
  }
259
315
 
260
316
  // Store user role in request for checkRolePermission to use
261
- req.userRole = result.rows[0].Role;
317
+ req.userRole = sessionRow.Role;
262
318
 
263
319
  next();
264
320
  } catch (err) {
@@ -277,25 +333,18 @@ async function validateApiSession(req, res, next) {
277
333
  }
278
334
 
279
335
  try {
280
- const { id, sessionId, role, allowedApps } = req.session.user;
336
+ const { sessionId, role, allowedApps } = req.session.user;
281
337
 
282
338
  // Defensive checks for sessionId and allowedApps
283
- if (!sessionId) {
339
+ if (!sessionId || !isUuid(sessionId)) {
284
340
  console.warn(`[mbkauthe] Missing sessionId for user "${req.session.user.username}"`);
285
341
  req.session.destroy();
286
342
  clearSessionCookies(res);
287
343
  return res.status(401).json(createErrorResponse(401, ErrorCodes.SESSION_EXPIRED));
288
344
  }
289
345
 
290
- // Normalize sessionId (DB id) for consistent comparison
291
- const normalizedSessionId = sessionId;
292
-
293
346
  // Validate session by DB primary key id and join to user
294
- const query = `SELECT s.id as sid, s.expires_at, u."Active", u."Role"
295
- FROM "Sessions" s
296
- JOIN "Users" u ON s."UserName" = u."UserName"
297
- WHERE s.id = $1 LIMIT 1`;
298
- const result = await dblogin.query({ name: 'validate-app-session-for-api', text: query, values: [normalizedSessionId] });
347
+ const result = await dblogin.query({ name: 'validate-app-session-for-api', text: SQL_VALIDATE_APP_SESSION, values: [sessionId] });
299
348
 
300
349
  if (result.rows.length === 0) {
301
350
  req.session.destroy();
@@ -306,11 +355,16 @@ async function validateApiSession(req, res, next) {
306
355
  const sessionRow = result.rows[0];
307
356
 
308
357
  // Check expired
309
- if (sessionRow.expires_at && new Date(sessionRow.expires_at) <= new Date()) {
358
+ if (sessionRow.expires_at) {
359
+ const expiresMs = sessionRow.expires_at instanceof Date
360
+ ? sessionRow.expires_at.getTime()
361
+ : Date.parse(sessionRow.expires_at);
362
+ if (!Number.isNaN(expiresMs) && expiresMs <= Date.now()) {
310
363
  req.session.destroy();
311
364
  clearSessionCookies(res);
312
365
  return res.status(401).json(createErrorResponse(401, ErrorCodes.SESSION_EXPIRED));
313
366
  }
367
+ }
314
368
 
315
369
 
316
370
  if (!result.rows[0].Active) {
@@ -332,7 +386,7 @@ async function validateApiSession(req, res, next) {
332
386
  }
333
387
 
334
388
  // Store user role in request for checkRolePermission to use
335
- req.userRole = result.rows[0].Role;
389
+ req.userRole = sessionRow.Role;
336
390
 
337
391
  next();
338
392
  } catch (err) {
@@ -444,6 +498,9 @@ const checkRolePermission = (requiredRoles, notAllowed) => {
444
498
  try {
445
499
  if (!req.session || !req.session.user || !req.session.user.id) {
446
500
  console.log("[mbkauthe] User not authenticated");
501
+ if (isJsonRequest(req)) {
502
+ return res.status(401).json(createErrorResponse(401, ErrorCodes.SESSION_NOT_FOUND));
503
+ }
447
504
  return renderError(res, req, {
448
505
  code: 401,
449
506
  error: "Not Logged In",
@@ -453,11 +510,19 @@ const checkRolePermission = (requiredRoles, notAllowed) => {
453
510
  });
454
511
  }
455
512
 
513
+ // SuperAdmin bypasses all role checks
514
+ if(req.session.role === "SuperAdmin") {
515
+ return next();
516
+ }
517
+
456
518
  // Use role from validateSession to avoid additional DB query
457
519
  const userRole = req.userRole;
458
520
 
459
521
  // Check notAllowed role
460
522
  if (notAllowed && userRole === notAllowed) {
523
+ if (isJsonRequest(req)) {
524
+ return res.status(403).json(createErrorResponse(403, ErrorCodes.ROLE_NOT_ALLOWED));
525
+ }
461
526
  return renderError(res, req, {
462
527
  code: 403,
463
528
  error: "Access Denied",
@@ -471,12 +536,15 @@ const checkRolePermission = (requiredRoles, notAllowed) => {
471
536
  const rolesArray = Array.isArray(requiredRoles) ? requiredRoles : [requiredRoles];
472
537
 
473
538
  // Check for "Any" or "any" role
474
- if (rolesArray.includes("Any") || rolesArray.includes("any")) {
539
+ if (rolesArray.includes("Any") || rolesArray.includes("any") || rolesArray.includes("*")) {
475
540
  return next();
476
541
  }
477
542
 
478
543
  // Check if user role is in allowed roles
479
544
  if (!rolesArray.includes(userRole)) {
545
+ if (isJsonRequest(req)) {
546
+ return res.status(403).json(createErrorResponse(403, ErrorCodes.INSUFFICIENT_PERMISSIONS));
547
+ }
480
548
  return renderError(res, req, {
481
549
  code: 403,
482
550
  error: "Access Denied",
@@ -502,7 +570,6 @@ const validateSessionAndRole = (requiredRole, notAllowed, strictTokenValidation
502
570
  };
503
571
  };
504
572
 
505
-
506
573
  const authenticate = (authentication) => {
507
574
  return (req, res, next) => {
508
575
  const token = req.headers["authorization"];
@@ -520,8 +587,13 @@ const authenticate = (authentication) => {
520
587
  const strictValidateSession = (req, res, next) => validateSession(req, res, next, true);
521
588
  const strictValidateSessionAndRole = (requiredRole, notAllowed) => validateSessionAndRole(requiredRole, notAllowed, true);
522
589
 
590
+ // Short aliases for convenience
591
+ const sessVal = validateSession;
592
+ const sessRole = validateSessionAndRole;
593
+
523
594
  export {
524
595
  validateSession, validateApiSession, checkRolePermission,
525
596
  validateSessionAndRole, authenticate, reloadSessionUser,
526
- strictValidateSession, strictValidateSessionAndRole
597
+ strictValidateSession, strictValidateSessionAndRole,
598
+ sessVal, sessRole
527
599
  }
package/lib/pool.js CHANGED
@@ -12,14 +12,9 @@ const poolConfig = {
12
12
  ssl: {
13
13
  rejectUnauthorized: true,
14
14
  },
15
-
16
- // Connection pool tuning for serverless/ephemeral environments (Vercel)
17
- // - keep max small to avoid exhausting DB connections
18
- // - reduce idle time so connections are returned sooner
19
- // - set a short connection timeout to fail fast
20
- max: 10,
15
+ max: 3,
21
16
  idleTimeoutMillis: 10000,
22
- connectionTimeoutMillis: 25000,
17
+ connectionTimeoutMillis: 10000,
23
18
  };
24
19
 
25
20
  export const dblogin = new Pool(poolConfig);
@@ -27,11 +22,21 @@ export const dblogin = new Pool(poolConfig);
27
22
  // Keep pool.js focused on pool setup; attach dev-only query logger from dedicated module.
28
23
  attachDevQueryLogger(dblogin);
29
24
 
25
+ /*
26
+ attachDevQueryLogger([
27
+ { pool: dblogin, name: "loginDB" },
28
+ { pool: dblogin1, name: "loginDB1" },
29
+ ]);
30
+ */
31
+
32
+ /*
30
33
  (async () => {
31
34
  try {
32
35
  const client = await dblogin.connect();
33
36
  client.release();
37
+ console.log("[mbkauthe] Database connection pool established successfully.");
34
38
  } catch (err) {
35
39
  console.error("[mbkauthe] Database connection error (pool):", err);
36
40
  }
37
- })();
41
+ })();
42
+ */
@@ -110,11 +110,17 @@ export async function checkTrustedDevice(req, username) {
110
110
  try {
111
111
  // Hash the provided device token before querying DB (we store token hashes in DB)
112
112
  const deviceTokenHash = hashDeviceToken(deviceToken);
113
+ // Single round-trip: validate trusted device AND refresh LastUsed.
113
114
  const deviceQuery = `
114
- SELECT td."UserName", td."LastUsed", td."ExpiresAt", u."id", u."Active", u."Role", u."AllowedApps"
115
- FROM "TrustedDevices" td
116
- JOIN "Users" u ON td."UserName" = u."UserName"
117
- WHERE td."DeviceToken" = $1 AND td."UserName" = $2 AND td."ExpiresAt" > NOW()
115
+ UPDATE "TrustedDevices" td
116
+ SET "LastUsed" = NOW()
117
+ FROM "Users" u
118
+ WHERE td."DeviceToken" = $1
119
+ AND td."UserName" = $2
120
+ AND td."ExpiresAt" > NOW()
121
+ AND u."UserName" = td."UserName"
122
+ AND u."Active" = TRUE
123
+ RETURNING td."UserName", td."ExpiresAt", u."id" as id, u."Active", u."Role", u."AllowedApps"
118
124
  `;
119
125
  const deviceResult = await dblogin.query({
120
126
  name: 'check-trusted-device',
@@ -138,13 +144,6 @@ export async function checkTrustedDevice(req, username) {
138
144
  }
139
145
  }
140
146
 
141
- // Update last used timestamp
142
- await dblogin.query({
143
- name: 'update-device-last-used',
144
- text: 'UPDATE "TrustedDevices" SET "LastUsed" = NOW() WHERE "DeviceToken" = $1',
145
- values: [deviceTokenHash]
146
- });
147
-
148
147
  console.log(`[mbkauthe] Trusted device validated for user: ${username}`);
149
148
  return {
150
149
  id: deviceUser.id,
@@ -193,21 +192,24 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
193
192
  const configuredMax = parseInt(mbkautheVar.MAX_SESSIONS_PER_USER, 10);
194
193
  const MAX_SESSIONS = Number.isInteger(configuredMax) && configuredMax > 0 ? configuredMax : 5;
195
194
 
196
- // Clean up expired sessions first to prevent accumulation
197
- await dblogin.query({
198
- name: 'cleanup-expired-sessions',
199
- text: `DELETE FROM "Sessions" WHERE "UserName" = $1 AND expires_at IS NOT NULL AND expires_at <= NOW()`,
200
- values: [username]
201
- });
202
-
203
- // Count active sessions for this user (by username)
195
+ // Clean up expired sessions + count active sessions in a single round-trip.
204
196
  const countRes = await dblogin.query({
205
- name: 'count-user-sessions',
206
- text: `SELECT id FROM "Sessions" WHERE "UserName" = $1 AND (expires_at IS NULL OR expires_at > NOW()) ORDER BY created_at ASC`,
197
+ name: 'cleanup-and-count-user-sessions',
198
+ text: `
199
+ WITH deleted AS (
200
+ DELETE FROM "Sessions"
201
+ WHERE "UserName" = $1
202
+ AND expires_at IS NOT NULL
203
+ AND expires_at <= NOW()
204
+ )
205
+ SELECT COUNT(*)::int AS count
206
+ FROM "Sessions"
207
+ WHERE "UserName" = $1
208
+ `,
207
209
  values: [username]
208
210
  });
209
211
 
210
- const currentSessions = countRes.rows.length;
212
+ const currentSessions = Number(countRes.rows?.[0]?.count ?? 0);
211
213
  // If we have MAX_SESSIONS or more, delete oldest to make room for exactly 1 new session
212
214
  if (currentSessions >= MAX_SESSIONS) {
213
215
  const sessionsToDelete = currentSessions - MAX_SESSIONS + 1; // +1 to make room for new session
@@ -215,15 +217,15 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
215
217
 
216
218
  await dblogin.query({
217
219
  name: 'prune-oldest-user-session',
218
- text: `DELETE FROM "Sessions" WHERE id IN (SELECT id FROM "Sessions" WHERE "UserName" = $1 AND (expires_at IS NULL OR expires_at > NOW()) ORDER BY created_at ASC LIMIT $2)`,
220
+ // Expired sessions were already removed above; remaining rows are active.
221
+ text: `DELETE FROM "Sessions" WHERE id IN (SELECT id FROM "Sessions" WHERE "UserName" = $1 ORDER BY created_at ASC LIMIT $2)`,
219
222
  values: [username, sessionsToDelete]
220
223
  });
221
224
  }
222
225
 
223
226
  const expiresAt = new Date(Date.now() + (cachedCookieOptions.maxAge || 0));
224
227
 
225
- // Insert new session record for the user (generate id explicitly to avoid relying on DB default)
226
- const newSessionId = crypto.randomUUID();
228
+ // Insert new session record for the user.
227
229
  let dbSessionId;
228
230
  try {
229
231
  const insertRes = await dblogin.query({
@@ -237,12 +239,18 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
237
239
  throw insertErr;
238
240
  }
239
241
 
240
- // Update last_login timestamp for the user
241
- await dblogin.query({
242
- name: 'login-update-last-login',
243
- text: `UPDATE "Users" SET "last_login" = NOW() WHERE "id" = $1`,
244
- values: [user.id]
245
- });
242
+ // Update last_login and fetch FullName/Image in a single query.
243
+ let profileRow = null;
244
+ try {
245
+ const profUpdateRes = await dblogin.query({
246
+ name: 'login-update-last-login-return-profile',
247
+ text: `UPDATE "Users" SET "last_login" = NOW() WHERE "id" = $1 RETURNING "FullName", "Image"`,
248
+ values: [user.id]
249
+ });
250
+ profileRow = profUpdateRes.rows?.[0] || null;
251
+ } catch (profileUpdateErr) {
252
+ console.error('[mbkauthe] Error updating last_login/returning profile:', profileUpdateErr);
253
+ }
246
254
 
247
255
  req.session.user = {
248
256
  id: user.id,
@@ -255,20 +263,26 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
255
263
  // Clear profile picture cache to fetch fresh data
256
264
  clearProfilePicCache(req, username);
257
265
 
258
- // Attempt to fetch FullName and Image from Users and store it in session for display purposes
266
+ // Store FullName/Image in session and cache cookie values.
259
267
  let loginProfileImage = null;
260
- try {
261
- const profileResult = await dblogin.query({
262
- name: 'login-get-fullname-and-image',
263
- text: 'SELECT "FullName", "Image" FROM "Users" WHERE "UserName" = $1 LIMIT 1',
264
- values: [username]
265
- });
266
- if (profileResult.rows.length > 0) {
267
- if (profileResult.rows[0].FullName) req.session.user.fullname = profileResult.rows[0].FullName;
268
- if (profileResult.rows[0].Image && profileResult.rows[0].Image.trim() !== '') loginProfileImage = profileResult.rows[0].Image;
268
+ if (profileRow) {
269
+ if (profileRow.FullName) req.session.user.fullname = profileRow.FullName;
270
+ if (typeof profileRow.Image === 'string' && profileRow.Image.trim() !== '') loginProfileImage = profileRow.Image;
271
+ } else {
272
+ // Fallback: try a read query if UPDATE...RETURNING failed unexpectedly.
273
+ try {
274
+ const profileResult = await dblogin.query({
275
+ name: 'login-get-fullname-and-image',
276
+ text: 'SELECT "FullName", "Image" FROM "Users" WHERE "UserName" = $1 LIMIT 1',
277
+ values: [username]
278
+ });
279
+ if (profileResult.rows.length > 0) {
280
+ if (profileResult.rows[0].FullName) req.session.user.fullname = profileResult.rows[0].FullName;
281
+ if (profileResult.rows[0].Image && profileResult.rows[0].Image.trim() !== '') loginProfileImage = profileResult.rows[0].Image;
282
+ }
283
+ } catch (profileErr) {
284
+ console.error("[mbkauthe] Error fetching FullName/Image for user:", profileErr);
269
285
  }
270
- } catch (profileErr) {
271
- console.error("[mbkauthe] Error fetching FullName/Image for user:", profileErr);
272
286
  }
273
287
 
274
288
  if (req.session.preAuthUser) {
@@ -1,6 +1,7 @@
1
1
  import express from "express";
2
2
  import { renderError } from "#response.js";
3
3
  import { dblogin } from "#pool.js";
4
+ import { getQueryCount, getQueryLog, resetQueryCount, resetQueryLog } from "../utils/dbQueryLogger.js";
4
5
  import { mbkautheVar } from "#config.js";
5
6
  import rateLimit from 'express-rate-limit';
6
7
 
@@ -39,8 +40,8 @@ router.get(["/db.json"], LogLimit, async (req, res) => {
39
40
  });
40
41
  }
41
42
 
42
- const queryCount = typeof dblogin.getQueryCount === 'function' ? dblogin.getQueryCount() : null;
43
- const queryLog = typeof dblogin.getQueryLog === 'function' ? dblogin.getQueryLog({ limit: queryLimit }) : [];
43
+ const queryCount = typeof getQueryCount === 'function' ? getQueryCount() : (typeof dblogin.getQueryCount === 'function' ? dblogin.getQueryCount() : 0);
44
+ const queryLog = typeof getQueryLog === 'function' ? getQueryLog({ limit: queryLimit }) : (typeof dblogin.getQueryLog === 'function' ? dblogin.getQueryLog({ limit: queryLimit }) : []);
44
45
 
45
46
  return res.json({ queryCount, queryLimit, queryLog, isDev });
46
47
  } catch (err) {
@@ -61,8 +62,11 @@ router.post(["/db/reset"], LogLimit, async (req, res) => {
61
62
  });
62
63
  }
63
64
 
64
- if (typeof dblogin.resetQueryCount === 'function') dblogin.resetQueryCount();
65
- if (typeof dblogin.resetQueryLog === 'function') dblogin.resetQueryLog();
65
+ if (typeof resetQueryCount === 'function') resetQueryCount();
66
+ else if (typeof dblogin.resetQueryCount === 'function') dblogin.resetQueryCount();
67
+
68
+ if (typeof resetQueryLog === 'function') resetQueryLog();
69
+ else if (typeof dblogin.resetQueryLog === 'function') dblogin.resetQueryLog();
66
70
 
67
71
  return res.json({ success: true, message: 'Query log and count have been reset.' });
68
72
  } catch (err) {
@@ -228,7 +228,24 @@ router.get('/api/checkSession', LoginLimit, async (req, res) => {
228
228
  return res.status(200).json({ sessionValid: false, expiry: null });
229
229
  }
230
230
 
231
- const result = await dblogin.query({ name: 'check-session-validity', text: `SELECT s.expires_at, u."Active" FROM "Sessions" s JOIN "Users" u ON s."UserName" = u."UserName" WHERE s.id = $1 LIMIT 1`, values: [sessionId] });
231
+ // Single round-trip: fetch app-session expiry and (if needed) connect-pg-simple expiry.
232
+ const result = await dblogin.query({
233
+ name: 'check-session-validity',
234
+ text: `
235
+ SELECT
236
+ s.expires_at,
237
+ u."Active",
238
+ CASE
239
+ WHEN s.expires_at IS NULL THEN (SELECT expire FROM "session" WHERE sid = $2)
240
+ ELSE NULL
241
+ END AS connect_expire
242
+ FROM "Sessions" s
243
+ JOIN "Users" u ON s."UserName" = u."UserName"
244
+ WHERE s.id = $1
245
+ LIMIT 1
246
+ `,
247
+ values: [sessionId, req.sessionID]
248
+ });
232
249
 
233
250
  if (result.rows.length === 0) {
234
251
  req.session.destroy(() => { });
@@ -243,12 +260,9 @@ router.get('/api/checkSession', LoginLimit, async (req, res) => {
243
260
  return res.status(200).json({ sessionValid: false, expiry: null });
244
261
  }
245
262
 
246
- // Determine expiry: prefer application session expiry if present else fallback to connect-pg-simple expire
247
- let expiry = row.expires_at ? new Date(row.expires_at).toISOString() : null;
248
- if (!expiry) {
249
- const sessResult = await dblogin.query({ name: 'get-session-expiry', text: 'SELECT expire FROM "session" WHERE sid = $1', values: [req.sessionID] });
250
- expiry = sessResult.rows.length > 0 && sessResult.rows[0].expire ? new Date(sessResult.rows[0].expire).toISOString() : null;
251
- }
263
+ // Determine expiry: prefer application session expiry if present else fallback to connect-pg-simple expiry.
264
+ const expirySource = row.expires_at || row.connect_expire || null;
265
+ const expiry = expirySource ? new Date(expirySource).toISOString() : null;
252
266
 
253
267
  return res.status(200).json({ sessionValid: true, expiry });
254
268
  } catch (err) {