mbkauthe 4.7.1 → 4.7.2

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/index.js CHANGED
@@ -46,7 +46,7 @@ app.engine("handlebars", engine({
46
46
  return Object.entries(obj).map(([key, value]) => ({ key, value }));
47
47
  },
48
48
  cacheBuster: function () {
49
- return packageJson.version;
49
+ return "?v=" + packageJson.version;
50
50
  }
51
51
  }
52
52
 
@@ -149,6 +149,8 @@ export const clearSessionCookies = (res) => {
149
149
  res.clearCookie("mbkauthe.sid", cachedClearCookieOptions);
150
150
  res.clearCookie("sessionId", cachedClearCookieOptions);
151
151
  res.clearCookie("fullName", cachedClearCookieOptions);
152
+ res.clearCookie("profileImageUrl", cachedClearCookieOptions);
153
+ res.clearCookie("profileImageUser", cachedClearCookieOptions);
152
154
  res.clearCookie("device_token", cachedClearCookieOptions);
153
155
  };
154
156
 
package/lib/pool.js CHANGED
@@ -1,9 +1,12 @@
1
1
  import pkg from "pg";
2
2
  const { Pool } = pkg;
3
3
  import { mbkautheVar } from "#config.js";
4
+ import { attachDevQueryLogger, runWithRequestContext, getRequestContext } from "./utils/dbQueryLogger.js";
4
5
  import dotenv from "dotenv";
5
6
  dotenv.config();
6
7
 
8
+ export { runWithRequestContext, getRequestContext };
9
+
7
10
  const poolConfig = {
8
11
  connectionString: mbkautheVar.LOGIN_DB,
9
12
  ssl: {
@@ -21,190 +24,8 @@ const poolConfig = {
21
24
 
22
25
  export const dblogin = new Pool(poolConfig);
23
26
 
24
-
25
-
26
- // For logging and testing purposes, we can track query counts and logs in development mode.
27
-
28
- import path from "path";
29
- import { AsyncLocalStorage } from "async_hooks";
30
-
31
- const isDev = process.env.env === 'dev';
32
- const requestContext = isDev ? new AsyncLocalStorage() : null;
33
-
34
- export const runWithRequestContext = (req, fn) => {
35
- if (!isDev || !requestContext) return fn();
36
- return requestContext.run({ req }, fn);
37
- };
38
-
39
- export const getRequestContext = () => {
40
- if (!isDev || !requestContext) return undefined;
41
- return requestContext.getStore();
42
- };
43
-
44
- if (isDev) {
45
-
46
- // Simple counter for all DB requests made via this pool. This is intentionally
47
- // lightweight.
48
- let _dbQueryCount = 0;
49
- const _dbQueryLog = [];
50
- const _MAX_QUERY_LOG_ENTRIES = 1000;
51
-
52
- const _origQuery = dblogin.query.bind(dblogin);
53
-
54
- dblogin.query = (...args) => {
55
- _dbQueryCount++;
56
-
57
- // Track query text for debugging/metrics.
58
- // `pg` supports (text, values, callback) or (config, callback).
59
- let queryText = '';
60
- let queryName = '';
61
- let queryValues;
62
- try {
63
- if (typeof args[0] === 'string') {
64
- queryText = args[0];
65
- queryValues = Array.isArray(args[1]) ? args[1] : undefined;
66
- } else if (args[0] && typeof args[0] === 'object') {
67
- queryText = args[0].text || '';
68
- queryName = args[0].name || '';
69
- queryValues = Array.isArray(args[0].values) ? args[0].values : undefined;
70
- }
71
- } catch {
72
- queryText = '';
73
- }
74
-
75
- if (!queryText) {
76
- return _origQuery(...args);
77
- }
78
-
79
- const startTime = process.hrtime.bigint();
80
- const toWorkspacePath = (filePath) => {
81
- const rel = path.relative(process.cwd(), filePath) || filePath;
82
- return rel.replace(/\\/g, '/');
83
- };
84
-
85
- const buildCallsite = () => {
86
- try {
87
- const stack = new Error().stack || '';
88
- const lines = stack.split('\n').map(l => l.trim());
89
- // Skip frames from this wrapper and node internals; pick first app frame.
90
- const frame = lines.find((line) =>
91
- line.startsWith('at ') &&
92
- !line.includes('/lib/pool.js') &&
93
- !line.includes('node:internal') &&
94
- !line.includes('internal/process')
95
- );
96
-
97
- if (!frame) return null;
98
-
99
- const withFunc = /^at\s+([^\s(]+)\s+\((.+):([0-9]+):([0-9]+)\)$/.exec(frame);
100
- const noFunc = /^at\s+(.+):([0-9]+):([0-9]+)$/.exec(frame);
101
-
102
- if (withFunc) {
103
- return {
104
- function: withFunc[1],
105
- file: toWorkspacePath(withFunc[2]),
106
- line: Number(withFunc[3]),
107
- column: Number(withFunc[4])
108
- };
109
- }
110
- if (noFunc) {
111
- return {
112
- function: null,
113
- file: toWorkspacePath(noFunc[1]),
114
- line: Number(noFunc[2]),
115
- column: Number(noFunc[3])
116
- };
117
- }
118
- } catch {
119
- return null;
120
- }
121
- return null;
122
- };
123
-
124
- const buildRequestContext = () => {
125
- const store = getRequestContext();
126
- const req = store?.req;
127
- if (!req) return null;
128
-
129
- const user = req.session?.user || null;
130
- return {
131
- method: req.method,
132
- url: req.originalUrl || req.url,
133
- ip: req.ip,
134
- userId: user?.id || null,
135
- username: user?.username || null
136
- };
137
- };
138
-
139
- const callsiteSnapshot = buildCallsite();
140
-
141
- const recordLog = (success, error) => {
142
- const durationMs = Number(process.hrtime.bigint() - startTime) / 1_000_000;
143
- const request = buildRequestContext();
144
-
145
- _dbQueryLog.push({
146
- time: new Date().toISOString(),
147
- query: queryText,
148
- name: queryName || undefined,
149
- values: queryValues,
150
- durationMs,
151
- success,
152
- error: error ? { message: error.message, code: error.code } : undefined,
153
- request,
154
- pool: {
155
- total: dblogin.totalCount,
156
- idle: dblogin.idleCount,
157
- waiting: dblogin.waitingCount
158
- },
159
- callsite: callsiteSnapshot
160
- });
161
-
162
- if (_dbQueryLog.length > _MAX_QUERY_LOG_ENTRIES) {
163
- _dbQueryLog.shift();
164
- }
165
- };
166
-
167
- try {
168
- const result = _origQuery(...args);
169
- if (result && typeof result.then === 'function') {
170
- return result
171
- .then((res) => {
172
- recordLog(true, null);
173
- return res;
174
- })
175
- .catch((err) => {
176
- recordLog(false, err);
177
- throw err;
178
- });
179
- }
180
-
181
- recordLog(true, null);
182
- return result;
183
- } catch (err) {
184
- recordLog(false, err);
185
- throw err;
186
- }
187
- };
188
-
189
- // Public helpers
190
-
191
- dblogin.getQueryCount = () => _dbQueryCount;
192
- dblogin.resetQueryCount = () => {
193
- _dbQueryCount = 0;
194
- };
195
-
196
- dblogin.getQueryLog = (options = {}) => {
197
- const { limit } = options;
198
- if (typeof limit === 'number') {
199
- return _dbQueryLog.slice(-limit);
200
- }
201
- return [..._dbQueryLog];
202
- };
203
-
204
- dblogin.resetQueryLog = () => {
205
- _dbQueryLog.length = 0;
206
- };
207
- }
27
+ // Keep pool.js focused on pool setup; attach dev-only query logger from dedicated module.
28
+ attachDevQueryLogger(dblogin);
208
29
 
209
30
  (async () => {
210
31
  try {
@@ -19,10 +19,13 @@ const router = express.Router();
19
19
 
20
20
  // Helper function to clear profile picture cache
21
21
  function clearProfilePicCache(req, username) {
22
- if (req.session && username) {
23
- const cacheKey = `profilepic_${username}`;
24
- delete req.session[cacheKey];
25
- }
22
+ if (!req || !req.res || !username) return;
23
+
24
+ const cookieUsername = req.cookies?.profileImageUser;
25
+ if (cookieUsername && cookieUsername !== username) return;
26
+
27
+ req.res.clearCookie('profileImageUrl', cachedClearCookieOptions);
28
+ req.res.clearCookie('profileImageUser', cachedClearCookieOptions);
26
29
  }
27
30
 
28
31
  // Rate limiters for auth routes
@@ -285,6 +288,9 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
285
288
  }
286
289
  // Cache display name client-side to avoid extra DB lookups
287
290
  res.cookie("fullName", req.session.user.fullname || username, { ...cachedCookieOptions, httpOnly: false });
291
+ const profileImageForCookie = loginProfileImage && typeof loginProfileImage === 'string' ? loginProfileImage : 'default';
292
+ res.cookie('profileImageUrl', profileImageForCookie, { ...cachedCookieOptions, httpOnly: false });
293
+ res.cookie('profileImageUser', username, { ...cachedCookieOptions, httpOnly: false });
288
294
  // Record which method was used to login (client-visible badge)
289
295
  if (method && typeof method === 'string') {
290
296
  try {
@@ -561,9 +567,6 @@ router.post("/api/verify-2fa", TwoFALimit, csrfProtection, async (req, res) => {
561
567
  const shouldTrustDevice = trustDevice === true || trustDevice === 'true';
562
568
 
563
569
  try {
564
- // Use cached allowedApps from preAuthUser to avoid extra database join
565
- const cachedAllowedApps = req.session.preAuthUser?.allowedApps;
566
-
567
570
  const query = `SELECT tfa."TwoFASecret" FROM "TwoFA" tfa WHERE tfa."UserName" = $1`;
568
571
  const twoFAResult = await dblogin.query({ name: 'verify-2fa-secret', text: query, values: [username] });
569
572
 
@@ -574,7 +577,7 @@ router.post("/api/verify-2fa", TwoFALimit, csrfProtection, async (req, res) => {
574
577
  }
575
578
 
576
579
  const sharedSecret = twoFAResult.rows[0].TwoFASecret;
577
- const allowedApps = cachedAllowedApps;
580
+ const allowedApps = req.session.preAuthUser?.allowedApps;
578
581
  const tokenValidates = speakeasy.totp.verify({
579
582
  secret: sharedSecret,
580
583
  encoding: "base32",
@@ -771,6 +774,9 @@ router.post("/api/switch-session", LoginLimit, async (req, res) => {
771
774
 
772
775
  // Sync sessionId cookie and remember list
773
776
  res.cookie('fullName', fullName, { ...cachedCookieOptions, httpOnly: false });
777
+ const switchProfileForCookie = switchProfileImage && typeof switchProfileImage === 'string' ? switchProfileImage : 'default';
778
+ res.cookie('profileImageUrl', switchProfileForCookie, { ...cachedCookieOptions, httpOnly: false });
779
+ res.cookie('profileImageUser', row.UserName, { ...cachedCookieOptions, httpOnly: false });
774
780
  const encryptedSid = encryptSessionId(row.sid);
775
781
  if (encryptedSid) {
776
782
  res.cookie('sessionId', encryptedSid, cachedCookieOptions);
@@ -6,6 +6,8 @@ import rateLimit from 'express-rate-limit';
6
6
 
7
7
  const router = express.Router();
8
8
 
9
+ const isDbLogsEnabled = () => process.env.env === "dev" && process.env.dbLogs === "true";
10
+
9
11
  // Rate limiter for info/test routes
10
12
  const LogLimit = rateLimit({
11
13
  windowMs: 1 * 60 * 1000,
@@ -23,33 +25,65 @@ const LogLimit = rateLimit({
23
25
  // DB stats API (JSON)
24
26
  router.get(["/db.json"], LogLimit, async (req, res) => {
25
27
  try {
26
- const queryCount = typeof dblogin.getQueryCount === 'function' ? dblogin.getQueryCount() : null;
28
+ const isDev = isDbLogsEnabled();
27
29
  const queryLimit = Number(req.query.limit) || 50;
28
- const queryLog = typeof dblogin.getQueryLog === 'function' ? dblogin.getQueryLog({ limit: queryLimit }) : [];
29
- const resetRequested = req.query.reset === '1';
30
30
 
31
- if (resetRequested) {
32
- if (typeof dblogin.resetQueryCount === 'function') dblogin.resetQueryCount();
33
- if (typeof dblogin.resetQueryLog === 'function') dblogin.resetQueryLog();
31
+ if (!isDev) {
32
+ return res.status(403).json({
33
+ success: false,
34
+ message: "DB logs are disabled.",
35
+ isDev,
36
+ queryCount: 0,
37
+ queryLimit,
38
+ queryLog: []
39
+ });
34
40
  }
35
41
 
36
- return res.json({ queryCount, queryLimit, queryLog, resetDone: resetRequested });
42
+ const queryCount = typeof dblogin.getQueryCount === 'function' ? dblogin.getQueryCount() : null;
43
+ const queryLog = typeof dblogin.getQueryLog === 'function' ? dblogin.getQueryLog({ limit: queryLimit }) : [];
44
+
45
+ return res.json({ queryCount, queryLimit, queryLog, isDev });
37
46
  } catch (err) {
38
47
  console.error('[mbkauthe] /db.json route error:', err);
39
48
  return res.status(500).json({ success: false, message: 'Could not fetch DB stats.' });
40
49
  }
41
50
  });
42
51
 
52
+ // Dedicated reset API for DB logs and counters
53
+ router.post(["/db/reset"], LogLimit, async (req, res) => {
54
+ try {
55
+ const isDev = isDbLogsEnabled();
56
+ if (!isDev) {
57
+ return res.status(403).json({
58
+ success: false,
59
+ message: "DB logs are disabled.",
60
+ isDev
61
+ });
62
+ }
63
+
64
+ if (typeof dblogin.resetQueryCount === 'function') dblogin.resetQueryCount();
65
+ if (typeof dblogin.resetQueryLog === 'function') dblogin.resetQueryLog();
66
+
67
+ return res.json({ success: true, message: 'Query log and count have been reset.' });
68
+ } catch (err) {
69
+ console.error('[mbkauthe] /db/reset route error:', err);
70
+ return res.status(500).json({ success: false, message: 'Could not reset DB stats.' });
71
+ }
72
+ });
73
+
43
74
  // DB stats page (HTML)
44
75
  router.get(["/db"], LogLimit, async (req, res) => {
45
76
  try {
77
+ const isDev = isDbLogsEnabled();
46
78
  const queryLimit = Number(req.query.limit) || 50;
47
79
  const resetDone = req.query.resetDone === '1';
48
80
  return res.render('pages/dbLogs.handlebars', {
49
81
  layout: false,
50
82
  appName: mbkautheVar.APP_NAME,
51
83
  queryLimit,
52
- resetDone
84
+ resetDone,
85
+ isDev,
86
+ disabledMessage: isDev ? null : 'DB logs are disabled.'
53
87
  });
54
88
  } catch (err) {
55
89
  console.error('[mbkauthe] /db route error:', err);
@@ -6,7 +6,7 @@ import { renderError, renderPage } from "#response.js";
6
6
  import { authenticate, validateSession, validateSessionAndRole } from "../middleware/auth.js";
7
7
  import { ErrorCodes, ErrorMessages, createErrorResponse } from "../utils/errors.js";
8
8
  import { dblogin } from "#pool.js";
9
- import { clearSessionCookies, decryptSessionId } from "#cookies.js";
9
+ import { clearSessionCookies, decryptSessionId, cachedCookieOptions } from "#cookies.js";
10
10
  import { fileURLToPath } from "url";
11
11
  import path from "path";
12
12
  import fs from "fs";
@@ -91,8 +91,12 @@ router.get('/user/profilepic', async (req, res) => {
91
91
  }
92
92
 
93
93
  const username = req.session.user.username;
94
- const cacheKey = `profilepic_${username}`;
95
- let imageUrl = req.session[cacheKey];
94
+ let imageUrl = null;
95
+ const cookieUser = req.cookies?.profileImageUser;
96
+ const cookieImageUrl = req.cookies?.profileImageUrl;
97
+ if (cookieUser === username && typeof cookieImageUrl === 'string' && cookieImageUrl.length > 0) {
98
+ imageUrl = cookieImageUrl;
99
+ }
96
100
 
97
101
  // If not in cache, fetch from DB
98
102
  if (!imageUrl) {
@@ -107,7 +111,8 @@ router.get('/user/profilepic', async (req, res) => {
107
111
  } else {
108
112
  imageUrl = 'default';
109
113
  }
110
- req.session[cacheKey] = imageUrl;
114
+ res.cookie('profileImageUrl', imageUrl, { ...cachedCookieOptions, httpOnly: false });
115
+ res.cookie('profileImageUser', username, { ...cachedCookieOptions, httpOnly: false });
111
116
  }
112
117
 
113
118
  // Generate ETag based on username and image URL
@@ -137,7 +142,8 @@ router.get('/user/profilepic', async (req, res) => {
137
142
 
138
143
  if (!imageResponse.ok) {
139
144
  console.warn(`[mbkauthe] Failed to fetch profile pic from ${imageUrl}, status: ${imageResponse.status}`);
140
- req.session[cacheKey] = 'default';
145
+ res.cookie('profileImageUrl', 'default', { ...cachedCookieOptions, httpOnly: false });
146
+ res.cookie('profileImageUser', username, { ...cachedCookieOptions, httpOnly: false });
141
147
  return serveDefaultIcon();
142
148
  }
143
149
 
@@ -147,7 +153,8 @@ router.get('/user/profilepic', async (req, res) => {
147
153
  imageResponse.body.pipe(res);
148
154
  } catch (fetchErr) {
149
155
  console.error('[mbkauthe] Error fetching external profile picture:', fetchErr);
150
- req.session[cacheKey] = 'default';
156
+ res.cookie('profileImageUrl', 'default', { ...cachedCookieOptions, httpOnly: false });
157
+ res.cookie('profileImageUser', username, { ...cachedCookieOptions, httpOnly: false });
151
158
  return serveDefaultIcon();
152
159
  }
153
160
 
@@ -503,8 +510,7 @@ router.get(["/info", "/i"], LoginLimit, async (req, res) => {
503
510
  }
504
511
 
505
512
  try {
506
- res.render("pages/info_mbkauthe.handlebars", {
507
- layout: false,
513
+ renderPage(req, res, "pages/info_mbkauthe.handlebars", false, {
508
514
  mbkautheVar: safe_mbkautheVar,
509
515
  CurrentVersion: packageJson.version,
510
516
  APP_VERSION: appVersion,
@@ -0,0 +1,247 @@
1
+ import path from "path";
2
+ import { AsyncLocalStorage } from "async_hooks";
3
+
4
+ const isDev = process.env.env === "dev" && process.env.dbLogs === "true";
5
+ const requestContext = isDev ? new AsyncLocalStorage() : null;
6
+
7
+ export const runWithRequestContext = (req, fn) => {
8
+ if (!isDev || !requestContext) return fn();
9
+ return requestContext.run({ req }, fn);
10
+ };
11
+
12
+ export const getRequestContext = () => {
13
+ if (!isDev || !requestContext) return undefined;
14
+ return requestContext.getStore();
15
+ };
16
+
17
+ export const attachDevQueryLogger = (pool) => {
18
+ if (!isDev || !pool || pool.__mbkQueryLoggerInstalled) {
19
+ return;
20
+ }
21
+
22
+ pool.__mbkQueryLoggerInstalled = true;
23
+
24
+ // Simple counter for all DB requests made via this pool. This is intentionally lightweight.
25
+ let dbQueryCount = 0;
26
+ const dbQueryLog = [];
27
+ const MAX_QUERY_LOG_ENTRIES = 1000;
28
+
29
+ const originalQuery = pool.query.bind(pool);
30
+
31
+ const safeValue = (value, depth = 0, seen = new WeakSet()) => {
32
+ if (value == null) return value;
33
+ if (typeof value === "string") {
34
+ return value.length > 300 ? `${value.slice(0, 300)}...` : value;
35
+ }
36
+ if (typeof value === "number" || typeof value === "boolean") return value;
37
+ if (typeof value === "bigint") return value.toString();
38
+ if (value instanceof Date) return value.toISOString();
39
+ if (Buffer.isBuffer(value)) return `[buffer:${value.length}]`;
40
+
41
+ if (Array.isArray(value)) {
42
+ if (depth >= 4) return `[array:${value.length}]`;
43
+ const sample = value.slice(0, 8).map((v) => safeValue(v, depth + 1, seen));
44
+ if (value.length > 8) sample.push(`...(${value.length - 8} more)`);
45
+ return sample;
46
+ }
47
+
48
+ if (typeof value === "object") {
49
+ if (seen.has(value)) return "[circular]";
50
+ seen.add(value);
51
+
52
+ const keys = Object.keys(value);
53
+ if (depth >= 4) {
54
+ const head = keys.slice(0, 5).join(", ");
55
+ return keys.length > 5 ? `[object:${head}, ...]` : `[object:${head}]`;
56
+ }
57
+
58
+ const out = {};
59
+ const entries = Object.entries(value).slice(0, 20);
60
+ for (const [k, v] of entries) {
61
+ out[k] = safeValue(v, depth + 1, seen);
62
+ }
63
+ if (keys.length > 20) {
64
+ out.__truncated = `${keys.length - 20} more keys`;
65
+ }
66
+
67
+ seen.delete(value);
68
+ return out;
69
+ }
70
+
71
+ return String(value);
72
+ };
73
+
74
+ const buildReturnValue = (result) => {
75
+ if (!result || typeof result !== "object") return undefined;
76
+
77
+ const returnValue = {
78
+ command: result.command || undefined,
79
+ rowCount: typeof result.rowCount === "number" ? result.rowCount : undefined,
80
+ };
81
+
82
+ if (Array.isArray(result.rows)) {
83
+ const previewSize = 3;
84
+ returnValue.returnedRows = result.rows.length;
85
+ returnValue.rowsPreview = result.rows.slice(0, previewSize).map((row) => safeValue(row));
86
+ if (result.rows.length > previewSize) {
87
+ returnValue.rowsTruncated = true;
88
+ }
89
+ }
90
+
91
+ return returnValue;
92
+ };
93
+
94
+ pool.query = (...args) => {
95
+ dbQueryCount++;
96
+
97
+ // `pg` supports (text, values, callback) or (config, callback).
98
+ let queryText = "";
99
+ let queryName = "";
100
+ let queryValues;
101
+ try {
102
+ if (typeof args[0] === "string") {
103
+ queryText = args[0];
104
+ queryValues = Array.isArray(args[1]) ? args[1] : undefined;
105
+ } else if (args[0] && typeof args[0] === "object") {
106
+ queryText = args[0].text || "";
107
+ queryName = args[0].name || "";
108
+ queryValues = Array.isArray(args[0].values) ? args[0].values : undefined;
109
+ }
110
+ } catch {
111
+ queryText = "";
112
+ }
113
+
114
+ if (!queryText) {
115
+ return originalQuery(...args);
116
+ }
117
+
118
+ const startTime = process.hrtime.bigint();
119
+ const toWorkspacePath = (filePath) => {
120
+ const rel = path.relative(process.cwd(), filePath) || filePath;
121
+ return rel.replace(/\\/g, "/");
122
+ };
123
+
124
+ const buildCallsite = () => {
125
+ try {
126
+ const stack = new Error().stack || "";
127
+ const lines = stack.split("\n").map((l) => l.trim());
128
+ const frame = lines.find(
129
+ (line) =>
130
+ line.startsWith("at ") &&
131
+ !line.includes("/lib/utils/dbQueryLogger.js") &&
132
+ !line.includes("node:internal") &&
133
+ !line.includes("internal/process")
134
+ );
135
+
136
+ if (!frame) return null;
137
+
138
+ const withFunc = /^at\s+([^\s(]+)\s+\((.+):([0-9]+):([0-9]+)\)$/.exec(frame);
139
+ const noFunc = /^at\s+(.+):([0-9]+):([0-9]+)$/.exec(frame);
140
+
141
+ if (withFunc) {
142
+ return {
143
+ function: withFunc[1],
144
+ file: toWorkspacePath(withFunc[2]),
145
+ line: Number(withFunc[3]),
146
+ column: Number(withFunc[4]),
147
+ };
148
+ }
149
+
150
+ if (noFunc) {
151
+ return {
152
+ function: null,
153
+ file: toWorkspacePath(noFunc[1]),
154
+ line: Number(noFunc[2]),
155
+ column: Number(noFunc[3]),
156
+ };
157
+ }
158
+ } catch {
159
+ return null;
160
+ }
161
+ return null;
162
+ };
163
+
164
+ const buildRequestContext = () => {
165
+ const store = getRequestContext();
166
+ const req = store?.req;
167
+ if (!req) return null;
168
+
169
+ const user = req.session?.user || null;
170
+ return {
171
+ method: req.method,
172
+ url: req.originalUrl || req.url,
173
+ ip: req.ip,
174
+ userId: user?.id || null,
175
+ username: user?.username || null,
176
+ };
177
+ };
178
+
179
+ const callsiteSnapshot = buildCallsite();
180
+
181
+ const recordLog = (success, error, result) => {
182
+ const durationMs = Number(process.hrtime.bigint() - startTime) / 1_000_000;
183
+ const request = buildRequestContext();
184
+ const returnValue = buildReturnValue(result);
185
+
186
+ dbQueryLog.push({
187
+ time: new Date().toISOString(),
188
+ query: queryText,
189
+ name: queryName || undefined,
190
+ values: queryValues,
191
+ durationMs,
192
+ success,
193
+ error: error ? { message: error.message, code: error.code } : undefined,
194
+ returnValue,
195
+ request,
196
+ pool: {
197
+ total: pool.totalCount,
198
+ idle: pool.idleCount,
199
+ waiting: pool.waitingCount,
200
+ },
201
+ callsite: callsiteSnapshot,
202
+ });
203
+
204
+ if (dbQueryLog.length > MAX_QUERY_LOG_ENTRIES) {
205
+ dbQueryLog.shift();
206
+ }
207
+ };
208
+
209
+ try {
210
+ const result = originalQuery(...args);
211
+ if (result && typeof result.then === "function") {
212
+ return result
213
+ .then((res) => {
214
+ recordLog(true, null, res);
215
+ return res;
216
+ })
217
+ .catch((err) => {
218
+ recordLog(false, err);
219
+ throw err;
220
+ });
221
+ }
222
+
223
+ recordLog(true, null, result);
224
+ return result;
225
+ } catch (err) {
226
+ recordLog(false, err);
227
+ throw err;
228
+ }
229
+ };
230
+
231
+ pool.getQueryCount = () => dbQueryCount;
232
+ pool.resetQueryCount = () => {
233
+ dbQueryCount = 0;
234
+ };
235
+
236
+ pool.getQueryLog = (options = {}) => {
237
+ const { limit } = options;
238
+ if (typeof limit === "number") {
239
+ return dbQueryLog.slice(-limit);
240
+ }
241
+ return [...dbQueryLog];
242
+ };
243
+
244
+ pool.resetQueryLog = () => {
245
+ dbQueryLog.length = 0;
246
+ };
247
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mbkauthe",
3
- "version": "4.7.1",
3
+ "version": "4.7.2",
4
4
  "description": "MBKTech's reusable authentication system for Node.js applications.",
5
5
  "main": "index.js",
6
6
  "type": "module",