mbkauthe 1.4.1 → 2.0.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
@@ -5,14 +5,14 @@ import session from "express-session";
5
5
  import pgSession from "connect-pg-simple";
6
6
  const PgSession = pgSession(session);
7
7
  import { dblogin } from "./pool.js";
8
- import { authenticate } from "./validateSessionAndRole.js";
8
+ import { authenticate, validateSession, validateSessionAndRole } from "./validateSessionAndRole.js";
9
9
  import fetch from 'node-fetch';
10
10
  import cookieParser from "cookie-parser";
11
11
  import bcrypt from 'bcrypt';
12
12
  import rateLimit from 'express-rate-limit';
13
13
  import speakeasy from "speakeasy";
14
- //import passport from 'passport';
15
- //import GitHubStrategy from 'passport-github2';
14
+ import passport from 'passport';
15
+ import GitHubStrategy from 'passport-github2';
16
16
 
17
17
  import { createRequire } from "module";
18
18
  import fs from "fs";
@@ -36,16 +36,26 @@ router.get('/mbkauthe/main.js', (req, res) => {
36
36
  });
37
37
 
38
38
  // CSRF protection middleware
39
- const csrfProtection = csurf({ cookie: false });
39
+ const csrfProtection = csurf({ cookie: true });
40
40
 
41
41
  // CORS and security headers
42
42
  router.use((req, res, next) => {
43
43
  const origin = req.headers.origin;
44
- if (origin && origin.endsWith(`.${mbkautheVar.DOMAIN}`)) {
45
- res.header('Access-Control-Allow-Origin', origin);
46
- res.header('Access-Control-Allow-Credentials', 'true');
47
- res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
48
- res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
44
+ if (origin) {
45
+ try {
46
+ const originUrl = new URL(origin);
47
+ const allowedDomain = `.${mbkautheVar.DOMAIN}`;
48
+ // Exact match or subdomain match (must end with .domain.com, not just domain.com)
49
+ if (originUrl.hostname === mbkautheVar.DOMAIN ||
50
+ (originUrl.hostname.endsWith(allowedDomain) && originUrl.hostname.charAt(originUrl.hostname.length - allowedDomain.length - 1) !== '.')) {
51
+ res.header('Access-Control-Allow-Origin', origin);
52
+ res.header('Access-Control-Allow-Credentials', 'true');
53
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
54
+ res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
55
+ }
56
+ } catch (err) {
57
+ // Invalid origin URL, skip CORS headers
58
+ }
49
59
  }
50
60
  next();
51
61
  });
@@ -71,6 +81,12 @@ const TwoFALimit = rateLimit({
71
81
  message: { success: false, message: "Too many 2FA attempts, please try again later" }
72
82
  });
73
83
 
84
+ const GitHubOAuthLimit = rateLimit({
85
+ windowMs: 5 * 60 * 1000,
86
+ max: 10,
87
+ message: "Too many GitHub login attempts, please try again later"
88
+ });
89
+
74
90
  const sessionConfig = {
75
91
  store: new PgSession({
76
92
  pool: dblogin,
@@ -85,7 +101,7 @@ const sessionConfig = {
85
101
  maxAge: mbkautheVar.COOKIE_EXPIRE_TIME * 24 * 60 * 60 * 1000,
86
102
  domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
87
103
  httpOnly: true,
88
- secure: mbkautheVar.IS_DEPLOYED === 'true' ? 'auto' : false,
104
+ secure: mbkautheVar.IS_DEPLOYED === 'true',
89
105
  sameSite: 'lax',
90
106
  path: '/'
91
107
  },
@@ -100,14 +116,16 @@ router.use(async (req, res, next) => {
100
116
  try {
101
117
  const sessionId = req.cookies.sessionId;
102
118
 
103
- // Validate sessionId format (should be 64 hex characters)
119
+ // Validate sessionId format (should be 64 hex characters) and normalize to lowercase
104
120
  if (typeof sessionId !== 'string' || !/^[a-f0-9]{64}$/i.test(sessionId)) {
105
121
  console.warn("[mbkauthe] Invalid sessionId format detected");
106
122
  return next();
107
123
  }
108
124
 
109
- const query = `SELECT id, "UserName", "Active", "Role", "SessionId", "AllowedApps" FROM "Users" WHERE "SessionId" = $1 AND "Active" = true`;
110
- const result = await dblogin.query({ name: 'get-user-by-sessionid', text: query, values: [sessionId] });
125
+ const normalizedSessionId = sessionId.toLowerCase();
126
+
127
+ const query = `SELECT id, "UserName", "Active", "Role", "SessionId", "AllowedApps" FROM "Users" WHERE LOWER("SessionId") = $1 AND "Active" = true`;
128
+ const result = await dblogin.query({ name: 'restore-user-session', text: query, values: [normalizedSessionId] });
111
129
 
112
130
  if (result.rows.length > 0) {
113
131
  const user = result.rows[0];
@@ -117,8 +135,7 @@ router.use(async (req, res, next) => {
117
135
  UserName: user.UserName,
118
136
  role: user.Role,
119
137
  Role: user.Role,
120
- sessionId,
121
- allowedApps: user.AllowedApps,
138
+ sessionId: normalizedSessionId,
122
139
  };
123
140
  }
124
141
  } catch (err) {
@@ -131,7 +148,7 @@ router.use(async (req, res, next) => {
131
148
  const getCookieOptions = () => ({
132
149
  maxAge: mbkautheVar.COOKIE_EXPIRE_TIME * 24 * 60 * 60 * 1000,
133
150
  domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
134
- secure: mbkautheVar.IS_DEPLOYED === 'true' ? 'auto' : false,
151
+ secure: mbkautheVar.IS_DEPLOYED === 'true',
135
152
  sameSite: 'lax',
136
153
  path: '/',
137
154
  httpOnly: true
@@ -139,32 +156,64 @@ const getCookieOptions = () => ({
139
156
 
140
157
  const getClearCookieOptions = () => ({
141
158
  domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
142
- secure: mbkautheVar.IS_DEPLOYED === 'true' ? 'auto' : false,
159
+ secure: mbkautheVar.IS_DEPLOYED === 'true',
143
160
  sameSite: 'lax',
144
161
  path: '/',
145
162
  httpOnly: true
146
163
  });
147
164
 
165
+ router.get('/mbkauthe/test', validateSession, LoginLimit, async(req, res) => {
166
+ if(req.session?.user) {
167
+ return res.send(`
168
+ <head> <script src="/mbkauthe/main.js"></script> </head>
169
+ <p>if you are seeing this page than User is logged in.</p>
170
+ <p>id: '${req.session.user.id}', UserName: '${req.session.user.username}', Role: '${req.session.user.role}', SessionId: '${req.session.user.sessionId}'</p>
171
+ <button onclick="logout()">Logout</button><br>
172
+ <a href="/mbkauthe/info">Info Page</a><br>
173
+ <a href="/mbkauthe/login">Login Page</a><br>
174
+ `);
175
+ }
176
+ });
177
+
148
178
  async function completeLoginProcess(req, res, user, redirectUrl = null) {
149
179
  try {
180
+ // Ensure both username formats are available for compatibility
181
+ const username = user.username || user.UserName;
182
+ if (!username) {
183
+ throw new Error('Username is required in user object');
184
+ }
185
+
150
186
  // smaller session id is sufficient and faster to generate/serialize
151
187
  const sessionId = crypto.randomBytes(32).toString("hex");
152
- console.log(`[mbkauthe] Generated session ID for username: ${user.username}`);
188
+ console.log(`[mbkauthe] Generated session ID for username: ${username}`);
189
+
190
+ // Regenerate session to prevent session fixation attacks
191
+ const oldSessionId = req.sessionID;
192
+ await new Promise((resolve, reject) => {
193
+ req.session.regenerate((err) => {
194
+ if (err) reject(err);
195
+ else resolve();
196
+ });
197
+ });
153
198
 
154
- // Delete old session record for this user
155
- await dblogin.query('DELETE FROM "session" WHERE username = $1', [user.username]);
199
+ // Delete all old sessions for this user from session table
200
+ await dblogin.query({
201
+ name: 'login-delete-old-user-sessions',
202
+ text: 'DELETE FROM "session" WHERE sess::text LIKE $1',
203
+ values: [`%"username":"${username}"%`]
204
+ });
156
205
 
157
- await dblogin.query(`UPDATE "Users" SET "SessionId" = $1 WHERE "id" = $2`, [
206
+ await dblogin.query({ name: 'login-update-session-id', text: `UPDATE "Users" SET "SessionId" = $1 WHERE "id" = $2`, values: [
158
207
  sessionId,
159
208
  user.id,
160
- ]);
209
+ ] });
161
210
 
162
211
  req.session.user = {
163
212
  id: user.id,
164
- username: user.username,
165
- UserName: user.UserName,
166
- role: user.role,
167
- Role: user.role,
213
+ username: username,
214
+ UserName: username,
215
+ role: user.role || user.Role,
216
+ Role: user.role || user.Role,
168
217
  sessionId,
169
218
  };
170
219
 
@@ -182,7 +231,7 @@ async function completeLoginProcess(req, res, user, redirectUrl = null) {
182
231
 
183
232
  const cookieOptions = getCookieOptions();
184
233
  res.cookie("sessionId", sessionId, cookieOptions);
185
- console.log(`[mbkauthe] User "${user.username}" logged in successfully`);
234
+ console.log(`[mbkauthe] User "${username}" logged in successfully`);
186
235
 
187
236
  const responsePayload = {
188
237
  success: true,
@@ -216,8 +265,8 @@ router.use(async (req, res, next) => {
216
265
 
217
266
  router.post("/mbkauthe/api/terminateAllSessions", authenticate(mbkautheVar.Main_SECRET_TOKEN), async (req, res) => {
218
267
  try {
219
- await dblogin.query(`UPDATE "Users" SET "SessionId" = NULL`);
220
- await dblogin.query('DELETE FROM "session"');
268
+ await dblogin.query({ name: 'terminate-all-user-sessions', text: `UPDATE "Users" SET "SessionId" = NULL` });
269
+ await dblogin.query({ name: 'terminate-all-db-sessions', text: 'DELETE FROM "session"' });
221
270
 
222
271
  req.session.destroy((err) => {
223
272
  if (err) {
@@ -280,11 +329,11 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
280
329
 
281
330
  try {
282
331
  const userQuery = `SELECT id, "UserName", "Password", "Active", "Role", "AllowedApps" FROM "Users" WHERE "UserName" = $1`;
283
- const userResult = await dblogin.query({ name: 'get-user-by-username', text: userQuery, values: [trimmedUsername] });
332
+ const userResult = await dblogin.query({ name: 'login-get-user', text: userQuery, values: [trimmedUsername] });
284
333
 
285
334
  if (userResult.rows.length === 0) {
286
- console.log(`[mbkauthe] Username does not exist: ${trimmedUsername}`);
287
- return res.status(404).json({ success: false, message: "Incorrect Username Or Password" });
335
+ console.log(`[mbkauthe] Login failed: invalid credentials`);
336
+ return res.status(401).json({ success: false, message: "Invalid credentials" });
288
337
  }
289
338
 
290
339
  const user = userResult.rows[0];
@@ -299,18 +348,18 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
299
348
  try {
300
349
  const result = await bcrypt.compare(password, user.Password);
301
350
  if (!result) {
302
- console.log("[mbkauthe] Incorrect password.");
303
- return res.status(401).json({ success: false, errorCode: 603, message: "Incorrect Username Or Password." });
351
+ console.log("[mbkauthe] Login failed: invalid credentials");
352
+ return res.status(401).json({ success: false, errorCode: 603, message: "Invalid credentials" });
304
353
  }
305
- console.log("[mbkauthe] Password matches!");
354
+ console.log("[mbkauthe] Password validated successfully");
306
355
  } catch (err) {
307
356
  console.error("[mbkauthe] Error comparing password:", err);
308
357
  return res.status(500).json({ success: false, errorCode: 605, message: `Internal Server Error` });
309
358
  }
310
359
  } else {
311
360
  if (user.Password !== password) {
312
- console.log(`[mbkauthe] Incorrect password for username: ${trimmedUsername}`);
313
- return res.status(401).json({ success: false, errorCode: 603, message: "Incorrect Username Or Password" });
361
+ console.log(`[mbkauthe] Login failed: invalid credentials`);
362
+ return res.status(401).json({ success: false, errorCode: 603, message: "Invalid credentials" });
314
363
  }
315
364
  }
316
365
 
@@ -329,7 +378,7 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
329
378
 
330
379
  if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLocaleLowerCase() === "true") {
331
380
  const query = `SELECT "TwoFAStatus" FROM "TwoFA" WHERE "UserName" = $1`;
332
- const twoFAResult = await dblogin.query({ name: 'get-2fa-status', text: query, values: [trimmedUsername] });
381
+ const twoFAResult = await dblogin.query({ name: 'login-check-2fa-status', text: query, values: [trimmedUsername] });
333
382
 
334
383
  if (twoFAResult.rows.length > 0 && twoFAResult.rows[0].TwoFAStatus) {
335
384
  // 2FA is enabled, prompt for token on a separate page
@@ -367,6 +416,7 @@ router.get("/mbkauthe/2fa", csrfProtection, (req, res) => {
367
416
  layout: false,
368
417
  customURL: mbkautheVar.loginRedirectURL || '/home',
369
418
  csrfToken: req.csrfToken(),
419
+ appName: mbkautheVar.APP_NAME.toLowerCase(),
370
420
  });
371
421
  });
372
422
 
@@ -391,7 +441,7 @@ router.post("/mbkauthe/api/verify-2fa", TwoFALimit, csrfProtection, async (req,
391
441
 
392
442
  try {
393
443
  const query = `SELECT "TwoFASecret" FROM "TwoFA" WHERE "UserName" = $1`;
394
- const twoFAResult = await dblogin.query(query, [username]);
444
+ const twoFAResult = await dblogin.query({ name: 'verify-2fa-secret', text: query, values: [username] });
395
445
 
396
446
  if (twoFAResult.rows.length === 0 || !twoFAResult.rows[0].TwoFASecret) {
397
447
  return res.status(500).json({ success: false, message: "2FA is not configured correctly." });
@@ -421,15 +471,15 @@ router.post("/mbkauthe/api/verify-2fa", TwoFALimit, csrfProtection, async (req,
421
471
  }
422
472
  });
423
473
 
424
- router.post("/mbkauthe/api/logout", LogoutLimit, csrfProtection, async (req, res) => {
474
+ router.post("/mbkauthe/api/logout", LogoutLimit, async (req, res) => {
425
475
  if (req.session.user) {
426
476
  try {
427
477
  const { id, username } = req.session.user;
428
478
 
429
- await dblogin.query(`UPDATE "Users" SET "SessionId" = NULL WHERE "id" = $1`, [id]);
479
+ await dblogin.query({ name: 'logout-clear-session', text: `UPDATE "Users" SET "SessionId" = NULL WHERE "id" = $1`, values: [id] });
430
480
 
431
481
  if (req.sessionID) {
432
- await dblogin.query('DELETE FROM "session" WHERE sid = $1', [req.sessionID]);
482
+ await dblogin.query({ name: 'logout-delete-session', text: 'DELETE FROM "session" WHERE sid = $1', values: [req.sessionID] });
433
483
  }
434
484
 
435
485
  req.session.destroy((err) => {
@@ -463,7 +513,7 @@ router.get("/mbkauthe/login", LoginLimit, csrfProtection, (req, res) => {
463
513
  userLoggedIn: !!req.session?.user,
464
514
  username: req.session?.user?.username || '',
465
515
  version: packageJson.version,
466
- appName: mbkautheVar.APP_NAME.toUpperCase(),
516
+ appName: mbkautheVar.APP_NAME.toLowerCase(),
467
517
  csrfToken: req.csrfToken(),
468
518
  });
469
519
  });
@@ -483,9 +533,19 @@ async function getLatestVersion() {
483
533
  }
484
534
  }
485
535
 
486
- router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (_, res) => {
487
- let latestVersion;
536
+ router.get("/mbkauthe/bg.avif", (req, res) => {
537
+ res.sendFile("bg.avif", { root: path.join(process.cwd(), "public") });
538
+ });
488
539
 
540
+ router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (req, res) => {
541
+ let latestVersion;
542
+ const parameters = req.query;
543
+ let authorized = false;
544
+
545
+ if (parameters.password && mbkautheVar.Main_SECRET_TOKEN) {
546
+ authorized = String(parameters.password) === String(mbkautheVar.Main_SECRET_TOKEN);
547
+ }
548
+
489
549
  try {
490
550
  latestVersion = await getLatestVersion();
491
551
  //latestVersion = "Under Development"; // Placeholder for the latest version
@@ -499,6 +559,7 @@ router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (_, res) => {
499
559
  mbkautheVar: mbkautheVar,
500
560
  version: packageJson.version,
501
561
  latestVersion,
562
+ authorized: authorized,
502
563
  });
503
564
  } catch (err) {
504
565
  console.error("[mbkauthe] Error fetching version information:", err);
@@ -515,7 +576,7 @@ router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (_, res) => {
515
576
  `);
516
577
  }
517
578
  });
518
- /*
579
+
519
580
  // Configure GitHub Strategy for login
520
581
  passport.use('github-login', new GitHubStrategy({
521
582
  clientID: process.env.GITHUB_CLIENT_ID,
@@ -526,28 +587,35 @@ passport.use('github-login', new GitHubStrategy({
526
587
  async (accessToken, refreshToken, profile, done) => {
527
588
  try {
528
589
  // Check if this GitHub account is linked to any user
529
- const githubUser = await dblogin.query(
530
- 'SELECT ug.*, u."UserName", u."Role", u."Active", u."AllowedApps" FROM user_github ug JOIN "Users" u ON ug.user_name = u."UserName" WHERE ug.github_id = $1',
531
- [profile.id]
532
- );
590
+ const githubUser = await dblogin.query({
591
+ name: 'github-login-get-user',
592
+ text: 'SELECT ug.*, u."UserName", u."Role", u."Active", u."AllowedApps", u."id" FROM user_github ug JOIN "Users" u ON ug.user_name = u."UserName" WHERE ug.github_id = $1',
593
+ values: [profile.id]
594
+ });
533
595
 
534
596
  if (githubUser.rows.length === 0) {
535
597
  // GitHub account is not linked to any user
536
- return done(new Error('GitHub account not linked to any user'));
598
+ const error = new Error('GitHub account not linked to any user');
599
+ error.code = 'GITHUB_NOT_LINKED';
600
+ return done(error);
537
601
  }
538
602
 
539
603
  const user = githubUser.rows[0];
540
604
 
541
605
  // Check if the user account is active
542
606
  if (!user.Active) {
543
- return done(new Error('Account is inactive'));
607
+ const error = new Error('Account is inactive');
608
+ error.code = 'ACCOUNT_INACTIVE';
609
+ return done(error);
544
610
  }
545
611
 
546
612
  // Check if user is authorized for this app (same logic as regular login)
547
613
  if (user.Role !== "SuperAdmin") {
548
614
  const allowedApps = user.AllowedApps;
549
615
  if (!allowedApps || !allowedApps.some(app => app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
550
- return done(new Error(`Not authorized to use ${mbkautheVar.APP_NAME}`));
616
+ const error = new Error(`Not authorized to use ${mbkautheVar.APP_NAME}`);
617
+ error.code = 'NOT_AUTHORIZED';
618
+ return done(error);
551
619
  }
552
620
  }
553
621
 
@@ -561,6 +629,7 @@ passport.use('github-login', new GitHubStrategy({
561
629
  });
562
630
  } catch (err) {
563
631
  console.error('[mbkauthe] GitHub login error:', err);
632
+ err.code = err.code || 'GITHUB_AUTH_ERROR';
564
633
  return done(err);
565
634
  }
566
635
  }
@@ -580,25 +649,128 @@ router.use(passport.initialize());
580
649
  router.use(passport.session());
581
650
 
582
651
  // GitHub login initiation
583
- router.get('/mbkauthe/api/github/login', passport.authenticate('github-login'));
652
+ router.get('/mbkauthe/api/github/login', GitHubOAuthLimit, (req, res, next) => {
653
+ // Store redirect parameter in session before OAuth flow (validate to prevent open redirect)
654
+ const redirect = req.query.redirect;
655
+ if (redirect && typeof redirect === 'string') {
656
+ // Only allow relative URLs or same-origin URLs to prevent open redirect attacks
657
+ if (redirect.startsWith('/') && !redirect.startsWith('//')) {
658
+ req.session.oauthRedirect = redirect;
659
+ } else {
660
+ console.warn(`[mbkauthe] Invalid redirect parameter rejected: ${redirect}`);
661
+ }
662
+ }
663
+ passport.authenticate('github-login')(req, res, next);
664
+ });
584
665
 
585
666
  // GitHub login callback
586
667
  router.get('/mbkauthe/api/github/login/callback',
587
- passport.authenticate('github-login', {
588
- failureRedirect: '/mbkauthe/login?error=github_auth_failed',
589
- session: false // We'll handle session manually
590
- }),
668
+ GitHubOAuthLimit,
669
+ (req, res, next) => {
670
+ passport.authenticate('github-login', {
671
+ session: false // We'll handle session manually
672
+ }, (err, user, info) => {
673
+ // Custom error handling for passport authentication
674
+ if (err) {
675
+ console.error('[mbkauthe] GitHub authentication error:', err);
676
+
677
+ // Map error codes to user-friendly messages
678
+ switch(err.code) {
679
+ case 'GITHUB_NOT_LINKED':
680
+ return res.status(403).render('Error/dError.handlebars', {
681
+ layout: false,
682
+ code: '403',
683
+ error: 'GitHub Account Not Linked',
684
+ message: 'Your GitHub account is not linked to any user in our system. To link your GitHub account, a User must connect their GitHub account to mbktech account through the user settings.',
685
+ page: '/mbkauthe/login',
686
+ pagename: 'Login',
687
+ version: packageJson.version,
688
+ app: mbkautheVar.APP_NAME
689
+ });
690
+
691
+ case 'ACCOUNT_INACTIVE':
692
+ return res.status(403).render('Error/dError.handlebars', {
693
+ layout: false,
694
+ code: '403',
695
+ error: 'Account Inactive',
696
+ message: 'Your account has been deactivated. Please contact your administrator.',
697
+ page: '/mbkauthe/login',
698
+ pagename: 'Login',
699
+ version: packageJson.version,
700
+ app: mbkautheVar.APP_NAME
701
+ });
702
+
703
+ case 'NOT_AUTHORIZED':
704
+ return res.status(403).render('Error/dError.handlebars', {
705
+ layout: false,
706
+ code: '403',
707
+ error: 'Not Authorized',
708
+ message: `You are not authorized to access ${mbkautheVar.APP_NAME}. Please contact your administrator.`,
709
+ page: '/mbkauthe/login',
710
+ pagename: 'Login',
711
+ version: packageJson.version,
712
+ app: mbkautheVar.APP_NAME
713
+ });
714
+
715
+ default:
716
+ return res.status(500).render('Error/dError.handlebars', {
717
+ layout: false,
718
+ code: '500',
719
+ error: 'Authentication Error',
720
+ message: 'An error occurred during GitHub authentication. Please try again.',
721
+ page: '/mbkauthe/login',
722
+ pagename: 'Login',
723
+ version: packageJson.version,
724
+ app: mbkautheVar.APP_NAME,
725
+ details: process.env.NODE_ENV === 'development' ? `${err.message}\n${err.stack}` : 'Error details hidden in production'
726
+ });
727
+ }
728
+ }
729
+
730
+ if (!user) {
731
+ console.error('[mbkauthe] GitHub callback: No user data received');
732
+ return res.status(401).render('Error/dError.handlebars', {
733
+ layout: false,
734
+ code: '401',
735
+ error: 'Authentication Failed',
736
+ message: 'GitHub authentication failed. Please try again.',
737
+ page: '/mbkauthe/login',
738
+ pagename: 'Login',
739
+ version: packageJson.version,
740
+ app: mbkautheVar.APP_NAME
741
+ });
742
+ }
743
+
744
+ // Authentication successful, attach user to request
745
+ req.user = user;
746
+ next();
747
+ })(req, res, next);
748
+ },
591
749
  async (req, res) => {
592
750
  try {
593
751
  const githubUser = req.user;
594
752
 
595
- // Find the actual user record
596
- const userQuery = `SELECT * FROM "Users" WHERE "UserName" = $1`;
597
- const userResult = await dblogin.query(userQuery, [githubUser.username]);
753
+ // Find the actual user record with named query
754
+ const userQuery = `SELECT id, "UserName", "Active", "Role", "AllowedApps" FROM "Users" WHERE "UserName" = $1`;
755
+ const userResult = await dblogin.query({
756
+ name: 'github-callback-get-user',
757
+ text: userQuery,
758
+ values: [githubUser.username]
759
+ });
598
760
 
599
761
  if (userResult.rows.length === 0) {
600
- console.log(`[mbkauthe] GitHub login: User not found: ${githubUser.username}`);
601
- return res.redirect('/mbkauthe/login?error=user_not_found');
762
+ console.error(`[mbkauthe] GitHub login: User not found: ${githubUser.username}`);
763
+ return res.status(404).render('Error/dError.handlebars', {
764
+ layout: false,
765
+ code: '404',
766
+ error: 'User Not Found',
767
+ message: 'Your GitHub account is linked, but the user account no longer exists in our system.',
768
+ page: '/mbkauthe/login',
769
+ pagename: 'Login',
770
+ version: packageJson.version,
771
+ app: mbkautheVar.APP_NAME,
772
+ details: `GitHub username: ${githubUser.username}\nPlease contact your administrator.`
773
+ });
602
774
  }
603
775
 
604
776
  const user = userResult.rows[0];
@@ -606,14 +778,20 @@ router.get('/mbkauthe/api/github/login/callback',
606
778
  // Check 2FA if enabled
607
779
  if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLowerCase() === "true") {
608
780
  const twoFAQuery = `SELECT "TwoFAStatus" FROM "TwoFA" WHERE "UserName" = $1`;
609
- const twoFAResult = await dblogin.query(twoFAQuery, [githubUser.username]);
781
+ const twoFAResult = await dblogin.query({
782
+ name: 'github-check-2fa-status',
783
+ text: twoFAQuery,
784
+ values: [githubUser.username]
785
+ });
610
786
 
611
787
  if (twoFAResult.rows.length > 0 && twoFAResult.rows[0].TwoFAStatus) {
612
788
  // 2FA is enabled, store pre-auth user and redirect to 2FA
613
789
  req.session.preAuthUser = {
614
790
  id: user.id,
615
791
  username: user.UserName,
792
+ UserName: user.UserName,
616
793
  role: user.Role,
794
+ Role: user.Role,
617
795
  loginMethod: 'github'
618
796
  };
619
797
  console.log(`[mbkauthe] GitHub login: 2FA required for user: ${githubUser.username}`);
@@ -621,62 +799,64 @@ router.get('/mbkauthe/api/github/login/callback',
621
799
  }
622
800
  }
623
801
 
624
- // Complete login process
802
+ // Complete login process using the shared function
625
803
  const userForSession = {
626
804
  id: user.id,
627
805
  username: user.UserName,
806
+ UserName: user.UserName,
628
807
  role: user.Role,
808
+ Role: user.Role
629
809
  };
630
810
 
631
- // Generate session and complete login
632
- const sessionId = crypto.randomBytes(32).toString("hex");
633
- console.log(`[mbkauthe] GitHub login: Generated session ID for username: ${user.UserName}`);
634
-
635
- // Delete old session record for this user
636
- await dblogin.query('DELETE FROM "session" WHERE username = $1', [user.UserName]);
811
+ // For OAuth redirect flow, we need to handle redirect differently
812
+ // Store the redirect URL before calling completeLoginProcess
813
+ const oauthRedirect = req.session.oauthRedirect;
814
+ delete req.session.oauthRedirect;
637
815
 
638
- await dblogin.query(`UPDATE "Users" SET "SessionId" = $1 WHERE "id" = $2`, [
639
- sessionId,
640
- user.id,
641
- ]);
642
-
643
- req.session.user = {
644
- id: user.id,
645
- username: user.UserName,
646
- role: user.Role,
647
- sessionId,
816
+ // Custom response handler for OAuth flow - wrap the response object
817
+ const originalJson = res.json.bind(res);
818
+ const originalStatus = res.status.bind(res);
819
+ let statusCode = 200;
820
+
821
+ res.status = function(code) {
822
+ statusCode = code;
823
+ return originalStatus(code);
648
824
  };
649
-
650
- req.session.save(async (err) => {
651
- if (err) {
652
- console.log("[mbkauthe] GitHub login session save error:", err);
653
- return res.redirect('/mbkauthe/login?error=session_error');
654
- }
655
-
656
- try {
657
- await dblogin.query(
658
- 'UPDATE "session" SET username = $1 WHERE sid = $2',
659
- [user.UserName, req.sessionID]
660
- );
661
- } catch (e) {
662
- console.log("[mbkauthe] GitHub login: Failed to update username in session table:", e);
825
+
826
+ res.json = function(data) {
827
+ if (data.success && statusCode === 200) {
828
+ // If login successful, redirect instead of sending JSON
829
+ const redirectUrl = oauthRedirect || mbkautheVar.loginRedirectURL || '/home';
830
+ console.log(`[mbkauthe] GitHub login: Redirecting to ${redirectUrl}`);
831
+ // Restore original methods before redirect
832
+ res.json = originalJson;
833
+ res.status = originalStatus;
834
+ return res.redirect(redirectUrl);
663
835
  }
836
+ // Restore original methods for error responses
837
+ res.json = originalJson;
838
+ res.status = originalStatus;
839
+ return originalJson(data);
840
+ };
664
841
 
665
- const cookieOptions = getCookieOptions();
666
- res.cookie("sessionId", sessionId, cookieOptions);
667
- console.log(`[mbkauthe] GitHub login: User "${user.UserName}" logged in successfully`);
668
-
669
- // Redirect to the configured URL or home
670
- const redirectUrl = mbkautheVar.loginRedirectURL || '/home';
671
- res.redirect(redirectUrl);
672
- });
842
+ await completeLoginProcess(req, res, userForSession);
673
843
 
674
844
  } catch (err) {
675
845
  console.error('[mbkauthe] GitHub login callback error:', err);
676
- res.redirect('/mbkauthe/login?error=internal_error');
846
+ return res.status(500).render('Error/dError.handlebars', {
847
+ layout: false,
848
+ code: '500',
849
+ error: 'Internal Server Error',
850
+ message: 'An error occurred during GitHub authentication. Please try again.',
851
+ page: '/mbkauthe/login',
852
+ pagename: 'Login',
853
+ version: packageJson.version,
854
+ app: mbkautheVar.APP_NAME,
855
+ details: process.env.NODE_ENV === 'development' ? `${err.message}\n${err.stack}` : 'Error details hidden in production'
856
+ });
677
857
  }
678
858
  }
679
859
  );
680
- */
860
+
681
861
  export { getLatestVersion };
682
862
  export default router;
package/lib/pool.js CHANGED
@@ -36,8 +36,8 @@ const poolConfig = {
36
36
  // - keep max small to avoid exhausting DB connections
37
37
  // - reduce idle time so connections are returned sooner
38
38
  // - set a short connection timeout to fail fast
39
- max: 10,
40
- idleTimeoutMillis: 50000,
39
+ max: 20,
40
+ idleTimeoutMillis: 10000,
41
41
  connectionTimeoutMillis: 5000,
42
42
  };
43
43