mbkauthe 4.6.2 → 4.7.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.
package/README.md CHANGED
@@ -30,6 +30,7 @@
30
30
  - Session fixation prevention
31
31
  - Dynamic profile picture routing with session caching
32
32
  - Modern responsive UI with desktop two-column layout
33
+ - Dev-only DB Query Monitor with callsite, timing, and request context
33
34
 
34
35
  ## 📦 Installation
35
36
 
@@ -46,7 +47,7 @@ Copy-Item .env.example .env
46
47
  ```
47
48
  See [docs/env.md](docs/env.md).
48
49
 
49
- **2. Set Up Database**
50
+ **2. Set Up Database**
50
51
  Run [docs/db.sql](docs/db.sql) to create tables and a default SuperAdmin (`support` / `12345678`). Change the password immediately. See [docs/db.md](docs/db.md).
51
52
 
52
53
  **3. Integrate with Express**
@@ -81,6 +82,14 @@ npm run dev
81
82
  - **Combined:** `validateSessionAndRole(['SuperAdmin', 'NormalUser'])`
82
83
  - **API Token Auth:** `authenticate(process.env.API_TOKEN)`
83
84
 
85
+ ## 🧰 Diagnostics (dev only)
86
+
87
+ These are only mounted when `process.env.env === "dev"`:
88
+
89
+ - **DB Query Monitor (HTML):** `/mbkauthe/db`
90
+ - **DB Query Monitor (JSON):** `/mbkauthe/db.json`
91
+ - **SuperAdmin check:** `/mbkauthe/validate-superadmin`
92
+
84
93
  ## 🔐 Security
85
94
 
86
95
  - Rate limiting, CSRF protection, secure cookies
@@ -102,6 +111,18 @@ Enable via `MBKAUTH_TWO_FA_ENABLE=true`. Trusted devices can skip 2FA for a set
102
111
  - **Custom Views:** `views/loginmbkauthe.handlebars`, `2fa.handlebars`, `Error/dError.handlebars`
103
112
  - **Database Access:** `import { dblogin } from 'mbkauthe'; const result = await dblogin.query('SELECT * FROM "Users"');`
104
113
 
114
+ ## 📄 API Reference
115
+
116
+ - Full endpoint list and details: [docs/api.md](docs/api.md)
117
+
118
+ ## 🧰 Diagnostics (dev only)
119
+
120
+ - **DB Query Monitor (HTML):** `/mbkauthe/db`
121
+ - **DB Query Monitor (JSON):** `/mbkauthe/db.json`
122
+ - **SuperAdmin check:** `/mbkauthe/debug/validate-superadmin`
123
+
124
+ These routes are only mounted when `process.env.env === "dev"`. They expose query timing, status/error, pool stats, request context, and callsite data for troubleshooting.
125
+
105
126
  ## 🚢 Deployment
106
127
 
107
128
  Checklist for production:
package/docs/api.md CHANGED
@@ -15,6 +15,8 @@ This document provides comprehensive API documentation for MBKAuthe authenticati
15
15
  - [Protected Endpoints](#protected-endpoints)
16
16
  - [OAuth Endpoints](#oauth-endpoints)
17
17
  - [Information Endpoints](#information-endpoints)
18
+ - [Diagnostics (Dev Only)](#diagnostics-dev-only)
19
+ - [Additional Endpoints](#additional-endpoints)
18
20
  - [Middleware Reference](#middleware-reference)
19
21
  - [Code Examples](#code-examples)
20
22
 
@@ -195,6 +197,150 @@ GET /mbkauthe/login?redirect=/dashboard
195
197
 
196
198
  ---
197
199
 
200
+ #### `GET /mbkauthe/2fa`
201
+
202
+ Renders the 2FA challenge page after a login that requires TOTP.
203
+
204
+ **Rate Limit:** 5 requests per minute
205
+
206
+ ---
207
+
208
+ #### `GET /mbkauthe/accounts`
209
+
210
+ Renders the account-switch page for remembered sessions on the device.
211
+
212
+ **Rate Limit:** 8 requests per minute
213
+
214
+ ---
215
+
216
+ #### `GET /mbkauthe/test`
217
+
218
+ Renders a test page for the current session context.
219
+
220
+ **Rate Limit:** 8 requests per minute
221
+
222
+ ---
223
+
224
+ #### `POST /mbkauthe/test`
225
+
226
+ Lightweight check to verify an authenticated session.
227
+
228
+ **Response:** `{ "success": true, "message": "You are logged in" }`
229
+
230
+ ---
231
+
232
+ ## Diagnostics (Dev Only)
233
+
234
+ These endpoints are only mounted when `process.env.env === "dev"`.
235
+
236
+ #### `GET /mbkauthe/db`
237
+
238
+ Renders the DB Query Monitor page. The UI fetches data from `/mbkauthe/db.json`.
239
+
240
+ **Query Parameters:**
241
+ - `limit` (optional) - number of most recent queries to show (default: 50)
242
+ - `resetDone` (optional) - UI notification flag used after reset
243
+
244
+ ---
245
+
246
+ #### `GET /mbkauthe/db.json`
247
+
248
+ Returns recent DB query diagnostics.
249
+
250
+ **Query Parameters:**
251
+ - `limit` (optional) - number of most recent queries to return (default: 50)
252
+ - `reset` (optional) - set to `1` to clear the query log and counter
253
+
254
+ **Response Body:**
255
+ ```json
256
+ {
257
+ "queryCount": 120,
258
+ "queryLimit": 50,
259
+ "resetDone": false,
260
+ "queryLog": [
261
+ {
262
+ "time": "2026-03-19T12:00:00.000Z",
263
+ "name": "login-get-user",
264
+ "query": "SELECT ...",
265
+ "values": ["user"],
266
+ "durationMs": 3.42,
267
+ "success": true,
268
+ "error": null,
269
+ "request": {
270
+ "method": "GET",
271
+ "url": "/mbkauthe/login",
272
+ "ip": "::1",
273
+ "userId": 1,
274
+ "username": "support"
275
+ },
276
+ "pool": {
277
+ "total": 2,
278
+ "idle": 1,
279
+ "waiting": 0
280
+ },
281
+ "callsite": {
282
+ "function": "validateSession",
283
+ "file": "lib/middleware/auth.js",
284
+ "line": 197,
285
+ "column": 30
286
+ }
287
+ }
288
+ ]
289
+ }
290
+ ```
291
+
292
+ ---
293
+
294
+ #### `GET /mbkauthe/validate-superadmin`
295
+
296
+ Validates that the current session has `SuperAdmin` role and returns a JSON summary.
297
+
298
+ ---
299
+
300
+ ## Additional Endpoints
301
+
302
+ The endpoints below are active in the router but are not fully expanded above. Use this list as a reference.
303
+
304
+ **Auth & Session:**
305
+
306
+ - `POST /mbkauthe/api/verify-2fa` - Verifies TOTP and completes login.
307
+ - `POST /mbkauthe/api/logout` - Logs out the current session.
308
+ - `GET /mbkauthe/api/account-sessions` - Lists remembered accounts for the current device.
309
+ - `POST /mbkauthe/api/switch-session` - Switches active session to another remembered account.
310
+ - `POST /mbkauthe/api/logout-all` - Logs out all remembered accounts on the device.
311
+
312
+ **Session Validation:**
313
+
314
+ - `GET /mbkauthe/api/checkSession` - Checks session validity (cookie-based).
315
+ - `POST /mbkauthe/api/checkSession` - Checks session validity by sessionId (body).
316
+ - `POST /mbkauthe/api/verifySession` - Returns session details by sessionId (body).
317
+
318
+ **OAuth:**
319
+
320
+ - `GET /mbkauthe/api/github/login` - Starts GitHub OAuth login flow.
321
+ - `GET /mbkauthe/api/github/login/callback` - GitHub OAuth callback.
322
+ - `GET /mbkauthe/api/google/login` - Starts Google OAuth login flow.
323
+ - `GET /mbkauthe/api/google/login/callback` - Google OAuth callback.
324
+
325
+ **Info & UI:**
326
+
327
+ - `GET /mbkauthe/info` and `GET /mbkauthe/i` - Info page.
328
+ - `GET /mbkauthe/info.json` and `GET /mbkauthe/i.json` - Info page JSON.
329
+ - `GET /mbkauthe/ErrorCode` - Error codes page.
330
+ - `GET /mbkauthe/user/profilepic` - User profile picture proxy.
331
+
332
+ **Admin:**
333
+
334
+ - `POST /mbkauthe/api/terminateAllSessions` - Terminates all sessions (requires `Main_SECRET_TOKEN`).
335
+
336
+ **Static Assets:**
337
+
338
+ - `GET /mbkauthe/main.js`
339
+ - `GET /mbkauthe/main.css`
340
+ - `GET /mbkauthe/bg.webp`
341
+
342
+ ---
343
+
198
344
  #### `POST /mbkauthe/api/login`
199
345
 
200
346
  Authenticates a user and creates a session.
package/docs/db.sql CHANGED
@@ -138,7 +138,10 @@ INSERT INTO "Users" ("UserName", "Password", "Role", "Active", "HaveMailAccount"
138
138
  VALUES ('support', '12345678', 'SuperAdmin', true, false, 'Support User')
139
139
  ON CONFLICT ("UserName") DO NOTHING;
140
140
 
141
- SELECT * FROM "Users" WHERE "UserName" = 'support';
141
+ INSERT INTO "Users" ("UserName", "Password", "Role", "Active", "HaveMailAccount", "FullName")
142
+ VALUES ('admin', '12345678', 'SuperAdmin', true, false, 'Admin User')
143
+ ON CONFLICT ("UserName") DO NOTHING;
144
+
142
145
 
143
146
  -- API Tokens for persistent programmatic access
144
147
  CREATE TABLE IF NOT EXISTS "ApiTokens" (
package/docs/env.md CHANGED
@@ -81,6 +81,12 @@ This document describes the environment variables MBKAuth expects and keeps brie
81
81
  - Example: `"loginRedirectURL":"/dashboard"`
82
82
  - Required: No
83
83
 
84
+ - env
85
+ - Description: Development flag to enable diagnostics (DB query monitor, debug endpoints).
86
+ - Values: `dev` to enable; any other value disables.
87
+ - Example: `env=dev`
88
+ - Required: No
89
+
84
90
  - bucket
85
91
  - Description: Optional external storage bucket name or identifier used for static assets or third-party integrations.
86
92
  - Default: an empty string `""` (no bucket configured)
package/index.js CHANGED
@@ -5,6 +5,7 @@ import { engine } from "express-handlebars";
5
5
  import path from "path";
6
6
  import { fileURLToPath } from "url";
7
7
  import { renderError, renderPage } from "#response.js";
8
+ import { packageJson } from "#config.js";
8
9
 
9
10
  const __filename = fileURLToPath(import.meta.url);
10
11
  const __dirname = path.dirname(__filename);
@@ -43,6 +44,9 @@ app.engine("handlebars", engine({
43
44
  return []; // Return an empty array if obj is undefined, null, or not an object
44
45
  }
45
46
  return Object.entries(obj).map(([key, value]) => ({ key, value }));
47
+ },
48
+ cacheBuster: function () {
49
+ return packageJson.version;
46
50
  }
47
51
  }
48
52
 
@@ -59,7 +63,7 @@ if (process.env.test === "dev") {
59
63
  return res.redirect("/mbkauthe/");
60
64
  });
61
65
  app.get("/dev/2fa", (req, res) => {
62
- return renderPage(req, res, "2fa", {
66
+ return renderPage(req, res, "pages/2fa.handlebars", {
63
67
  layout: false,
64
68
  pagename: "Two-Factor Authentication",
65
69
  page: "/home"
@@ -109,7 +113,8 @@ export {
109
113
  sessionConfig,
110
114
  corsMiddleware,
111
115
  sessionRestorationMiddleware,
112
- sessionCookieSyncMiddleware
116
+ sessionCookieSyncMiddleware,
117
+ requestContextMiddleware
113
118
  } from "./lib/middleware/index.js";
114
119
  export { validateTokenScope } from "./lib/middleware/scopeValidator.js";
115
120
  export { renderError, getUserContext, renderPage, proxycall } from "#response.js";
package/lib/main.js CHANGED
@@ -6,11 +6,13 @@ import {
6
6
  sessionConfig,
7
7
  corsMiddleware,
8
8
  sessionRestorationMiddleware,
9
- sessionCookieSyncMiddleware
9
+ sessionCookieSyncMiddleware,
10
+ requestContextMiddleware
10
11
  } from "./middleware/index.js";
11
12
  import authRoutes from "./routes/auth.js";
12
13
  import oauthRoutes from "./routes/oauth.js";
13
14
  import miscRoutes from "./routes/misc.js";
15
+ import dbLogsRoutes from "./routes/dbLogs.js";
14
16
  import { fileURLToPath } from "url";
15
17
  import path from "path";
16
18
 
@@ -45,6 +47,11 @@ router.use(session(sessionConfig));
45
47
  // Session restoration
46
48
  router.use(sessionRestorationMiddleware);
47
49
 
50
+ // Attach request context for DB query logging (dev only)
51
+ if (process.env.env === 'dev') {
52
+ router.use(requestContextMiddleware);
53
+ }
54
+
48
55
  // Initialize passport
49
56
  router.use(passport.initialize());
50
57
  router.use(passport.session());
@@ -57,6 +64,10 @@ router.use('/mbkauthe', authRoutes);
57
64
  router.use('/mbkauthe', oauthRoutes);
58
65
  router.use('/mbkauthe', miscRoutes);
59
66
 
67
+ if (process.env.env === 'dev') {
68
+ router.use('/mbkauthe', dbLogsRoutes);
69
+ }
70
+
60
71
  // Redirect shortcuts for login
61
72
  router.get(["/login", "/signin"], async (req, res) => {
62
73
  const queryParams = new URLSearchParams(req.query).toString();
@@ -64,7 +75,7 @@ router.get(["/login", "/signin"], async (req, res) => {
64
75
  return res.redirect(redirectUrl);
65
76
  });
66
77
 
67
- router.get(['/icon.svg',"/favicon.ico", "/icon.png"], (req, res) => {
78
+ router.get(['/icon.svg', "/favicon.ico", "/icon.png"], (req, res) => {
68
79
  res.setHeader('Cache-Control', 'public, max-age=31536000');
69
80
  res.sendFile(path.join(__dirname, '..', 'public', 'M.png'));
70
81
  });
@@ -1,7 +1,7 @@
1
1
  import session from "express-session";
2
2
  import pgSession from "connect-pg-simple";
3
3
  const PgSession = pgSession(session);
4
- import { dblogin } from "#pool.js";
4
+ import { dblogin, runWithRequestContext } from "#pool.js";
5
5
  import { mbkautheVar } from "#config.js";
6
6
  import { cachedCookieOptions, decryptSessionId, encryptSessionId } from "#cookies.js";
7
7
 
@@ -142,4 +142,9 @@ export function sessionCookieSyncMiddleware(req, res, next) {
142
142
  }
143
143
  }
144
144
  next();
145
+ }
146
+
147
+ // Request context middleware (used for DB query logging)
148
+ export function requestContextMiddleware(req, res, next) {
149
+ return runWithRequestContext(req, () => next());
145
150
  }
package/lib/pool.js CHANGED
@@ -21,7 +21,28 @@ const poolConfig = {
21
21
 
22
22
  export const dblogin = new Pool(poolConfig);
23
23
 
24
- if(process.env.env === 'dev') {
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
+
25
46
  // Simple counter for all DB requests made via this pool. This is intentionally
26
47
  // lightweight.
27
48
  let _dbQueryCount = 0;
@@ -36,29 +57,133 @@ if(process.env.env === 'dev') {
36
57
  // Track query text for debugging/metrics.
37
58
  // `pg` supports (text, values, callback) or (config, callback).
38
59
  let queryText = '';
60
+ let queryName = '';
61
+ let queryValues;
39
62
  try {
40
63
  if (typeof args[0] === 'string') {
41
64
  queryText = args[0];
65
+ queryValues = Array.isArray(args[1]) ? args[1] : undefined;
42
66
  } else if (args[0] && typeof args[0] === 'object') {
43
67
  queryText = args[0].text || '';
68
+ queryName = args[0].name || '';
69
+ queryValues = Array.isArray(args[0].values) ? args[0].values : undefined;
44
70
  }
45
71
  } catch {
46
72
  queryText = '';
47
73
  }
48
74
 
49
- if (queryText) {
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
+
50
145
  _dbQueryLog.push({
51
146
  time: new Date().toISOString(),
52
147
  query: queryText,
53
- values: Array.isArray(args[1]) ? args[1] : undefined
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
54
160
  });
55
161
 
56
162
  if (_dbQueryLog.length > _MAX_QUERY_LOG_ENTRIES) {
57
163
  _dbQueryLog.shift();
58
164
  }
59
- }
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
+ }
60
180
 
61
- return _origQuery(...args);
181
+ recordLog(true, null);
182
+ return result;
183
+ } catch (err) {
184
+ recordLog(false, err);
185
+ throw err;
186
+ }
62
187
  };
63
188
 
64
189
  // Public helpers
@@ -517,7 +517,7 @@ router.get("/2fa", csrfProtection, (req, res) => {
517
517
  redirectToUse = mbkautheVar.loginRedirectURL || '/dashboard';
518
518
  }
519
519
 
520
- res.render("2fa.handlebars", {
520
+ res.render("pages/2fa.handlebars", {
521
521
  layout: false,
522
522
  customURL: redirectToUse,
523
523
  csrfToken: req.csrfToken(),
@@ -828,7 +828,7 @@ router.post("/api/logout-all", LoginLimit, async (req, res) => {
828
828
  // GET /mbkauthe/login
829
829
  router.get("/login", LoginLimit, csrfProtection, (req, res) => {
830
830
  const lastLogin = req.cookies && typeof req.cookies.lastLoginMethod === 'string' ? req.cookies.lastLoginMethod : null;
831
- return res.render("loginmbkauthe.handlebars", {
831
+ return res.render("pages/loginmbkauthe.handlebars", {
832
832
  layout: false,
833
833
  githubLoginEnabled: mbkautheVar.GITHUB_LOGIN_ENABLED,
834
834
  googleLoginEnabled: mbkautheVar.GOOGLE_LOGIN_ENABLED,
@@ -853,7 +853,7 @@ router.get("/accounts", LoginLimit, csrfProtection, (req, res) => {
853
853
  ? redirectFromQuery
854
854
  : (mbkautheVar.loginRedirectURL || '/dashboard');
855
855
 
856
- return res.render("accountSwitch.handlebars", {
856
+ return res.render("pages/accountSwitch.handlebars", {
857
857
  layout: false,
858
858
  customURL: safeRedirect,
859
859
  version: packageJson.version,
@@ -0,0 +1,67 @@
1
+ import express from "express";
2
+ import { renderError } from "#response.js";
3
+ import { dblogin } from "#pool.js";
4
+ import { mbkautheVar } from "#config.js";
5
+ import rateLimit from 'express-rate-limit';
6
+
7
+ const router = express.Router();
8
+
9
+ // Rate limiter for info/test routes
10
+ const LogLimit = rateLimit({
11
+ windowMs: 1 * 60 * 1000,
12
+ max: 50,
13
+ message: { success: false, message: "Too many attempts, please try again later" },
14
+ skip: (req) => {
15
+ return !!req.session.user;
16
+ },
17
+ validate: {
18
+ trustProxy: false,
19
+ xForwardedForHeader: false
20
+ }
21
+ });
22
+
23
+ // DB stats API (JSON)
24
+ router.get(["/db.json"], LogLimit, async (req, res) => {
25
+ try {
26
+ const queryCount = typeof dblogin.getQueryCount === 'function' ? dblogin.getQueryCount() : null;
27
+ 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
+
31
+ if (resetRequested) {
32
+ if (typeof dblogin.resetQueryCount === 'function') dblogin.resetQueryCount();
33
+ if (typeof dblogin.resetQueryLog === 'function') dblogin.resetQueryLog();
34
+ }
35
+
36
+ return res.json({ queryCount, queryLimit, queryLog, resetDone: resetRequested });
37
+ } catch (err) {
38
+ console.error('[mbkauthe] /db.json route error:', err);
39
+ return res.status(500).json({ success: false, message: 'Could not fetch DB stats.' });
40
+ }
41
+ });
42
+
43
+ // DB stats page (HTML)
44
+ router.get(["/db"], LogLimit, async (req, res) => {
45
+ try {
46
+ const queryLimit = Number(req.query.limit) || 50;
47
+ const resetDone = req.query.resetDone === '1';
48
+ return res.render('pages/dbLogs.handlebars', {
49
+ layout: false,
50
+ appName: mbkautheVar.APP_NAME,
51
+ queryLimit,
52
+ resetDone
53
+ });
54
+ } catch (err) {
55
+ console.error('[mbkauthe] /db route error:', err);
56
+ return renderError(res, req, {
57
+ layout: false,
58
+ code: 500,
59
+ error: "Internal Server Error",
60
+ message: "Could not fetch DB stats.",
61
+ pagename: "DB Stats",
62
+ page: "/mbkauthe/info",
63
+ });
64
+ }
65
+ });
66
+
67
+ export default router;