mbkauthe 4.8.2 → 4.8.4
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 +25 -6
- package/docs/api.md +45 -27
- package/docs/auth-flows.mmd +9 -9
- package/docs/auth-processes.mmd +71 -0
- package/docs/db.md +36 -276
- package/docs/db.sql +6 -9
- package/docs/env.md +2 -8
- package/docs/error-messages.md +3 -3
- package/docs/images/auth-flows.svg +1 -1
- package/docs/images/auth-process.svg +1 -0
- package/index.d.ts +1 -2
- package/index.js +1 -1
- package/lib/config/cookies.js +6 -6
- package/lib/config/index.js +4 -10
- package/lib/createTable.js +5 -5
- package/lib/middleware/auth.js +30 -20
- package/lib/middleware/index.js +2 -2
- package/lib/pool.js +2 -2
- package/lib/routes/auth.js +26 -38
- package/lib/routes/dbLogs.js +3 -3
- package/lib/routes/misc.js +22 -22
- package/lib/utils/timingSafeToken.js +35 -0
- package/package.json +1 -1
- package/public/main.js +3 -3
- package/views/Error/dError.handlebars +1 -1
- package/views/pages/2fa.handlebars +1 -0
- package/views/pages/accountSwitch.handlebars +4 -4
- package/views/pages/loginmbkauthe.handlebars +2 -1
- package/views/profilemenu.handlebars +2 -2
package/lib/routes/auth.js
CHANGED
|
@@ -93,7 +93,7 @@ async function invalidateDbSession(sessionId) {
|
|
|
93
93
|
try {
|
|
94
94
|
await dblogin.query({ name: 'invalidate-app-session', text: 'DELETE FROM "Sessions" WHERE id = $1', values: [sessionId] });
|
|
95
95
|
} catch (err) {
|
|
96
|
-
console.error(
|
|
96
|
+
console.error(`[mbkauthe] Error invalidating session:`, err);
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
@@ -153,7 +153,7 @@ export async function checkTrustedDevice(req, username) {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
} catch (deviceErr) {
|
|
156
|
-
console.error(
|
|
156
|
+
console.error(`[mbkauthe] Error checking trusted device:`, deviceErr);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
return null;
|
|
@@ -239,7 +239,7 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
|
|
|
239
239
|
await dbClient.query('COMMIT');
|
|
240
240
|
} catch (err) {
|
|
241
241
|
await dbClient.query('ROLLBACK').catch(() => {});
|
|
242
|
-
console.error(
|
|
242
|
+
console.error(`[mbkauthe] Error enforcing session limit or inserting app session:`, err);
|
|
243
243
|
throw err;
|
|
244
244
|
} finally {
|
|
245
245
|
dbClient.release();
|
|
@@ -255,7 +255,7 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
|
|
|
255
255
|
});
|
|
256
256
|
profileRow = profUpdateRes.rows?.[0] || null;
|
|
257
257
|
} catch (profileUpdateErr) {
|
|
258
|
-
console.error(
|
|
258
|
+
console.error(`[mbkauthe] Error updating last_login/returning profile:`, profileUpdateErr);
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
req.session.user = {
|
|
@@ -287,7 +287,7 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
|
|
|
287
287
|
if (profileResult.rows[0].Image && profileResult.rows[0].Image.trim() !== '') loginProfileImage = profileResult.rows[0].Image;
|
|
288
288
|
}
|
|
289
289
|
} catch (profileErr) {
|
|
290
|
-
console.error(
|
|
290
|
+
console.error(`[mbkauthe] Error fetching FullName/Image for user:`, profileErr);
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
|
|
@@ -297,7 +297,7 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
|
|
|
297
297
|
|
|
298
298
|
req.session.save(async (err) => {
|
|
299
299
|
if (err) {
|
|
300
|
-
console.error(
|
|
300
|
+
console.error(`[mbkauthe] Session save error:`, err);
|
|
301
301
|
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
302
302
|
}
|
|
303
303
|
|
|
@@ -316,7 +316,7 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
|
|
|
316
316
|
try {
|
|
317
317
|
res.cookie('lastLoginMethod', method, { ...cachedCookieOptions, httpOnly: false });
|
|
318
318
|
} catch (err) {
|
|
319
|
-
console.error(
|
|
319
|
+
console.error(`[mbkauthe] Failed to set lastLoginMethod cookie:`, err);
|
|
320
320
|
}
|
|
321
321
|
}
|
|
322
322
|
|
|
@@ -351,7 +351,7 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
|
|
|
351
351
|
res.cookie("device_token", deviceToken, getDeviceTokenCookieOptions());
|
|
352
352
|
console.log(`[mbkauthe] Trusted device token created for user: ${username}`);
|
|
353
353
|
} catch (deviceErr) {
|
|
354
|
-
console.error(
|
|
354
|
+
console.error(`[mbkauthe] Error creating trusted device:`, deviceErr);
|
|
355
355
|
// Continue with login even if device trust fails
|
|
356
356
|
}
|
|
357
357
|
}
|
|
@@ -371,14 +371,14 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
|
|
|
371
371
|
res.status(200).json(responsePayload);
|
|
372
372
|
});
|
|
373
373
|
} catch (err) {
|
|
374
|
-
console.error(
|
|
374
|
+
console.error(`[mbkauthe] Error during login completion:`, err);
|
|
375
375
|
res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
376
376
|
}
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
// POST /mbkauthe/api/login
|
|
380
380
|
router.post("/api/login", LoginLimit, async (req, res) => {
|
|
381
|
-
console.log(
|
|
381
|
+
console.log(`[mbkauthe] Login request received`);
|
|
382
382
|
|
|
383
383
|
const { username, password, redirect } = req.body;
|
|
384
384
|
|
|
@@ -415,7 +415,7 @@ router.post("/api/login", LoginLimit, async (req, res) => {
|
|
|
415
415
|
try {
|
|
416
416
|
// Combined query: fetch user data and 2FA status in one query
|
|
417
417
|
const userQuery = `
|
|
418
|
-
SELECT u.id, u."UserName", u."
|
|
418
|
+
SELECT u.id, u."UserName", u."PasswordEnc", u."Active", u."Role", u."AllowedApps",
|
|
419
419
|
tfa."TwoFAStatus"
|
|
420
420
|
FROM "Users" u
|
|
421
421
|
LEFT JOIN "TwoFA" tfa ON u."UserName" = tfa."UserName"
|
|
@@ -432,25 +432,13 @@ router.post("/api/login", LoginLimit, async (req, res) => {
|
|
|
432
432
|
|
|
433
433
|
const user = userResult.rows[0];
|
|
434
434
|
|
|
435
|
-
//
|
|
436
|
-
if (!user.Password && !user.PasswordEnc) {
|
|
437
|
-
console.error("[mbkauthe] User account has no password set");
|
|
438
|
-
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Check password based on EncPass configuration - ALWAYS validate password first
|
|
435
|
+
// Password verification (hash-only). We never read/compare plaintext passwords.
|
|
442
436
|
let passwordMatches = false;
|
|
443
|
-
if (
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
449
|
-
} else {
|
|
450
|
-
// Use raw password comparison
|
|
451
|
-
if (user.Password) {
|
|
452
|
-
passwordMatches = user.Password === password;
|
|
453
|
-
}
|
|
437
|
+
if (user.PasswordEnc) {
|
|
438
|
+
const hashedInputPassword = hashPassword(password, user.UserName);
|
|
439
|
+
const stored = Buffer.from(String(user.PasswordEnc), 'utf8');
|
|
440
|
+
const computed = Buffer.from(String(hashedInputPassword), 'utf8');
|
|
441
|
+
passwordMatches = stored.length === computed.length && crypto.timingSafeEqual(stored, computed);
|
|
454
442
|
}
|
|
455
443
|
|
|
456
444
|
if (!passwordMatches) {
|
|
@@ -523,7 +511,7 @@ router.post("/api/login", LoginLimit, async (req, res) => {
|
|
|
523
511
|
await completeLoginProcess(req, res, userForSession, requestedRedirect, false, 'password');
|
|
524
512
|
|
|
525
513
|
} catch (err) {
|
|
526
|
-
console.error(
|
|
514
|
+
console.error(`[mbkauthe] Error during login process:`, err);
|
|
527
515
|
res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
528
516
|
}
|
|
529
517
|
});
|
|
@@ -627,7 +615,7 @@ router.post("/api/verify-2fa", TwoFALimit, csrfProtection, async (req, res) => {
|
|
|
627
615
|
await completeLoginProcess(req, res, userForSession, redirectUrl, shouldTrustDevice, methodToUse);
|
|
628
616
|
|
|
629
617
|
} catch (err) {
|
|
630
|
-
console.error(
|
|
618
|
+
console.error(`[mbkauthe] Error during 2FA verification:`, err);
|
|
631
619
|
res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
632
620
|
}
|
|
633
621
|
});
|
|
@@ -660,7 +648,7 @@ router.post("/api/logout", LogoutLimit, async (req, res) => {
|
|
|
660
648
|
|
|
661
649
|
req.session.destroy((err) => {
|
|
662
650
|
if (err) {
|
|
663
|
-
console.error(
|
|
651
|
+
console.error(`[mbkauthe] Error destroying session:`, err);
|
|
664
652
|
return res.status(500).json({ success: false, message: "Logout failed" });
|
|
665
653
|
}
|
|
666
654
|
|
|
@@ -670,7 +658,7 @@ router.post("/api/logout", LogoutLimit, async (req, res) => {
|
|
|
670
658
|
res.status(200).json({ success: true, message: "Logout successful" });
|
|
671
659
|
});
|
|
672
660
|
} catch (err) {
|
|
673
|
-
console.error(
|
|
661
|
+
console.error(`[mbkauthe] Database query error during logout:`, err);
|
|
674
662
|
res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
675
663
|
}
|
|
676
664
|
} else {
|
|
@@ -716,7 +704,7 @@ router.get("/api/account-sessions", LoginLimit, async (req, res) => {
|
|
|
716
704
|
if (!acct.image && prof.rows[0].Image && prof.rows[0].Image.trim() !== '') image = prof.rows[0].Image;
|
|
717
705
|
}
|
|
718
706
|
} catch (profileErr) {
|
|
719
|
-
console.error(
|
|
707
|
+
console.error(`[mbkauthe] Error fetching fullname/image for account list:`, profileErr);
|
|
720
708
|
}
|
|
721
709
|
}
|
|
722
710
|
|
|
@@ -728,7 +716,7 @@ router.get("/api/account-sessions", LoginLimit, async (req, res) => {
|
|
|
728
716
|
isCurrent: currentSessionId && row.sid === currentSessionId
|
|
729
717
|
});
|
|
730
718
|
} catch (err) {
|
|
731
|
-
console.error(
|
|
719
|
+
console.error(`[mbkauthe] Error validating remembered account:`, err);
|
|
732
720
|
}
|
|
733
721
|
}
|
|
734
722
|
|
|
@@ -770,7 +758,7 @@ router.post("/api/switch-session", LoginLimit, async (req, res) => {
|
|
|
770
758
|
if (prof.rows[0].Image && prof.rows[0].Image.trim() !== '') switchProfileImage = prof.rows[0].Image;
|
|
771
759
|
}
|
|
772
760
|
} catch (profileErr) {
|
|
773
|
-
console.error(
|
|
761
|
+
console.error(`[mbkauthe] Error fetching fullname/image during switch:`, profileErr);
|
|
774
762
|
}
|
|
775
763
|
|
|
776
764
|
// Regenerate session to avoid fixation
|
|
@@ -814,7 +802,7 @@ router.post("/api/switch-session", LoginLimit, async (req, res) => {
|
|
|
814
802
|
redirect: safeRedirect
|
|
815
803
|
});
|
|
816
804
|
} catch (err) {
|
|
817
|
-
console.error(
|
|
805
|
+
console.error(`[mbkauthe] Error during session switch:`, err);
|
|
818
806
|
return res.status(500).json(createErrorResponse(500, ErrorCodes.INTERNAL_SERVER_ERROR));
|
|
819
807
|
}
|
|
820
808
|
});
|
|
@@ -846,7 +834,7 @@ router.post("/api/logout-all", LoginLimit, async (req, res) => {
|
|
|
846
834
|
|
|
847
835
|
return res.json({ success: true, message: 'All accounts logged out' });
|
|
848
836
|
} catch (err) {
|
|
849
|
-
console.error(
|
|
837
|
+
console.error(`[mbkauthe] Error during logout-all:`, err);
|
|
850
838
|
return res.status(500).json(createErrorResponse(500, ErrorCodes.INTERNAL_SERVER_ERROR));
|
|
851
839
|
}
|
|
852
840
|
});
|
package/lib/routes/dbLogs.js
CHANGED
|
@@ -45,7 +45,7 @@ router.get(["/db.json"], LogLimit, async (req, res) => {
|
|
|
45
45
|
|
|
46
46
|
return res.json({ queryCount, queryLimit, queryLog, isDev });
|
|
47
47
|
} catch (err) {
|
|
48
|
-
console.error(
|
|
48
|
+
console.error(`[mbkauthe] /db.json route error:`, err);
|
|
49
49
|
return res.status(500).json({ success: false, message: 'Could not fetch DB stats.' });
|
|
50
50
|
}
|
|
51
51
|
});
|
|
@@ -70,7 +70,7 @@ router.post(["/db/reset"], LogLimit, async (req, res) => {
|
|
|
70
70
|
|
|
71
71
|
return res.json({ success: true, message: 'Query log and count have been reset.' });
|
|
72
72
|
} catch (err) {
|
|
73
|
-
console.error(
|
|
73
|
+
console.error(`[mbkauthe] /db/reset route error:`, err);
|
|
74
74
|
return res.status(500).json({ success: false, message: 'Could not reset DB stats.' });
|
|
75
75
|
}
|
|
76
76
|
});
|
|
@@ -90,7 +90,7 @@ router.get(["/db"], LogLimit, async (req, res) => {
|
|
|
90
90
|
disabledMessage: isDev ? null : 'DB logs are disabled.'
|
|
91
91
|
});
|
|
92
92
|
} catch (err) {
|
|
93
|
-
console.error(
|
|
93
|
+
console.error(`[mbkauthe] /db route error:`, err);
|
|
94
94
|
return renderError(res, req, {
|
|
95
95
|
layout: false,
|
|
96
96
|
code: 500,
|
package/lib/routes/misc.js
CHANGED
|
@@ -3,7 +3,7 @@ import fetch from 'node-fetch';
|
|
|
3
3
|
import rateLimit from 'express-rate-limit';
|
|
4
4
|
import { mbkautheVar, packageJson, appVersion } from "#config.js";
|
|
5
5
|
import { renderError, renderPage } from "#response.js";
|
|
6
|
-
import { authenticate,
|
|
6
|
+
import { authenticate, sessVal, sessRole } from "../middleware/auth.js";
|
|
7
7
|
import { ErrorCodes, ErrorMessages, createErrorResponse } from "../utils/errors.js";
|
|
8
8
|
import { dblogin } from "#pool.js";
|
|
9
9
|
import { clearSessionCookies, decryptSessionId, cachedCookieOptions } from "#cookies.js";
|
|
@@ -60,7 +60,7 @@ router.get("/bg.webp", (req, res) => {
|
|
|
60
60
|
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
|
61
61
|
const stream = fs.createReadStream(imgPath);
|
|
62
62
|
stream.on('error', (err) => {
|
|
63
|
-
console.error(
|
|
63
|
+
console.error(`[mbkauthe] Error streaming bg.webp:`, err);
|
|
64
64
|
res.status(404).send('Image not found');
|
|
65
65
|
});
|
|
66
66
|
stream.pipe(res);
|
|
@@ -78,7 +78,7 @@ router.get('/user/profilepic', async (req, res) => {
|
|
|
78
78
|
}
|
|
79
79
|
const stream = fs.createReadStream(iconPath);
|
|
80
80
|
stream.on('error', (err) => {
|
|
81
|
-
console.error(
|
|
81
|
+
console.error(`[mbkauthe] Error streaming icon.svg:`, err);
|
|
82
82
|
res.status(404).send('Icon not found');
|
|
83
83
|
});
|
|
84
84
|
stream.pipe(res);
|
|
@@ -152,21 +152,21 @@ router.get('/user/profilepic', async (req, res) => {
|
|
|
152
152
|
|
|
153
153
|
imageResponse.body.pipe(res);
|
|
154
154
|
} catch (fetchErr) {
|
|
155
|
-
console.error(
|
|
155
|
+
console.error(`[mbkauthe] Error fetching external profile picture:`, fetchErr);
|
|
156
156
|
res.cookie('profileImageUrl', 'default', { ...cachedCookieOptions, httpOnly: false });
|
|
157
157
|
res.cookie('profileImageUser', username, { ...cachedCookieOptions, httpOnly: false });
|
|
158
158
|
return serveDefaultIcon();
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
} catch (err) {
|
|
162
|
-
console.error(
|
|
162
|
+
console.error(`[mbkauthe] Error fetching profile picture:`, err);
|
|
163
163
|
return serveDefaultIcon();
|
|
164
164
|
}
|
|
165
165
|
});
|
|
166
166
|
|
|
167
167
|
if (process.env.env === 'dev') {
|
|
168
168
|
// Dev-only diagnostic endpoint to verify SuperAdmin role enforcement
|
|
169
|
-
router.get(['/validate-superadmin'],
|
|
169
|
+
router.get(['/validate-superadmin'], sessRole("SuperAdmin"), LoginLimit, async (req, res) => {
|
|
170
170
|
try {
|
|
171
171
|
const user = req.session?.user || null;
|
|
172
172
|
return res.json({
|
|
@@ -180,14 +180,14 @@ if (process.env.env === 'dev') {
|
|
|
180
180
|
} : null
|
|
181
181
|
});
|
|
182
182
|
} catch (err) {
|
|
183
|
-
console.error(
|
|
183
|
+
console.error(`[mbkauthe] debug validate-superadmin error:`, err);
|
|
184
184
|
return res.status(500).json(createErrorResponse(500, ErrorCodes.INTERNAL_SERVER_ERROR));
|
|
185
185
|
}
|
|
186
186
|
});
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
// Test route
|
|
190
|
-
router.get(['/test', '/'],
|
|
190
|
+
router.get(['/test', '/'], sessVal, LoginLimit, async (req, res) => {
|
|
191
191
|
const { username, fullname, role, id, sessionId, allowedApps } = req.session.user;
|
|
192
192
|
|
|
193
193
|
const sessionExpiry = req.session.cookie?.expires
|
|
@@ -208,7 +208,7 @@ router.get(['/test', '/'], validateSession, LoginLimit, async (req, res) => {
|
|
|
208
208
|
});
|
|
209
209
|
});
|
|
210
210
|
|
|
211
|
-
router.post('/test',
|
|
211
|
+
router.post('/test', sessVal, LoginLimit, async (req, res) => {
|
|
212
212
|
if (req.session?.user) {
|
|
213
213
|
return res.json({ success: true, message: "You are logged in" });
|
|
214
214
|
}
|
|
@@ -266,7 +266,7 @@ router.get('/api/checkSession', LoginLimit, async (req, res) => {
|
|
|
266
266
|
|
|
267
267
|
return res.status(200).json({ sessionValid: true, expiry });
|
|
268
268
|
} catch (err) {
|
|
269
|
-
console.error(
|
|
269
|
+
console.error(`[mbkauthe] checkSession error:`, err);
|
|
270
270
|
return res.status(200).json({ sessionValid: false, expiry: null });
|
|
271
271
|
}
|
|
272
272
|
});
|
|
@@ -342,7 +342,7 @@ router.post('/api/checkSession', LoginLimit, async (req, res) => {
|
|
|
342
342
|
const expiry = row.expires_at ? new Date(row.expires_at).toISOString() : null;
|
|
343
343
|
return res.status(200).json({ sessionValid: true, expiry });
|
|
344
344
|
} catch (err) {
|
|
345
|
-
console.error(
|
|
345
|
+
console.error(`[mbkauthe] checkSession (body) error:`, err);
|
|
346
346
|
return res.status(200).json({ sessionValid: false, expiry: null });
|
|
347
347
|
}
|
|
348
348
|
});
|
|
@@ -374,7 +374,7 @@ router.post('/api/verifySession', LoginLimit, async (req, res) => {
|
|
|
374
374
|
const expiry = row.expires_at ? new Date(row.expires_at).toISOString() : null;
|
|
375
375
|
return res.status(200).json({ valid: true, expiry, username: row.UserName, role: row.Role });
|
|
376
376
|
} catch (err) {
|
|
377
|
-
console.error(
|
|
377
|
+
console.error(`[mbkauthe] verifySession error:`, err);
|
|
378
378
|
return res.status(200).json({ valid: false, expiry: null });
|
|
379
379
|
}
|
|
380
380
|
});
|
|
@@ -465,7 +465,7 @@ router.get("/ErrorCode", (req, res) => {
|
|
|
465
465
|
errorCategories: categoriesWithErrors
|
|
466
466
|
});
|
|
467
467
|
} catch (err) {
|
|
468
|
-
console.error(
|
|
468
|
+
console.error(`[mbkauthe] Error rendering error codes page:`, err);
|
|
469
469
|
return renderError(res, req, {
|
|
470
470
|
layout: false,
|
|
471
471
|
code: 500,
|
|
@@ -488,7 +488,7 @@ export async function getLatestVersion() {
|
|
|
488
488
|
const latestPackageJson = await response.json();
|
|
489
489
|
return typeof latestPackageJson.version === 'string' ? latestPackageJson.version : null;
|
|
490
490
|
} catch (error) {
|
|
491
|
-
console.error(
|
|
491
|
+
console.error(`[mbkauthe] Error fetching latest version from GitHub`, error);
|
|
492
492
|
return null;
|
|
493
493
|
}
|
|
494
494
|
}
|
|
@@ -503,7 +503,7 @@ export async function checkVersion() {
|
|
|
503
503
|
} else if (hasValidLatest) {
|
|
504
504
|
console.info(`[mbkauthe] Running latest version (${packageJson.version}).`);
|
|
505
505
|
} else {
|
|
506
|
-
console.info(
|
|
506
|
+
console.info(`[mbkauthe] Skipped version check warning: latest version unavailable.`);
|
|
507
507
|
}
|
|
508
508
|
} catch (error) {
|
|
509
509
|
console.warn(`[mbkauthe] Failed to check for updates: ${error.message}`);
|
|
@@ -520,7 +520,7 @@ router.get(["/info", "/i"], LoginLimit, async (req, res) => {
|
|
|
520
520
|
try {
|
|
521
521
|
latestVersion = await getLatestVersion();
|
|
522
522
|
} catch (err) {
|
|
523
|
-
console.error(
|
|
523
|
+
console.error(`[mbkauthe] Error fetching package-lock.json:`, err);
|
|
524
524
|
}
|
|
525
525
|
|
|
526
526
|
try {
|
|
@@ -531,7 +531,7 @@ router.get(["/info", "/i"], LoginLimit, async (req, res) => {
|
|
|
531
531
|
latestVersion
|
|
532
532
|
});
|
|
533
533
|
} catch (err) {
|
|
534
|
-
console.error(
|
|
534
|
+
console.error(`[mbkauthe] Error fetching version information:`, err);
|
|
535
535
|
res.status(500).send(`
|
|
536
536
|
<html>
|
|
537
537
|
<head>
|
|
@@ -551,13 +551,13 @@ router.get(["/info.json", "/i.json"], LoginLimit, async (req, res) => {
|
|
|
551
551
|
try {
|
|
552
552
|
latestVersion = await getLatestVersion();
|
|
553
553
|
} catch (err) {
|
|
554
|
-
console.error(
|
|
554
|
+
console.error(`[mbkauthe] Error fetching package-lock.json:`, err);
|
|
555
555
|
}
|
|
556
556
|
|
|
557
557
|
try {
|
|
558
558
|
res.json({ mbkautheVar: safe_mbkautheVar, CurrentVersion: packageJson.version, APP_VERSION: appVersion, latestVersion });
|
|
559
559
|
} catch (err) {
|
|
560
|
-
console.error(
|
|
560
|
+
console.error(`[mbkauthe] Error fetching version information:`, err);
|
|
561
561
|
res.status(500).json({ success: false, message: "Failed to fetch version information" });
|
|
562
562
|
}
|
|
563
563
|
});
|
|
@@ -579,20 +579,20 @@ router.post("/api/terminateAllSessions", AdminOperationLimit, authenticate(mbkau
|
|
|
579
579
|
|
|
580
580
|
req.session.destroy((err) => {
|
|
581
581
|
if (err) {
|
|
582
|
-
console.log(
|
|
582
|
+
console.log(`[mbkauthe] Error destroying session:`, err);
|
|
583
583
|
return res.status(500).json({ success: false, message: "Failed to terminate sessions" });
|
|
584
584
|
}
|
|
585
585
|
|
|
586
586
|
clearSessionCookies(res);
|
|
587
587
|
|
|
588
|
-
console.log(
|
|
588
|
+
console.log(`[mbkauthe] All sessions terminated successfully`);
|
|
589
589
|
res.status(200).json({
|
|
590
590
|
success: true,
|
|
591
591
|
message: "All sessions terminated successfully",
|
|
592
592
|
});
|
|
593
593
|
});
|
|
594
594
|
} catch (err) {
|
|
595
|
-
console.error(
|
|
595
|
+
console.error(`[mbkauthe] Database query error during session termination:`, err);
|
|
596
596
|
res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
597
597
|
}
|
|
598
598
|
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export function extractAuthorizationToken(authorizationHeader) {
|
|
4
|
+
if (typeof authorizationHeader !== "string") return "";
|
|
5
|
+
|
|
6
|
+
const raw = authorizationHeader.trim();
|
|
7
|
+
if (!raw) return "";
|
|
8
|
+
|
|
9
|
+
const bearerMatch = /^bearer\s+(.+)$/i.exec(raw);
|
|
10
|
+
if (bearerMatch) return bearerMatch[1].trim();
|
|
11
|
+
|
|
12
|
+
return raw;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function sha256Buffer(value) {
|
|
16
|
+
return createHash("sha256").update(value, "utf8").digest();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Constant-time comparison of two strings by hashing them first.
|
|
21
|
+
*
|
|
22
|
+
* Notes:
|
|
23
|
+
* - Always computes both hashes (32 bytes each) and uses timingSafeEqual.
|
|
24
|
+
* - Returns false when expectedToken is empty/unset.
|
|
25
|
+
*/
|
|
26
|
+
export function timingSafeTokenMatch(providedToken, expectedToken) {
|
|
27
|
+
const provided = typeof providedToken === "string" ? providedToken : "";
|
|
28
|
+
const expected = typeof expectedToken === "string" ? expectedToken : "";
|
|
29
|
+
|
|
30
|
+
const providedHash = sha256Buffer(provided);
|
|
31
|
+
const expectedHash = sha256Buffer(expected);
|
|
32
|
+
|
|
33
|
+
const matches = timingSafeEqual(providedHash, expectedHash);
|
|
34
|
+
return matches && expected.length > 0;
|
|
35
|
+
}
|
package/package.json
CHANGED
package/public/main.js
CHANGED
|
@@ -25,7 +25,7 @@ async function logout() {
|
|
|
25
25
|
alert(result.message);
|
|
26
26
|
}
|
|
27
27
|
} catch (error) {
|
|
28
|
-
console.error(
|
|
28
|
+
console.error(`[mbkauthe] Error during logout:`, error);
|
|
29
29
|
alert(`Logout failed: ${error.message}`);
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -67,7 +67,7 @@ async function selectiveCacheClear() {
|
|
|
67
67
|
window.location.reload();
|
|
68
68
|
|
|
69
69
|
} catch (error) {
|
|
70
|
-
console.error(
|
|
70
|
+
console.error(`[mbkauthe] selective cache clear failed:`, error);
|
|
71
71
|
window.location.reload();
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -86,7 +86,7 @@ function checkSession() {
|
|
|
86
86
|
window.location.reload(); // Reload the page to update the session status
|
|
87
87
|
}
|
|
88
88
|
})
|
|
89
|
-
.catch((error) => console.error(
|
|
89
|
+
.catch((error) => console.error(`[mbkauthe] Error checking session:`, error));
|
|
90
90
|
}
|
|
91
91
|
// Call validateSession every 2 minutes (120000 milliseconds)
|
|
92
92
|
// setInterval(checkSession, validateSessionInterval);
|
|
@@ -362,7 +362,7 @@
|
|
|
362
362
|
emptyStateLink.href = `/mbkauthe/login${encoded ? `?redirect=${encoded}` : ''}`;
|
|
363
363
|
}
|
|
364
364
|
} catch (err) {
|
|
365
|
-
console.error(
|
|
365
|
+
console.error(`[mbkauthe] Failed to set login hrefs with redirect query param:`, err);
|
|
366
366
|
}
|
|
367
367
|
})();
|
|
368
368
|
|
|
@@ -468,7 +468,7 @@
|
|
|
468
468
|
});
|
|
469
469
|
|
|
470
470
|
} catch (err) {
|
|
471
|
-
console.error(
|
|
471
|
+
console.error(`[mbkauthe] Failed to load accounts:`, err);
|
|
472
472
|
list.innerHTML = '<div class="empty-state">Failed to load accounts.</div>';
|
|
473
473
|
}
|
|
474
474
|
}
|
|
@@ -501,7 +501,7 @@
|
|
|
501
501
|
loadAccounts();
|
|
502
502
|
}
|
|
503
503
|
} catch (err) {
|
|
504
|
-
console.error(
|
|
504
|
+
console.error(`[mbkauthe] Switch failed:`, err);
|
|
505
505
|
showMessage('Could not switch account right now. Please try again.', 'Switch Account');
|
|
506
506
|
}
|
|
507
507
|
}
|
|
@@ -521,7 +521,7 @@
|
|
|
521
521
|
showMessage(data.message || 'Could not sign out all accounts', 'Sign out');
|
|
522
522
|
}
|
|
523
523
|
} catch (err) {
|
|
524
|
-
console.error(
|
|
524
|
+
console.error(`[mbkauthe] Sign-out-all failed:`, err);
|
|
525
525
|
showMessage('Could not sign out all accounts right now. Please try again.', 'Sign out');
|
|
526
526
|
}
|
|
527
527
|
}
|
|
@@ -287,6 +287,7 @@
|
|
|
287
287
|
const pageRedirect = new URLSearchParams(window.location.search).get('redirect');
|
|
288
288
|
fetch('/mbkauthe/api/login', {
|
|
289
289
|
method: 'POST',
|
|
290
|
+
credentials: 'include',
|
|
290
291
|
headers: {
|
|
291
292
|
'Content-Type': 'application/json'
|
|
292
293
|
},
|
|
@@ -326,7 +327,7 @@
|
|
|
326
327
|
.catch(error => {
|
|
327
328
|
loginButton.disabled = false;
|
|
328
329
|
loginButtonText.textContent = 'Login';
|
|
329
|
-
console.error(
|
|
330
|
+
console.error(`[mbkauthe] Error:`, error);
|
|
330
331
|
showMessage('An error occurred. Please try again.', 'Login Error');
|
|
331
332
|
});
|
|
332
333
|
});
|
|
@@ -277,7 +277,7 @@
|
|
|
277
277
|
alert(result.message || 'Logout failed. Please try again.');
|
|
278
278
|
}
|
|
279
279
|
} catch (error) {
|
|
280
|
-
console.error(
|
|
280
|
+
console.error(`[mbkauthe] Error during logout:`, error);
|
|
281
281
|
alert('Logout failed. Please try again.');
|
|
282
282
|
} finally {
|
|
283
283
|
logoutButton.disabled = false;
|
|
@@ -299,7 +299,7 @@
|
|
|
299
299
|
switchAccountLink.href = baseHref + encodeURIComponent(currentUrl);
|
|
300
300
|
} catch (err) {
|
|
301
301
|
// non-fatal, but log for debugging
|
|
302
|
-
console.error(
|
|
302
|
+
console.error(`[mbkauthe] Failed to set redirect URL for switch account link`, err);
|
|
303
303
|
}
|
|
304
304
|
})();
|
|
305
305
|
})();
|