mbkauthe 2.5.0 → 3.1.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/LICENSE +339 -373
- package/README.md +142 -279
- package/docs/api.md +210 -82
- package/docs/db.md +1 -1
- package/docs/error-messages.md +557 -0
- package/index.d.ts +248 -0
- package/index.js +43 -32
- package/lib/config/cookies.js +52 -0
- package/lib/{config.js → config/index.js} +15 -95
- package/lib/config/security.js +8 -0
- package/lib/{pool.js → database/pool.js} +1 -1
- package/lib/main.js +38 -1038
- package/lib/{validateSessionAndRole.js → middleware/auth.js} +5 -3
- package/lib/middleware/index.js +106 -0
- package/lib/routes/auth.js +521 -0
- package/lib/routes/misc.js +263 -0
- package/lib/routes/oauth.js +325 -0
- package/lib/utils/errors.js +257 -0
- package/lib/utils/response.js +21 -0
- package/package.json +21 -11
- package/public/main.js +4 -4
- package/test.spec.js +178 -0
- package/views/Error/dError.handlebars +1 -1
- package/views/errorCodes.handlebars +341 -0
- package/views/showmessage.handlebars +7 -6
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import fetch from 'node-fetch';
|
|
3
|
+
import rateLimit from 'express-rate-limit';
|
|
4
|
+
import { mbkautheVar, packageJson, appVersion } from "../config/index.js";
|
|
5
|
+
import { renderError } from "../utils/response.js";
|
|
6
|
+
import { authenticate, validateSession } from "../middleware/auth.js";
|
|
7
|
+
import { ErrorCodes, ErrorMessages } from "../utils/errors.js";
|
|
8
|
+
import { dblogin } from "../database/pool.js";
|
|
9
|
+
import { clearSessionCookies } from "../config/cookies.js";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import fs from "fs";
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const router = express.Router();
|
|
18
|
+
// Rate limiter for info/test routes
|
|
19
|
+
const LoginLimit = rateLimit({
|
|
20
|
+
windowMs: 1 * 60 * 1000,
|
|
21
|
+
max: 8,
|
|
22
|
+
message: { success: false, message: "Too many attempts, please try again later" },
|
|
23
|
+
skip: (req) => {
|
|
24
|
+
return !!req.session.user;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Static file routes
|
|
29
|
+
router.get('/main.js', (req, res) => {
|
|
30
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
|
31
|
+
res.sendFile(path.join(__dirname, '..', '..', 'public', 'main.js'));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
router.get("/bg.webp", (req, res) => {
|
|
35
|
+
const imgPath = path.join(__dirname, "..", "..", "public", "bg.webp");
|
|
36
|
+
res.setHeader('Content-Type', 'image/webp');
|
|
37
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
|
38
|
+
const stream = fs.createReadStream(imgPath);
|
|
39
|
+
stream.on('error', (err) => {
|
|
40
|
+
console.error('[mbkauthe] Error streaming bg.webp:', err);
|
|
41
|
+
res.status(404).send('Image not found');
|
|
42
|
+
});
|
|
43
|
+
stream.pipe(res);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Test route
|
|
47
|
+
router.get('/test', validateSession, LoginLimit, async (req, res) => {
|
|
48
|
+
if (req.session?.user) {
|
|
49
|
+
return res.send(`
|
|
50
|
+
<head>
|
|
51
|
+
<script src="/mbkauthe/main.js"></script>
|
|
52
|
+
<style>
|
|
53
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; background: #f5f5f5; }
|
|
54
|
+
.card { background: white; border-radius: 8px; padding: 25px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); margin-bottom: 20px; }
|
|
55
|
+
.success { color: #16a085; border-left: 4px solid #16a085; padding-left: 15px; }
|
|
56
|
+
.user-info { background: #ecf0f1; padding: 15px; border-radius: 4px; font-family: monospace; font-size: 14px; }
|
|
57
|
+
button { background: #e74c3c; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; margin: 10px 5px; }
|
|
58
|
+
button:hover { background: #c0392b; }
|
|
59
|
+
a { color: #3498db; text-decoration: none; margin: 0 10px; padding: 8px 12px; border: 1px solid #3498db; border-radius: 4px; display: inline-block; }
|
|
60
|
+
a:hover { background: #3498db; color: white; }
|
|
61
|
+
</style>
|
|
62
|
+
</head>
|
|
63
|
+
<div class="card">
|
|
64
|
+
<p class="success">✅ Authentication successful! User is logged in.</p>
|
|
65
|
+
<p>Welcome, <strong>${req.session.user.username}</strong>! Your role: <strong>${req.session.user.role}</strong></p>
|
|
66
|
+
<div class="user-info">
|
|
67
|
+
User ID: ${req.session.user.id}<br>
|
|
68
|
+
Session ID: ${req.session.user.sessionId.slice(0, 5)}...
|
|
69
|
+
</div>
|
|
70
|
+
<button onclick="logout()">Logout</button>
|
|
71
|
+
<a href="/mbkauthe/info">Info Page</a>
|
|
72
|
+
<a href="/mbkauthe/login">Login Page</a>
|
|
73
|
+
</div>
|
|
74
|
+
`);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Error codes page
|
|
79
|
+
router.get("/ErrorCode", (req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
// Helper function to get error name from ErrorCodes
|
|
82
|
+
const getErrorName = (code) => {
|
|
83
|
+
return Object.keys(ErrorCodes).find(key => ErrorCodes[key] === code) || 'UNKNOWN_ERROR';
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Dynamically organize errors by category based on code ranges
|
|
87
|
+
const errorCategories = [
|
|
88
|
+
{
|
|
89
|
+
name: 'Authentication Errors',
|
|
90
|
+
icon: '🔑',
|
|
91
|
+
range: '(600-699)',
|
|
92
|
+
category: 'authentication',
|
|
93
|
+
codes: [601, 602, 603, 604, 605]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'Two-Factor Authentication Errors',
|
|
97
|
+
icon: '📱',
|
|
98
|
+
range: '(700-799)',
|
|
99
|
+
category: '2fa',
|
|
100
|
+
codes: [701, 702, 703, 704]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: 'Session Management Errors',
|
|
104
|
+
icon: '🔄',
|
|
105
|
+
range: '(800-899)',
|
|
106
|
+
category: 'session',
|
|
107
|
+
codes: [801, 802, 803]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'Authorization Errors',
|
|
111
|
+
icon: '🛡️',
|
|
112
|
+
range: '(900-999)',
|
|
113
|
+
category: 'authorization',
|
|
114
|
+
codes: [901, 902]
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'Input Validation Errors',
|
|
118
|
+
icon: '✏️',
|
|
119
|
+
range: '(1000-1099)',
|
|
120
|
+
category: 'validation',
|
|
121
|
+
codes: [1001, 1002, 1003, 1004]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'Rate Limiting Errors',
|
|
125
|
+
icon: '⏱️',
|
|
126
|
+
range: '(1100-1199)',
|
|
127
|
+
category: 'ratelimit',
|
|
128
|
+
codes: [1101]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'Server Errors',
|
|
132
|
+
icon: '⚠️',
|
|
133
|
+
range: '(1200-1299)',
|
|
134
|
+
category: 'server',
|
|
135
|
+
codes: [1201, 1202, 1203]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'OAuth Errors',
|
|
139
|
+
icon: '🔗',
|
|
140
|
+
range: '(1300-1399)',
|
|
141
|
+
category: 'oauth',
|
|
142
|
+
codes: [1301, 1302, 1303]
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
// Build error data from ErrorMessages
|
|
147
|
+
const categoriesWithErrors = errorCategories.map(category => ({
|
|
148
|
+
...category,
|
|
149
|
+
errors: category.codes
|
|
150
|
+
.filter(code => ErrorMessages[code]) // Only include if message exists
|
|
151
|
+
.map(code => ({
|
|
152
|
+
code,
|
|
153
|
+
name: getErrorName(code),
|
|
154
|
+
...ErrorMessages[code]
|
|
155
|
+
}))
|
|
156
|
+
})).filter(category => category.errors.length > 0); // Remove empty categories
|
|
157
|
+
|
|
158
|
+
res.render("errorCodes.handlebars", {
|
|
159
|
+
layout: false,
|
|
160
|
+
pageTitle: 'Error Codes',
|
|
161
|
+
appName: mbkautheVar.APP_NAME,
|
|
162
|
+
errorCategories: categoriesWithErrors
|
|
163
|
+
});
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.error("[mbkauthe] Error rendering error codes page:", err);
|
|
166
|
+
return renderError(res, {
|
|
167
|
+
layout: false,
|
|
168
|
+
code: 500,
|
|
169
|
+
error: "Internal Server Error",
|
|
170
|
+
message: "Could not load error codes page.",
|
|
171
|
+
pagename: "Error Codes",
|
|
172
|
+
page: "/mbkauthe/info",
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Fetch latest version from GitHub
|
|
178
|
+
export async function getLatestVersion() {
|
|
179
|
+
try {
|
|
180
|
+
const response = await fetch('https://raw.githubusercontent.com/MIbnEKhalid/mbkauthe/main/package.json');
|
|
181
|
+
if (!response.ok) {
|
|
182
|
+
console.error(`[mbkauthe] GitHub API responded with status ${response.status}`);
|
|
183
|
+
return "0.0.0";
|
|
184
|
+
}
|
|
185
|
+
const latestPackageJson = await response.json();
|
|
186
|
+
return latestPackageJson.version;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('[mbkauthe] Error fetching latest version from GitHub:', error);
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Info page
|
|
194
|
+
router.get(["/info", "/i"], LoginLimit, async (req, res) => {
|
|
195
|
+
let latestVersion;
|
|
196
|
+
const parameters = req.query;
|
|
197
|
+
let authorized = false;
|
|
198
|
+
|
|
199
|
+
if (parameters.password && mbkautheVar.Main_SECRET_TOKEN) {
|
|
200
|
+
authorized = String(parameters.password) === String(mbkautheVar.Main_SECRET_TOKEN);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
latestVersion = await getLatestVersion();
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error("[mbkauthe] Error fetching package-lock.json:", err);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
res.render("info.handlebars", {
|
|
211
|
+
layout: false,
|
|
212
|
+
mbkautheVar: mbkautheVar,
|
|
213
|
+
version: packageJson.version,
|
|
214
|
+
APP_VERSION: appVersion,
|
|
215
|
+
latestVersion,
|
|
216
|
+
authorized: authorized,
|
|
217
|
+
});
|
|
218
|
+
} catch (err) {
|
|
219
|
+
console.error("[mbkauthe] Error fetching version information:", err);
|
|
220
|
+
res.status(500).send(`
|
|
221
|
+
<html>
|
|
222
|
+
<head>
|
|
223
|
+
<title>Error</title>
|
|
224
|
+
</head>
|
|
225
|
+
<body>
|
|
226
|
+
<h1>Error</h1>
|
|
227
|
+
<p>Failed to fetch version information. Please try again later.</p>
|
|
228
|
+
</body>
|
|
229
|
+
</html>
|
|
230
|
+
`);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Terminate all sessions (admin endpoint)
|
|
235
|
+
router.post("/api/terminateAllSessions", authenticate(mbkautheVar.Main_SECRET_TOKEN), async (req, res) => {
|
|
236
|
+
try {
|
|
237
|
+
// Run both operations in parallel for better performance
|
|
238
|
+
await Promise.all([
|
|
239
|
+
dblogin.query({ name: 'terminate-all-user-sessions', text: `UPDATE "Users" SET "SessionId" = NULL` }),
|
|
240
|
+
dblogin.query({ name: 'terminate-all-db-sessions', text: 'DELETE FROM "session"' })
|
|
241
|
+
]);
|
|
242
|
+
|
|
243
|
+
req.session.destroy((err) => {
|
|
244
|
+
if (err) {
|
|
245
|
+
console.log("[mbkauthe] Error destroying session:", err);
|
|
246
|
+
return res.status(500).json({ success: false, message: "Failed to terminate sessions" });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
clearSessionCookies(res);
|
|
250
|
+
|
|
251
|
+
console.log("[mbkauthe] All sessions terminated successfully");
|
|
252
|
+
res.status(200).json({
|
|
253
|
+
success: true,
|
|
254
|
+
message: "All sessions terminated successfully",
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
} catch (err) {
|
|
258
|
+
console.error("[mbkauthe] Database query error during session termination:", err);
|
|
259
|
+
res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
export default router;
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import passport from 'passport';
|
|
3
|
+
import GitHubStrategy from 'passport-github2';
|
|
4
|
+
import rateLimit from 'express-rate-limit';
|
|
5
|
+
import { dblogin } from "../database/pool.js";
|
|
6
|
+
import { mbkautheVar } from "../config/index.js";
|
|
7
|
+
import { renderError } from "../utils/response.js";
|
|
8
|
+
import { checkTrustedDevice, completeLoginProcess } from "./auth.js";
|
|
9
|
+
|
|
10
|
+
const router = express.Router();
|
|
11
|
+
|
|
12
|
+
// Rate limiter for OAuth routes
|
|
13
|
+
const GitHubOAuthLimit = rateLimit({
|
|
14
|
+
windowMs: 5 * 60 * 1000,
|
|
15
|
+
max: 10,
|
|
16
|
+
message: "Too many GitHub login attempts, please try again later"
|
|
17
|
+
});
|
|
18
|
+
|
|
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
|
+
});
|
|
34
|
+
|
|
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
|
+
}
|
|
41
|
+
|
|
42
|
+
const user = githubUser.rows[0];
|
|
43
|
+
|
|
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';
|
|
48
|
+
return done(error);
|
|
49
|
+
}
|
|
50
|
+
|
|
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
|
+
}
|
|
60
|
+
|
|
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);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
));
|
|
76
|
+
|
|
77
|
+
// Serialize/Deserialize user for GitHub login
|
|
78
|
+
passport.serializeUser((user, done) => {
|
|
79
|
+
done(null, user);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
passport.deserializeUser((user, done) => {
|
|
83
|
+
done(null, user);
|
|
84
|
+
});
|
|
85
|
+
|
|
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');
|
|
166
|
+
return renderError(res, {
|
|
167
|
+
code: '401',
|
|
168
|
+
error: 'Authentication Failed',
|
|
169
|
+
message: 'GitHub authentication failed. Please try again.',
|
|
170
|
+
page: '/mbkauthe/login',
|
|
171
|
+
pagename: 'Login'
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
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]
|
|
196
|
+
});
|
|
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.',
|
|
204
|
+
page: '/mbkauthe/login',
|
|
205
|
+
pagename: 'Login',
|
|
206
|
+
details: `GitHub username: ${githubUser.username}\nPlease contact your administrator.`
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const user = userResult.rows[0];
|
|
211
|
+
|
|
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`);
|
|
216
|
+
|
|
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,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// For OAuth redirect flow
|
|
227
|
+
const oauthRedirect = req.session.oauthRedirect;
|
|
228
|
+
delete req.session.oauthRedirect;
|
|
229
|
+
|
|
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;
|
|
234
|
+
|
|
235
|
+
res.status = function (code) {
|
|
236
|
+
statusCode = code;
|
|
237
|
+
return originalStatus(code);
|
|
238
|
+
};
|
|
239
|
+
|
|
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
|
+
};
|
|
252
|
+
|
|
253
|
+
return await completeLoginProcess(req, res, userForSession);
|
|
254
|
+
}
|
|
255
|
+
|
|
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
|
+
}
|
|
272
|
+
|
|
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);
|
|
303
|
+
}
|
|
304
|
+
res.json = originalJson;
|
|
305
|
+
res.status = originalStatus;
|
|
306
|
+
return originalJson(data);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
await completeLoginProcess(req, res, userForSession);
|
|
310
|
+
|
|
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
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
export default router;
|