mbkauthe 1.4.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![Publish to npm](https://github.com/MIbnEKhalid/mbkauthe/actions/workflows/publish.yml/badge.svg?branch=main)](https://github.com/MIbnEKhalid/mbkauthe/actions/workflows/publish.yml)
7
7
  [![CodeQL Advanced](https://github.com/MIbnEKhalid/mbkauthe/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/MIbnEKhalid/mbkauthe/actions/workflows/codeql.yml)
8
8
 
9
- **MBKAuth** is a reusable, production-ready authentication system for Node.js applications built by MBKTechStudio. It provides secure session management, two-factor authentication (2FA), role-based access control, and multi-application support out of the box.
9
+ **MBKAuth** is a reusable, production-ready authentication system for Node.js applications built by MBKTech.org. It provides secure session management, two-factor authentication (2FA), role-based access control, and multi-application support out of the box.
10
10
 
11
11
  ## ✨ Features
12
12
 
@@ -313,8 +313,8 @@ Found a bug or need help? Please [open an issue](https://github.com/MIbnEKhalid/
313
313
 
314
314
  - [npm Package](https://www.npmjs.com/package/mbkauthe)
315
315
  - [GitHub Repository](https://github.com/MIbnEKhalid/mbkauthe)
316
- - [MBKTechStudio](https://mbktechstudio.com)
316
+ - [MBKTech.org](https://mbktech.org)
317
317
 
318
318
  ---
319
319
 
320
- Made with ❤️ by [MBKTechStudio](https://mbktechstudio.com)
320
+ Made with ❤️ by [MBKTech.org](https://mbktech.org)
package/docs/api.md CHANGED
@@ -328,10 +328,77 @@ Displays MBKAuthe version information and configuration.
328
328
 
329
329
  #### `GET /mbkauthe/main.js`
330
330
 
331
- Serves the client-side JavaScript file.
331
+ Serves the client-side JavaScript file containing helper functions for authentication operations.
332
+
333
+ **Purpose:** Provides frontend JavaScript utilities including:
334
+ - `logout()` - Logout function with confirmation dialog and cache clearing
335
+ - `logoutuser()` - Alias for logout function
336
+ - `nuclearCacheClear()` - Comprehensive cache and storage clearing (preserves rememberedUsername)
337
+ - `getCookieValue(cookieName)` - Cookie retrieval helper
338
+ - `loadpage(url)` - Page navigation helper
339
+ - `formatDate(date)` - Date formatting utility
340
+ - `reloadPage()` - Page reload helper
341
+ - `checkSession()` - Session validity checker
332
342
 
333
343
  **Response:** JavaScript file (Content-Type: application/javascript)
334
344
 
345
+ **Usage:**
346
+ ```html
347
+ <script src="/mbkauthe/main.js"></script>
348
+ <button onclick="logout()">Logout</button>
349
+ ```
350
+
351
+ **Main Functions:**
352
+
353
+ **`logout()`**
354
+ - Shows confirmation dialog before logout
355
+ - Clears all caches except rememberedUsername
356
+ - Calls `/mbkauthe/api/logout` endpoint
357
+ - Redirects to home page on success
358
+
359
+ **`nuclearCacheClear()`**
360
+ - Clears service workers and cache storage
361
+ - Clears localStorage and sessionStorage (preserves rememberedUsername)
362
+ - Clears IndexedDB
363
+ - Clears cookies
364
+ - Forces page reload
365
+
366
+
367
+ ---
368
+
369
+ #### `GET /mbkauthe/test`
370
+
371
+ Test endpoint to verify authentication and display user session information.
372
+
373
+ **Authentication:** Session required
374
+
375
+ **Rate Limit:** 8 requests per minute
376
+
377
+ **Response:** HTML page displaying:
378
+ - Current username
379
+ - User role
380
+ - Logout button
381
+ - Quick links to info and login pages
382
+
383
+ **Example Response:**
384
+ ```html
385
+ <head>
386
+ <script src="/mbkauthe/main.js"></script>
387
+ </head>
388
+ <p>if you are seeing this page than User is logged in.</p>
389
+ <p>id: '${req.session.user.id}', UserName: '${req.session.user.username}', Role: '${req.session.user.role}', SessionId: '${req.session.user.sessionId}'</p>
390
+ <button onclick="logout()">Logout</button><br>
391
+ <a href="/mbkauthe/info">Info Page</a><br>
392
+ <a href="/mbkauthe/login">Login Page</a><br>
393
+ ```
394
+
395
+ **Usage:**
396
+ ```
397
+ GET /mbkauthe/test
398
+ ```
399
+
400
+ **Note:** This endpoint is primarily for testing and debugging authentication. It should not be used in production environments.
401
+
335
402
  ---
336
403
 
337
404
  ## Middleware Reference
@@ -347,7 +414,7 @@ import { validateSession } from 'mbkauthe';
347
414
  app.get('/protected', validateSession, (req, res) => {
348
415
  // User is authenticated
349
416
  const user = req.session.user;
350
- // user contains: { id, username, UserName, role, Role, sessionId, allowedApps }
417
+ // user contains: { id, username, UserName, role, Role, sessionId }
351
418
  res.send(`Welcome ${user.username}!`);
352
419
  });
353
420
  ```
@@ -369,7 +436,6 @@ req.session.user = {
369
436
  role: "NormalUser", // User role
370
437
  Role: "NormalUser", // User role (alias)
371
438
  sessionId: "abc123...", // 64-char hex session ID
372
- allowedApps: ["app1"] // Array of allowed applications
373
439
  }
374
440
  ```
375
441
 
package/env.md CHANGED
@@ -54,7 +54,7 @@ DOMAIN=localhost
54
54
  **Description:** Your application's domain name.
55
55
 
56
56
  **Configuration:**
57
- - **Production:** Set to your actual domain (e.g., `mbktechstudio.com`)
57
+ - **Production:** Set to your actual domain (e.g., `mbktech.com`)
58
58
  - **Development:** Use `localhost` or set `IS_DEPLOYED=false`
59
59
  - **Subdomains:** When `IS_DEPLOYED=true`, sessions are shared across all subdomains
60
60
 
package/lib/main.js CHANGED
@@ -5,14 +5,14 @@ import session from "express-session";
5
5
  import pgSession from "connect-pg-simple";
6
6
  const PgSession = pgSession(session);
7
7
  import { dblogin } from "./pool.js";
8
- import { authenticate } from "./validateSessionAndRole.js";
8
+ import { authenticate, validateSession, validateSessionAndRole } from "./validateSessionAndRole.js";
9
9
  import fetch from 'node-fetch';
10
10
  import cookieParser from "cookie-parser";
11
11
  import bcrypt from 'bcrypt';
12
12
  import rateLimit from 'express-rate-limit';
13
13
  import speakeasy from "speakeasy";
14
- //import passport from 'passport';
15
- //import GitHubStrategy from 'passport-github2';
14
+ import passport from 'passport';
15
+ import GitHubStrategy from 'passport-github2';
16
16
 
17
17
  import { createRequire } from "module";
18
18
  import fs from "fs";
@@ -36,16 +36,26 @@ router.get('/mbkauthe/main.js', (req, res) => {
36
36
  });
37
37
 
38
38
  // CSRF protection middleware
39
- const csrfProtection = csurf({ cookie: false });
39
+ const csrfProtection = csurf({ cookie: true });
40
40
 
41
41
  // CORS and security headers
42
42
  router.use((req, res, next) => {
43
43
  const origin = req.headers.origin;
44
- if (origin && origin.endsWith(`.${mbkautheVar.DOMAIN}`)) {
45
- res.header('Access-Control-Allow-Origin', origin);
46
- res.header('Access-Control-Allow-Credentials', 'true');
47
- res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
48
- res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
44
+ if (origin) {
45
+ try {
46
+ const originUrl = new URL(origin);
47
+ const allowedDomain = `.${mbkautheVar.DOMAIN}`;
48
+ // Exact match or subdomain match (must end with .domain.com, not just domain.com)
49
+ if (originUrl.hostname === mbkautheVar.DOMAIN ||
50
+ (originUrl.hostname.endsWith(allowedDomain) && originUrl.hostname.charAt(originUrl.hostname.length - allowedDomain.length - 1) !== '.')) {
51
+ res.header('Access-Control-Allow-Origin', origin);
52
+ res.header('Access-Control-Allow-Credentials', 'true');
53
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
54
+ res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
55
+ }
56
+ } catch (err) {
57
+ // Invalid origin URL, skip CORS headers
58
+ }
49
59
  }
50
60
  next();
51
61
  });
@@ -71,6 +81,12 @@ const TwoFALimit = rateLimit({
71
81
  message: { success: false, message: "Too many 2FA attempts, please try again later" }
72
82
  });
73
83
 
84
+ const GitHubOAuthLimit = rateLimit({
85
+ windowMs: 5 * 60 * 1000,
86
+ max: 10,
87
+ message: "Too many GitHub login attempts, please try again later"
88
+ });
89
+
74
90
  const sessionConfig = {
75
91
  store: new PgSession({
76
92
  pool: dblogin,
@@ -85,7 +101,7 @@ const sessionConfig = {
85
101
  maxAge: mbkautheVar.COOKIE_EXPIRE_TIME * 24 * 60 * 60 * 1000,
86
102
  domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
87
103
  httpOnly: true,
88
- secure: mbkautheVar.IS_DEPLOYED === 'true' ? 'auto' : false,
104
+ secure: mbkautheVar.IS_DEPLOYED === 'true',
89
105
  sameSite: 'lax',
90
106
  path: '/'
91
107
  },
@@ -100,14 +116,16 @@ router.use(async (req, res, next) => {
100
116
  try {
101
117
  const sessionId = req.cookies.sessionId;
102
118
 
103
- // Validate sessionId format (should be 64 hex characters)
119
+ // Validate sessionId format (should be 64 hex characters) and normalize to lowercase
104
120
  if (typeof sessionId !== 'string' || !/^[a-f0-9]{64}$/i.test(sessionId)) {
105
121
  console.warn("[mbkauthe] Invalid sessionId format detected");
106
122
  return next();
107
123
  }
108
124
 
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] });
125
+ const normalizedSessionId = sessionId.toLowerCase();
126
+
127
+ const query = `SELECT id, "UserName", "Active", "Role", "SessionId", "AllowedApps" FROM "Users" WHERE LOWER("SessionId") = $1 AND "Active" = true`;
128
+ const result = await dblogin.query({ name: 'restore-user-session', text: query, values: [normalizedSessionId] });
111
129
 
112
130
  if (result.rows.length > 0) {
113
131
  const user = result.rows[0];
@@ -117,8 +135,7 @@ router.use(async (req, res, next) => {
117
135
  UserName: user.UserName,
118
136
  role: user.Role,
119
137
  Role: user.Role,
120
- sessionId,
121
- allowedApps: user.AllowedApps,
138
+ sessionId: normalizedSessionId,
122
139
  };
123
140
  }
124
141
  } catch (err) {
@@ -131,7 +148,7 @@ router.use(async (req, res, next) => {
131
148
  const getCookieOptions = () => ({
132
149
  maxAge: mbkautheVar.COOKIE_EXPIRE_TIME * 24 * 60 * 60 * 1000,
133
150
  domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
134
- secure: mbkautheVar.IS_DEPLOYED === 'true' ? 'auto' : false,
151
+ secure: mbkautheVar.IS_DEPLOYED === 'true',
135
152
  sameSite: 'lax',
136
153
  path: '/',
137
154
  httpOnly: true
@@ -139,32 +156,64 @@ const getCookieOptions = () => ({
139
156
 
140
157
  const getClearCookieOptions = () => ({
141
158
  domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
142
- secure: mbkautheVar.IS_DEPLOYED === 'true' ? 'auto' : false,
159
+ secure: mbkautheVar.IS_DEPLOYED === 'true',
143
160
  sameSite: 'lax',
144
161
  path: '/',
145
162
  httpOnly: true
146
163
  });
147
164
 
165
+ router.get('/mbkauthe/test', validateSession, LoginLimit, async(req, res) => {
166
+ if(req.session?.user) {
167
+ return res.send(`
168
+ <head> <script src="/mbkauthe/main.js"></script> </head>
169
+ <p>if you are seeing this page than User is logged in.</p>
170
+ <p>id: '${req.session.user.id}', UserName: '${req.session.user.username}', Role: '${req.session.user.role}', SessionId: '${req.session.user.sessionId}'</p>
171
+ <button onclick="logout()">Logout</button><br>
172
+ <a href="/mbkauthe/info">Info Page</a><br>
173
+ <a href="/mbkauthe/login">Login Page</a><br>
174
+ `);
175
+ }
176
+ });
177
+
148
178
  async function completeLoginProcess(req, res, user, redirectUrl = null) {
149
179
  try {
180
+ // Ensure both username formats are available for compatibility
181
+ const username = user.username || user.UserName;
182
+ if (!username) {
183
+ throw new Error('Username is required in user object');
184
+ }
185
+
150
186
  // smaller session id is sufficient and faster to generate/serialize
151
187
  const sessionId = crypto.randomBytes(32).toString("hex");
152
- console.log(`[mbkauthe] Generated session ID for username: ${user.username}`);
188
+ console.log(`[mbkauthe] Generated session ID for username: ${username}`);
189
+
190
+ // Regenerate session to prevent session fixation attacks
191
+ const oldSessionId = req.sessionID;
192
+ await new Promise((resolve, reject) => {
193
+ req.session.regenerate((err) => {
194
+ if (err) reject(err);
195
+ else resolve();
196
+ });
197
+ });
153
198
 
154
- // Delete old session record for this user
155
- await dblogin.query('DELETE FROM "session" WHERE username = $1', [user.username]);
199
+ // Delete all old sessions for this user from session table
200
+ await dblogin.query({
201
+ name: 'login-delete-old-user-sessions',
202
+ text: 'DELETE FROM "session" WHERE sess::text LIKE $1',
203
+ values: [`%"username":"${username}"%`]
204
+ });
156
205
 
157
- await dblogin.query(`UPDATE "Users" SET "SessionId" = $1 WHERE "id" = $2`, [
206
+ await dblogin.query({ name: 'login-update-session-id', text: `UPDATE "Users" SET "SessionId" = $1 WHERE "id" = $2`, values: [
158
207
  sessionId,
159
208
  user.id,
160
- ]);
209
+ ] });
161
210
 
162
211
  req.session.user = {
163
212
  id: user.id,
164
- username: user.username,
165
- UserName: user.UserName,
166
- role: user.role,
167
- Role: user.role,
213
+ username: username,
214
+ UserName: username,
215
+ role: user.role || user.Role,
216
+ Role: user.role || user.Role,
168
217
  sessionId,
169
218
  };
170
219
 
@@ -182,7 +231,7 @@ async function completeLoginProcess(req, res, user, redirectUrl = null) {
182
231
 
183
232
  const cookieOptions = getCookieOptions();
184
233
  res.cookie("sessionId", sessionId, cookieOptions);
185
- console.log(`[mbkauthe] User "${user.username}" logged in successfully`);
234
+ console.log(`[mbkauthe] User "${username}" logged in successfully`);
186
235
 
187
236
  const responsePayload = {
188
237
  success: true,
@@ -216,8 +265,8 @@ router.use(async (req, res, next) => {
216
265
 
217
266
  router.post("/mbkauthe/api/terminateAllSessions", authenticate(mbkautheVar.Main_SECRET_TOKEN), async (req, res) => {
218
267
  try {
219
- await dblogin.query(`UPDATE "Users" SET "SessionId" = NULL`);
220
- await dblogin.query('DELETE FROM "session"');
268
+ await dblogin.query({ name: 'terminate-all-user-sessions', text: `UPDATE "Users" SET "SessionId" = NULL` });
269
+ await dblogin.query({ name: 'terminate-all-db-sessions', text: 'DELETE FROM "session"' });
221
270
 
222
271
  req.session.destroy((err) => {
223
272
  if (err) {
@@ -280,11 +329,11 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
280
329
 
281
330
  try {
282
331
  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] });
332
+ const userResult = await dblogin.query({ name: 'login-get-user', text: userQuery, values: [trimmedUsername] });
284
333
 
285
334
  if (userResult.rows.length === 0) {
286
- console.log(`[mbkauthe] Username does not exist: ${trimmedUsername}`);
287
- return res.status(404).json({ success: false, message: "Incorrect Username Or Password" });
335
+ console.log(`[mbkauthe] Login failed: invalid credentials`);
336
+ return res.status(401).json({ success: false, message: "Invalid credentials" });
288
337
  }
289
338
 
290
339
  const user = userResult.rows[0];
@@ -299,18 +348,18 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
299
348
  try {
300
349
  const result = await bcrypt.compare(password, user.Password);
301
350
  if (!result) {
302
- console.log("[mbkauthe] Incorrect password.");
303
- return res.status(401).json({ success: false, errorCode: 603, message: "Incorrect Username Or Password." });
351
+ console.log("[mbkauthe] Login failed: invalid credentials");
352
+ return res.status(401).json({ success: false, errorCode: 603, message: "Invalid credentials" });
304
353
  }
305
- console.log("[mbkauthe] Password matches!");
354
+ console.log("[mbkauthe] Password validated successfully");
306
355
  } catch (err) {
307
356
  console.error("[mbkauthe] Error comparing password:", err);
308
357
  return res.status(500).json({ success: false, errorCode: 605, message: `Internal Server Error` });
309
358
  }
310
359
  } else {
311
360
  if (user.Password !== password) {
312
- console.log(`[mbkauthe] Incorrect password for username: ${trimmedUsername}`);
313
- return res.status(401).json({ success: false, errorCode: 603, message: "Incorrect Username Or Password" });
361
+ console.log(`[mbkauthe] Login failed: invalid credentials`);
362
+ return res.status(401).json({ success: false, errorCode: 603, message: "Invalid credentials" });
314
363
  }
315
364
  }
316
365
 
@@ -329,7 +378,7 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
329
378
 
330
379
  if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLocaleLowerCase() === "true") {
331
380
  const query = `SELECT "TwoFAStatus" FROM "TwoFA" WHERE "UserName" = $1`;
332
- const twoFAResult = await dblogin.query({ name: 'get-2fa-status', text: query, values: [trimmedUsername] });
381
+ const twoFAResult = await dblogin.query({ name: 'login-check-2fa-status', text: query, values: [trimmedUsername] });
333
382
 
334
383
  if (twoFAResult.rows.length > 0 && twoFAResult.rows[0].TwoFAStatus) {
335
384
  // 2FA is enabled, prompt for token on a separate page
@@ -367,6 +416,7 @@ router.get("/mbkauthe/2fa", csrfProtection, (req, res) => {
367
416
  layout: false,
368
417
  customURL: mbkautheVar.loginRedirectURL || '/home',
369
418
  csrfToken: req.csrfToken(),
419
+ appName: mbkautheVar.APP_NAME.toLowerCase(),
370
420
  });
371
421
  });
372
422
 
@@ -391,7 +441,7 @@ router.post("/mbkauthe/api/verify-2fa", TwoFALimit, csrfProtection, async (req,
391
441
 
392
442
  try {
393
443
  const query = `SELECT "TwoFASecret" FROM "TwoFA" WHERE "UserName" = $1`;
394
- const twoFAResult = await dblogin.query(query, [username]);
444
+ const twoFAResult = await dblogin.query({ name: 'verify-2fa-secret', text: query, values: [username] });
395
445
 
396
446
  if (twoFAResult.rows.length === 0 || !twoFAResult.rows[0].TwoFASecret) {
397
447
  return res.status(500).json({ success: false, message: "2FA is not configured correctly." });
@@ -421,15 +471,15 @@ router.post("/mbkauthe/api/verify-2fa", TwoFALimit, csrfProtection, async (req,
421
471
  }
422
472
  });
423
473
 
424
- router.post("/mbkauthe/api/logout", LogoutLimit, csrfProtection, async (req, res) => {
474
+ router.post("/mbkauthe/api/logout", LogoutLimit, async (req, res) => {
425
475
  if (req.session.user) {
426
476
  try {
427
477
  const { id, username } = req.session.user;
428
478
 
429
- await dblogin.query(`UPDATE "Users" SET "SessionId" = NULL WHERE "id" = $1`, [id]);
479
+ await dblogin.query({ name: 'logout-clear-session', text: `UPDATE "Users" SET "SessionId" = NULL WHERE "id" = $1`, values: [id] });
430
480
 
431
481
  if (req.sessionID) {
432
- await dblogin.query('DELETE FROM "session" WHERE sid = $1', [req.sessionID]);
482
+ await dblogin.query({ name: 'logout-delete-session', text: 'DELETE FROM "session" WHERE sid = $1', values: [req.sessionID] });
433
483
  }
434
484
 
435
485
  req.session.destroy((err) => {
@@ -463,7 +513,7 @@ router.get("/mbkauthe/login", LoginLimit, csrfProtection, (req, res) => {
463
513
  userLoggedIn: !!req.session?.user,
464
514
  username: req.session?.user?.username || '',
465
515
  version: packageJson.version,
466
- appName: mbkautheVar.APP_NAME.toUpperCase(),
516
+ appName: mbkautheVar.APP_NAME.toLowerCase(),
467
517
  csrfToken: req.csrfToken(),
468
518
  });
469
519
  });
@@ -483,9 +533,19 @@ async function getLatestVersion() {
483
533
  }
484
534
  }
485
535
 
486
- router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (_, res) => {
487
- let latestVersion;
536
+ router.get("/mbkauthe/bg.avif", (req, res) => {
537
+ res.sendFile("bg.avif", { root: path.join(process.cwd(), "public") });
538
+ });
488
539
 
540
+ router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (req, res) => {
541
+ let latestVersion;
542
+ const parameters = req.query;
543
+ let authorized = false;
544
+
545
+ if (parameters.password && mbkautheVar.Main_SECRET_TOKEN) {
546
+ authorized = String(parameters.password) === String(mbkautheVar.Main_SECRET_TOKEN);
547
+ }
548
+
489
549
  try {
490
550
  latestVersion = await getLatestVersion();
491
551
  //latestVersion = "Under Development"; // Placeholder for the latest version
@@ -499,6 +559,7 @@ router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (_, res) => {
499
559
  mbkautheVar: mbkautheVar,
500
560
  version: packageJson.version,
501
561
  latestVersion,
562
+ authorized: authorized,
502
563
  });
503
564
  } catch (err) {
504
565
  console.error("[mbkauthe] Error fetching version information:", err);
@@ -515,7 +576,7 @@ router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (_, res) => {
515
576
  `);
516
577
  }
517
578
  });
518
- /*
579
+
519
580
  // Configure GitHub Strategy for login
520
581
  passport.use('github-login', new GitHubStrategy({
521
582
  clientID: process.env.GITHUB_CLIENT_ID,
@@ -526,28 +587,35 @@ passport.use('github-login', new GitHubStrategy({
526
587
  async (accessToken, refreshToken, profile, done) => {
527
588
  try {
528
589
  // Check if this GitHub account is linked to any user
529
- const githubUser = await dblogin.query(
530
- 'SELECT ug.*, u."UserName", u."Role", u."Active", u."AllowedApps" FROM user_github ug JOIN "Users" u ON ug.user_name = u."UserName" WHERE ug.github_id = $1',
531
- [profile.id]
532
- );
590
+ const githubUser = await dblogin.query({
591
+ name: 'github-login-get-user',
592
+ 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',
593
+ values: [profile.id]
594
+ });
533
595
 
534
596
  if (githubUser.rows.length === 0) {
535
597
  // GitHub account is not linked to any user
536
- return done(new Error('GitHub account not linked to any user'));
598
+ const error = new Error('GitHub account not linked to any user');
599
+ error.code = 'GITHUB_NOT_LINKED';
600
+ return done(error);
537
601
  }
538
602
 
539
603
  const user = githubUser.rows[0];
540
604
 
541
605
  // Check if the user account is active
542
606
  if (!user.Active) {
543
- return done(new Error('Account is inactive'));
607
+ const error = new Error('Account is inactive');
608
+ error.code = 'ACCOUNT_INACTIVE';
609
+ return done(error);
544
610
  }
545
611
 
546
612
  // Check if user is authorized for this app (same logic as regular login)
547
613
  if (user.Role !== "SuperAdmin") {
548
614
  const allowedApps = user.AllowedApps;
549
615
  if (!allowedApps || !allowedApps.some(app => app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
550
- return done(new Error(`Not authorized to use ${mbkautheVar.APP_NAME}`));
616
+ const error = new Error(`Not authorized to use ${mbkautheVar.APP_NAME}`);
617
+ error.code = 'NOT_AUTHORIZED';
618
+ return done(error);
551
619
  }
552
620
  }
553
621
 
@@ -561,6 +629,7 @@ passport.use('github-login', new GitHubStrategy({
561
629
  });
562
630
  } catch (err) {
563
631
  console.error('[mbkauthe] GitHub login error:', err);
632
+ err.code = err.code || 'GITHUB_AUTH_ERROR';
564
633
  return done(err);
565
634
  }
566
635
  }
@@ -580,25 +649,128 @@ router.use(passport.initialize());
580
649
  router.use(passport.session());
581
650
 
582
651
  // GitHub login initiation
583
- router.get('/mbkauthe/api/github/login', passport.authenticate('github-login'));
652
+ router.get('/mbkauthe/api/github/login', GitHubOAuthLimit, (req, res, next) => {
653
+ // Store redirect parameter in session before OAuth flow (validate to prevent open redirect)
654
+ const redirect = req.query.redirect;
655
+ if (redirect && typeof redirect === 'string') {
656
+ // Only allow relative URLs or same-origin URLs to prevent open redirect attacks
657
+ if (redirect.startsWith('/') && !redirect.startsWith('//')) {
658
+ req.session.oauthRedirect = redirect;
659
+ } else {
660
+ console.warn(`[mbkauthe] Invalid redirect parameter rejected: ${redirect}`);
661
+ }
662
+ }
663
+ passport.authenticate('github-login')(req, res, next);
664
+ });
584
665
 
585
666
  // GitHub login callback
586
667
  router.get('/mbkauthe/api/github/login/callback',
587
- passport.authenticate('github-login', {
588
- failureRedirect: '/mbkauthe/login?error=github_auth_failed',
589
- session: false // We'll handle session manually
590
- }),
668
+ GitHubOAuthLimit,
669
+ (req, res, next) => {
670
+ passport.authenticate('github-login', {
671
+ session: false // We'll handle session manually
672
+ }, (err, user, info) => {
673
+ // Custom error handling for passport authentication
674
+ if (err) {
675
+ console.error('[mbkauthe] GitHub authentication error:', err);
676
+
677
+ // Map error codes to user-friendly messages
678
+ switch(err.code) {
679
+ case 'GITHUB_NOT_LINKED':
680
+ return res.status(403).render('Error/dError.handlebars', {
681
+ layout: false,
682
+ code: '403',
683
+ error: 'GitHub Account Not Linked',
684
+ 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.',
685
+ page: '/mbkauthe/login',
686
+ pagename: 'Login',
687
+ version: packageJson.version,
688
+ app: mbkautheVar.APP_NAME
689
+ });
690
+
691
+ case 'ACCOUNT_INACTIVE':
692
+ return res.status(403).render('Error/dError.handlebars', {
693
+ layout: false,
694
+ code: '403',
695
+ error: 'Account Inactive',
696
+ message: 'Your account has been deactivated. Please contact your administrator.',
697
+ page: '/mbkauthe/login',
698
+ pagename: 'Login',
699
+ version: packageJson.version,
700
+ app: mbkautheVar.APP_NAME
701
+ });
702
+
703
+ case 'NOT_AUTHORIZED':
704
+ return res.status(403).render('Error/dError.handlebars', {
705
+ layout: false,
706
+ code: '403',
707
+ error: 'Not Authorized',
708
+ message: `You are not authorized to access ${mbkautheVar.APP_NAME}. Please contact your administrator.`,
709
+ page: '/mbkauthe/login',
710
+ pagename: 'Login',
711
+ version: packageJson.version,
712
+ app: mbkautheVar.APP_NAME
713
+ });
714
+
715
+ default:
716
+ return res.status(500).render('Error/dError.handlebars', {
717
+ layout: false,
718
+ code: '500',
719
+ error: 'Authentication Error',
720
+ message: 'An error occurred during GitHub authentication. Please try again.',
721
+ page: '/mbkauthe/login',
722
+ pagename: 'Login',
723
+ version: packageJson.version,
724
+ app: mbkautheVar.APP_NAME,
725
+ details: process.env.NODE_ENV === 'development' ? `${err.message}\n${err.stack}` : 'Error details hidden in production'
726
+ });
727
+ }
728
+ }
729
+
730
+ if (!user) {
731
+ console.error('[mbkauthe] GitHub callback: No user data received');
732
+ return res.status(401).render('Error/dError.handlebars', {
733
+ layout: false,
734
+ code: '401',
735
+ error: 'Authentication Failed',
736
+ message: 'GitHub authentication failed. Please try again.',
737
+ page: '/mbkauthe/login',
738
+ pagename: 'Login',
739
+ version: packageJson.version,
740
+ app: mbkautheVar.APP_NAME
741
+ });
742
+ }
743
+
744
+ // Authentication successful, attach user to request
745
+ req.user = user;
746
+ next();
747
+ })(req, res, next);
748
+ },
591
749
  async (req, res) => {
592
750
  try {
593
751
  const githubUser = req.user;
594
752
 
595
- // Find the actual user record
596
- const userQuery = `SELECT * FROM "Users" WHERE "UserName" = $1`;
597
- const userResult = await dblogin.query(userQuery, [githubUser.username]);
753
+ // Find the actual user record with named query
754
+ const userQuery = `SELECT id, "UserName", "Active", "Role", "AllowedApps" FROM "Users" WHERE "UserName" = $1`;
755
+ const userResult = await dblogin.query({
756
+ name: 'github-callback-get-user',
757
+ text: userQuery,
758
+ values: [githubUser.username]
759
+ });
598
760
 
599
761
  if (userResult.rows.length === 0) {
600
- console.log(`[mbkauthe] GitHub login: User not found: ${githubUser.username}`);
601
- return res.redirect('/mbkauthe/login?error=user_not_found');
762
+ console.error(`[mbkauthe] GitHub login: User not found: ${githubUser.username}`);
763
+ return res.status(404).render('Error/dError.handlebars', {
764
+ layout: false,
765
+ code: '404',
766
+ error: 'User Not Found',
767
+ message: 'Your GitHub account is linked, but the user account no longer exists in our system.',
768
+ page: '/mbkauthe/login',
769
+ pagename: 'Login',
770
+ version: packageJson.version,
771
+ app: mbkautheVar.APP_NAME,
772
+ details: `GitHub username: ${githubUser.username}\nPlease contact your administrator.`
773
+ });
602
774
  }
603
775
 
604
776
  const user = userResult.rows[0];
@@ -606,14 +778,20 @@ router.get('/mbkauthe/api/github/login/callback',
606
778
  // Check 2FA if enabled
607
779
  if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLowerCase() === "true") {
608
780
  const twoFAQuery = `SELECT "TwoFAStatus" FROM "TwoFA" WHERE "UserName" = $1`;
609
- const twoFAResult = await dblogin.query(twoFAQuery, [githubUser.username]);
781
+ const twoFAResult = await dblogin.query({
782
+ name: 'github-check-2fa-status',
783
+ text: twoFAQuery,
784
+ values: [githubUser.username]
785
+ });
610
786
 
611
787
  if (twoFAResult.rows.length > 0 && twoFAResult.rows[0].TwoFAStatus) {
612
788
  // 2FA is enabled, store pre-auth user and redirect to 2FA
613
789
  req.session.preAuthUser = {
614
790
  id: user.id,
615
791
  username: user.UserName,
792
+ UserName: user.UserName,
616
793
  role: user.Role,
794
+ Role: user.Role,
617
795
  loginMethod: 'github'
618
796
  };
619
797
  console.log(`[mbkauthe] GitHub login: 2FA required for user: ${githubUser.username}`);
@@ -621,62 +799,64 @@ router.get('/mbkauthe/api/github/login/callback',
621
799
  }
622
800
  }
623
801
 
624
- // Complete login process
802
+ // Complete login process using the shared function
625
803
  const userForSession = {
626
804
  id: user.id,
627
805
  username: user.UserName,
806
+ UserName: user.UserName,
628
807
  role: user.Role,
808
+ Role: user.Role
629
809
  };
630
810
 
631
- // Generate session and complete login
632
- const sessionId = crypto.randomBytes(32).toString("hex");
633
- console.log(`[mbkauthe] GitHub login: Generated session ID for username: ${user.UserName}`);
634
-
635
- // Delete old session record for this user
636
- await dblogin.query('DELETE FROM "session" WHERE username = $1', [user.UserName]);
811
+ // For OAuth redirect flow, we need to handle redirect differently
812
+ // Store the redirect URL before calling completeLoginProcess
813
+ const oauthRedirect = req.session.oauthRedirect;
814
+ delete req.session.oauthRedirect;
637
815
 
638
- await dblogin.query(`UPDATE "Users" SET "SessionId" = $1 WHERE "id" = $2`, [
639
- sessionId,
640
- user.id,
641
- ]);
642
-
643
- req.session.user = {
644
- id: user.id,
645
- username: user.UserName,
646
- role: user.Role,
647
- sessionId,
816
+ // Custom response handler for OAuth flow - wrap the response object
817
+ const originalJson = res.json.bind(res);
818
+ const originalStatus = res.status.bind(res);
819
+ let statusCode = 200;
820
+
821
+ res.status = function(code) {
822
+ statusCode = code;
823
+ return originalStatus(code);
648
824
  };
649
-
650
- req.session.save(async (err) => {
651
- if (err) {
652
- console.log("[mbkauthe] GitHub login session save error:", err);
653
- return res.redirect('/mbkauthe/login?error=session_error');
654
- }
655
-
656
- try {
657
- await dblogin.query(
658
- 'UPDATE "session" SET username = $1 WHERE sid = $2',
659
- [user.UserName, req.sessionID]
660
- );
661
- } catch (e) {
662
- console.log("[mbkauthe] GitHub login: Failed to update username in session table:", e);
825
+
826
+ res.json = function(data) {
827
+ if (data.success && statusCode === 200) {
828
+ // If login successful, redirect instead of sending JSON
829
+ const redirectUrl = oauthRedirect || mbkautheVar.loginRedirectURL || '/home';
830
+ console.log(`[mbkauthe] GitHub login: Redirecting to ${redirectUrl}`);
831
+ // Restore original methods before redirect
832
+ res.json = originalJson;
833
+ res.status = originalStatus;
834
+ return res.redirect(redirectUrl);
663
835
  }
836
+ // Restore original methods for error responses
837
+ res.json = originalJson;
838
+ res.status = originalStatus;
839
+ return originalJson(data);
840
+ };
664
841
 
665
- const cookieOptions = getCookieOptions();
666
- res.cookie("sessionId", sessionId, cookieOptions);
667
- console.log(`[mbkauthe] GitHub login: User "${user.UserName}" logged in successfully`);
668
-
669
- // Redirect to the configured URL or home
670
- const redirectUrl = mbkautheVar.loginRedirectURL || '/home';
671
- res.redirect(redirectUrl);
672
- });
842
+ await completeLoginProcess(req, res, userForSession);
673
843
 
674
844
  } catch (err) {
675
845
  console.error('[mbkauthe] GitHub login callback error:', err);
676
- res.redirect('/mbkauthe/login?error=internal_error');
846
+ return res.status(500).render('Error/dError.handlebars', {
847
+ layout: false,
848
+ code: '500',
849
+ error: 'Internal Server Error',
850
+ message: 'An error occurred during GitHub authentication. Please try again.',
851
+ page: '/mbkauthe/login',
852
+ pagename: 'Login',
853
+ version: packageJson.version,
854
+ app: mbkautheVar.APP_NAME,
855
+ details: process.env.NODE_ENV === 'development' ? `${err.message}\n${err.stack}` : 'Error details hidden in production'
856
+ });
677
857
  }
678
858
  }
679
859
  );
680
- */
860
+
681
861
  export { getLatestVersion };
682
862
  export default router;
package/lib/pool.js CHANGED
@@ -36,8 +36,8 @@ const poolConfig = {
36
36
  // - keep max small to avoid exhausting DB connections
37
37
  // - reduce idle time so connections are returned sooner
38
38
  // - set a short connection timeout to fail fast
39
- max: 10,
40
- idleTimeoutMillis: 50000,
39
+ max: 20,
40
+ idleTimeoutMillis: 10000,
41
41
  connectionTimeoutMillis: 5000,
42
42
  };
43
43
 
@@ -1,11 +1,10 @@
1
1
  import { dblogin } from "./pool.js";
2
2
  const mbkautheVar = JSON.parse(process.env.mbkautheVar);
3
- let pool = dblogin;
4
3
 
5
4
  const getCookieOptions = () => ({
6
5
  maxAge: mbkautheVar.COOKIE_EXPIRE_TIME * 24 * 60 * 60 * 1000,
7
6
  domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
8
- secure: mbkautheVar.IS_DEPLOYED === 'true' ? 'auto' : false,
7
+ secure: mbkautheVar.IS_DEPLOYED === 'true',
9
8
  sameSite: 'lax',
10
9
  path: '/',
11
10
  httpOnly: true
@@ -13,44 +12,13 @@ const getCookieOptions = () => ({
13
12
 
14
13
  const getClearCookieOptions = () => ({
15
14
  domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
16
- secure: mbkautheVar.IS_DEPLOYED === 'true' ? 'auto' : false,
15
+ secure: mbkautheVar.IS_DEPLOYED === 'true',
17
16
  sameSite: 'lax',
18
17
  path: '/',
19
18
  httpOnly: true
20
19
  });
21
20
 
22
21
  async function validateSession(req, res, next) {
23
- if (!req.session.user && req.cookies.sessionId) {
24
- try {
25
- const sessionId = req.cookies.sessionId;
26
-
27
- // Validate sessionId format (should be 64 hex characters)
28
- if (typeof sessionId !== 'string' || !/^[a-f0-9]{64}$/i.test(sessionId)) {
29
- console.warn("[mbkauthe] Invalid sessionId format detected");
30
- return next();
31
- }
32
-
33
- const query = `SELECT id, "UserName", "Active", "Role", "SessionId", "AllowedApps" FROM "Users" WHERE "SessionId" = $1 AND "Active" = true`;
34
- const result = await dblogin.query({ name: 'get-user-by-sessionid', text: query, values: [sessionId] });
35
-
36
- if (result.rows.length > 0) {
37
- const user = result.rows[0];
38
- req.session.user = {
39
- id: user.id,
40
- username: user.UserName,
41
- UserName: user.UserName,
42
- role: user.Role,
43
- Role: user.Role,
44
- sessionId,
45
- allowedApps: user.AllowedApps,
46
- };
47
- }
48
- } catch (err) {
49
- console.error("[mbkauthe] Session validation error:", err);
50
- return res.status(500).json({ success: false, message: "Internal Server Error" });
51
- }
52
- }
53
-
54
22
  if (!req.session.user) {
55
23
  console.log("[mbkauthe] User not authenticated");
56
24
  console.log("[mbkauthe]: ", req.session.user);
@@ -67,11 +35,14 @@ async function validateSession(req, res, next) {
67
35
  try {
68
36
  const { id, sessionId, role, allowedApps } = req.session.user;
69
37
 
70
- // Fetch only SessionId and Active status for validation (reduced query)
38
+ // Normalize sessionId to lowercase for consistent comparison
39
+ const normalizedSessionId = sessionId.toLowerCase();
40
+
41
+ // Single optimized query to validate session
71
42
  const query = `SELECT "SessionId", "Active" FROM "Users" WHERE "id" = $1`;
72
- const result = await dblogin.query({ name: 'get-user-by-id', text: query, values: [id] });
43
+ const result = await dblogin.query({ name: 'validate-user-session', text: query, values: [id] });
73
44
 
74
- if (result.rows.length === 0 || result.rows[0].SessionId !== sessionId) {
45
+ if (result.rows.length === 0 || result.rows[0].SessionId.toLowerCase() !== normalizedSessionId) {
75
46
  console.log(`[mbkauthe] Session invalidated for user "${req.session.user.username}"`);
76
47
  req.session.destroy();
77
48
  const cookieOptions = getClearCookieOptions();
@@ -101,7 +72,7 @@ async function validateSession(req, res, next) {
101
72
  error: "Account Inactive",
102
73
  message: "Your Account Is Inactive. Please Contact Support.",
103
74
  pagename: "Support",
104
- page: "https://mbktechstudio.com/Support",
75
+ page: "https://mbktech.org/Support",
105
76
  });
106
77
  }
107
78
 
@@ -131,12 +102,11 @@ async function validateSession(req, res, next) {
131
102
  }
132
103
  }
133
104
 
134
- const checkRolePermission = (requiredRole, notAllowed) => {
105
+ const checkRolePermission = (requiredRoles, notAllowed) => {
135
106
  return async (req, res, next) => {
136
107
  try {
137
108
  if (!req.session || !req.session.user || !req.session.user.id) {
138
109
  console.log("[mbkauthe] User not authenticated");
139
- console.log("[mbkauthe]: ", req.session);
140
110
  return res.render("Error/dError.handlebars", {
141
111
  layout: false,
142
112
  code: 401,
@@ -150,10 +120,10 @@ const checkRolePermission = (requiredRole, notAllowed) => {
150
120
  const userId = req.session.user.id;
151
121
 
152
122
  const query = `SELECT "Role" FROM "Users" WHERE "id" = $1`;
153
- const result = await dblogin.query({ name: 'get-role-by-id', text: query, values: [userId] });
123
+ const result = await dblogin.query({ name: 'check-role-permission', text: query, values: [userId] });
154
124
 
155
125
  if (result.rows.length === 0) {
156
- return res.status(401).json({ success: false, message: "User not found" });
126
+ return res.status(401).json({ success: false, message: "Authentication failed" });
157
127
  }
158
128
 
159
129
  const userRole = result.rows[0].Role;
@@ -164,22 +134,27 @@ const checkRolePermission = (requiredRole, notAllowed) => {
164
134
  layout: false,
165
135
  code: 403,
166
136
  error: "Access Denied",
167
- message: `You are not allowed to access this resource with role: ${notAllowed}`,
137
+ message: "You are not allowed to access this resource",
168
138
  pagename: "Home",
169
139
  page: `/${mbkautheVar.loginRedirectURL}`
170
140
  });
171
141
  }
172
142
 
173
- if (requiredRole === "Any" || requiredRole === "any") {
143
+ // Convert to array if single role provided
144
+ const rolesArray = Array.isArray(requiredRoles) ? requiredRoles : [requiredRoles];
145
+
146
+ // Check for "Any" or "any" role
147
+ if (rolesArray.includes("Any") || rolesArray.includes("any")) {
174
148
  return next();
175
149
  }
176
150
 
177
- if (userRole !== requiredRole) {
151
+ // Check if user role is in allowed roles
152
+ if (!rolesArray.includes(userRole)) {
178
153
  return res.render("Error/dError.handlebars", {
179
154
  layout: false,
180
155
  code: 403,
181
156
  error: "Access Denied",
182
- message: `You do not have permission to access this resource. Required role: ${requiredRole}`,
157
+ message: "You do not have permission to access this resource",
183
158
  pagename: "Home",
184
159
  page: `/${mbkautheVar.loginRedirectURL}`
185
160
  });
@@ -253,7 +228,7 @@ const authapi = (requiredRole = []) => {
253
228
  LIMIT 1
254
229
  `;
255
230
 
256
- const result = await pool.query(jointQuery, [token]);
231
+ const result = await dblogin.query({ name: 'validate-api-key', text: jointQuery, values: [token] });
257
232
 
258
233
  if (result.rows.length === 0) {
259
234
  console.warn("[mbkauthe] [authapi] Invalid token or associated user inactive");
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "mbkauthe",
3
- "version": "1.4.2",
4
- "description": "MBKTechStudio's reusable authentication system for Node.js applications.",
3
+ "version": "2.0.0",
4
+ "description": "MBKTech's reusable authentication system for Node.js applications.",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
- "dev": "set test=dev&& nodemon index.js"
8
+ "dev": "cross-env test=dev nodemon index.js"
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
@@ -41,12 +41,15 @@
41
41
  "express-session": "^1.18.1",
42
42
  "marked": "^15.0.11",
43
43
  "node-fetch": "^3.3.2",
44
+ "passport": "^0.7.0",
45
+ "passport-github2": "^0.1.12",
44
46
  "path": "^0.12.7",
45
47
  "pg": "^8.14.1",
46
48
  "speakeasy": "^2.0.0",
47
49
  "url": "^0.11.4"
48
50
  },
49
51
  "devDependencies": {
52
+ "cross-env": "^7.0.3",
50
53
  "nodemon": "^3.1.11"
51
54
  }
52
55
  }
package/public/bg.avif ADDED
Binary file
package/public/main.js CHANGED
@@ -5,9 +5,7 @@ async function logout() {
5
5
  }
6
6
 
7
7
  try {
8
- // Clear all caches before logging out (except rememberedUsername)
9
- await nuclearCacheClear();
10
-
8
+ // First, logout from server
11
9
  const response = await fetch("/mbkauthe/api/logout", {
12
10
  method: "POST",
13
11
  headers: {
@@ -20,15 +18,15 @@ async function logout() {
20
18
  const result = await response.json();
21
19
 
22
20
  if (response.ok) {
23
- alert(result.message);
24
- // Force a full page reload with cache bypass
25
- window.location.href = window.location.origin;
21
+ // Then clear all caches after successful logout (except rememberedUsername)
22
+ await nuclearCacheClear();
23
+ // nuclearCacheClear already redirects, so no need for additional redirect
26
24
  } else {
27
25
  alert(result.message);
28
26
  }
29
27
  } catch (error) {
30
28
  console.error("Error during logout:", error);
31
- alert("Logout failed");
29
+ alert(`Logout failed: ${error.message}`);
32
30
  }
33
31
  }
34
32
 
@@ -5,13 +5,13 @@
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <meta name="description"
8
- content="Log in to portal.mbktechstudio.com to access your resources and manage projects securely.">
8
+ content="Log in to portal.mbktech.org to access your resources and manage projects securely.">
9
9
  <meta name="keywords" content="MBK Tech Studio, Web-Portal, Web, Portal, Admin-Panel, Admin, login">
10
10
  <meta property="og:title" content="Login | MBK Tech Studio" />
11
- <meta property="og:image" content="https://www.mbktechstudio.com/Assets/Images/Icon/logo.png" />
11
+ <meta property="og:image" content="https://mbktech.org/Assets/Images/Icon/logo.png" />
12
12
  <meta property="og:url" content="/mbkauthe/2fa">
13
13
  <title>2FA | MBK Tech Studio Portal</title>
14
- <link rel="icon" type="image/x-icon" href="https://mbktechstudio.com/Assets/Images/Icon/dgicon.svg">
14
+ <link rel="icon" type="image/x-icon" href="https://mbktech.org/Assets/Images/Icon/dgicon.svg">
15
15
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
16
16
  {{> sharedStyles}}
17
17
  </head>
@@ -28,7 +28,7 @@
28
28
  </path>
29
29
  </g>
30
30
  </svg>
31
- <span class="logo-text">{{appName}} <span class="logo-comp">MBKTECHStudio</span></span>
31
+ <span class="logo-text">{{appName}} <span class="logo-comp">mbktech</span></span>
32
32
  </a>
33
33
  </div>
34
34
  </header>
@@ -63,10 +63,10 @@
63
63
 
64
64
  <p class="terms-info">
65
65
  By logging in, you agree to our
66
- <a href="https://portal.mbktechstudio.com/info/Terms&Conditions" target="_blank"
66
+ <a href="https://portal.mbktech.org/info/Terms&Conditions" target="_blank"
67
67
  class="terms-link">Terms & Conditions</a>
68
68
  and
69
- <a href="https://portal.mbktechstudio.com/info/PrivacyPolicy" target="_blank"
69
+ <a href="https://portal.mbktech.org/info/PrivacyPolicy" target="_blank"
70
70
  class="terms-link">Privacy Policy</a>.
71
71
  </p>
72
72
  </form>
@@ -5,7 +5,7 @@
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>{{code}} - {{error}}</title>
8
- <link rel="icon" type="image/x-icon" href="https://mbktechstudio.com/Assets/Images/Icon/dgicon.svg">
8
+ <link rel="icon" type="image/x-icon" href="https://mbktech.org/Assets/Images/Icon/dgicon.svg">
9
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
10
  {{> sharedStyles}}
11
11
  <style>
@@ -186,8 +186,8 @@
186
186
  </path>
187
187
  </g>
188
188
  </svg>
189
- <span class="logo-text">{{#if app}}{{app}}{{else}}MBK Authe{{/if}} <span
190
- class="logo-comp">MBKTECHStudio</span></span>
189
+ <span class="logo-text">{{#if app}}{{app}}{{else}}mbkauthe{{/if}} <span
190
+ class="logo-comp">mbktech</span></span>
191
191
  </a>
192
192
  </div>
193
193
  </header>
@@ -5,7 +5,7 @@
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>Version and Configuration Information</title>
8
- <link rel="icon" type="image/x-icon" href="https://mbktechstudio.com/Assets/Images/Icon/dgicon.svg">
8
+ <link rel="icon" type="image/x-icon" href="https://mbktech.org/Assets/Images/Icon/dgicon.svg">
9
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
10
  {{> sharedStyles}}
11
11
  <style>
@@ -184,7 +184,7 @@
184
184
  </path>
185
185
  </g>
186
186
  </svg>
187
- <span class="logo-text">{{mbkautheVar.APP_NAME}} <span class="logo-comp">MBKTECHStudio</span></span>
187
+ <span class="logo-text">{{mbkautheVar.APP_NAME}} <span class="logo-comp">mbktech</span></span>
188
188
  </a>
189
189
  </div>
190
190
  </header>
@@ -224,6 +224,16 @@
224
224
  <div class="info-value">{{mbkautheVar.APP_NAME}}</div>
225
225
  </div>
226
226
  <div class="info-row">
227
+ <div class="info-label">Domain:</div>
228
+ <div class="info-value">{{mbkautheVar.DOMAIN}}</div>
229
+ </div>
230
+ <div class="info-row">
231
+ <div class="info-label">Login Redirect URL:</div>
232
+ <div class="info-value">{{mbkautheVar.loginRedirectURL}}</div>
233
+ </div>
234
+ {{#if authorized}}
235
+
236
+ <div class="info-row">
227
237
  <div class="info-label">Two Factor Authentication:</div>
228
238
  <div class="info-value">{{mbkautheVar.MBKAUTH_TWO_FA_ENABLE}}</div>
229
239
  </div>
@@ -235,14 +245,7 @@
235
245
  <div class="info-label">Deployment Status:</div>
236
246
  <div class="info-value">{{mbkautheVar.IS_DEPLOYED}}</div>
237
247
  </div>
238
- <div class="info-row">
239
- <div class="info-label">Domain:</div>
240
- <div class="info-value">{{mbkautheVar.DOMAIN}}</div>
241
- </div>
242
- <div class="info-row">
243
- <div class="info-label">Login Redirect URL:</div>
244
- <div class="info-value">{{mbkautheVar.loginRedirectURL}}</div>
245
- </div>
248
+ {{/if}}
246
249
  </div>
247
250
  </div>
248
251
  </section>
@@ -5,13 +5,13 @@
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <meta name="description"
8
- content="Log in to portal.mbktechstudio.com to access your resources and manage projects securely.">
8
+ content="Log in to portal.mbktech.org to access your resources and manage projects securely.">
9
9
  <meta name="keywords" content="MBK Tech Studio, Web-Portal, Web, Portal, Admin-Panel, Admin, login">
10
10
  <meta property="og:title" content="Login | MBK Tech Studio" />
11
- <meta property="og:image" content="https://www.mbktechstudio.com/Assets/Images/Icon/logo.png" />
11
+ <meta property="og:image" content="https://mbktech.org/Assets/Images/Icon/logo.png" />
12
12
  <meta property="og:url" content="/mbkauthe/login">
13
13
  <title>Login | MBK Tech Studio Portal</title>
14
- <link rel="icon" type="image/x-icon" href="https://mbktechstudio.com/Assets/Images/Icon/dgicon.svg">
14
+ <link rel="icon" type="image/x-icon" href="https://mbktech.org/Assets/Images/Icon/dgicon.svg">
15
15
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
16
16
  {{> sharedStyles}}
17
17
  </head>
@@ -28,7 +28,7 @@
28
28
  </path>
29
29
  </g>
30
30
  </svg>
31
- <span class="logo-text">{{appName}} <span class="logo-comp">MBKTECHStudio</span></span>
31
+ <span class="logo-text">{{appName}} <span class="logo-comp">mbktech</span></span>
32
32
  </a>
33
33
  </div>
34
34
  </header>
@@ -68,17 +68,16 @@
68
68
  <button type="submit" class="btn-login" id="loginButton">
69
69
  <span id="loginButtonText">Login</span>
70
70
  </button>
71
- {{#if githubLoginEnabled }}
72
71
  <div class="social-login">
73
72
  <div class="divider">
74
73
  <span>or</span>
75
74
  </div>
76
- <a href="/mbkauthe/api/github/login" class="btn-github">
75
+ <!-- Use JS to initiate GitHub login and pass redirect param to backend -->
76
+ <a type="button" id="githubLoginBtn" class="btn-github">
77
77
  <i class="fab fa-github"></i>
78
78
  <span>Continue with GitHub</span>
79
79
  </a>
80
80
  </div>
81
- {{/if }}
82
81
 
83
82
  <div class="remember-me">
84
83
  <input type="checkbox" id="rememberMe" name="rememberMe">
@@ -94,16 +93,16 @@
94
93
  {{/if }}
95
94
 
96
95
  <div class="login-links">
97
- <a href="https://portal.mbktechstudio.com/forgot-password" class="login-link">Forgot Password?</a>
98
- <a href="https://www.mbktechstudio.com/Support" target="_blank" class="login-link">Need Help?</a>
96
+ <a href="https://portal.mbktech.org/forgot-password" class="login-link">Forgot Password?</a>
97
+ <a href="https://mbktech.org/Support" target="_blank" class="login-link">Need Help?</a>
99
98
  </div>
100
99
 
101
100
  <p class="terms-info">
102
101
  By logging in, you agree to our
103
- <a href="https://portal.mbktechstudio.com/info/Terms&Conditions" target="_blank"
102
+ <a href="https://portal.mbktech.org/info/Terms&Conditions" target="_blank"
104
103
  class="terms-link">Terms & Conditions</a>
105
104
  and
106
- <a href="https://portal.mbktechstudio.com/info/PrivacyPolicy" target="_blank"
105
+ <a href="https://portal.mbktech.org/info/PrivacyPolicy" target="_blank"
107
106
  class="terms-link">Privacy Policy</a>.
108
107
  </p>
109
108
  </form>
@@ -128,13 +127,13 @@
128
127
  });
129
128
 
130
129
  function fpass() {
131
- showMessage(`If you have forgotten your password, please contact support at <a href="https://www.mbktechstudio.com/Support" target="_blank">https://www.mbktechstudio.com/Support</a> to reset it.`, `Forgot Password`);
130
+ showMessage(`If you have forgotten your password, please contact support at <a href="https://mbktech.org/Support" target="_blank">https://mbktech.org/Support</a> to reset it.`, `Forgot Password`);
132
131
  }
133
132
 
134
133
  // Info dialogs
135
134
  function usernameinfo() {
136
- showMessage(`Your username is the part of your MBK Tech Studio email before the @ (e.g., abc.xyz@mbktechstudio.com
137
- → abc.xyz). For guests or if you’ve forgotten your credentials, contact <a href="https://mbktechstudio.com/Support">Support</a>.`, `What is my username?`);
135
+ showMessage(`Your username is the part of your MBK Tech Studio email before the @ (e.g., abc.xyz@mbktech.org
136
+ → abc.xyz). For guests or if you’ve forgotten your credentials, contact <a href="https://mbktech.org/Support">Support</a>.`, `What is my username?`);
138
137
  }
139
138
 
140
139
  function tokeninfo() {
@@ -272,6 +271,45 @@
272
271
  document.getElementById('loginPassword').focus();
273
272
  }
274
273
  });
274
+
275
+ // GitHub login: Attempt to POST redirect to backend, fallback to direct navigation
276
+ async function startGithubLogin() {
277
+ const urlParams = new URLSearchParams(window.location.search);
278
+ const redirect = urlParams.get('redirect') || '{{customURL}}';
279
+
280
+ try {
281
+ // Try POSTing to the backend so it can establish any session state
282
+ const resp = await fetch('/mbkauthe/api/github/login', {
283
+ method: 'POST',
284
+ headers: { 'Content-Type': 'application/json' },
285
+ credentials: 'include',
286
+ body: JSON.stringify({ redirect })
287
+ });
288
+
289
+ // If backend responds with a JSON containing redirectUrl, navigate there
290
+ if (resp.ok) {
291
+ // If server redirected directly (resp.redirected), follow the final URL
292
+ if (resp.redirected) {
293
+ window.location.href = resp.url;
294
+ return;
295
+ }
296
+ const data = await resp.json().catch(() => null);
297
+ if (data && data.redirectUrl) {
298
+ window.location.href = data.redirectUrl;
299
+ return;
300
+ }
301
+ }
302
+ } catch (error) {
303
+ // swallow and fallback to direct navigation
304
+ console.warn('[mbkauthe] GitHub login POST failed, falling back to direct redirect', error);
305
+ }
306
+
307
+ // Fallback: navigate to the backend GET endpoint with redirect query
308
+ window.location.href = `/mbkauthe/api/github/login?redirect=${encodeURIComponent(redirect)}`;
309
+ }
310
+
311
+ const githubBtn = document.getElementById('githubLoginBtn');
312
+ if (githubBtn) githubBtn.addEventListener('click', startGithubLogin);
275
313
  </script>
276
314
  </body>
277
315
 
@@ -98,7 +98,7 @@
98
98
  left: 0;
99
99
  width: 100%;
100
100
  height: 100%;
101
- background: url(https://images.unsplash.com/photo-1555066931-4365d14bab8c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80) center/cover no-repeat;
101
+ background: url(/mbkauthe/bg.avif) center/cover no-repeat;
102
102
  opacity: .1;
103
103
  z-index: 0;
104
104
  }
@@ -20,7 +20,7 @@
20
20
  document.querySelector(".showmessageWindow .error-code").style.display = "none";
21
21
  }
22
22
 
23
- document.querySelector(".showmessageWindow .error-code").href = `https://mbktechstudio.com/ErrorCode/#${errorCode}`;
23
+ document.querySelector(".showmessageWindow .error-code").href = `https://mbktech.org/ErrorCode/#${errorCode}`;
24
24
  */
25
25
  document
26
26
  .querySelector(".showMessageblurWindow")