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
- return await completeLoginProcess(req, res, userForSession);
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
- await completeLoginProcess(req, res, userForSession);
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: mbkautheVar.loginRedirectURL || '/dashboard',
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
- const redirectUrl = mbkautheVar.loginRedirectURL || '/dashboard';
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
- if (result.rows.length === 0 || result.rows[0].SessionId.toLowerCase() !== normalizedSessionId) {
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
- if (!allowedApps || !allowedApps.some(app => app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mbkauthe",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "MBKTech's reusable authentication system for Node.js applications.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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 || '{{{ customURL }}}';
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
- window.location.href = '/mbkauthe/2fa';
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: #24292e;
282
+ background: #262b30;
283
283
  color: white;
284
- font-weight: 500;
285
- font-size: 0.95rem;
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: #3b4045;
293
- box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2);
294
- transform: translateY(-2px);
292
+ background: transparent;
293
+ box-shadow: var(--box-shadow);
295
294
  }
296
295
 
297
296
  .btn-github i {