mbkauthe 1.3.4 → 1.4.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/README.md +249 -187
- package/docs/api.md +841 -0
- package/docs/db.md +54 -31
- package/index.js +13 -2
- package/lib/main.js +106 -38
- package/lib/pool.js +14 -7
- package/lib/validateSessionAndRole.js +75 -83
- package/package.json +5 -5
- package/public/main.js +134 -0
- package/views/2fa.handlebars +35 -445
- package/views/Error/dError.handlebars +71 -551
- package/views/info.handlebars +180 -160
- package/views/loginmbkauthe.handlebars +37 -557
- package/views/sharedStyles.handlebars +498 -0
- package/views/showmessage.handlebars +97 -62
package/docs/db.md
CHANGED
|
@@ -28,12 +28,12 @@ Ensure your `user_github` table exists with these columns:
|
|
|
28
28
|
```sql
|
|
29
29
|
CREATE TABLE user_github (
|
|
30
30
|
id SERIAL PRIMARY KEY,
|
|
31
|
-
user_name VARCHAR(
|
|
32
|
-
github_id VARCHAR(255) UNIQUE
|
|
31
|
+
user_name VARCHAR(50) REFERENCES "Users"("UserName"),
|
|
32
|
+
github_id VARCHAR(255) UNIQUE,
|
|
33
33
|
github_username VARCHAR(255),
|
|
34
|
-
access_token
|
|
35
|
-
created_at
|
|
36
|
-
updated_at
|
|
34
|
+
access_token VARCHAR(255),
|
|
35
|
+
created_at TimeStamp WITH TIME ZONE DEFAULT NOW(),
|
|
36
|
+
updated_at TimeStamp WITH TIME ZONE DEFAULT NOW()
|
|
37
37
|
);
|
|
38
38
|
```
|
|
39
39
|
|
|
@@ -149,18 +149,32 @@ The GitHub login feature is now fully integrated into your mbkauthe system and r
|
|
|
149
149
|
- `AllowedApps`(JSONB):
|
|
150
150
|
|
|
151
151
|
- **Schema:**
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
152
|
+
```sql
|
|
153
|
+
CREATE TYPE role AS ENUM ('SuperAdmin', 'NormalUser', 'Guest');
|
|
154
|
+
|
|
155
|
+
CREATE TABLE "Users" (
|
|
156
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
157
|
+
"UserName" VARCHAR(50) NOT NULL UNIQUE,
|
|
158
|
+
"Password" VARCHAR(61) NOT NULL, -- For bcrypt hash
|
|
159
|
+
"Role" role DEFAULT 'NormalUser' NOT NULL,
|
|
160
|
+
"Active" BOOLEAN DEFAULT FALSE,
|
|
161
|
+
"HaveMailAccount" BOOLEAN DEFAULT FALSE,
|
|
162
|
+
"AllowedApps" JSONB DEFAULT '["mbkauthe", "portal"]',
|
|
163
|
+
"SessionId" VARCHAR(213),
|
|
164
|
+
"IsOnline" BOOLEAN DEFAULT FALSE,
|
|
165
|
+
"created_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
166
|
+
"updated_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
167
|
+
"last_login" TIMESTAMP WITH TIME ZONE
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
-- Add indexes for Users table
|
|
171
|
+
CREATE INDEX IF NOT EXISTS idx_users_session_id ON "Users" ("SessionId")
|
|
172
|
+
CREATE INDEX idx_users_username ON "Users" USING BTREE ("UserName");
|
|
173
|
+
CREATE INDEX idx_users_role ON "Users" USING BTREE ("Role");
|
|
174
|
+
CREATE INDEX idx_users_active ON "Users" USING BTREE ("Active");
|
|
175
|
+
CREATE INDEX idx_users_isonline ON "Users" USING BTREE ("IsOnline");
|
|
176
|
+
CREATE INDEX idx_users_last_login ON "Users" USING BTREE (last_login);
|
|
177
|
+
```
|
|
164
178
|
|
|
165
179
|
### Session Table
|
|
166
180
|
|
|
@@ -171,13 +185,20 @@ The GitHub login feature is now fully integrated into your mbkauthe system and r
|
|
|
171
185
|
- `expire` (TIMESTAMP): Expiration timestamp for the session.
|
|
172
186
|
|
|
173
187
|
- **Schema:**
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
```sql
|
|
189
|
+
CREATE TABLE "session" (
|
|
190
|
+
sid VARCHAR(33) PRIMARY KEY NOT NULL,
|
|
191
|
+
sess JSONB NOT NULL,
|
|
192
|
+
expire TimeStamp WITH TIME ZONE Not Null,
|
|
193
|
+
"UserName" VARCHAR(50) REFERENCES "Users"("UserName"),
|
|
194
|
+
last_activity TimeStamp WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
-- Add indexes for session table
|
|
198
|
+
CREATE INDEX idx_session_expire ON "session" USING BTREE (expire);
|
|
199
|
+
CREATE INDEX idx_session_username ON "session" USING BTREE ("UserName");
|
|
200
|
+
CREATE INDEX idx_session_last_activity ON "session" USING BTREE (last_activity);
|
|
201
|
+
```
|
|
181
202
|
|
|
182
203
|
### Two-Factor Authentication Table
|
|
183
204
|
|
|
@@ -188,13 +209,15 @@ The GitHub login feature is now fully integrated into your mbkauthe system and r
|
|
|
188
209
|
- `TwoFASecret` (TEXT): The secret key used for two-factor authentication.
|
|
189
210
|
|
|
190
211
|
- **Schema:**
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
212
|
+
```sql
|
|
213
|
+
CREATE TABLE "TwoFA" (
|
|
214
|
+
"UserName" VARCHAR(50) primary key REFERENCES "Users"("UserName"),
|
|
215
|
+
"TwoFAStatus" boolean NOT NULL,
|
|
216
|
+
"TwoFASecret" TEXT
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
CREATE INDEX IF NOT EXISTS idx_twofa_username ON "TwoFA" ("UserName")
|
|
220
|
+
```
|
|
198
221
|
|
|
199
222
|
### Query to Add a User
|
|
200
223
|
|
package/index.js
CHANGED
|
@@ -34,10 +34,21 @@ if (mbkautheVar.COOKIE_EXPIRE_TIME !== undefined) {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const app = express();
|
|
37
|
-
if (process.env.test === "
|
|
38
|
-
console.log("[mbkauthe]
|
|
37
|
+
if (process.env.test === "dev") {
|
|
38
|
+
console.log("[mbkauthe] Dev mode is enabled. Starting server in dev mode.");
|
|
39
39
|
const port = 5555;
|
|
40
40
|
app.use(router);
|
|
41
|
+
app.use((req, res) => {
|
|
42
|
+
console.log(`Path not found: ${req.method} ${req.url}`);
|
|
43
|
+
return res.status(404).render("Error/dError.handlebars", {
|
|
44
|
+
layout: false,
|
|
45
|
+
code: 404,
|
|
46
|
+
error: "Not Found",
|
|
47
|
+
message: "The requested page was not found.",
|
|
48
|
+
pagename: "Home",
|
|
49
|
+
page: "/mbkauthe/login",
|
|
50
|
+
});
|
|
51
|
+
});
|
|
41
52
|
app.listen(port, () => {
|
|
42
53
|
console.log(`[mbkauthe] Server running on http://localhost:${port}`);
|
|
43
54
|
});
|
package/lib/main.js
CHANGED
|
@@ -31,6 +31,10 @@ router.use(express.json());
|
|
|
31
31
|
router.use(express.urlencoded({ extended: true }));
|
|
32
32
|
router.use(cookieParser());
|
|
33
33
|
|
|
34
|
+
router.get('/mbkauthe/main.js', (req, res) => {
|
|
35
|
+
res.sendFile(path.join(process.cwd(), 'public', 'main.js'));
|
|
36
|
+
});
|
|
37
|
+
|
|
34
38
|
// CSRF protection middleware
|
|
35
39
|
const csrfProtection = csurf({ cookie: false });
|
|
36
40
|
|
|
@@ -55,6 +59,18 @@ const LoginLimit = rateLimit({
|
|
|
55
59
|
}
|
|
56
60
|
});
|
|
57
61
|
|
|
62
|
+
const LogoutLimit = rateLimit({
|
|
63
|
+
windowMs: 1 * 60 * 1000,
|
|
64
|
+
max: 10,
|
|
65
|
+
message: { success: false, message: "Too many logout attempts, please try again later" }
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const TwoFALimit = rateLimit({
|
|
69
|
+
windowMs: 1 * 60 * 1000,
|
|
70
|
+
max: 5,
|
|
71
|
+
message: { success: false, message: "Too many 2FA attempts, please try again later" }
|
|
72
|
+
});
|
|
73
|
+
|
|
58
74
|
const sessionConfig = {
|
|
59
75
|
store: new PgSession({
|
|
60
76
|
pool: dblogin,
|
|
@@ -79,11 +95,19 @@ const sessionConfig = {
|
|
|
79
95
|
router.use(session(sessionConfig));
|
|
80
96
|
|
|
81
97
|
router.use(async (req, res, next) => {
|
|
98
|
+
// Only restore session if not already present and sessionId cookie exists
|
|
82
99
|
if (!req.session.user && req.cookies.sessionId) {
|
|
83
100
|
try {
|
|
84
101
|
const sessionId = req.cookies.sessionId;
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
|
|
103
|
+
// Validate sessionId format (should be 64 hex characters)
|
|
104
|
+
if (typeof sessionId !== 'string' || !/^[a-f0-9]{64}$/i.test(sessionId)) {
|
|
105
|
+
console.warn("[mbkauthe] Invalid sessionId format detected");
|
|
106
|
+
return next();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const query = `SELECT id, "UserName", "Active", "Role", "SessionId", "AllowedApps" FROM "Users" WHERE "SessionId" = $1 AND "Active" = true`;
|
|
110
|
+
const result = await dblogin.query({ name: 'get-user-by-sessionid', text: query, values: [sessionId] });
|
|
87
111
|
|
|
88
112
|
if (result.rows.length > 0) {
|
|
89
113
|
const user = result.rows[0];
|
|
@@ -91,7 +115,10 @@ router.use(async (req, res, next) => {
|
|
|
91
115
|
id: user.id,
|
|
92
116
|
username: user.UserName,
|
|
93
117
|
UserName: user.UserName,
|
|
118
|
+
role: user.Role,
|
|
119
|
+
Role: user.Role,
|
|
94
120
|
sessionId,
|
|
121
|
+
allowedApps: user.AllowedApps,
|
|
95
122
|
};
|
|
96
123
|
}
|
|
97
124
|
} catch (err) {
|
|
@@ -110,9 +137,18 @@ const getCookieOptions = () => ({
|
|
|
110
137
|
httpOnly: true
|
|
111
138
|
});
|
|
112
139
|
|
|
140
|
+
const getClearCookieOptions = () => ({
|
|
141
|
+
domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
|
|
142
|
+
secure: mbkautheVar.IS_DEPLOYED === 'true' ? 'auto' : false,
|
|
143
|
+
sameSite: 'lax',
|
|
144
|
+
path: '/',
|
|
145
|
+
httpOnly: true
|
|
146
|
+
});
|
|
147
|
+
|
|
113
148
|
async function completeLoginProcess(req, res, user, redirectUrl = null) {
|
|
114
149
|
try {
|
|
115
|
-
|
|
150
|
+
// smaller session id is sufficient and faster to generate/serialize
|
|
151
|
+
const sessionId = crypto.randomBytes(32).toString("hex");
|
|
116
152
|
console.log(`[mbkauthe] Generated session ID for username: ${user.username}`);
|
|
117
153
|
|
|
118
154
|
// Delete old session record for this user
|
|
@@ -138,17 +174,11 @@ async function completeLoginProcess(req, res, user, redirectUrl = null) {
|
|
|
138
174
|
|
|
139
175
|
req.session.save(async (err) => {
|
|
140
176
|
if (err) {
|
|
141
|
-
console.
|
|
177
|
+
console.error("[mbkauthe] Session save error:", err);
|
|
142
178
|
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
143
179
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
'UPDATE "session" SET username = $1 WHERE sid = $2',
|
|
147
|
-
[user.username, req.sessionID]
|
|
148
|
-
);
|
|
149
|
-
} catch (e) {
|
|
150
|
-
console.log("[mbkauthe] Failed to update username in session table:", e);
|
|
151
|
-
}
|
|
180
|
+
// avoid writing back into the session table here to reduce DB writes;
|
|
181
|
+
// the pg session store will already persist the session data.
|
|
152
182
|
|
|
153
183
|
const cookieOptions = getCookieOptions();
|
|
154
184
|
res.cookie("sessionId", sessionId, cookieOptions);
|
|
@@ -167,7 +197,7 @@ async function completeLoginProcess(req, res, user, redirectUrl = null) {
|
|
|
167
197
|
res.status(200).json(responsePayload);
|
|
168
198
|
});
|
|
169
199
|
} catch (err) {
|
|
170
|
-
console.
|
|
200
|
+
console.error("[mbkauthe] Error during login completion:", err);
|
|
171
201
|
res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
172
202
|
}
|
|
173
203
|
}
|
|
@@ -175,8 +205,11 @@ async function completeLoginProcess(req, res, user, redirectUrl = null) {
|
|
|
175
205
|
router.use(async (req, res, next) => {
|
|
176
206
|
if (req.session && req.session.user) {
|
|
177
207
|
const cookieOptions = getCookieOptions();
|
|
178
|
-
|
|
179
|
-
|
|
208
|
+
// Only set cookies if they're missing or different
|
|
209
|
+
if (req.cookies.sessionId !== req.session.user.sessionId) {
|
|
210
|
+
res.cookie("username", req.session.user.username, { ...cookieOptions, httpOnly: false });
|
|
211
|
+
res.cookie("sessionId", req.session.user.sessionId, cookieOptions);
|
|
212
|
+
}
|
|
180
213
|
}
|
|
181
214
|
next();
|
|
182
215
|
});
|
|
@@ -192,7 +225,7 @@ router.post("/mbkauthe/api/terminateAllSessions", authenticate(mbkautheVar.Main_
|
|
|
192
225
|
return res.status(500).json({ success: false, message: "Failed to terminate sessions" });
|
|
193
226
|
}
|
|
194
227
|
|
|
195
|
-
const cookieOptions =
|
|
228
|
+
const cookieOptions = getClearCookieOptions();
|
|
196
229
|
res.clearCookie("mbkauthe.sid", cookieOptions);
|
|
197
230
|
res.clearCookie("sessionId", cookieOptions);
|
|
198
231
|
res.clearCookie("username", cookieOptions);
|
|
@@ -204,7 +237,7 @@ router.post("/mbkauthe/api/terminateAllSessions", authenticate(mbkautheVar.Main_
|
|
|
204
237
|
});
|
|
205
238
|
});
|
|
206
239
|
} catch (err) {
|
|
207
|
-
console.
|
|
240
|
+
console.error("[mbkauthe] Database query error during session termination:", err);
|
|
208
241
|
res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
209
242
|
}
|
|
210
243
|
});
|
|
@@ -213,8 +246,8 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
213
246
|
console.log("[mbkauthe] Login request received");
|
|
214
247
|
|
|
215
248
|
const { username, password } = req.body;
|
|
216
|
-
console.log(`[mbkauthe] Login attempt for username: ${username}`);
|
|
217
249
|
|
|
250
|
+
// Input validation
|
|
218
251
|
if (!username || !password) {
|
|
219
252
|
console.log("[mbkauthe] Missing username or password");
|
|
220
253
|
return res.status(400).json({
|
|
@@ -223,17 +256,45 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
223
256
|
});
|
|
224
257
|
}
|
|
225
258
|
|
|
259
|
+
// Validate username format and length
|
|
260
|
+
if (typeof username !== 'string' || username.trim().length === 0 || username.length > 255) {
|
|
261
|
+
console.warn("[mbkauthe] Invalid username format");
|
|
262
|
+
return res.status(400).json({
|
|
263
|
+
success: false,
|
|
264
|
+
message: "Invalid username format",
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Validate password length
|
|
269
|
+
if (typeof password !== 'string' || password.length < 8 || password.length > 255) {
|
|
270
|
+
console.warn("[mbkauthe] Invalid password length");
|
|
271
|
+
return res.status(400).json({
|
|
272
|
+
success: false,
|
|
273
|
+
message: "Password must be at least 8 characters long",
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
console.log(`[mbkauthe] Login attempt for username: ${username.trim()}`);
|
|
278
|
+
|
|
279
|
+
const trimmedUsername = username.trim();
|
|
280
|
+
|
|
226
281
|
try {
|
|
227
|
-
const userQuery = `SELECT
|
|
228
|
-
const userResult = await dblogin.query(userQuery, [
|
|
282
|
+
const userQuery = `SELECT id, "UserName", "Password", "Active", "Role", "AllowedApps" FROM "Users" WHERE "UserName" = $1`;
|
|
283
|
+
const userResult = await dblogin.query({ name: 'get-user-by-username', text: userQuery, values: [trimmedUsername] });
|
|
229
284
|
|
|
230
285
|
if (userResult.rows.length === 0) {
|
|
231
|
-
console.log(`[mbkauthe] Username does not exist: ${
|
|
286
|
+
console.log(`[mbkauthe] Username does not exist: ${trimmedUsername}`);
|
|
232
287
|
return res.status(404).json({ success: false, message: "Incorrect Username Or Password" });
|
|
233
288
|
}
|
|
234
289
|
|
|
235
290
|
const user = userResult.rows[0];
|
|
236
291
|
|
|
292
|
+
// Validate user has password field
|
|
293
|
+
if (!user.Password) {
|
|
294
|
+
console.error("[mbkauthe] User account has no password set");
|
|
295
|
+
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
296
|
+
}
|
|
297
|
+
|
|
237
298
|
if (mbkautheVar.EncryptedPassword === "true") {
|
|
238
299
|
try {
|
|
239
300
|
const result = await bcrypt.compare(password, user.Password);
|
|
@@ -248,13 +309,13 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
248
309
|
}
|
|
249
310
|
} else {
|
|
250
311
|
if (user.Password !== password) {
|
|
251
|
-
console.log(`[mbkauthe] Incorrect password for username: ${
|
|
312
|
+
console.log(`[mbkauthe] Incorrect password for username: ${trimmedUsername}`);
|
|
252
313
|
return res.status(401).json({ success: false, errorCode: 603, message: "Incorrect Username Or Password" });
|
|
253
314
|
}
|
|
254
315
|
}
|
|
255
316
|
|
|
256
317
|
if (!user.Active) {
|
|
257
|
-
console.log(`[mbkauthe] Inactive account for username: ${
|
|
318
|
+
console.log(`[mbkauthe] Inactive account for username: ${trimmedUsername}`);
|
|
258
319
|
return res.status(403).json({ success: false, message: "Account is inactive" });
|
|
259
320
|
}
|
|
260
321
|
|
|
@@ -268,7 +329,7 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
268
329
|
|
|
269
330
|
if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLocaleLowerCase() === "true") {
|
|
270
331
|
const query = `SELECT "TwoFAStatus" FROM "TwoFA" WHERE "UserName" = $1`;
|
|
271
|
-
const twoFAResult = await dblogin.query(query, [
|
|
332
|
+
const twoFAResult = await dblogin.query({ name: 'get-2fa-status', text: query, values: [trimmedUsername] });
|
|
272
333
|
|
|
273
334
|
if (twoFAResult.rows.length > 0 && twoFAResult.rows[0].TwoFAStatus) {
|
|
274
335
|
// 2FA is enabled, prompt for token on a separate page
|
|
@@ -276,9 +337,9 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
276
337
|
id: user.id,
|
|
277
338
|
username: user.UserName,
|
|
278
339
|
role: user.Role,
|
|
279
|
-
Role: user.
|
|
340
|
+
Role: user.Role,
|
|
280
341
|
};
|
|
281
|
-
console.log(`[mbkauthe] 2FA required for user: ${
|
|
342
|
+
console.log(`[mbkauthe] 2FA required for user: ${trimmedUsername}`);
|
|
282
343
|
return res.json({ success: true, twoFactorRequired: true });
|
|
283
344
|
}
|
|
284
345
|
}
|
|
@@ -288,12 +349,12 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
288
349
|
id: user.id,
|
|
289
350
|
username: user.UserName,
|
|
290
351
|
role: user.Role,
|
|
291
|
-
Role: user.
|
|
352
|
+
Role: user.Role,
|
|
292
353
|
};
|
|
293
354
|
await completeLoginProcess(req, res, userForSession);
|
|
294
355
|
|
|
295
356
|
} catch (err) {
|
|
296
|
-
console.
|
|
357
|
+
console.error("[mbkauthe] Error during login process:", err);
|
|
297
358
|
res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
298
359
|
}
|
|
299
360
|
});
|
|
@@ -309,7 +370,7 @@ router.get("/mbkauthe/2fa", csrfProtection, (req, res) => {
|
|
|
309
370
|
});
|
|
310
371
|
});
|
|
311
372
|
|
|
312
|
-
router.post("/mbkauthe/api/verify-2fa", async (req, res) => {
|
|
373
|
+
router.post("/mbkauthe/api/verify-2fa", TwoFALimit, csrfProtection, async (req, res) => {
|
|
313
374
|
if (!req.session.preAuthUser) {
|
|
314
375
|
return res.status(401).json({ success: false, message: "Not authorized. Please login first." });
|
|
315
376
|
}
|
|
@@ -317,10 +378,17 @@ router.post("/mbkauthe/api/verify-2fa", async (req, res) => {
|
|
|
317
378
|
const { token } = req.body;
|
|
318
379
|
const { username, id, role } = req.session.preAuthUser;
|
|
319
380
|
|
|
320
|
-
|
|
381
|
+
// Validate 2FA token
|
|
382
|
+
if (!token || typeof token !== 'string') {
|
|
321
383
|
return res.status(400).json({ success: false, message: "2FA token is required" });
|
|
322
384
|
}
|
|
323
385
|
|
|
386
|
+
// Validate token format (should be 6 digits)
|
|
387
|
+
const sanitizedToken = token.trim();
|
|
388
|
+
if (!/^\d{6}$/.test(sanitizedToken)) {
|
|
389
|
+
return res.status(400).json({ success: false, message: "Invalid 2FA token format" });
|
|
390
|
+
}
|
|
391
|
+
|
|
324
392
|
try {
|
|
325
393
|
const query = `SELECT "TwoFASecret" FROM "TwoFA" WHERE "UserName" = $1`;
|
|
326
394
|
const twoFAResult = await dblogin.query(query, [username]);
|
|
@@ -333,7 +401,7 @@ router.post("/mbkauthe/api/verify-2fa", async (req, res) => {
|
|
|
333
401
|
const tokenValidates = speakeasy.totp.verify({
|
|
334
402
|
secret: sharedSecret,
|
|
335
403
|
encoding: "base32",
|
|
336
|
-
token:
|
|
404
|
+
token: sanitizedToken,
|
|
337
405
|
window: 1,
|
|
338
406
|
});
|
|
339
407
|
|
|
@@ -348,12 +416,12 @@ router.post("/mbkauthe/api/verify-2fa", async (req, res) => {
|
|
|
348
416
|
await completeLoginProcess(req, res, userForSession, redirectUrl);
|
|
349
417
|
|
|
350
418
|
} catch (err) {
|
|
351
|
-
console.
|
|
419
|
+
console.error("[mbkauthe] Error during 2FA verification:", err);
|
|
352
420
|
res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
353
421
|
}
|
|
354
422
|
});
|
|
355
423
|
|
|
356
|
-
router.post("/mbkauthe/api/logout", async (req, res) => {
|
|
424
|
+
router.post("/mbkauthe/api/logout", LogoutLimit, csrfProtection, async (req, res) => {
|
|
357
425
|
if (req.session.user) {
|
|
358
426
|
try {
|
|
359
427
|
const { id, username } = req.session.user;
|
|
@@ -366,11 +434,11 @@ router.post("/mbkauthe/api/logout", async (req, res) => {
|
|
|
366
434
|
|
|
367
435
|
req.session.destroy((err) => {
|
|
368
436
|
if (err) {
|
|
369
|
-
console.
|
|
437
|
+
console.error("[mbkauthe] Error destroying session:", err);
|
|
370
438
|
return res.status(500).json({ success: false, message: "Logout failed" });
|
|
371
439
|
}
|
|
372
440
|
|
|
373
|
-
const cookieOptions =
|
|
441
|
+
const cookieOptions = getClearCookieOptions();
|
|
374
442
|
res.clearCookie("mbkauthe.sid", cookieOptions);
|
|
375
443
|
res.clearCookie("sessionId", cookieOptions);
|
|
376
444
|
res.clearCookie("username", cookieOptions);
|
|
@@ -379,7 +447,7 @@ router.post("/mbkauthe/api/logout", async (req, res) => {
|
|
|
379
447
|
res.status(200).json({ success: true, message: "Logout successful" });
|
|
380
448
|
});
|
|
381
449
|
} catch (err) {
|
|
382
|
-
console.
|
|
450
|
+
console.error("[mbkauthe] Database query error during logout:", err);
|
|
383
451
|
res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
384
452
|
}
|
|
385
453
|
} else {
|
|
@@ -404,7 +472,7 @@ async function getLatestVersion() {
|
|
|
404
472
|
try {
|
|
405
473
|
const response = await fetch('https://raw.githubusercontent.com/MIbnEKhalid/mbkauthe/main/package.json');
|
|
406
474
|
if (!response.ok) {
|
|
407
|
-
console.
|
|
475
|
+
console.error(`GitHub API responded with status ${response.status}`);
|
|
408
476
|
return "0.0.0";
|
|
409
477
|
}
|
|
410
478
|
const latestPackageJson = await response.json();
|
|
@@ -561,7 +629,7 @@ router.get('/mbkauthe/api/github/login/callback',
|
|
|
561
629
|
};
|
|
562
630
|
|
|
563
631
|
// Generate session and complete login
|
|
564
|
-
const sessionId = crypto.randomBytes(
|
|
632
|
+
const sessionId = crypto.randomBytes(32).toString("hex");
|
|
565
633
|
console.log(`[mbkauthe] GitHub login: Generated session ID for username: ${user.UserName}`);
|
|
566
634
|
|
|
567
635
|
// Delete old session record for this user
|
package/lib/pool.js
CHANGED
|
@@ -15,15 +15,15 @@ if (!mbkautheVar) {
|
|
|
15
15
|
}
|
|
16
16
|
const requiredKeys = ["APP_NAME", "SESSION_SECRET_KEY", "IS_DEPLOYED", "LOGIN_DB", "MBKAUTH_TWO_FA_ENABLE", "DOMAIN"];
|
|
17
17
|
requiredKeys.forEach(key => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if (!mbkautheVar[key]) {
|
|
19
|
+
throw new Error(`mbkautheVar.${key} is required`);
|
|
20
|
+
}
|
|
21
21
|
});
|
|
22
22
|
if (mbkautheVar.COOKIE_EXPIRE_TIME !== undefined) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
const expireTime = parseFloat(mbkautheVar.COOKIE_EXPIRE_TIME);
|
|
24
|
+
if (isNaN(expireTime) || expireTime <= 0) {
|
|
25
|
+
throw new Error("mbkautheVar.COOKIE_EXPIRE_TIME must be a valid positive number");
|
|
26
|
+
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
const poolConfig = {
|
|
@@ -32,6 +32,13 @@ const poolConfig = {
|
|
|
32
32
|
rejectUnauthorized: true,
|
|
33
33
|
},
|
|
34
34
|
|
|
35
|
+
// Connection pool tuning for serverless/ephemeral environments (Vercel)
|
|
36
|
+
// - keep max small to avoid exhausting DB connections
|
|
37
|
+
// - reduce idle time so connections are returned sooner
|
|
38
|
+
// - set a short connection timeout to fail fast
|
|
39
|
+
max: 10,
|
|
40
|
+
idleTimeoutMillis: 50000,
|
|
41
|
+
connectionTimeoutMillis: 5000,
|
|
35
42
|
};
|
|
36
43
|
|
|
37
44
|
export const dblogin = new Pool(poolConfig);
|