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