mbkauthe 4.8.4 → 5.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.
@@ -1,15 +1,213 @@
1
1
  import express from "express";
2
+ import rateLimit from "express-rate-limit";
2
3
  import { renderError } from "#response.js";
3
4
  import { dblogin } from "#pool.js";
4
5
  import { getQueryCount, getQueryLog, resetQueryCount, resetQueryLog } from "../utils/dbQueryLogger.js";
5
6
  import { mbkautheVar } from "#config.js";
6
- import rateLimit from 'express-rate-limit';
7
7
 
8
8
  const router = express.Router();
9
9
 
10
10
  const isDbLogsEnabled = () => process.env.env === "dev" && process.env.dbLogs === "true";
11
11
 
12
- // Rate limiter for info/test routes
12
+ const clampLimit = (value, fallback = 50, max = 500) => {
13
+ const parsed = Number(value);
14
+ if (!Number.isFinite(parsed) || parsed < 1) {
15
+ return fallback;
16
+ }
17
+ return Math.min(max, Math.floor(parsed));
18
+ };
19
+
20
+ const normalizeStringFilter = (value) => {
21
+ if (typeof value !== "string") return "";
22
+ return value.trim();
23
+ };
24
+
25
+ const parseSuccessFilter = (value) => {
26
+ if (value === true || value === "true") return true;
27
+ if (value === false || value === "false") return false;
28
+ return null;
29
+ };
30
+
31
+ const getRawQueryLog = () => {
32
+ if (typeof getQueryLog === "function") return getQueryLog();
33
+ if (typeof dblogin.getQueryLog === "function") return dblogin.getQueryLog();
34
+ return [];
35
+ };
36
+
37
+ const filterQueryLog = (queryLog, filters) => {
38
+ const poolName = normalizeStringFilter(filters.pool);
39
+ const username = normalizeStringFilter(filters.username).toLowerCase();
40
+ const url = normalizeStringFilter(filters.url).toLowerCase();
41
+ const success = parseSuccessFilter(filters.success);
42
+
43
+ return queryLog.filter((entry) => {
44
+ if (poolName && (entry?.pool?.name || "") !== poolName) {
45
+ return false;
46
+ }
47
+
48
+ if (username) {
49
+ const candidate = String(entry?.request?.username || entry?.request?.userId || "").toLowerCase();
50
+ if (!candidate.includes(username)) {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ if (url) {
56
+ const candidateUrl = String(entry?.request?.url || "").toLowerCase();
57
+ if (!candidateUrl.includes(url)) {
58
+ return false;
59
+ }
60
+ }
61
+
62
+ if (success !== null && Boolean(entry?.success) !== success) {
63
+ return false;
64
+ }
65
+
66
+ return true;
67
+ });
68
+ };
69
+
70
+ const sortQueryLogNewestFirst = (queryLog) =>
71
+ [...queryLog].sort((a, b) => {
72
+ const left = Date.parse(b?.time || "") || 0;
73
+ const right = Date.parse(a?.time || "") || 0;
74
+ return left - right;
75
+ });
76
+
77
+ const average = (numbers) => {
78
+ if (!numbers.length) return 0;
79
+ return numbers.reduce((sum, value) => sum + value, 0) / numbers.length;
80
+ };
81
+
82
+ const buildSummary = (queryLog) => {
83
+ const durations = queryLog
84
+ .map((entry) => Number(entry?.durationMs))
85
+ .filter((value) => Number.isFinite(value));
86
+ const executionDurations = queryLog
87
+ .map((entry) => Number(entry?.executionDurationMs))
88
+ .filter((value) => Number.isFinite(value));
89
+ const waitDurations = queryLog
90
+ .map((entry) => Number(entry?.poolWait?.waitMs))
91
+ .filter((value) => Number.isFinite(value));
92
+ const errorCount = queryLog.filter((entry) => entry?.success === false).length;
93
+ const pressuredQueries = queryLog.filter((entry) => entry?.poolWait?.hadPoolPressure).length;
94
+
95
+ const slowestQueries = [...queryLog]
96
+ .sort((a, b) => (Number(b?.durationMs) || 0) - (Number(a?.durationMs) || 0))
97
+ .slice(0, 5)
98
+ .map((entry) => ({
99
+ time: entry.time,
100
+ query: entry.query,
101
+ name: entry.name,
102
+ fingerprint: entry.fingerprint,
103
+ durationMs: entry.durationMs,
104
+ executionDurationMs: entry.executionDurationMs,
105
+ waitMs: entry.poolWait?.waitMs || 0,
106
+ success: entry.success,
107
+ request: entry.request,
108
+ pool: entry.pool,
109
+ }));
110
+
111
+ const repeatedGroupsMap = new Map();
112
+ for (const entry of queryLog) {
113
+ const key = entry?.fingerprint || entry?.normalizedQuery || entry?.query;
114
+ if (!key) continue;
115
+
116
+ const existing = repeatedGroupsMap.get(key);
117
+ if (existing) {
118
+ existing.count += 1;
119
+ existing.totalDurationMs += Number(entry?.durationMs) || 0;
120
+ existing.totalExecutionDurationMs += Number(entry?.executionDurationMs) || 0;
121
+ existing.totalWaitMs += Number(entry?.poolWait?.waitMs) || 0;
122
+ existing.errorCount += entry?.success === false ? 1 : 0;
123
+ if ((Date.parse(entry?.time || "") || 0) > (Date.parse(existing.lastSeen || "") || 0)) {
124
+ existing.lastSeen = entry.time;
125
+ }
126
+ continue;
127
+ }
128
+
129
+ repeatedGroupsMap.set(key, {
130
+ fingerprint: entry.fingerprint,
131
+ normalizedQuery: entry.normalizedQuery,
132
+ sampleQuery: entry.query,
133
+ sampleName: entry.name,
134
+ poolName: entry?.pool?.name || null,
135
+ requestUrl: entry?.request?.url || null,
136
+ count: 1,
137
+ totalDurationMs: Number(entry?.durationMs) || 0,
138
+ totalExecutionDurationMs: Number(entry?.executionDurationMs) || 0,
139
+ totalWaitMs: Number(entry?.poolWait?.waitMs) || 0,
140
+ errorCount: entry?.success === false ? 1 : 0,
141
+ lastSeen: entry.time,
142
+ });
143
+ }
144
+
145
+ const repeatedGroups = [...repeatedGroupsMap.values()]
146
+ .filter((group) => group.count > 1)
147
+ .sort((a, b) => {
148
+ if (b.count !== a.count) return b.count - a.count;
149
+ return b.totalDurationMs - a.totalDurationMs;
150
+ })
151
+ .slice(0, 8)
152
+ .map((group) => ({
153
+ fingerprint: group.fingerprint,
154
+ normalizedQuery: group.normalizedQuery,
155
+ sampleQuery: group.sampleQuery,
156
+ sampleName: group.sampleName,
157
+ poolName: group.poolName,
158
+ requestUrl: group.requestUrl,
159
+ count: group.count,
160
+ avgDurationMs: group.totalDurationMs / group.count,
161
+ avgExecutionDurationMs: group.totalExecutionDurationMs / group.count,
162
+ avgWaitMs: group.totalWaitMs / group.count,
163
+ errorCount: group.errorCount,
164
+ lastSeen: group.lastSeen,
165
+ }));
166
+
167
+ return {
168
+ totalVisible: queryLog.length,
169
+ avgDurationMs: average(durations),
170
+ avgExecutionDurationMs: average(executionDurations),
171
+ avgWaitMs: average(waitDurations),
172
+ errorCount,
173
+ pressuredQueries,
174
+ slowestQueries,
175
+ repeatedGroups,
176
+ };
177
+ };
178
+
179
+ const buildResponsePayload = (req) => {
180
+ const queryCount = typeof getQueryCount === "function"
181
+ ? getQueryCount()
182
+ : typeof dblogin.getQueryCount === "function"
183
+ ? dblogin.getQueryCount()
184
+ : 0;
185
+ const queryLimit = clampLimit(req.query.limit);
186
+ const filters = {
187
+ pool: normalizeStringFilter(req.query.pool),
188
+ username: normalizeStringFilter(req.query.username),
189
+ url: normalizeStringFilter(req.query.url),
190
+ success: parseSuccessFilter(req.query.success),
191
+ };
192
+ const filtered = filterQueryLog(getRawQueryLog(), filters);
193
+ const ordered = sortQueryLogNewestFirst(filtered);
194
+ const queryLog = ordered.slice(0, queryLimit);
195
+ const summary = buildSummary(queryLog);
196
+
197
+ return {
198
+ queryCount,
199
+ queryLimit,
200
+ filters: {
201
+ pool: filters.pool,
202
+ username: filters.username,
203
+ url: filters.url,
204
+ success: filters.success,
205
+ },
206
+ summary,
207
+ queryLog,
208
+ };
209
+ };
210
+
13
211
  const LogLimit = rateLimit({
14
212
  windowMs: 1 * 60 * 1000,
15
213
  max: 50,
@@ -19,15 +217,14 @@ const LogLimit = rateLimit({
19
217
  },
20
218
  validate: {
21
219
  trustProxy: false,
22
- xForwardedForHeader: false
23
- }
220
+ xForwardedForHeader: false,
221
+ },
24
222
  });
25
223
 
26
- // DB stats API (JSON)
27
224
  router.get(["/db.json"], LogLimit, async (req, res) => {
28
225
  try {
29
226
  const isDev = isDbLogsEnabled();
30
- const queryLimit = Number(req.query.limit) || 50;
227
+ const queryLimit = clampLimit(req.query.limit);
31
228
 
32
229
  if (!isDev) {
33
230
  return res.status(403).json({
@@ -36,21 +233,33 @@ router.get(["/db.json"], LogLimit, async (req, res) => {
36
233
  isDev,
37
234
  queryCount: 0,
38
235
  queryLimit,
39
- queryLog: []
236
+ filters: {
237
+ pool: normalizeStringFilter(req.query.pool),
238
+ username: normalizeStringFilter(req.query.username),
239
+ url: normalizeStringFilter(req.query.url),
240
+ success: parseSuccessFilter(req.query.success),
241
+ },
242
+ summary: {
243
+ totalVisible: 0,
244
+ avgDurationMs: 0,
245
+ avgExecutionDurationMs: 0,
246
+ avgWaitMs: 0,
247
+ errorCount: 0,
248
+ pressuredQueries: 0,
249
+ slowestQueries: [],
250
+ repeatedGroups: [],
251
+ },
252
+ queryLog: [],
40
253
  });
41
254
  }
42
255
 
43
- const queryCount = typeof getQueryCount === 'function' ? getQueryCount() : (typeof dblogin.getQueryCount === 'function' ? dblogin.getQueryCount() : 0);
44
- const queryLog = typeof getQueryLog === 'function' ? getQueryLog({ limit: queryLimit }) : (typeof dblogin.getQueryLog === 'function' ? dblogin.getQueryLog({ limit: queryLimit }) : []);
45
-
46
- return res.json({ queryCount, queryLimit, queryLog, isDev });
256
+ return res.json({ ...buildResponsePayload(req), isDev });
47
257
  } catch (err) {
48
- console.error(`[mbkauthe] /db.json route error:`, err);
49
- return res.status(500).json({ success: false, message: 'Could not fetch DB stats.' });
258
+ console.error("[mbkauthe] /db.json route error:", err);
259
+ return res.status(500).json({ success: false, message: "Could not fetch DB stats." });
50
260
  }
51
261
  });
52
262
 
53
- // Dedicated reset API for DB logs and counters
54
263
  router.post(["/db/reset"], LogLimit, async (req, res) => {
55
264
  try {
56
265
  const isDev = isDbLogsEnabled();
@@ -58,39 +267,48 @@ router.post(["/db/reset"], LogLimit, async (req, res) => {
58
267
  return res.status(403).json({
59
268
  success: false,
60
269
  message: "DB logs are disabled.",
61
- isDev
270
+ isDev,
62
271
  });
63
272
  }
64
273
 
65
- if (typeof resetQueryCount === 'function') resetQueryCount();
66
- else if (typeof dblogin.resetQueryCount === 'function') dblogin.resetQueryCount();
274
+ if (typeof resetQueryCount === "function") resetQueryCount();
275
+ else if (typeof dblogin.resetQueryCount === "function") dblogin.resetQueryCount();
67
276
 
68
- if (typeof resetQueryLog === 'function') resetQueryLog();
69
- else if (typeof dblogin.resetQueryLog === 'function') dblogin.resetQueryLog();
277
+ if (typeof resetQueryLog === "function") resetQueryLog();
278
+ else if (typeof dblogin.resetQueryLog === "function") dblogin.resetQueryLog();
70
279
 
71
- return res.json({ success: true, message: 'Query log and count have been reset.' });
280
+ return res.json({ success: true, message: "Query log and count have been reset." });
72
281
  } catch (err) {
73
- console.error(`[mbkauthe] /db/reset route error:`, err);
74
- return res.status(500).json({ success: false, message: 'Could not reset DB stats.' });
282
+ console.error("[mbkauthe] /db/reset route error:", err);
283
+ return res.status(500).json({ success: false, message: "Could not reset DB stats." });
75
284
  }
76
285
  });
77
286
 
78
- // DB stats page (HTML)
79
287
  router.get(["/db"], LogLimit, async (req, res) => {
80
288
  try {
81
289
  const isDev = isDbLogsEnabled();
82
- const queryLimit = Number(req.query.limit) || 50;
83
- const resetDone = req.query.resetDone === '1';
84
- return res.render('pages/dbLogs.handlebars', {
290
+ const queryLimit = clampLimit(req.query.limit);
291
+ const resetDone = req.query.resetDone === "1";
292
+ const successFilter = parseSuccessFilter(req.query.success);
293
+
294
+ return res.render("pages/dbLogs.handlebars", {
85
295
  layout: false,
86
296
  appName: mbkautheVar.APP_NAME,
87
297
  queryLimit,
88
298
  resetDone,
89
299
  isDev,
90
- disabledMessage: isDev ? null : 'DB logs are disabled.'
300
+ filters: {
301
+ pool: normalizeStringFilter(req.query.pool),
302
+ username: normalizeStringFilter(req.query.username),
303
+ url: normalizeStringFilter(req.query.url),
304
+ successAny: successFilter === null,
305
+ successTrue: successFilter === true,
306
+ successFalse: successFilter === false,
307
+ },
308
+ disabledMessage: isDev ? null : "DB logs are disabled.",
91
309
  });
92
310
  } catch (err) {
93
- console.error(`[mbkauthe] /db route error:`, err);
311
+ console.error("[mbkauthe] /db route error:", err);
94
312
  return renderError(res, req, {
95
313
  layout: false,
96
314
  code: 500,
@@ -102,4 +320,4 @@ router.get(["/db"], LogLimit, async (req, res) => {
102
320
  }
103
321
  });
104
322
 
105
- export default router;
323
+ export default router;
@@ -7,10 +7,12 @@ 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";
10
+ import { AuthRepository } from "../db/AuthRepository.js";
10
11
  import { fileURLToPath } from "url";
11
12
  import path from "path";
12
13
  import fs from "fs";
13
14
  import dotenv from "dotenv";
15
+ import { createLogger } from "../utils/logger.js";
14
16
 
15
17
  dotenv.config();
16
18
 
@@ -18,6 +20,8 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
20
 
19
21
 
20
22
  const router = express.Router();
23
+ const authRepo = new AuthRepository({ db: dblogin });
24
+ const logMisc = createLogger("misc");
21
25
  // Rate limiter for info/test routes
22
26
  const LoginLimit = rateLimit({
23
27
  windowMs: 1 * 60 * 1000,
@@ -100,14 +104,10 @@ router.get('/user/profilepic', async (req, res) => {
100
104
 
101
105
  // If not in cache, fetch from DB
102
106
  if (!imageUrl) {
103
- const result = await dblogin.query({
104
- name: 'get-user-profile-pic',
105
- text: 'SELECT "Image" FROM "Users" WHERE "UserName" = $1 LIMIT 1',
106
- values: [username]
107
- });
107
+ const profile = await authRepo.getUserImageByUsername(username, 'get-user-profile-pic');
108
108
 
109
- if (result.rows.length > 0 && result.rows[0].Image && result.rows[0].Image.trim() !== '') {
110
- imageUrl = result.rows[0].Image;
109
+ if (profile && profile.Image && profile.Image.trim() !== '') {
110
+ imageUrl = profile.Image;
111
111
  } else {
112
112
  imageUrl = 'default';
113
113
  }
@@ -229,31 +229,13 @@ router.get('/api/checkSession', LoginLimit, async (req, res) => {
229
229
  }
230
230
 
231
231
  // Single round-trip: fetch app-session expiry and (if needed) connect-pg-simple expiry.
232
- const result = await dblogin.query({
233
- name: 'check-session-validity',
234
- text: `
235
- SELECT
236
- s.expires_at,
237
- u."Active",
238
- CASE
239
- WHEN s.expires_at IS NULL THEN (SELECT expire FROM "session" WHERE sid = $2)
240
- ELSE NULL
241
- END AS connect_expire
242
- FROM "Sessions" s
243
- JOIN "Users" u ON s."UserName" = u."UserName"
244
- WHERE s.id = $1
245
- LIMIT 1
246
- `,
247
- values: [sessionId, req.sessionID]
248
- });
232
+ const row = await authRepo.getSessionValidity(sessionId, req.sessionID, 'check-session-validity');
249
233
 
250
- if (result.rows.length === 0) {
234
+ if (!row) {
251
235
  req.session.destroy(() => { });
252
236
  clearSessionCookies(res);
253
237
  return res.status(200).json({ sessionValid: false, expiry: null });
254
238
  }
255
-
256
- const row = result.rows[0];
257
239
  if ((row.expires_at && new Date(row.expires_at) <= new Date()) || !row.Active) {
258
240
  req.session.destroy(() => { });
259
241
  clearSessionCookies(res);
@@ -298,17 +280,8 @@ function normalizeSessionIdFromBody(body = {}) {
298
280
  }
299
281
 
300
282
  async function getSessionValidationRow(sessionId, queryName = 'check-session-validity-by-id') {
301
- const result = await dblogin.query({
302
- name: queryName,
303
- text: `SELECT s.expires_at, u."Active", u."UserName", u."Role" FROM "Sessions" s JOIN "Users" u ON s."UserName" = u."UserName" WHERE s.id = $1 LIMIT 1`,
304
- values: [sessionId]
305
- });
306
-
307
- if (result.rows.length === 0) {
308
- return null;
309
- }
310
-
311
- return result.rows[0];
283
+ const row = await authRepo.getSessionValidationRow(sessionId, queryName);
284
+ return row || null;
312
285
  }
313
286
 
314
287
  function isSessionRowValid(row) {
@@ -501,9 +474,9 @@ export async function checkVersion() {
501
474
  if (hasValidLatest && latestVersion !== packageJson.version) {
502
475
  console.warn(`[mbkauthe] Current version (${packageJson.version}) is outdated. Latest version: ${latestVersion}. Consider updating mbkauthe.`);
503
476
  } else if (hasValidLatest) {
504
- console.info(`[mbkauthe] Running latest version (${packageJson.version}).`);
477
+ logMisc(`Running latest version (${packageJson.version}).`);
505
478
  } else {
506
- console.info(`[mbkauthe] Skipped version check warning: latest version unavailable.`);
479
+ logMisc(`Skipped version check warning: latest version unavailable.`);
507
480
  }
508
481
  } catch (error) {
509
482
  console.warn(`[mbkauthe] Failed to check for updates: ${error.message}`);
@@ -567,25 +540,19 @@ router.post("/api/terminateAllSessions", AdminOperationLimit, authenticate(mbkau
567
540
  try {
568
541
  // Run both operations in parallel for better performance
569
542
  await Promise.all([
570
- dblogin.query({
571
- name: 'terminate-all-app-sessions',
572
- text: 'DELETE FROM "Sessions"'
573
- }),
574
- dblogin.query({
575
- name: 'terminate-all-db-sessions',
576
- text: 'DELETE FROM "session" WHERE expire > NOW()'
577
- })
543
+ authRepo.deleteAllAppSessions('terminate-all-app-sessions'),
544
+ authRepo.deleteActiveSessionStoreRows('terminate-all-db-sessions')
578
545
  ]);
579
546
 
580
547
  req.session.destroy((err) => {
581
548
  if (err) {
582
- console.log(`[mbkauthe] Error destroying session:`, err);
549
+ console.error(`[mbkauthe] Error destroying session:`, err);
583
550
  return res.status(500).json({ success: false, message: "Failed to terminate sessions" });
584
551
  }
585
552
 
586
553
  clearSessionCookies(res);
587
554
 
588
- console.log(`[mbkauthe] All sessions terminated successfully`);
555
+ logMisc(`All sessions terminated successfully`);
589
556
  res.status(200).json({
590
557
  success: true,
591
558
  message: "All sessions terminated successfully",
@@ -8,8 +8,12 @@ import { dblogin } from "#pool.js";
8
8
  import { mbkautheVar } from "#config.js";
9
9
  import { renderError } from "../utils/response.js";
10
10
  import { checkTrustedDevice, completeLoginProcess } from "./auth.js";
11
+ import { AuthRepository } from "../db/AuthRepository.js";
12
+ import { createLogger } from "../utils/logger.js";
11
13
 
12
14
  const router = express.Router();
15
+ const authRepo = new AuthRepository({ db: dblogin });
16
+ const logOAuth = createLogger("oauth");
13
17
 
14
18
  // CSRF protection middleware
15
19
  const csrfProtection = csurf({ cookie: true });
@@ -39,28 +43,19 @@ const githubClientSecret = mbkautheVar.GITHUB_APP_CLIENT_SECRET || mbkautheVar.G
39
43
  // Common OAuth strategy handler
40
44
  const createOAuthStrategy = async (provider, profile, done) => {
41
45
  try {
42
- console.log(`[mbkauthe] ${provider} OAuth callback for user: ${profile.emails?.[0]?.value || profile.id}`);
46
+ logOAuth(`${provider} OAuth callback for user: ${profile.emails?.[0]?.value || profile.id}`);
43
47
 
44
48
  const isGitHub = provider === 'GitHub';
45
- const tableName = isGitHub ? 'user_github' : 'user_google';
46
- const idField = isGitHub ? 'github_id' : 'google_id';
47
- const queryName = isGitHub ? 'github-login-get-user' : 'google-login-get-user';
48
49
 
49
50
  // Check if this OAuth account is linked to any user
50
- const oauthUser = await dblogin.query({
51
- name: queryName,
52
- text: `SELECT ug.*, u."UserName", u."Role", u."Active", u."AllowedApps", u."id" FROM ${tableName} ug JOIN "Users" u ON ug.user_name = u."UserName" WHERE ug.${idField} = $1`,
53
- values: [profile.id]
54
- });
51
+ const user = await authRepo.getOAuthUserByProviderId(provider, profile.id);
55
52
 
56
- if (oauthUser.rows.length === 0) {
53
+ if (!user) {
57
54
  const error = new Error(`${provider} account not linked to any user`);
58
55
  error.code = `${provider.toUpperCase()}_NOT_LINKED`;
59
56
  return done(error);
60
57
  }
61
58
 
62
- const user = oauthUser.rows[0];
63
-
64
59
  // Check if the user account is active
65
60
  if (!user.Active) {
66
61
  const error = new Error('Account is inactive');
@@ -83,6 +78,8 @@ const createOAuthStrategy = async (provider, profile, done) => {
83
78
  id: user.id,
84
79
  username: user.UserName,
85
80
  role: user.Role,
81
+ allowedApps: user.AllowedApps,
82
+ TwoFAStatus: user.TwoFAStatus,
86
83
  };
87
84
 
88
85
  if (isGitHub) {
@@ -149,7 +146,7 @@ if ((mbkautheVar.GOOGLE_LOGIN_ENABLED || "").toLowerCase() === "true") {
149
146
 
150
147
  // Print consolidated OAuth summary
151
148
  if (enabledProviders.length > 0) {
152
- console.log(`[mbkauthe] Social providers: ${enabledProviders.join(', ')}`);
149
+ logOAuth(`Social providers: ${enabledProviders.join(', ')}`);
153
150
  }
154
151
 
155
152
  // Serialize/Deserialize user for OAuth login
@@ -182,7 +179,7 @@ const createOAuthInitiation = (provider, enabledFlag, clientIdFlag, clientSecret
182
179
  // Store CSRF token for validation on callback
183
180
  const csrfToken = req.csrfToken();
184
181
  req.session.oauthCsrfToken = csrfToken;
185
- console.log(`[mbkauthe] ${provider} OAuth initiation started`);
182
+ logOAuth(`${provider} OAuth initiation started`);
186
183
 
187
184
  // Store redirect parameter in session before OAuth flow
188
185
  const redirect = req.query.redirect;
@@ -207,7 +204,7 @@ const createOAuthInitiation = (provider, enabledFlag, clientIdFlag, clientSecret
207
204
  pagename: 'Login'
208
205
  });
209
206
  }
210
- console.log(`[mbkauthe] ${provider} OAuth session saved successfully`);
207
+ logOAuth(`${provider} OAuth session saved successfully`);
211
208
  passport.authenticate(`${provider.toLowerCase()}-login`, { state: csrfToken })(req, res, next);
212
209
  });
213
210
  } else {
@@ -299,39 +296,11 @@ const validateOAuthCallback = (req, res) => {
299
296
  return true;
300
297
  };
301
298
 
302
- const finishProviderLogin = async (req, res, provider, username, detailValue = '') => {
303
- const userQuery = `
304
- SELECT u.id, u."UserName", u."Active", u."Role", u."AllowedApps",
305
- tfa."TwoFAStatus"
306
- FROM "Users" u
307
- LEFT JOIN "TwoFA" tfa ON u."UserName" = tfa."UserName"
308
- WHERE u."UserName" = $1
309
- `;
310
-
311
- const userResult = await dblogin.query({
312
- name: `${provider.toLowerCase()}-callback-get-user`,
313
- text: userQuery,
314
- values: [username]
315
- });
316
-
317
- if (userResult.rows.length === 0) {
318
- console.error(`[mbkauthe] ${provider} login: User not found: ${username}`);
319
- return renderError(res, req, {
320
- code: 404,
321
- error: 'User Not Found',
322
- message: `Your ${provider} account is linked, but the user account no longer exists in our system.`,
323
- page: '/mbkauthe/login',
324
- pagename: 'Login',
325
- details: `${provider} identifier: ${detailValue}\nPlease contact your administrator.`
326
- });
327
- }
328
-
329
- const user = userResult.rows[0];
330
-
299
+ const finishProviderLogin = async (req, res, provider, user, detailValue = '') => {
331
300
  // Check for trusted device
332
301
  const trustedDeviceUser = await checkTrustedDevice(req, user.UserName);
333
302
  if (trustedDeviceUser && (mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLowerCase() === "true" && user.TwoFAStatus) {
334
- console.log(`[mbkauthe] ${provider} trusted device login for user: ${user.UserName}, skipping 2FA only`);
303
+ logOAuth(`${provider} trusted device login for user: ${user.UserName}, skipping 2FA only`);
335
304
  return await handleOAuthRedirect(req, res, user, 'trusted', provider.toLowerCase());
336
305
  }
337
306
 
@@ -347,7 +316,7 @@ const finishProviderLogin = async (req, res, provider, username, detailValue = '
347
316
  loginMethod: provider.toLowerCase(),
348
317
  redirectUrl: oauthRedirect || null
349
318
  };
350
- console.log(`[mbkauthe] ${provider} login: 2FA required for user: ${username}`);
319
+ logOAuth(`${provider} login: 2FA required for user: ${user.UserName}`);
351
320
  return res.redirect('/mbkauthe/2fa');
352
321
  }
353
322
 
@@ -394,7 +363,13 @@ const createOAuthCallback = (provider, strategy) => {
394
363
  req,
395
364
  res,
396
365
  provider,
397
- oauthUser.username,
366
+ {
367
+ id: oauthUser.id,
368
+ UserName: oauthUser.username,
369
+ Role: oauthUser.role,
370
+ AllowedApps: oauthUser.allowedApps,
371
+ TwoFAStatus: oauthUser.TwoFAStatus
372
+ },
398
373
  provider === 'GitHub' ? (oauthUser.githubUsername || oauthUser.username) : (oauthUser.googleEmail || oauthUser.username)
399
374
  );
400
375
  } catch (err) {
@@ -436,7 +411,7 @@ const handleOAuthRedirect = async (req, res, user, type, method = null) => {
436
411
  res.json = function (data) {
437
412
  if (data.success && statusCode === 200) {
438
413
  const redirectUrl = oauthRedirect || mbkautheVar.loginRedirectURL || '/dashboard';
439
- console.log(`[mbkauthe] ${method || 'social'} ${type} login: Redirecting to ${redirectUrl}`);
414
+ logOAuth(`${method || 'social'} ${type} login: Redirecting to ${redirectUrl}`);
440
415
  res.json = originalJson;
441
416
  res.status = originalStatus;
442
417
  return res.redirect(redirectUrl);