mbkauthe 3.0.0 → 3.2.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,6 +1,8 @@
1
1
  import express from "express";
2
2
  import passport from 'passport';
3
3
  import GitHubStrategy from 'passport-github2';
4
+ import GoogleStrategy from 'passport-google-oauth20';
5
+ import csurf from 'csurf';
4
6
  import rateLimit from 'express-rate-limit';
5
7
  import { dblogin } from "../database/pool.js";
6
8
  import { mbkautheVar } from "../config/index.js";
@@ -9,72 +11,124 @@ import { checkTrustedDevice, completeLoginProcess } from "./auth.js";
9
11
 
10
12
  const router = express.Router();
11
13
 
14
+ // CSRF protection middleware
15
+ const csrfProtection = csurf({ cookie: true });
16
+
12
17
  // Rate limiter for OAuth routes
13
- const GitHubOAuthLimit = rateLimit({
18
+ const createOAuthLimit = (provider) => rateLimit({
14
19
  windowMs: 5 * 60 * 1000,
15
20
  max: 10,
16
- message: "Too many GitHub login attempts, please try again later"
21
+ message: `Too many ${provider} login attempts, please try again later`,
22
+ standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
23
+ legacyHeaders: false, // Disable the `X-RateLimit-*` headers
24
+ // Skip validation of X-Forwarded-For header to prevent errors
25
+ validate: {
26
+ xForwardedForHeader: false,
27
+ trustProxy: false
28
+ },
29
+ // Use a more robust key generator that handles proxy scenarios
30
+ keyGenerator: (req) => {
31
+ // Use IP from connection if available, otherwise fallback to a default
32
+ return req.ip || req.connection?.remoteAddress || req.socket?.remoteAddress || 'unknown';
33
+ }
17
34
  });
18
35
 
19
- // Configure GitHub Strategy for login
20
- passport.use('github-login', new GitHubStrategy({
21
- clientID: mbkautheVar.GITHUB_CLIENT_ID,
22
- clientSecret: mbkautheVar.GITHUB_CLIENT_SECRET,
23
- callbackURL: '/mbkauthe/api/github/login/callback',
24
- scope: ['user:email']
25
- },
26
- async (accessToken, refreshToken, profile, done) => {
27
- try {
28
- // Check if this GitHub account is linked to any user
29
- const githubUser = await dblogin.query({
30
- name: 'github-login-get-user',
31
- 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',
32
- values: [profile.id]
33
- });
36
+ const GitHubOAuthLimit = createOAuthLimit('GitHub');
37
+ const GoogleOAuthLimit = createOAuthLimit('Google');
38
+
39
+ // Common OAuth strategy handler
40
+ const createOAuthStrategy = async (provider, profile, done) => {
41
+ try {
42
+ console.log(`[mbkauthe] ${provider} OAuth callback for user: ${profile.emails?.[0]?.value || profile.id}`);
43
+
44
+ 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
+ // 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
+ });
34
55
 
35
- if (githubUser.rows.length === 0) {
36
- // GitHub account is not linked to any user
37
- const error = new Error('GitHub account not linked to any user');
38
- error.code = 'GITHUB_NOT_LINKED';
39
- return done(error);
40
- }
56
+ if (oauthUser.rows.length === 0) {
57
+ const error = new Error(`${provider} account not linked to any user`);
58
+ error.code = `${provider.toUpperCase()}_NOT_LINKED`;
59
+ return done(error);
60
+ }
61
+
62
+ const user = oauthUser.rows[0];
41
63
 
42
- const user = githubUser.rows[0];
64
+ // Check if the user account is active
65
+ if (!user.Active) {
66
+ const error = new Error('Account is inactive');
67
+ error.code = 'ACCOUNT_INACTIVE';
68
+ return done(error);
69
+ }
43
70
 
44
- // Check if the user account is active
45
- if (!user.Active) {
46
- const error = new Error('Account is inactive');
47
- error.code = 'ACCOUNT_INACTIVE';
71
+ // Check if user is authorized for this app
72
+ if (user.Role !== "SuperAdmin") {
73
+ const allowedApps = user.AllowedApps;
74
+ if (!allowedApps || !allowedApps.some(app => app && app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
75
+ const error = new Error(`Not authorized to use ${mbkautheVar.APP_NAME}`);
76
+ error.code = 'NOT_AUTHORIZED';
48
77
  return done(error);
49
78
  }
79
+ }
50
80
 
51
- // Check if user is authorized for this app (same logic as regular login)
52
- if (user.Role !== "SuperAdmin") {
53
- const allowedApps = user.AllowedApps;
54
- if (!allowedApps || !allowedApps.some(app => app && app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
55
- const error = new Error(`Not authorized to use ${mbkautheVar.APP_NAME}`);
56
- error.code = 'NOT_AUTHORIZED';
57
- return done(error);
58
- }
59
- }
81
+ // Return user data for login
82
+ const userData = {
83
+ id: user.id,
84
+ username: user.UserName,
85
+ role: user.Role,
86
+ };
87
+
88
+ if (isGitHub) {
89
+ userData.githubId = user.github_id;
90
+ userData.githubUsername = user.github_username;
91
+ } else {
92
+ userData.googleId = user.google_id;
93
+ userData.googleEmail = user.google_email;
94
+ }
60
95
 
61
- // Return user data for login
62
- return done(null, {
63
- id: user.id,
64
- username: user.UserName,
65
- role: user.Role,
66
- githubId: user.github_id,
67
- githubUsername: user.github_username
68
- });
69
- } catch (err) {
70
- console.error('[mbkauthe] GitHub login error:', err);
71
- err.code = err.code || 'GITHUB_AUTH_ERROR';
72
- return done(err);
96
+ return done(null, userData);
97
+ } catch (err) {
98
+ console.error(`[mbkauthe] ${provider} login error:`, err);
99
+
100
+ // Handle specific OAuth errors
101
+ if (err.name === 'TokenError' || err.code === 'invalid_grant') {
102
+ err.code = 'invalid_grant';
103
+ err.message = 'OAuth token validation failed. This may be due to an expired authorization code or clock synchronization issues.';
104
+ } else {
105
+ err.code = err.code || `${provider.toUpperCase()}_AUTH_ERROR`;
73
106
  }
107
+ return done(err);
74
108
  }
109
+ };
110
+
111
+ // Configure GitHub Strategy for login
112
+ passport.use('github-login', new GitHubStrategy({
113
+ clientID: mbkautheVar.GITHUB_CLIENT_ID,
114
+ clientSecret: mbkautheVar.GITHUB_CLIENT_SECRET,
115
+ callbackURL: '/mbkauthe/api/github/login/callback',
116
+ scope: ['user:email']
117
+ }, (accessToken, refreshToken, profile, done) =>
118
+ createOAuthStrategy('GitHub', profile, done)
75
119
  ));
76
120
 
77
- // Serialize/Deserialize user for GitHub login
121
+ // Configure Google Strategy for login
122
+ passport.use('google-login', new GoogleStrategy({
123
+ clientID: mbkautheVar.GOOGLE_CLIENT_ID,
124
+ clientSecret: mbkautheVar.GOOGLE_CLIENT_SECRET,
125
+ callbackURL: '/mbkauthe/api/google/login/callback',
126
+ scope: ['profile', 'email']
127
+ }, (accessToken, refreshToken, profile, done) =>
128
+ createOAuthStrategy('Google', profile, done)
129
+ ));
130
+
131
+ // Serialize/Deserialize user for OAuth login
78
132
  passport.serializeUser((user, done) => {
79
133
  done(null, user);
80
134
  });
@@ -83,243 +137,304 @@ passport.deserializeUser((user, done) => {
83
137
  done(null, user);
84
138
  });
85
139
 
86
- // GitHub login initiation
87
- router.get('/api/github/login', GitHubOAuthLimit, (req, res, next) => {
88
- if (mbkautheVar.GITHUB_LOGIN_ENABLED) {
89
- // Store redirect parameter in session before OAuth flow (validate to prevent open redirect)
90
- const redirect = req.query.redirect;
91
- if (redirect && typeof redirect === 'string') {
92
- // Only allow relative URLs or same-origin URLs to prevent open redirect attacks
93
- if (redirect.startsWith('/') && !redirect.startsWith('//')) {
94
- req.session.oauthRedirect = redirect;
95
- } else {
96
- console.warn(`[mbkauthe] Invalid redirect parameter rejected: ${redirect}`);
97
- }
98
- }
99
- passport.authenticate('github-login')(req, res, next);
100
- }
101
- else {
102
- return renderError(res, {
103
- code: '403',
104
- error: 'GitHub Login Disabled',
105
- message: 'GitHub login is currently disabled. Please use your username and password to log in.',
106
- page: '/mbkauthe/login',
107
- pagename: 'Login',
108
- });
109
- }
110
- });
111
-
112
- // GitHub login callback
113
- router.get('/api/github/login/callback',
114
- GitHubOAuthLimit,
115
- (req, res, next) => {
116
- passport.authenticate('github-login', {
117
- session: false // We'll handle session manually
118
- }, (err, user, info) => {
119
- // Custom error handling for passport authentication
120
- if (err) {
121
- console.error('[mbkauthe] GitHub authentication error:', err);
122
-
123
- // Map error codes to user-friendly messages
124
- switch (err.code) {
125
- case 'GITHUB_NOT_LINKED':
126
- return renderError(res, {
127
- code: '403',
128
- error: 'GitHub Account Not Linked',
129
- 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.',
130
- page: '/mbkauthe/login',
131
- pagename: 'Login'
132
- });
133
-
134
- case 'ACCOUNT_INACTIVE':
135
- return renderError(res, {
136
- code: '403',
137
- error: 'Account Inactive',
138
- message: 'Your account has been deactivated. Please contact your administrator.',
139
- page: '/mbkauthe/login',
140
- pagename: 'Login'
141
- });
142
-
143
- case 'NOT_AUTHORIZED':
144
- return renderError(res, {
145
- code: '403',
146
- error: 'Not Authorized',
147
- message: `You are not authorized to access ${mbkautheVar.APP_NAME}. Please contact your administrator.`,
148
- page: '/mbkauthe/login',
149
- pagename: 'Login'
150
- });
151
-
152
- default:
153
- return renderError(res, {
154
- code: '500',
155
- error: 'Authentication Error',
156
- message: 'An error occurred during GitHub authentication. Please try again.',
157
- page: '/mbkauthe/login',
158
- pagename: 'Login',
159
- details: process.env.NODE_ENV === 'development' ? `${err.message}\n${err.stack}` : 'Error details hidden in production'
160
- });
161
- }
162
- }
163
-
164
- if (!user) {
165
- console.error('[mbkauthe] GitHub callback: No user data received');
140
+ // Common OAuth initiation handler
141
+ const createOAuthInitiation = (provider, enabledFlag, clientIdFlag, clientSecretFlag) => {
142
+ return (req, res, next) => {
143
+ if (enabledFlag) {
144
+ // Validate OAuth configuration for Google
145
+ if (provider === 'Google' && (!clientIdFlag || !clientSecretFlag)) {
146
+ console.error(`[mbkauthe] ${provider} OAuth not properly configured`);
166
147
  return renderError(res, {
167
- code: '401',
168
- error: 'Authentication Failed',
169
- message: 'GitHub authentication failed. Please try again.',
148
+ code: 500,
149
+ error: 'Configuration Error',
150
+ message: `${provider} authentication is not properly configured. Please contact your administrator.`,
170
151
  page: '/mbkauthe/login',
171
152
  pagename: 'Login'
172
153
  });
173
154
  }
174
155
 
175
- // Authentication successful, attach user to request
176
- req.user = user;
177
- next();
178
- })(req, res, next);
179
- },
180
- async (req, res) => {
181
- try {
182
- const githubUser = req.user;
183
-
184
- // Combined query: fetch user data and 2FA status in one query
185
- const userQuery = `
186
- SELECT u.id, u."UserName", u."Active", u."Role", u."AllowedApps",
187
- tfa."TwoFAStatus"
188
- FROM "Users" u
189
- LEFT JOIN "TwoFA" tfa ON u."UserName" = tfa."UserName"
190
- WHERE u."UserName" = $1
191
- `;
192
- const userResult = await dblogin.query({
193
- name: 'github-callback-get-user',
194
- text: userQuery,
195
- values: [githubUser.username]
156
+ // Store CSRF token for validation on callback
157
+ const csrfToken = req.csrfToken();
158
+ req.session.oauthCsrfToken = csrfToken;
159
+ console.log(`[mbkauthe] ${provider} OAuth initiation started`);
160
+
161
+ // Store redirect parameter in session before OAuth flow
162
+ const redirect = req.query.redirect;
163
+ if (redirect && typeof redirect === 'string') {
164
+ // Only allow relative URLs to prevent open redirect attacks
165
+ if (redirect.startsWith('/') && !redirect.startsWith('//')) {
166
+ req.session.oauthRedirect = redirect;
167
+ } else {
168
+ console.warn(`[mbkauthe] Invalid redirect parameter rejected: ${redirect}`);
169
+ }
170
+ }
171
+
172
+ // Save session before OAuth redirect to ensure CSRF token is persisted
173
+ req.session.save((err) => {
174
+ if (err) {
175
+ console.error(`[mbkauthe] ${provider} session save error:`, err);
176
+ return renderError(res, {
177
+ code: 500,
178
+ error: 'Session Error',
179
+ message: 'Failed to initialize OAuth flow. Please try again.',
180
+ page: '/mbkauthe/login',
181
+ pagename: 'Login'
182
+ });
183
+ }
184
+ console.log(`[mbkauthe] ${provider} OAuth session saved successfully`);
185
+ passport.authenticate(`${provider.toLowerCase()}-login`, { state: csrfToken })(req, res, next);
196
186
  });
197
-
198
- if (userResult.rows.length === 0) {
199
- console.error(`[mbkauthe] GitHub login: User not found: ${githubUser.username}`);
200
- return renderError(res, {
201
- code: '404',
202
- error: 'User Not Found',
203
- message: 'Your GitHub account is linked, but the user account no longer exists in our system.',
187
+ } else {
188
+ return renderError(res, {
189
+ code: 403,
190
+ error: `${provider} Login Disabled`,
191
+ message: `${provider} login is currently disabled. Please use your username and password to log in.`,
192
+ page: '/mbkauthe/login',
193
+ pagename: 'Login'
194
+ });
195
+ }
196
+ };
197
+ };
198
+
199
+ // Common OAuth error handler
200
+ const createOAuthErrorHandler = (provider) => {
201
+ return (err) => {
202
+ const providerUpper = provider.toUpperCase();
203
+ switch (err.code) {
204
+ case 'invalid_grant':
205
+ case 'OAUTH_TOKEN_ERROR':
206
+ return {
207
+ code: 400,
208
+ error: 'OAuth Token Error',
209
+ message: `The ${provider} authentication token has expired or is invalid. Please try signing in again.`,
204
210
  page: '/mbkauthe/login',
205
211
  pagename: 'Login',
206
- details: `GitHub username: ${githubUser.username}\nPlease contact your administrator.`
207
- });
208
- }
212
+ details: process.env.NODE_ENV === 'development' ? `OAuth Error: ${err.message}` : 'Please refresh and try again'
213
+ };
209
214
 
210
- const user = userResult.rows[0];
215
+ case `${providerUpper}_NOT_LINKED`:
216
+ return {
217
+ code: 403,
218
+ error: `${provider} Account Not Linked`,
219
+ message: `Your ${provider} account is not linked to any user in our system. To link your ${provider} account, a User must connect their ${provider} account to mbktech account through the user settings.`,
220
+ page: '/mbkauthe/login',
221
+ pagename: 'Login'
222
+ };
211
223
 
212
- // Check for trusted device after OAuth authentication
213
- const trustedDeviceUser = await checkTrustedDevice(req, user.UserName);
214
- if (trustedDeviceUser && (mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLowerCase() === "true" && user.TwoFAStatus) {
215
- console.log(`[mbkauthe] GitHub trusted device login for user: ${user.UserName}, skipping 2FA only`);
224
+ case 'ACCOUNT_INACTIVE':
225
+ return {
226
+ code: 403,
227
+ error: 'Account Inactive',
228
+ message: 'Your account has been deactivated. Please contact your administrator.',
229
+ page: '/mbkauthe/login',
230
+ pagename: 'Login'
231
+ };
216
232
 
217
- const userForSession = {
218
- id: user.id,
219
- username: user.UserName,
220
- UserName: user.UserName,
221
- role: user.Role,
222
- Role: user.Role,
223
- allowedApps: user.AllowedApps,
233
+ case 'NOT_AUTHORIZED':
234
+ return {
235
+ code: 403,
236
+ error: 'Not Authorized',
237
+ message: `You are not authorized to access ${mbkautheVar.APP_NAME}. Please contact your administrator.`,
238
+ page: '/mbkauthe/login',
239
+ pagename: 'Login'
224
240
  };
225
241
 
226
- // For OAuth redirect flow
227
- const oauthRedirect = req.session.oauthRedirect;
228
- delete req.session.oauthRedirect;
242
+ default:
243
+ return {
244
+ code: 500,
245
+ error: 'Authentication Error',
246
+ message: `An error occurred during ${provider} authentication. Please try again.`,
247
+ page: '/mbkauthe/login',
248
+ pagename: 'Login'
249
+ };
250
+ }
251
+ };
252
+ };
253
+
254
+ // Common OAuth callback validation
255
+ const validateOAuthCallback = (req, res) => {
256
+ const state = req.query.state;
257
+ const sessionCsrfToken = req.session.oauthCsrfToken;
258
+
259
+ if (!state || !sessionCsrfToken || state !== sessionCsrfToken) {
260
+ console.warn('[mbkauthe] OAuth CSRF token mismatch - possible CSRF attack');
261
+ console.warn(` - State present: ${!!state}`);
262
+ console.warn(` - Session token present: ${!!sessionCsrfToken}`);
263
+ console.warn(` - Tokens match: ${state === sessionCsrfToken}`);
264
+ delete req.session.oauthCsrfToken;
265
+ renderError(res, {
266
+ code: 403,
267
+ error: 'Invalid Request',
268
+ message: 'OAuth security validation failed. Please try again.',
269
+ page: '/mbkauthe/login',
270
+ pagename: 'Login'
271
+ });
272
+ return false; // Invalid - response already sent
273
+ }
274
+
275
+ console.log(`[mbkauthe] OAuth CSRF validation passed`);
276
+ delete req.session.oauthCsrfToken;
277
+ return true; // Valid
278
+ };
279
+
280
+ // Common OAuth callback handler
281
+ const createOAuthCallback = (provider, strategy) => {
282
+ const errorHandler = createOAuthErrorHandler(provider);
283
+
284
+ return [
285
+ (req, res, next) => {
286
+ const isValid = validateOAuthCallback(req, res);
287
+ if (!isValid) return; // Response already sent
288
+
289
+ passport.authenticate(strategy, { session: false }, (err, user, info) => {
290
+ if (err) {
291
+ console.error(`[mbkauthe] ${provider} authentication error:`, err);
292
+ const errorData = errorHandler(err);
293
+ renderError(res, errorData);
294
+ return; // Stop execution after sending error
295
+ }
229
296
 
230
- // Custom response handler for OAuth flow
231
- const originalJson = res.json.bind(res);
232
- const originalStatus = res.status.bind(res);
233
- let statusCode = 200;
297
+ if (!user) {
298
+ console.error(`[mbkauthe] ${provider} callback: No user data received`);
299
+ renderError(res, {
300
+ code: 401,
301
+ error: 'Authentication Failed',
302
+ message: `${provider} authentication failed. Please try again.`,
303
+ page: '/mbkauthe/login',
304
+ pagename: 'Login'
305
+ });
306
+ return; // Stop execution after sending error
307
+ }
234
308
 
235
- res.status = function (code) {
236
- statusCode = code;
237
- return originalStatus(code);
238
- };
309
+ req.user = user;
310
+ next();
311
+ })(req, res, next);
312
+ },
313
+ async (req, res) => {
314
+ try {
315
+ const oauthUser = req.user;
316
+ const userQuery = `
317
+ SELECT u.id, u."UserName", u."Active", u."Role", u."AllowedApps",
318
+ tfa."TwoFAStatus"
319
+ FROM "Users" u
320
+ LEFT JOIN "TwoFA" tfa ON u."UserName" = tfa."UserName"
321
+ WHERE u."UserName" = $1
322
+ `;
323
+
324
+ const userResult = await dblogin.query({
325
+ name: `${provider.toLowerCase()}-callback-get-user`,
326
+ text: userQuery,
327
+ values: [oauthUser.username]
328
+ });
239
329
 
240
- res.json = function (data) {
241
- if (data.success && statusCode === 200) {
242
- const redirectUrl = oauthRedirect || mbkautheVar.loginRedirectURL || '/dashboard';
243
- console.log(`[mbkauthe] GitHub trusted device login: Redirecting to ${redirectUrl}`);
244
- res.json = originalJson;
245
- res.status = originalStatus;
246
- return res.redirect(redirectUrl);
247
- }
248
- res.json = originalJson;
249
- res.status = originalStatus;
250
- return originalJson(data);
251
- };
330
+ if (userResult.rows.length === 0) {
331
+ console.error(`[mbkauthe] ${provider} login: User not found: ${oauthUser.username}`);
332
+ return renderError(res, {
333
+ code: 404,
334
+ error: 'User Not Found',
335
+ message: `Your ${provider} account is linked, but the user account no longer exists in our system.`,
336
+ page: '/mbkauthe/login',
337
+ pagename: 'Login',
338
+ details: `${provider} ${provider === 'GitHub' ? 'username' : 'email'}: ${oauthUser[provider === 'GitHub' ? 'githubUsername' : 'googleEmail']}\nPlease contact your administrator.`
339
+ });
340
+ }
252
341
 
253
- return await completeLoginProcess(req, res, userForSession);
254
- }
342
+ const user = userResult.rows[0];
255
343
 
256
- // Check 2FA if enabled
257
- if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLowerCase() === "true" && user.TwoFAStatus) {
258
- const oauthRedirect = req.session.oauthRedirect;
259
- if (oauthRedirect) delete req.session.oauthRedirect;
260
- req.session.preAuthUser = {
261
- id: user.id,
262
- username: user.UserName,
263
- UserName: user.UserName,
264
- role: user.Role,
265
- Role: user.Role,
266
- loginMethod: 'github',
267
- redirectUrl: oauthRedirect || null
268
- };
269
- console.log(`[mbkauthe] GitHub login: 2FA required for user: ${githubUser.username}`);
270
- return res.redirect('/mbkauthe/2fa');
271
- }
344
+ // Check for trusted device
345
+ const trustedDeviceUser = await checkTrustedDevice(req, user.UserName);
346
+ if (trustedDeviceUser && (mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLowerCase() === "true" && user.TwoFAStatus) {
347
+ console.log(`[mbkauthe] ${provider} trusted device login for user: ${user.UserName}, skipping 2FA only`);
348
+ return await handleOAuthRedirect(req, res, user, 'trusted');
349
+ }
272
350
 
273
- // Complete login process
274
- const userForSession = {
275
- id: user.id,
276
- username: user.UserName,
277
- UserName: user.UserName,
278
- role: user.Role,
279
- Role: user.Role,
280
- allowedApps: user.AllowedApps,
281
- };
282
-
283
- const oauthRedirect = req.session.oauthRedirect;
284
- delete req.session.oauthRedirect;
285
-
286
- // Custom response handler for OAuth flow
287
- const originalJson = res.json.bind(res);
288
- const originalStatus = res.status.bind(res);
289
- let statusCode = 200;
290
-
291
- res.status = function (code) {
292
- statusCode = code;
293
- return originalStatus(code);
294
- };
295
-
296
- res.json = function (data) {
297
- if (data.success && statusCode === 200) {
298
- const redirectUrl = oauthRedirect || mbkautheVar.loginRedirectURL || '/dashboard';
299
- console.log(`[mbkauthe] GitHub login: Redirecting to ${redirectUrl}`);
300
- res.json = originalJson;
301
- res.status = originalStatus;
302
- return res.redirect(redirectUrl);
351
+ // Check 2FA if enabled
352
+ if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLowerCase() === "true" && user.TwoFAStatus) {
353
+ const oauthRedirect = req.session.oauthRedirect;
354
+ if (oauthRedirect) delete req.session.oauthRedirect;
355
+ req.session.preAuthUser = {
356
+ id: user.id,
357
+ username: user.UserName,
358
+ UserName: user.UserName,
359
+ role: user.Role,
360
+ Role: user.Role,
361
+ loginMethod: provider.toLowerCase(),
362
+ redirectUrl: oauthRedirect || null
363
+ };
364
+ console.log(`[mbkauthe] ${provider} login: 2FA required for user: ${oauthUser.username}`);
365
+ return res.redirect('/mbkauthe/2fa');
303
366
  }
304
- res.json = originalJson;
305
- res.status = originalStatus;
306
- return originalJson(data);
307
- };
308
367
 
309
- await completeLoginProcess(req, res, userForSession);
368
+ // Complete login process
369
+ await handleOAuthRedirect(req, res, user, 'complete');
310
370
 
311
- } catch (err) {
312
- console.error('[mbkauthe] GitHub login callback error:', err);
313
- return renderError(res, {
314
- code: '500',
315
- error: 'Internal Server Error',
316
- message: 'An error occurred during GitHub authentication. Please try again.',
317
- page: '/mbkauthe/login',
318
- pagename: 'Login',
319
- details: process.env.NODE_ENV === 'development' ? `${err.message}\n${err.stack}` : 'Error details hidden in production'
320
- });
371
+ } catch (err) {
372
+ console.error(`[mbkauthe] ${provider} login callback error:`, err);
373
+ return renderError(res, {
374
+ code: 500,
375
+ error: 'Internal Server Error',
376
+ message: `An error occurred during ${provider} authentication. Please try again.`,
377
+ page: '/mbkauthe/login',
378
+ pagename: 'Login'
379
+ });
380
+ }
321
381
  }
322
- }
382
+ ];
383
+ };
384
+
385
+ // Helper function to handle OAuth redirect flow
386
+ const handleOAuthRedirect = async (req, res, user, type) => {
387
+ const userForSession = {
388
+ id: user.id,
389
+ username: user.UserName,
390
+ UserName: user.UserName,
391
+ role: user.Role,
392
+ Role: user.Role,
393
+ allowedApps: user.AllowedApps,
394
+ };
395
+
396
+ const oauthRedirect = req.session.oauthRedirect;
397
+ delete req.session.oauthRedirect;
398
+
399
+ // Custom response handler for OAuth flow
400
+ const originalJson = res.json.bind(res);
401
+ const originalStatus = res.status.bind(res);
402
+ let statusCode = 200;
403
+
404
+ res.status = function (code) {
405
+ statusCode = code;
406
+ return originalStatus(code);
407
+ };
408
+
409
+ res.json = function (data) {
410
+ if (data.success && statusCode === 200) {
411
+ const redirectUrl = oauthRedirect || mbkautheVar.loginRedirectURL || '/dashboard';
412
+ console.log(`[mbkauthe] OAuth ${type} login: Redirecting to ${redirectUrl}`);
413
+ res.json = originalJson;
414
+ res.status = originalStatus;
415
+ return res.redirect(redirectUrl);
416
+ }
417
+ res.json = originalJson;
418
+ res.status = originalStatus;
419
+ return originalJson(data);
420
+ };
421
+
422
+ return await completeLoginProcess(req, res, userForSession);
423
+ };
424
+
425
+ // GitHub login initiation
426
+ router.get('/api/github/login', GitHubOAuthLimit, csrfProtection,
427
+ createOAuthInitiation('GitHub', mbkautheVar.GITHUB_LOGIN_ENABLED, mbkautheVar.GITHUB_CLIENT_ID, mbkautheVar.GITHUB_CLIENT_SECRET)
428
+ );
429
+
430
+ // Google login initiation
431
+ router.get('/api/google/login', GoogleOAuthLimit, csrfProtection,
432
+ createOAuthInitiation('Google', mbkautheVar.GOOGLE_LOGIN_ENABLED, mbkautheVar.GOOGLE_CLIENT_ID, mbkautheVar.GOOGLE_CLIENT_SECRET)
323
433
  );
324
434
 
325
- export default router;
435
+
436
+ // OAuth callback routes
437
+ router.get('/api/github/login/callback', GitHubOAuthLimit, ...createOAuthCallback('GitHub', 'github-login'));
438
+ router.get('/api/google/login/callback', GoogleOAuthLimit, ...createOAuthCallback('Google', 'google-login'));
439
+
440
+ export default router;