mbkauthe 4.9.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,8 +13,13 @@ import { ErrorCodes, createErrorResponse } from '../utils/errors.js';
13
13
  export function validateTokenScope(req, res, next) {
14
14
  // Only validate for API token requests (not cookie-based sessions)
15
15
  // Check if this request was authenticated via API token
16
- if (req.session?.user?.sessionId === 'api-token-session' && req.session?.user?.tokenScope) {
17
- const tokenScope = req.session.user.tokenScope;
16
+ const tokenScope = req.auth?.type === 'api-token'
17
+ ? req.auth.tokenScope
18
+ : req.session?.user?.sessionId === 'api-token-session'
19
+ ? req.session.user.tokenScope
20
+ : null;
21
+
22
+ if (tokenScope) {
18
23
  const requestMethod = req.method;
19
24
 
20
25
  // Check if scope allows this HTTP method
@@ -29,4 +34,4 @@ export function validateTokenScope(req, res, next) {
29
34
  }
30
35
 
31
36
  next();
32
- }
37
+ }
package/lib/pool.js CHANGED
@@ -9,12 +9,11 @@ export { runWithRequestContext, getRequestContext };
9
9
 
10
10
  const poolConfig = {
11
11
  connectionString: mbkautheVar.LOGIN_DB,
12
- ssl: {
13
- rejectUnauthorized: true,
14
- },
15
- max: 3,
16
- idleTimeoutMillis: 10000,
17
- connectionTimeoutMillis: 10000,
12
+ ssl: { rejectUnauthorized: true },
13
+ max: 10,
14
+ idleTimeoutMillis: 30000,
15
+ connectionTimeoutMillis: 5000,
16
+ application_name: `${mbkautheVar.APP_NAME}-mbkauthe-app`,
18
17
  };
19
18
 
20
19
  export const dblogin = new Pool(poolConfig);
@@ -12,12 +12,14 @@ import {
12
12
  encryptSessionId
13
13
  } from "#cookies.js";
14
14
  import { packageJson } from "#config.js";
15
- import { hashPassword, hashApiToken } from "#config.js";
15
+ import { hashPassword } from "#config.js";
16
16
  import { ErrorCodes, createErrorResponse, logError } from "../utils/errors.js";
17
17
  import { AuthRepository } from "../db/AuthRepository.js";
18
+ import { createLogger } from "../utils/logger.js";
18
19
 
19
20
  const router = express.Router();
20
21
  const authRepo = new AuthRepository({ db: dblogin });
22
+ const logAuth = createLogger("auth");
21
23
 
22
24
  // Helper function to clear profile picture cache
23
25
  function clearProfilePicCache(req, username) {
@@ -113,7 +115,7 @@ export async function checkTrustedDevice(req, username) {
113
115
  if (deviceUser) {
114
116
 
115
117
  if (!deviceUser.Active) {
116
- console.log(`[mbkauthe] Trusted device check: inactive account for username: ${username}`);
118
+ logAuth(`Trusted device check: inactive account for username: ${username}`);
117
119
  return null;
118
120
  }
119
121
 
@@ -125,7 +127,7 @@ export async function checkTrustedDevice(req, username) {
125
127
  }
126
128
  }
127
129
 
128
- console.log(`[mbkauthe] Trusted device validated for user: ${username}`);
130
+ logAuth(`Trusted device validated for user: ${username}`);
129
131
  return {
130
132
  id: deviceUser.id,
131
133
  username: username,
@@ -173,11 +175,11 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
173
175
  let dbSessionId;
174
176
  try {
175
177
  await authRepo.withTransaction(async (txRepo) => {
176
- await txRepo.lockTable("Sessions", "SHARE ROW EXCLUSIVE");
178
+ await txRepo.advisoryTransactionLock(`sessions:${username}`, "lock-user-sessions");
177
179
  const currentSessions = await txRepo.cleanupAndCountUserSessions(username);
178
180
  if (currentSessions >= MAX_SESSIONS) {
179
181
  const sessionsToDelete = currentSessions - MAX_SESSIONS + 1; // +1 to make room for new session
180
- console.log(`[mbkauthe] User "${username}" has ${currentSessions} active sessions, exceeding max of ${MAX_SESSIONS}. Deleting ${sessionsToDelete} oldest sessions.`);
182
+ logAuth(`User "${username}" has ${currentSessions} active sessions, exceeding max of ${MAX_SESSIONS}. Deleting ${sessionsToDelete} oldest sessions.`);
181
183
 
182
184
  await txRepo.deleteOldestSessionsForUser(username, sessionsToDelete, "prune-oldest-user-session");
183
185
  }
@@ -297,14 +299,14 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
297
299
 
298
300
  // Send raw token to client as httpOnly cookie only
299
301
  res.cookie("device_token", deviceToken, getDeviceTokenCookieOptions());
300
- console.log(`[mbkauthe] Trusted device token created for user: ${username}`);
302
+ logAuth(`Trusted device token created for user: ${username}`);
301
303
  } catch (deviceErr) {
302
304
  console.error(`[mbkauthe] Error creating trusted device:`, deviceErr);
303
305
  // Continue with login even if device trust fails
304
306
  }
305
307
  }
306
308
 
307
- console.log(`[mbkauthe] User "${username}" logged in successfully (last_login updated)`);
309
+ logAuth(`User "${username}" logged in successfully (last_login updated)`);
308
310
 
309
311
  const responsePayload = {
310
312
  success: true,
@@ -326,7 +328,7 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
326
328
 
327
329
  // POST /mbkauthe/api/login
328
330
  router.post("/api/login", LoginLimit, async (req, res) => {
329
- console.log(`[mbkauthe] Login request received`);
331
+ logAuth(`Login request received`);
330
332
 
331
333
  const { username, password, redirect } = req.body;
332
334
 
@@ -356,7 +358,7 @@ router.post("/api/login", LoginLimit, async (req, res) => {
356
358
  );
357
359
  }
358
360
 
359
- console.log(`[mbkauthe] Login attempt for username: ${username.trim()}`);
361
+ logAuth(`Login attempt for username: ${username.trim()}`);
360
362
 
361
363
  const trimmedUsername = username.trim();
362
364
 
@@ -413,7 +415,7 @@ router.post("/api/login", LoginLimit, async (req, res) => {
413
415
  // Check for trusted device AFTER password validation
414
416
  const trustedDeviceUser = await checkTrustedDevice(req, trimmedUsername);
415
417
  if (trustedDeviceUser && (mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLocaleLowerCase() === "true" && user.TwoFAStatus) {
416
- console.log(`[mbkauthe] Trusted device login for user: ${trimmedUsername}, skipping 2FA only`);
418
+ logAuth(`Trusted device login for user: ${trimmedUsername}, skipping 2FA only`);
417
419
 
418
420
  const userForSession = {
419
421
  id: user.id,
@@ -435,7 +437,7 @@ router.post("/api/login", LoginLimit, async (req, res) => {
435
437
  allowedApps: user.AllowedApps,
436
438
  redirectUrl: requestedRedirect
437
439
  };
438
- console.log(`[mbkauthe] 2FA required for user: ${trimmedUsername}`);
440
+ logAuth(`2FA required for user: ${trimmedUsername}`);
439
441
  return res.json({ success: true, twoFactorRequired: true, redirectUrl: requestedRedirect });
440
442
  }
441
443
 
@@ -592,7 +594,7 @@ router.post("/api/logout", LogoutLimit, async (req, res) => {
592
594
 
593
595
  clearSessionCookies(res);
594
596
 
595
- console.log(`[mbkauthe] User "${username}" logged out successfully`);
597
+ logAuth(`User "${username}" logged out successfully`);
596
598
  res.status(200).json({ success: true, message: "Logout successful" });
597
599
  });
598
600
  } catch (err) {
@@ -611,50 +613,52 @@ router.get("/api/account-sessions", LoginLimit, async (req, res) => {
611
613
  return res.json({ accounts: [], currentSessionId: req.session?.user?.sessionId || null });
612
614
  }
613
615
 
614
- const validated = [];
615
616
  const currentSessionId = req.session?.user?.sessionId || null;
616
617
 
618
+ const validAccountEntries = [];
617
619
  for (const acct of storedAccounts) {
618
620
  if (!isUuid(acct.sessionId)) {
619
621
  removeAccountFromCookie(req, res, acct.sessionId);
620
622
  continue;
621
623
  }
624
+ validAccountEntries.push(acct);
625
+ }
622
626
 
623
- try {
624
- const row = await fetchActiveSession(acct.sessionId);
625
- if (!row) {
627
+ const sessionIds = validAccountEntries.map((acct) => acct.sessionId);
628
+
629
+ try {
630
+ const sessionRows = await authRepo.getSessionsWithUsersByIds(sessionIds, "multi-session-fetch-many");
631
+ const sessionMap = new Map(sessionRows.map((row) => [row.sid, row]));
632
+ const validated = [];
633
+
634
+ for (const acct of validAccountEntries) {
635
+ const row = sessionMap.get(acct.sessionId);
636
+ const expired = row?.expires_at && new Date(row.expires_at) <= new Date();
637
+ const authorized = row && row.Active && (
638
+ row.Role === "SuperAdmin" ||
639
+ (Array.isArray(row.AllowedApps) && row.AllowedApps.some(app => app && app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase()))
640
+ );
641
+
642
+ if (!row || expired || !authorized) {
626
643
  await invalidateDbSession(acct.sessionId);
627
644
  removeAccountFromCookie(req, res, acct.sessionId);
628
645
  continue;
629
646
  }
630
647
 
631
- let fullName = acct.fullName || acct.username;
632
- let image = acct.image || null;
633
- if (!acct.fullName || !acct.image) {
634
- try {
635
- const prof = await authRepo.getUserProfileByUsername(row.UserName, "multi-session-fullname-image");
636
- if (prof) {
637
- if (!acct.fullName && prof.FullName) fullName = prof.FullName;
638
- if (!acct.image && prof.Image && prof.Image.trim() !== '') image = prof.Image;
639
- }
640
- } catch (profileErr) {
641
- console.error(`[mbkauthe] Error fetching fullname/image for account list:`, profileErr);
642
- }
643
- }
644
-
645
648
  validated.push({
646
649
  sessionId: row.sid,
647
650
  username: row.UserName,
648
- fullName,
649
- image,
651
+ fullName: acct.fullName || row.FullName || acct.username || row.UserName,
652
+ image: acct.image || (typeof row.Image === 'string' && row.Image.trim() !== '' ? row.Image : null),
650
653
  isCurrent: currentSessionId && row.sid === currentSessionId
651
654
  });
652
- } catch (err) {
653
- console.error(`[mbkauthe] Error validating remembered account:`, err);
654
655
  }
655
- }
656
656
 
657
- return res.json({ accounts: validated, currentSessionId });
657
+ return res.json({ accounts: validated, currentSessionId });
658
+ } catch (err) {
659
+ console.error(`[mbkauthe] Error validating remembered accounts:`, err);
660
+ return res.status(500).json(createErrorResponse(500, ErrorCodes.INTERNAL_SERVER_ERROR));
661
+ }
658
662
  });
659
663
 
660
664
  // Switch active session to another remembered account
@@ -679,17 +683,8 @@ router.post("/api/switch-session", LoginLimit, async (req, res) => {
679
683
  return res.status(401).json(createErrorResponse(401, ErrorCodes.SESSION_EXPIRED));
680
684
  }
681
685
 
682
- let fullName = row.UserName;
683
- let switchProfileImage = null;
684
- try {
685
- const prof = await authRepo.getUserProfileByUsername(row.UserName, "multi-session-switch-fullname-image");
686
- if (prof) {
687
- if (prof.FullName) fullName = prof.FullName;
688
- if (prof.Image && prof.Image.trim() !== '') switchProfileImage = prof.Image;
689
- }
690
- } catch (profileErr) {
691
- console.error(`[mbkauthe] Error fetching fullname/image during switch:`, profileErr);
692
- }
686
+ const fullName = row.FullName || row.UserName;
687
+ const switchProfileImage = typeof row.Image === 'string' && row.Image.trim() !== '' ? row.Image : null;
693
688
 
694
689
  // Regenerate session to avoid fixation
695
690
  await new Promise((resolve, reject) => {
@@ -1,15 +1,213 @@
1
1
  import express from "express";
2
+ import rateLimit from "express-rate-limit";
2
3
  import { renderError } from "#response.js";
3
4
  import { dblogin } from "#pool.js";
4
5
  import { getQueryCount, getQueryLog, resetQueryCount, resetQueryLog } from "../utils/dbQueryLogger.js";
5
6
  import { mbkautheVar } from "#config.js";
6
- import rateLimit from 'express-rate-limit';
7
7
 
8
8
  const router = express.Router();
9
9
 
10
10
  const isDbLogsEnabled = () => process.env.env === "dev" && process.env.dbLogs === "true";
11
11
 
12
- // Rate limiter for info/test routes
12
+ const clampLimit = (value, fallback = 50, max = 500) => {
13
+ const parsed = Number(value);
14
+ if (!Number.isFinite(parsed) || parsed < 1) {
15
+ return fallback;
16
+ }
17
+ return Math.min(max, Math.floor(parsed));
18
+ };
19
+
20
+ const normalizeStringFilter = (value) => {
21
+ if (typeof value !== "string") return "";
22
+ return value.trim();
23
+ };
24
+
25
+ const parseSuccessFilter = (value) => {
26
+ if (value === true || value === "true") return true;
27
+ if (value === false || value === "false") return false;
28
+ return null;
29
+ };
30
+
31
+ const getRawQueryLog = () => {
32
+ if (typeof getQueryLog === "function") return getQueryLog();
33
+ if (typeof dblogin.getQueryLog === "function") return dblogin.getQueryLog();
34
+ return [];
35
+ };
36
+
37
+ const filterQueryLog = (queryLog, filters) => {
38
+ const poolName = normalizeStringFilter(filters.pool);
39
+ const username = normalizeStringFilter(filters.username).toLowerCase();
40
+ const url = normalizeStringFilter(filters.url).toLowerCase();
41
+ const success = parseSuccessFilter(filters.success);
42
+
43
+ return queryLog.filter((entry) => {
44
+ if (poolName && (entry?.pool?.name || "") !== poolName) {
45
+ return false;
46
+ }
47
+
48
+ if (username) {
49
+ const candidate = String(entry?.request?.username || entry?.request?.userId || "").toLowerCase();
50
+ if (!candidate.includes(username)) {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ if (url) {
56
+ const candidateUrl = String(entry?.request?.url || "").toLowerCase();
57
+ if (!candidateUrl.includes(url)) {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ if (success !== null && Boolean(entry?.success) !== success) {
63
+ return false;
64
+ }
65
+
66
+ return true;
67
+ });
68
+ };
69
+
70
+ const sortQueryLogNewestFirst = (queryLog) =>
71
+ [...queryLog].sort((a, b) => {
72
+ const left = Date.parse(b?.time || "") || 0;
73
+ const right = Date.parse(a?.time || "") || 0;
74
+ return left - right;
75
+ });
76
+
77
+ const average = (numbers) => {
78
+ if (!numbers.length) return 0;
79
+ return numbers.reduce((sum, value) => sum + value, 0) / numbers.length;
80
+ };
81
+
82
+ const buildSummary = (queryLog) => {
83
+ const durations = queryLog
84
+ .map((entry) => Number(entry?.durationMs))
85
+ .filter((value) => Number.isFinite(value));
86
+ const executionDurations = queryLog
87
+ .map((entry) => Number(entry?.executionDurationMs))
88
+ .filter((value) => Number.isFinite(value));
89
+ const waitDurations = queryLog
90
+ .map((entry) => Number(entry?.poolWait?.waitMs))
91
+ .filter((value) => Number.isFinite(value));
92
+ const errorCount = queryLog.filter((entry) => entry?.success === false).length;
93
+ const pressuredQueries = queryLog.filter((entry) => entry?.poolWait?.hadPoolPressure).length;
94
+
95
+ const slowestQueries = [...queryLog]
96
+ .sort((a, b) => (Number(b?.durationMs) || 0) - (Number(a?.durationMs) || 0))
97
+ .slice(0, 5)
98
+ .map((entry) => ({
99
+ time: entry.time,
100
+ query: entry.query,
101
+ name: entry.name,
102
+ fingerprint: entry.fingerprint,
103
+ durationMs: entry.durationMs,
104
+ executionDurationMs: entry.executionDurationMs,
105
+ waitMs: entry.poolWait?.waitMs || 0,
106
+ success: entry.success,
107
+ request: entry.request,
108
+ pool: entry.pool,
109
+ }));
110
+
111
+ const repeatedGroupsMap = new Map();
112
+ for (const entry of queryLog) {
113
+ const key = entry?.fingerprint || entry?.normalizedQuery || entry?.query;
114
+ if (!key) continue;
115
+
116
+ const existing = repeatedGroupsMap.get(key);
117
+ if (existing) {
118
+ existing.count += 1;
119
+ existing.totalDurationMs += Number(entry?.durationMs) || 0;
120
+ existing.totalExecutionDurationMs += Number(entry?.executionDurationMs) || 0;
121
+ existing.totalWaitMs += Number(entry?.poolWait?.waitMs) || 0;
122
+ existing.errorCount += entry?.success === false ? 1 : 0;
123
+ if ((Date.parse(entry?.time || "") || 0) > (Date.parse(existing.lastSeen || "") || 0)) {
124
+ existing.lastSeen = entry.time;
125
+ }
126
+ continue;
127
+ }
128
+
129
+ repeatedGroupsMap.set(key, {
130
+ fingerprint: entry.fingerprint,
131
+ normalizedQuery: entry.normalizedQuery,
132
+ sampleQuery: entry.query,
133
+ sampleName: entry.name,
134
+ poolName: entry?.pool?.name || null,
135
+ requestUrl: entry?.request?.url || null,
136
+ count: 1,
137
+ totalDurationMs: Number(entry?.durationMs) || 0,
138
+ totalExecutionDurationMs: Number(entry?.executionDurationMs) || 0,
139
+ totalWaitMs: Number(entry?.poolWait?.waitMs) || 0,
140
+ errorCount: entry?.success === false ? 1 : 0,
141
+ lastSeen: entry.time,
142
+ });
143
+ }
144
+
145
+ const repeatedGroups = [...repeatedGroupsMap.values()]
146
+ .filter((group) => group.count > 1)
147
+ .sort((a, b) => {
148
+ if (b.count !== a.count) return b.count - a.count;
149
+ return b.totalDurationMs - a.totalDurationMs;
150
+ })
151
+ .slice(0, 8)
152
+ .map((group) => ({
153
+ fingerprint: group.fingerprint,
154
+ normalizedQuery: group.normalizedQuery,
155
+ sampleQuery: group.sampleQuery,
156
+ sampleName: group.sampleName,
157
+ poolName: group.poolName,
158
+ requestUrl: group.requestUrl,
159
+ count: group.count,
160
+ avgDurationMs: group.totalDurationMs / group.count,
161
+ avgExecutionDurationMs: group.totalExecutionDurationMs / group.count,
162
+ avgWaitMs: group.totalWaitMs / group.count,
163
+ errorCount: group.errorCount,
164
+ lastSeen: group.lastSeen,
165
+ }));
166
+
167
+ return {
168
+ totalVisible: queryLog.length,
169
+ avgDurationMs: average(durations),
170
+ avgExecutionDurationMs: average(executionDurations),
171
+ avgWaitMs: average(waitDurations),
172
+ errorCount,
173
+ pressuredQueries,
174
+ slowestQueries,
175
+ repeatedGroups,
176
+ };
177
+ };
178
+
179
+ const buildResponsePayload = (req) => {
180
+ const queryCount = typeof getQueryCount === "function"
181
+ ? getQueryCount()
182
+ : typeof dblogin.getQueryCount === "function"
183
+ ? dblogin.getQueryCount()
184
+ : 0;
185
+ const queryLimit = clampLimit(req.query.limit);
186
+ const filters = {
187
+ pool: normalizeStringFilter(req.query.pool),
188
+ username: normalizeStringFilter(req.query.username),
189
+ url: normalizeStringFilter(req.query.url),
190
+ success: parseSuccessFilter(req.query.success),
191
+ };
192
+ const filtered = filterQueryLog(getRawQueryLog(), filters);
193
+ const ordered = sortQueryLogNewestFirst(filtered);
194
+ const queryLog = ordered.slice(0, queryLimit);
195
+ const summary = buildSummary(queryLog);
196
+
197
+ return {
198
+ queryCount,
199
+ queryLimit,
200
+ filters: {
201
+ pool: filters.pool,
202
+ username: filters.username,
203
+ url: filters.url,
204
+ success: filters.success,
205
+ },
206
+ summary,
207
+ queryLog,
208
+ };
209
+ };
210
+
13
211
  const LogLimit = rateLimit({
14
212
  windowMs: 1 * 60 * 1000,
15
213
  max: 50,
@@ -19,15 +217,14 @@ const LogLimit = rateLimit({
19
217
  },
20
218
  validate: {
21
219
  trustProxy: false,
22
- xForwardedForHeader: false
23
- }
220
+ xForwardedForHeader: false,
221
+ },
24
222
  });
25
223
 
26
- // DB stats API (JSON)
27
224
  router.get(["/db.json"], LogLimit, async (req, res) => {
28
225
  try {
29
226
  const isDev = isDbLogsEnabled();
30
- const queryLimit = Number(req.query.limit) || 50;
227
+ const queryLimit = clampLimit(req.query.limit);
31
228
 
32
229
  if (!isDev) {
33
230
  return res.status(403).json({
@@ -36,21 +233,33 @@ router.get(["/db.json"], LogLimit, async (req, res) => {
36
233
  isDev,
37
234
  queryCount: 0,
38
235
  queryLimit,
39
- queryLog: []
236
+ filters: {
237
+ pool: normalizeStringFilter(req.query.pool),
238
+ username: normalizeStringFilter(req.query.username),
239
+ url: normalizeStringFilter(req.query.url),
240
+ success: parseSuccessFilter(req.query.success),
241
+ },
242
+ summary: {
243
+ totalVisible: 0,
244
+ avgDurationMs: 0,
245
+ avgExecutionDurationMs: 0,
246
+ avgWaitMs: 0,
247
+ errorCount: 0,
248
+ pressuredQueries: 0,
249
+ slowestQueries: [],
250
+ repeatedGroups: [],
251
+ },
252
+ queryLog: [],
40
253
  });
41
254
  }
42
255
 
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 }) : []);
45
-
46
- return res.json({ queryCount, queryLimit, queryLog, isDev });
256
+ return res.json({ ...buildResponsePayload(req), isDev });
47
257
  } catch (err) {
48
- console.error(`[mbkauthe] /db.json route error:`, err);
49
- return res.status(500).json({ success: false, message: 'Could not fetch DB stats.' });
258
+ console.error("[mbkauthe] /db.json route error:", err);
259
+ return res.status(500).json({ success: false, message: "Could not fetch DB stats." });
50
260
  }
51
261
  });
52
262
 
53
- // Dedicated reset API for DB logs and counters
54
263
  router.post(["/db/reset"], LogLimit, async (req, res) => {
55
264
  try {
56
265
  const isDev = isDbLogsEnabled();
@@ -58,39 +267,48 @@ router.post(["/db/reset"], LogLimit, async (req, res) => {
58
267
  return res.status(403).json({
59
268
  success: false,
60
269
  message: "DB logs are disabled.",
61
- isDev
270
+ isDev,
62
271
  });
63
272
  }
64
273
 
65
- if (typeof resetQueryCount === 'function') resetQueryCount();
66
- else if (typeof dblogin.resetQueryCount === 'function') dblogin.resetQueryCount();
274
+ if (typeof resetQueryCount === "function") resetQueryCount();
275
+ else if (typeof dblogin.resetQueryCount === "function") dblogin.resetQueryCount();
67
276
 
68
- if (typeof resetQueryLog === 'function') resetQueryLog();
69
- else if (typeof dblogin.resetQueryLog === 'function') dblogin.resetQueryLog();
277
+ if (typeof resetQueryLog === "function") resetQueryLog();
278
+ else if (typeof dblogin.resetQueryLog === "function") dblogin.resetQueryLog();
70
279
 
71
- return res.json({ success: true, message: 'Query log and count have been reset.' });
280
+ return res.json({ success: true, message: "Query log and count have been reset." });
72
281
  } catch (err) {
73
- console.error(`[mbkauthe] /db/reset route error:`, err);
74
- return res.status(500).json({ success: false, message: 'Could not reset DB stats.' });
282
+ console.error("[mbkauthe] /db/reset route error:", err);
283
+ return res.status(500).json({ success: false, message: "Could not reset DB stats." });
75
284
  }
76
285
  });
77
286
 
78
- // DB stats page (HTML)
79
287
  router.get(["/db"], LogLimit, async (req, res) => {
80
288
  try {
81
289
  const isDev = isDbLogsEnabled();
82
- const queryLimit = Number(req.query.limit) || 50;
83
- const resetDone = req.query.resetDone === '1';
84
- return res.render('pages/dbLogs.handlebars', {
290
+ const queryLimit = clampLimit(req.query.limit);
291
+ const resetDone = req.query.resetDone === "1";
292
+ const successFilter = parseSuccessFilter(req.query.success);
293
+
294
+ return res.render("pages/dbLogs.handlebars", {
85
295
  layout: false,
86
296
  appName: mbkautheVar.APP_NAME,
87
297
  queryLimit,
88
298
  resetDone,
89
299
  isDev,
90
- disabledMessage: isDev ? null : 'DB logs are disabled.'
300
+ filters: {
301
+ pool: normalizeStringFilter(req.query.pool),
302
+ username: normalizeStringFilter(req.query.username),
303
+ url: normalizeStringFilter(req.query.url),
304
+ successAny: successFilter === null,
305
+ successTrue: successFilter === true,
306
+ successFalse: successFilter === false,
307
+ },
308
+ disabledMessage: isDev ? null : "DB logs are disabled.",
91
309
  });
92
310
  } catch (err) {
93
- console.error(`[mbkauthe] /db route error:`, err);
311
+ console.error("[mbkauthe] /db route error:", err);
94
312
  return renderError(res, req, {
95
313
  layout: false,
96
314
  code: 500,
@@ -102,4 +320,4 @@ router.get(["/db"], LogLimit, async (req, res) => {
102
320
  }
103
321
  });
104
322
 
105
- export default router;
323
+ export default router;
@@ -12,6 +12,7 @@ import { fileURLToPath } from "url";
12
12
  import path from "path";
13
13
  import fs from "fs";
14
14
  import dotenv from "dotenv";
15
+ import { createLogger } from "../utils/logger.js";
15
16
 
16
17
  dotenv.config();
17
18
 
@@ -20,6 +21,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
21
 
21
22
  const router = express.Router();
22
23
  const authRepo = new AuthRepository({ db: dblogin });
24
+ const logMisc = createLogger("misc");
23
25
  // Rate limiter for info/test routes
24
26
  const LoginLimit = rateLimit({
25
27
  windowMs: 1 * 60 * 1000,
@@ -472,9 +474,9 @@ export async function checkVersion() {
472
474
  if (hasValidLatest && latestVersion !== packageJson.version) {
473
475
  console.warn(`[mbkauthe] Current version (${packageJson.version}) is outdated. Latest version: ${latestVersion}. Consider updating mbkauthe.`);
474
476
  } else if (hasValidLatest) {
475
- console.info(`[mbkauthe] Running latest version (${packageJson.version}).`);
477
+ logMisc(`Running latest version (${packageJson.version}).`);
476
478
  } else {
477
- console.info(`[mbkauthe] Skipped version check warning: latest version unavailable.`);
479
+ logMisc(`Skipped version check warning: latest version unavailable.`);
478
480
  }
479
481
  } catch (error) {
480
482
  console.warn(`[mbkauthe] Failed to check for updates: ${error.message}`);
@@ -544,13 +546,13 @@ router.post("/api/terminateAllSessions", AdminOperationLimit, authenticate(mbkau
544
546
 
545
547
  req.session.destroy((err) => {
546
548
  if (err) {
547
- console.log(`[mbkauthe] Error destroying session:`, err);
549
+ console.error(`[mbkauthe] Error destroying session:`, err);
548
550
  return res.status(500).json({ success: false, message: "Failed to terminate sessions" });
549
551
  }
550
552
 
551
553
  clearSessionCookies(res);
552
554
 
553
- console.log(`[mbkauthe] All sessions terminated successfully`);
555
+ logMisc(`All sessions terminated successfully`);
554
556
  res.status(200).json({
555
557
  success: true,
556
558
  message: "All sessions terminated successfully",