mbkauthe 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/main.js
CHANGED
|
@@ -328,7 +328,7 @@ router.post("/mbkauthe/api/terminateAllSessions", authenticate(mbkautheVar.Main_
|
|
|
328
328
|
router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
329
329
|
console.log("[mbkauthe] Login request received");
|
|
330
330
|
|
|
331
|
-
const { username, password } = req.body;
|
|
331
|
+
const { username, password, redirect } = req.body;
|
|
332
332
|
|
|
333
333
|
// Input validation
|
|
334
334
|
if (!username || !password) {
|
|
@@ -401,7 +401,7 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
401
401
|
|
|
402
402
|
if (deviceUser.Role !== "SuperAdmin") {
|
|
403
403
|
const allowedApps = deviceUser.AllowedApps;
|
|
404
|
-
if (!allowedApps || !allowedApps.some(app => app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
|
|
404
|
+
if (!allowedApps || !allowedApps.some(app => app && app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
|
|
405
405
|
console.warn(`[mbkauthe] User \"${trimmedUsername}\" is not authorized to use the application \"${mbkautheVar.APP_NAME}\"`);
|
|
406
406
|
return res.status(403).json({ success: false, message: `You Are Not Authorized To Use The Application \"${mbkautheVar.APP_NAME}\"` });
|
|
407
407
|
}
|
|
@@ -424,7 +424,9 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
424
424
|
Role: deviceUser.Role,
|
|
425
425
|
allowedApps: deviceUser.AllowedApps,
|
|
426
426
|
};
|
|
427
|
-
|
|
427
|
+
// If a redirect is provided, validate and pass it through
|
|
428
|
+
const requestedRedirect = typeof redirect === 'string' && redirect.startsWith('/') && !redirect.startsWith('//') ? redirect : null;
|
|
429
|
+
return await completeLoginProcess(req, res, userForSession, requestedRedirect);
|
|
428
430
|
}
|
|
429
431
|
} catch (deviceErr) {
|
|
430
432
|
console.error("[mbkauthe] Error checking trusted device:", deviceErr);
|
|
@@ -481,7 +483,7 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
481
483
|
|
|
482
484
|
if (user.Role !== "SuperAdmin") {
|
|
483
485
|
const allowedApps = user.AllowedApps;
|
|
484
|
-
if (!allowedApps || !allowedApps.some(app => app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
|
|
486
|
+
if (!allowedApps || !allowedApps.some(app => app && app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
|
|
485
487
|
console.warn(`[mbkauthe] User \"${user.UserName}\" is not authorized to use the application \"${mbkautheVar.APP_NAME}\"`);
|
|
486
488
|
return res.status(403).json({ success: false, message: `You Are Not Authorized To Use The Application \"${mbkautheVar.APP_NAME}\"` });
|
|
487
489
|
}
|
|
@@ -489,14 +491,16 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
489
491
|
|
|
490
492
|
if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLocaleLowerCase() === "true" && user.TwoFAStatus) {
|
|
491
493
|
// 2FA is enabled, prompt for token on a separate page
|
|
494
|
+
const requestedRedirect = typeof redirect === 'string' && redirect.startsWith('/') && !redirect.startsWith('//') ? redirect : null;
|
|
492
495
|
req.session.preAuthUser = {
|
|
493
496
|
id: user.id,
|
|
494
497
|
username: user.UserName,
|
|
495
498
|
role: user.Role,
|
|
496
499
|
Role: user.Role,
|
|
500
|
+
redirectUrl: requestedRedirect
|
|
497
501
|
};
|
|
498
502
|
console.log(`[mbkauthe] 2FA required for user: ${trimmedUsername}`);
|
|
499
|
-
return res.json({ success: true, twoFactorRequired: true });
|
|
503
|
+
return res.json({ success: true, twoFactorRequired: true, redirectUrl: requestedRedirect });
|
|
500
504
|
}
|
|
501
505
|
|
|
502
506
|
// If 2FA is not enabled, proceed with login
|
|
@@ -507,7 +511,8 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
507
511
|
Role: user.Role,
|
|
508
512
|
allowedApps: user.AllowedApps,
|
|
509
513
|
};
|
|
510
|
-
|
|
514
|
+
const requestedRedirect = typeof redirect === 'string' && redirect.startsWith('/') && !redirect.startsWith('//') ? redirect : null;
|
|
515
|
+
await completeLoginProcess(req, res, userForSession, requestedRedirect);
|
|
511
516
|
|
|
512
517
|
} catch (err) {
|
|
513
518
|
console.error("[mbkauthe] Error during login process:", err);
|
|
@@ -519,9 +524,19 @@ router.get("/mbkauthe/2fa", csrfProtection, (req, res) => {
|
|
|
519
524
|
if (!req.session.preAuthUser) {
|
|
520
525
|
return res.redirect("/mbkauthe/login");
|
|
521
526
|
}
|
|
527
|
+
|
|
528
|
+
// Prefer explicit redirect from query string, else from session preAuthUser redirectUrl, else fallback value
|
|
529
|
+
let redirectFromQuery = req.query && typeof req.query.redirect === 'string' ? req.query.redirect : null;
|
|
530
|
+
let redirectToUse = redirectFromQuery || req.session.preAuthUser.redirectUrl || (mbkautheVar.loginRedirectURL || '/dashboard');
|
|
531
|
+
|
|
532
|
+
// Validate redirectToUse to prevent open redirect attacks
|
|
533
|
+
if (redirectToUse && !(typeof redirectToUse === 'string' && redirectToUse.startsWith('/') && !redirectToUse.startsWith('//'))) {
|
|
534
|
+
redirectToUse = mbkautheVar.loginRedirectURL || '/dashboard';
|
|
535
|
+
}
|
|
536
|
+
|
|
522
537
|
res.render("2fa.handlebars", {
|
|
523
538
|
layout: false,
|
|
524
|
-
customURL:
|
|
539
|
+
customURL: redirectToUse,
|
|
525
540
|
csrfToken: req.csrfToken(),
|
|
526
541
|
appName: mbkautheVar.APP_NAME.toLowerCase(),
|
|
527
542
|
DEVICE_TRUST_DURATION_DAYS: mbkautheVar.DEVICE_TRUST_DURATION_DAYS
|
|
@@ -574,7 +589,14 @@ router.post("/mbkauthe/api/verify-2fa", TwoFALimit, csrfProtection, async (req,
|
|
|
574
589
|
|
|
575
590
|
// 2FA successful, complete login with optional device trust
|
|
576
591
|
const userForSession = { id, username, role, allowedApps };
|
|
577
|
-
|
|
592
|
+
// Prefer redirect stored in preAuthUser or in query/body, fallback to configured default
|
|
593
|
+
let redirectFromSession = req.session.preAuthUser && req.session.preAuthUser.redirectUrl ? req.session.preAuthUser.redirectUrl : null;
|
|
594
|
+
if (redirectFromSession && (!(typeof redirectFromSession === 'string') || !redirectFromSession.startsWith('/') || redirectFromSession.startsWith('//'))) {
|
|
595
|
+
redirectFromSession = null;
|
|
596
|
+
}
|
|
597
|
+
const redirectUrl = redirectFromSession || mbkautheVar.loginRedirectURL || '/dashboard';
|
|
598
|
+
// Clear preAuthUser after successful login
|
|
599
|
+
if (req.session.preAuthUser) delete req.session.preAuthUser;
|
|
578
600
|
await completeLoginProcess(req, res, userForSession, redirectUrl, shouldTrustDevice);
|
|
579
601
|
|
|
580
602
|
} catch (err) {
|
|
@@ -724,7 +746,7 @@ passport.use('github-login', new GitHubStrategy({
|
|
|
724
746
|
// Check if user is authorized for this app (same logic as regular login)
|
|
725
747
|
if (user.Role !== "SuperAdmin") {
|
|
726
748
|
const allowedApps = user.AllowedApps;
|
|
727
|
-
if (!allowedApps || !allowedApps.some(app => app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
|
|
749
|
+
if (!allowedApps || !allowedApps.some(app => app && app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
|
|
728
750
|
const error = new Error(`Not authorized to use ${mbkautheVar.APP_NAME}`);
|
|
729
751
|
error.code = 'NOT_AUTHORIZED';
|
|
730
752
|
return done(error);
|
|
@@ -889,13 +911,17 @@ router.get('/mbkauthe/api/github/login/callback',
|
|
|
889
911
|
// Check 2FA if enabled
|
|
890
912
|
if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLowerCase() === "true" && user.TwoFAStatus) {
|
|
891
913
|
// 2FA is enabled, store pre-auth user and redirect to 2FA
|
|
914
|
+
// If this was an oauth flow, use oauthRedirect set earlier
|
|
915
|
+
const oauthRedirect = req.session.oauthRedirect;
|
|
916
|
+
if (oauthRedirect) delete req.session.oauthRedirect;
|
|
892
917
|
req.session.preAuthUser = {
|
|
893
918
|
id: user.id,
|
|
894
919
|
username: user.UserName,
|
|
895
920
|
UserName: user.UserName,
|
|
896
921
|
role: user.Role,
|
|
897
922
|
Role: user.Role,
|
|
898
|
-
loginMethod: 'github'
|
|
923
|
+
loginMethod: 'github',
|
|
924
|
+
redirectUrl: oauthRedirect || null
|
|
899
925
|
};
|
|
900
926
|
console.log(`[mbkauthe] GitHub login: 2FA required for user: ${githubUser.username}`);
|
|
901
927
|
return res.redirect('/mbkauthe/2fa');
|
|
@@ -17,6 +17,20 @@ async function validateSession(req, res, next) {
|
|
|
17
17
|
try {
|
|
18
18
|
const { id, sessionId, role, allowedApps } = req.session.user;
|
|
19
19
|
|
|
20
|
+
// Defensive checks for sessionId and allowedApps
|
|
21
|
+
if (!sessionId) {
|
|
22
|
+
console.warn(`[mbkauthe] Missing sessionId for user "${req.session.user.username}"`);
|
|
23
|
+
req.session.destroy();
|
|
24
|
+
clearSessionCookies(res);
|
|
25
|
+
return renderError(res, {
|
|
26
|
+
code: 401,
|
|
27
|
+
error: "Session Expired",
|
|
28
|
+
message: "Your Session Has Expired. Please Log In Again.",
|
|
29
|
+
pagename: "Login",
|
|
30
|
+
page: `/mbkauthe/login?redirect=${encodeURIComponent(req.originalUrl)}`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
// Normalize sessionId to lowercase for consistent comparison
|
|
21
35
|
const normalizedSessionId = sessionId.toLowerCase();
|
|
22
36
|
|
|
@@ -24,7 +38,11 @@ async function validateSession(req, res, next) {
|
|
|
24
38
|
const query = `SELECT "SessionId", "Active", "Role" FROM "Users" WHERE "id" = $1`;
|
|
25
39
|
const result = await dblogin.query({ name: 'validate-user-session', text: query, values: [id] });
|
|
26
40
|
|
|
27
|
-
|
|
41
|
+
const dbSessionId = result.rows.length > 0 && result.rows[0].SessionId ? String(result.rows[0].SessionId).toLowerCase() : null;
|
|
42
|
+
if (!dbSessionId || dbSessionId !== normalizedSessionId) {
|
|
43
|
+
if (result.rows.length > 0 && !result.rows[0].SessionId) {
|
|
44
|
+
console.warn(`[mbkauthe] DB sessionId is null for user "${req.session.user.username}"`);
|
|
45
|
+
}
|
|
28
46
|
console.log(`[mbkauthe] Session invalidated for user "${req.session.user.username}"`);
|
|
29
47
|
req.session.destroy();
|
|
30
48
|
clearSessionCookies(res);
|
|
@@ -51,7 +69,9 @@ async function validateSession(req, res, next) {
|
|
|
51
69
|
}
|
|
52
70
|
|
|
53
71
|
if (role !== "SuperAdmin") {
|
|
54
|
-
|
|
72
|
+
// If allowedApps is not provided or not an array, treat as no access
|
|
73
|
+
const hasAllowedApps = Array.isArray(allowedApps) && allowedApps.length > 0;
|
|
74
|
+
if (!hasAllowedApps || !allowedApps.some(app => app && app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
|
|
55
75
|
console.warn(`[mbkauthe] User \"${req.session.user.username}\" is not authorized to use the application \"${mbkautheVar.APP_NAME}\"`);
|
|
56
76
|
req.session.destroy();
|
|
57
77
|
clearSessionCookies(res);
|
package/package.json
CHANGED
package/views/2fa.handlebars
CHANGED
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
const data = await response.json();
|
|
88
88
|
if (data.success) {
|
|
89
89
|
loginButtonText.textContent = 'Success! Redirecting...';
|
|
90
|
-
window.location.href = data.redirectUrl || '{{
|
|
90
|
+
window.location.href = data.redirectUrl || '{{ customURL }}';
|
|
91
91
|
} else {
|
|
92
92
|
loginButton.disabled = false;
|
|
93
93
|
loginButtonText.textContent = 'Verify';
|
|
@@ -130,6 +130,8 @@
|
|
|
130
130
|
loginButton.disabled = true;
|
|
131
131
|
loginButtonText.textContent = 'Authenticating...';
|
|
132
132
|
|
|
133
|
+
// Pass redirect query param through to server so it can be used by 2FA flow
|
|
134
|
+
const pageRedirect = new URLSearchParams(window.location.search).get('redirect');
|
|
133
135
|
fetch('/mbkauthe/api/login', {
|
|
134
136
|
method: 'POST',
|
|
135
137
|
headers: {
|
|
@@ -138,14 +140,17 @@
|
|
|
138
140
|
body: JSON.stringify({
|
|
139
141
|
username,
|
|
140
142
|
password
|
|
143
|
+
, redirect: pageRedirect || null
|
|
141
144
|
})
|
|
142
145
|
})
|
|
143
146
|
.then(response => response.json())
|
|
144
147
|
.then(data => {
|
|
145
148
|
if (data.success) {
|
|
146
149
|
if (data.twoFactorRequired) {
|
|
147
|
-
// Redirect to 2FA page
|
|
148
|
-
|
|
150
|
+
// Redirect to 2FA page (= pass along redirect target)
|
|
151
|
+
const redirectTarget = data.redirectUrl || pageRedirect;
|
|
152
|
+
const redirectQuery = redirectTarget ? `?redirect=${encodeURIComponent(redirectTarget)}` : '';
|
|
153
|
+
window.location.href = `/mbkauthe/2fa${redirectQuery}`;
|
|
149
154
|
} else {
|
|
150
155
|
loginButtonText.textContent = 'Success! Redirecting...';
|
|
151
156
|
sessionStorage.setItem('sessionId', data.sessionId);
|
|
@@ -279,19 +279,18 @@
|
|
|
279
279
|
width: 100%;
|
|
280
280
|
padding: 12px 20px;
|
|
281
281
|
border-radius: var(--border-radius);
|
|
282
|
-
background: #
|
|
282
|
+
background: #262b30;
|
|
283
283
|
color: white;
|
|
284
|
-
|
|
285
|
-
font-
|
|
284
|
+
border: solid 0.13rem #262b30;
|
|
285
|
+
font-weight: 700;
|
|
286
|
+
font-size: 1rem;
|
|
286
287
|
text-decoration: none;
|
|
287
288
|
transition: var(--transition);
|
|
288
|
-
box-shadow: var(--box-shadow);
|
|
289
289
|
}
|
|
290
290
|
|
|
291
291
|
.btn-github:hover {
|
|
292
|
-
background:
|
|
293
|
-
box-shadow:
|
|
294
|
-
transform: translateY(-2px);
|
|
292
|
+
background: transparent;
|
|
293
|
+
box-shadow: var(--box-shadow);
|
|
295
294
|
}
|
|
296
295
|
|
|
297
296
|
.btn-github i {
|