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 +22 -1
- package/docs/api.md +146 -0
- package/docs/db.sql +4 -1
- package/docs/env.md +6 -0
- package/index.js +7 -2
- package/lib/main.js +13 -2
- package/lib/middleware/index.js +6 -1
- package/lib/pool.js +130 -5
- package/lib/routes/auth.js +3 -3
- package/lib/routes/dbLogs.js +67 -0
- package/lib/routes/misc.js +27 -60
- package/package.json +33 -4
- package/public/main.css +866 -0
- package/views/pages/dbLogs.handlebars +476 -0
- package/views/{test.handlebars → pages/test.handlebars} +4 -1
- package/views/sharedStyles.handlebars +1 -896
- package/views/versionInfo.handlebars +2 -2
- /package/views/{2fa.handlebars → pages/2fa.handlebars} +0 -0
- /package/views/{accountSwitch.handlebars → pages/accountSwitch.handlebars} +0 -0
- /package/views/{errorCodes.handlebars → pages/errorCodes.handlebars} +0 -0
- /package/views/{info_mbkauthe.handlebars → pages/info_mbkauthe.handlebars} +0 -0
- /package/views/{loginmbkauthe.handlebars → pages/loginmbkauthe.handlebars} +0 -0
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
|
-
|
|
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
|
});
|
package/lib/middleware/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/lib/routes/auth.js
CHANGED
|
@@ -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;
|