mbkauthe 1.2.1 → 1.3.1

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
@@ -14,9 +14,7 @@
14
14
  - [Middleware Function Documentation](#middleware-function-documentation)
15
15
  - [validateSession(session)](#validatesessionsession)
16
16
  - [checkRolePermission(userRole, requiredRoles)](#checkrolepermissionuserrole-requiredroles)
17
- - [validateSessionAndRole(session, userRole, requiredRoles)](#validatesessionandrolesession-userrole-requiredroles)
18
- - [getUserData(session)](#getuserdatasession)
19
- - [authenticate(session)](#authenticatesession)
17
+ - [validateSessionAndRole(session, userRole, requiredRoles)]
20
18
  - [API Endpoints](#api-endpoints)
21
19
  - [Login](#login)
22
20
  - [Logout](#logout)
@@ -155,17 +153,6 @@ router.get(["/admin"], validateSessionAndRole("SuperAdmin"), (req, res) => {
155
153
  ```
156
154
  ---
157
155
 
158
- ### `getUserData(session)`
159
- Retrieves user data based on the session.
160
-
161
- - **Parameters:**
162
- - `session` (Object): The session object containing user information.
163
-
164
- - **Returns:**
165
- - `Object|null`: Returns the user data object if found, otherwise `null`.
166
-
167
- ---
168
-
169
156
  ### `authenticate(session)`
170
157
  Authenticates the user by validating the session and retrieving user data.
171
158
 
package/docs/db.md CHANGED
@@ -1,3 +1,127 @@
1
+ # GitHub Login Setup Guide
2
+
3
+ ## Overview
4
+ This GitHub login feature allows users to authenticate using their GitHub account if it's already linked to their account in the system. Users must first connect their GitHub account through the regular account linking process, then they can use GitHub to log in directly.
5
+
6
+ ## Setup Instructions
7
+
8
+ ### 1. Environment Variables
9
+ Add these to your `.env` file:
10
+
11
+ ```env
12
+ # GitHub OAuth App Configuration
13
+ GITHUB_CLIENT_ID=your_github_client_id
14
+ GITHUB_CLIENT_SECRET=your_github_client_secret
15
+ GITHUB_LOGIN_CALLBACK_URL=https://yourdomain.com/mbkauthe/api/github/login/callback
16
+ BASE_URL=https://yourdomain.com
17
+ ```
18
+
19
+ ### 2. GitHub OAuth App Setup
20
+ 1. Go to GitHub Settings > Developer settings > OAuth Apps
21
+ 2. Create a new OAuth App
22
+ 3. Set the Authorization callback URL to: `https://yourdomain.com/mbkauthe/api/github/login/callback`
23
+ 4. Copy the Client ID and Client Secret to your `.env` file
24
+
25
+ ### 3. Database Schema
26
+ Ensure your `user_github` table exists with these columns:
27
+
28
+ ```sql
29
+ CREATE TABLE user_github (
30
+ id SERIAL PRIMARY KEY,
31
+ user_name VARCHAR(255) REFERENCES "Users"("UserName"),
32
+ github_id VARCHAR(255) UNIQUE NOT NULL,
33
+ github_username VARCHAR(255),
34
+ access_token TEXT,
35
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
36
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
37
+ );
38
+ ```
39
+
40
+ ## How It Works
41
+
42
+ ### Login Flow
43
+ 1. User clicks "Login with GitHub" on the login page
44
+ 2. User is redirected to GitHub for authentication
45
+ 3. GitHub redirects back to `/mbkauthe/api/github/login/callback`
46
+ 4. System checks if the GitHub ID exists in `user_github` table
47
+ 5. If found and user is active/authorized:
48
+ - If 2FA is enabled, redirect to 2FA page
49
+ - If no 2FA, complete login and redirect to home
50
+ 6. If not found, redirect to login page with error
51
+
52
+ ### Account Linking
53
+ Users must first link their GitHub account through your existing GitHub connection system (likely in user settings) before they can use GitHub login.
54
+
55
+ ## API Routes Added
56
+
57
+ ### `/mbkauthe/api/github/login`
58
+ - **Method**: GET
59
+ - **Description**: Initiates GitHub OAuth flow
60
+ - **Redirects to**: GitHub authorization page
61
+
62
+ ### `/mbkauthe/api/github/login/callback`
63
+ - **Method**: GET
64
+ - **Description**: Handles GitHub OAuth callback
65
+ - **Parameters**: `code` (from GitHub), `state` (optional)
66
+ - **Success**: Redirects to home page or configured redirect URL
67
+ - **Error**: Redirects to login page with error parameter
68
+
69
+ ## Error Handling
70
+
71
+ The system handles various error cases:
72
+ - `github_auth_failed`: GitHub OAuth failed
73
+ - `user_not_found`: GitHub account not linked to any user
74
+ - `session_error`: Session save failed
75
+ - `internal_error`: General server error
76
+
77
+ ## Testing
78
+
79
+ 1. Create a test user in your `Users` table
80
+ 2. Link a GitHub account to that user using your existing connection system
81
+ 3. Try logging in with GitHub using the new login button
82
+ 4. Check console logs for debugging information
83
+
84
+ ## Login Page Updates
85
+
86
+ The login page now includes:
87
+ - A "Continue with GitHub" button
88
+ - A divider ("or") between regular and GitHub login
89
+ - Proper styling that matches your existing design
90
+
91
+ ## Security Notes
92
+
93
+ - Only users with active accounts can log in
94
+ - App authorization is checked (same as regular login)
95
+ - 2FA is respected if enabled
96
+ - Session management is handled the same way as regular login
97
+ - GitHub access tokens are stored securely
98
+
99
+ ## Troubleshooting
100
+
101
+ 1. **GitHub OAuth errors**: Check your GitHub OAuth app configuration
102
+ 2. **Database errors**: Ensure `user_github` table exists and has proper relationships
103
+ 3. **Session errors**: Check your session configuration
104
+ 4. **2FA issues**: Verify 2FA table structure and configuration
105
+
106
+ ## Environment Variables Summary
107
+
108
+ ```env
109
+ # Required for GitHub Login
110
+ GITHUB_CLIENT_ID=your_github_client_id
111
+ GITHUB_CLIENT_SECRET=your_github_client_secret
112
+ GITHUB_LOGIN_CALLBACK_URL=https://yourdomain.com/mbkauthe/api/github/login/callback
113
+
114
+ # Optional (used as fallback)
115
+ BASE_URL=https://yourdomain.com
116
+ ```
117
+
118
+ The GitHub login feature is now fully integrated into your mbkauthe system and ready to use!
119
+
120
+
121
+
122
+
123
+
124
+
1
125
  ## Database structure
2
126
 
3
127
  [<- Back](README.md)
package/index.js CHANGED
@@ -44,8 +44,8 @@ if (process.env.test === "true") {
44
44
  }
45
45
 
46
46
  app.set("views", [
47
- path.join(__dirname, "views"),
48
- path.join(__dirname, "node_modules/mbkauthe/views")
47
+ path.join(__dirname, "views"),
48
+ path.join(__dirname, "node_modules/mbkauthe/views")
49
49
  ]);
50
50
 
51
51
  app.engine("handlebars", engine({
@@ -55,6 +55,28 @@ app.engine("handlebars", engine({
55
55
  path.join(__dirname, "node_modules/mbkauthe/views"),
56
56
  path.join(__dirname, "node_modules/mbkauthe/views/Error"),
57
57
  ],
58
+ helpers: {
59
+ eq: function (a, b) {
60
+ return a === b;
61
+ },
62
+ encodeURIComponent: function (str) {
63
+ return encodeURIComponent(str);
64
+ },
65
+ formatTimestamp: function (timestamp) {
66
+ return new Date(timestamp).toLocaleString();
67
+ },
68
+ jsonStringify: function (context) {
69
+ return JSON.stringify(context);
70
+ },
71
+ json: (obj) => JSON.stringify(obj, null, 2),
72
+ objectEntries: function (obj) {
73
+ if (!obj || typeof obj !== 'object') {
74
+ return []; // Return an empty array if obj is undefined, null, or not an object
75
+ }
76
+ return Object.entries(obj).map(([key, value]) => ({ key, value }));
77
+ }
78
+ }
79
+
58
80
  }));
59
81
 
60
82
  app.set("view engine", "handlebars");
@@ -63,10 +85,10 @@ import { createRequire } from "module";
63
85
  const require = createRequire(import.meta.url);
64
86
  const packageJson = require("./package.json");
65
87
  const latestVersion = await getLatestVersion();
66
- if(latestVersion !== packageJson.version) {
88
+ if (latestVersion !== packageJson.version) {
67
89
  console.warn(`[mbkauthe] Warning: The current version (${packageJson.version}) is not the latest version (${latestVersion}). Please update mbkauthe.`);
68
90
  }
69
91
 
70
- export { validateSession, checkRolePermission, validateSessionAndRole, getUserData, authenticate, authapi } from "./lib/validateSessionAndRole.js";
92
+ export { validateSession, checkRolePermission, validateSessionAndRole, authenticate, authapi } from "./lib/validateSessionAndRole.js";
71
93
  export { dblogin } from "./lib/pool.js";
72
94
  export default router;
package/lib/main.js CHANGED
@@ -11,6 +11,8 @@ 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
16
 
15
17
  import { createRequire } from "module";
16
18
  import fs from "fs";
@@ -383,6 +385,7 @@ router.post("/mbkauthe/api/logout", async (req, res) => {
383
385
  router.get("/mbkauthe/login", LoginLimit, csrfProtection, (req, res) => {
384
386
  return res.render("loginmbkauthe.handlebars", {
385
387
  layout: false,
388
+ githubLoginEnabled: mbkautheVar.GITHUB_LOGIN_ENABLED,
386
389
  customURL: mbkautheVar.loginRedirectURL || '/home',
387
390
  userLoggedIn: !!req.session?.user,
388
391
  username: req.session?.user?.username || '',
@@ -396,7 +399,8 @@ async function getLatestVersion() {
396
399
  try {
397
400
  const response = await fetch('https://raw.githubusercontent.com/MIbnEKhalid/mbkauthe/main/package.json');
398
401
  if (!response.ok) {
399
- throw new Error(`GitHub API responded with status ${response.status}`);
402
+ console.Error(`GitHub API responded with status ${response.status}`);
403
+ return "0.0.0";
400
404
  }
401
405
  const latestPackageJson = await response.json();
402
406
  return latestPackageJson.version;
@@ -406,455 +410,23 @@ async function getLatestVersion() {
406
410
  }
407
411
  }
408
412
 
409
- async function getPackageLock() {
410
- const packageLockPath = path.resolve(process.cwd(), "package-lock.json");
411
-
412
- return new Promise((resolve, reject) => {
413
- fs.readFile(packageLockPath, "utf8", (err, data) => {
414
- if (err) {
415
- console.error("[mbkauthe] Error reading package-lock.json:", err);
416
- return reject({ success: false, message: "Failed to read package-lock.json" });
417
- }
418
- try {
419
- const packageLock = JSON.parse(data);
420
- const mbkautheData = {
421
- name: 'mbkauthe',
422
- version: packageLock.packages['node_modules/mbkauthe']?.version || packageJson.version,
423
- resolved: packageLock.packages['node_modules/mbkauthe']?.resolved || '',
424
- integrity: packageLock.packages['node_modules/mbkauthe']?.integrity || '',
425
- license: packageLock.packages['node_modules/mbkauthe']?.license || packageJson.license,
426
- dependencies: packageLock.packages['node_modules/mbkauthe']?.dependencies || {}
427
- };
428
- const rootDependency = packageLock.dependencies?.mbkauthe || {};
429
- resolve({ mbkautheData, rootDependency });
430
- } catch (parseError) {
431
- console.error("[mbkauthe] Error parsing package-lock.json:", parseError);
432
- reject("Error parsing package-lock.json");
433
- }
434
- });
435
- });
436
- }
437
-
438
- function formatJson(json) {
439
- if (typeof json === 'string') {
440
- try {
441
- json = JSON.parse(json);
442
- } catch (e) {
443
- return json;
444
- }
445
- }
446
-
447
- // First stringify with proper indentation
448
- let jsonString = JSON.stringify(json, null, 2);
449
-
450
- // Escape HTML special characters EXCEPT for our span tags
451
- jsonString = jsonString
452
- .replace(/&/g, '&amp;')
453
- .replace(/</g, '&lt;')
454
- .replace(/>/g, '&gt;');
455
-
456
- // Now apply syntax highlighting (after escaping)
457
- jsonString = jsonString
458
- // Highlight keys
459
- .replace(/"([^"]+)":/g, '"<span style="color: #2b6cb0;">$1</span>":')
460
- // Highlight string values
461
- .replace(/:\s*"([^"]+)"/g, ': "<span style="color: #38a169;">$1</span>"')
462
- // Highlight numbers
463
- .replace(/: (\d+)/g, ': <span style="color: #dd6b20;">$1</span>')
464
- // Highlight booleans and null
465
- .replace(/: (true|false|null)/g, ': <span style="color: #805ad5;">$1</span>');
466
-
467
- return jsonString;
468
- }
469
-
470
413
  router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (_, res) => {
471
- let pkgl = {};
472
414
  let latestVersion;
473
415
 
474
416
  try {
475
- pkgl = await getPackageLock();
476
417
  latestVersion = await getLatestVersion();
477
418
  //latestVersion = "Under Development"; // Placeholder for the latest version
478
419
  } catch (err) {
479
420
  console.error("[mbkauthe] Error fetching package-lock.json:", err);
480
- pkgl = { error: "Failed to fetch package-lock.json" };
481
421
  }
482
422
 
483
423
  try {
484
- res.status(200).send(`
485
- <html>
486
- <head>
487
- <title>Version and Configuration Information</title>
488
- <link rel="icon" type="image/x-icon" href="https://mbktechstudio.com/Assets/Images/Icon/dgicon.svg">
489
-
490
- <style>
491
- :root {
492
- --bg-color: #121212;
493
- --card-bg: #1e1e1e;
494
- --text-color: #e0e0e0;
495
- --text-secondary: #a0a0a0;
496
- --primary: #bb86fc;
497
- --primary-dark: #3700b3;
498
- --secondary: #03dac6;
499
- --border-color: #333;
500
- --success: #4caf50;
501
- --warning: #ff9800;
502
- --error: #f44336;
503
- --key-color: #bb86fc;
504
- --string-color: #03dac6;
505
- --number-color: #ff7043;
506
- --boolean-color: #7986cb;
507
- }
508
-
509
- body {
510
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
511
- margin: 0;
512
- padding: 20px;
513
- background-color: var(--bg-color);
514
- color: var(--text-color);
515
- }
516
-
517
- .container {
518
- max-width: 1000px;
519
- margin: 0 auto;
520
- padding: 20px;
521
- background: var(--card-bg);
522
- border-radius: 8px;
523
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
524
- }
525
-
526
- h1 {
527
- color: var(--primary);
528
- text-align: center;
529
- margin-bottom: 30px;
530
- padding-bottom: 10px;
531
- border-bottom: 1px solid var(--border-color);
532
- font-weight: bold;
533
- letter-spacing: 1px;
534
- }
535
-
536
- .info-section {
537
- margin-bottom: 25px;
538
- padding: 20px;
539
- border: 1px solid var(--border-color);
540
- border-radius: 8px;
541
- background-color: rgba(30, 30, 30, 0.7);
542
- transition: all 0.3s ease;
543
- }
544
-
545
- .info-section:hover {
546
- border-color: var(--primary);
547
- box-shadow: 0 0 0 1px var(--primary);
548
- }
549
-
550
- .info-section h2 {
551
- color: var(--primary);
552
- border-bottom: 2px solid var(--primary-dark);
553
- padding-bottom: 8px;
554
- margin-top: 0;
555
- margin-bottom: 15px;
556
- font-size: 1.2em;
557
- display: flex;
558
- justify-content: space-between;
559
- align-items: center;
560
- }
561
-
562
- .info-row {
563
- display: flex;
564
- margin-bottom: 10px;
565
- padding-bottom: 10px;
566
- border-bottom: 1px solid var(--border-color);
567
- }
568
-
569
- .info-label {
570
- font-weight: 600;
571
- color: var(--text-secondary);
572
- min-width: 220px;
573
- font-size: 0.95em;
574
- }
575
-
576
- .info-value {
577
- flex: 1;
578
- word-break: break-word;
579
- color: var(--text-color);
580
- }
581
-
582
- .json-container {
583
- background: #252525;
584
- border: 1px solid var(--border-color);
585
- border-radius: 6px;
586
- padding: 12px;
587
- margin-top: 10px;
588
- max-height: 400px;
589
- overflow: auto;
590
- font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;
591
- font-size: 0.85em;
592
- white-space: pre-wrap;
593
- position: relative;
594
- }
595
-
596
- .json-container pre {
597
- margin: 0;
598
- font-family: inherit;
599
- }
600
-
601
- .json-container .key {
602
- color: var(--key-color);
603
- }
604
-
605
- .json-container .string {
606
- color: var(--string-color);
607
- }
608
-
609
- .json-container .number {
610
- color: var(--number-color);
611
- }
612
-
613
- .json-container .boolean {
614
- color: var(--boolean-color);
615
- }
616
-
617
- .json-container .null {
618
- color: var(--boolean-color);
619
- opacity: 0.7;
620
- }
621
-
622
- .version-status {
623
- display: inline-block;
624
- padding: 3px 10px;
625
- border-radius: 12px;
626
- font-size: 0.8em;
627
- font-weight: 600;
628
- margin-left: 10px;
629
- }
630
-
631
- .version-up-to-date {
632
- background: rgba(76, 175, 80, 0.2);
633
- color: var(--success);
634
- border: 1px solid var(--success);
635
- }
636
-
637
- .version-outdated {
638
- background: rgba(244, 67, 54, 0.2);
639
- color: var(--error);
640
- border: 1px solid var(--error);
641
- }
642
-
643
- .version-fetch-error {
644
- background: rgba(255, 152, 0, 0.2);
645
- color: var(--warning);
646
- border: 1px solid var(--warning);
647
- }
648
-
649
- .copy-btn {
650
- background: var(--primary-dark);
651
- color: white;
652
- border: none;
653
- padding: 5px 12px;
654
- border-radius: 4px;
655
- cursor: pointer;
656
- font-size: 0.8em;
657
- transition: all 0.2s ease;
658
- display: flex;
659
- align-items: center;
660
- gap: 5px;
661
- }
662
-
663
- .copy-btn:hover {
664
- background: var(--primary);
665
- transform: translateY(-1px);
666
- }
667
-
668
- .copy-btn:active {
669
- transform: translateY(0);
670
- }
671
-
672
- /* Scrollbar styling */
673
- ::-webkit-scrollbar {
674
- width: 8px;
675
- height: 8px;
676
- }
677
-
678
- ::-webkit-scrollbar-track {
679
- background: #2d2d2d;
680
- border-radius: 4px;
681
- }
682
-
683
- ::-webkit-scrollbar-thumb {
684
- background: #555;
685
- border-radius: 4px;
686
- }
687
-
688
- ::-webkit-scrollbar-thumb:hover {
689
- background: var(--primary);
690
- }
691
-
692
- /* Tooltip for copy button */
693
- .tooltip {
694
- position: relative;
695
- display: inline-block;
696
- }
697
-
698
- .tooltip .tooltiptext {
699
- visibility: hidden;
700
- width: 120px;
701
- background-color: #333;
702
- color: #fff;
703
- text-align: center;
704
- border-radius: 6px;
705
- padding: 5px;
706
- position: absolute;
707
- z-index: 1;
708
- bottom: 125%;
709
- left: 50%;
710
- margin-left: -60px;
711
- opacity: 0;
712
- transition: opacity 0.3s;
713
- font-size: 0.8em;
714
- }
715
-
716
- .tooltip:hover .tooltiptext {
717
- visibility: visible;
718
- opacity: 1;
719
- }
720
- </style>
721
- <link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
722
- </head>
723
-
724
- <body>
725
- <div class="container">
726
- <h1>Version and Configuration Dashboard</h1>
727
-
728
- <div class="info-section">
729
- <h2>Version Information</h2>
730
- <div class="info-row">
731
- <div class="info-label">Current Version:</div>
732
- <div class="info-value" id="CurrentVersion">${packageJson.version}</div>
733
- </div>
734
- <div class="info-row">
735
- <div class="info-label">Latest Version:</div>
736
- <div class="info-value">
737
- ${latestVersion || 'Could not fetch latest version'}
738
- ${latestVersion ? `
739
- <span class="version-status ${packageJson.version === latestVersion ? 'version-up-to-date' : 'version-outdated'}">
740
- ${packageJson.version === latestVersion ? 'Up to date' : 'Update available'}
741
- </span>
742
- ` : `
743
- <span class="version-status version-fetch-error">
744
- Fetch error
745
- </span>
746
- `}
747
- </div>
748
- </div>
749
- </div>
750
-
751
- <div class="info-section">
752
- <h2>Configuration Information</h2>
753
- <div class="info-row">
754
- <div class="info-label">APP_NAME:</div>
755
- <div class="info-value">${mbkautheVar.APP_NAME}</div>
756
- </div>
757
- <div class="info-row">
758
- <div class="info-label">MBKAUTH_TWO_FA_ENABLE:</div>
759
- <div class="info-value">${mbkautheVar.MBKAUTH_TWO_FA_ENABLE}</div>
760
- </div>
761
- <div class="info-row">
762
- <div class="info-label">COOKIE_EXPIRE_TIME:</div>
763
- <div class="info-value">${mbkautheVar.COOKIE_EXPIRE_TIME} Days</div>
764
- </div>
765
- <div class="info-row">
766
- <div class="info-label">IS_DEPLOYED:</div>
767
- <div class="info-value">${mbkautheVar.IS_DEPLOYED}</div>
768
- </div>
769
- <div class="info-row">
770
- <div class="info-label">DOMAIN:</div>
771
- <div class="info-value">${mbkautheVar.DOMAIN}</div>
772
- </div>
773
- <div class="info-row">
774
- <div class="info-label">Login Redirect URL:</div>
775
- <div class="info-value">${mbkautheVar.loginRedirectURL}</div>
776
- </div>
777
- </div>
778
-
779
- <div class="info-section">
780
- <h2>
781
- Package Information
782
- <button class="copy-btn tooltip" onclick="copyToClipboard('package-json')">
783
- <span class="tooltiptext">Copy to clipboard</span>
784
- Copy JSON
785
- </button>
786
- </h2>
787
- <div id="package-json" class="json-container"><pre>${JSON.stringify(packageJson, null, 2)}</pre></div>
788
- </div>
789
-
790
- <div class="info-section">
791
- <h2>
792
- Package Lock
793
- <button class="copy-btn tooltip" onclick="copyToClipboard('package-lock')">
794
- <span class="tooltiptext">Copy to clipboard</span>
795
- Copy JSON
796
- </button>
797
- </h2>
798
- <div id="package-lock" class="json-container"><pre>${JSON.stringify(pkgl, null, 2)}</pre></div>
799
- </div>
800
- </div>
801
-
802
- <script>
803
- document.addEventListener('DOMContentLoaded', function() {
804
- // Apply syntax highlighting to all JSON containers
805
- const jsonContainers = document.querySelectorAll('.json-container pre');
806
- jsonContainers.forEach(container => {
807
- container.innerHTML = syntaxHighlight(container.textContent);
808
- });
809
- });
810
-
811
- function syntaxHighlight(json) {
812
- if (typeof json !== 'string') {
813
- json = JSON.stringify(json, null, 2);
814
- }
815
-
816
- // Escape HTML
817
- json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
818
-
819
- // Apply syntax highlighting
820
- return json.replace(
821
- /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
822
- function(match) {
823
- let cls = 'number';
824
- if (/^"/.test(match)) {
825
- if (/:$/.test(match)) {
826
- cls = 'key';
827
- } else {
828
- cls = 'string';
829
- }
830
- } else if (/true|false/.test(match)) {
831
- cls = 'boolean';
832
- } else if (/null/.test(match)) {
833
- cls = 'null';
834
- }
835
- return '<span class="' + cls + '">' + match + '</span>';
836
- }
837
- );
838
- }
839
-
840
- function copyToClipboard(elementId) {
841
- const element = document.getElementById(elementId);
842
- const text = element.textContent;
843
- navigator.clipboard.writeText(text).then(() => {
844
- const btn = element.parentElement.querySelector('.copy-btn');
845
- const originalText = btn.innerHTML;
846
- btn.innerHTML = '<span class="tooltiptext">Copied!</span>✓ Copied';
847
- setTimeout(() => {
848
- btn.innerHTML = '<span class="tooltiptext">Copy to clipboard</span>' + originalText.replace('✓ Copied', 'Copy JSON');
849
- }, 2000);
850
- }).catch(err => {
851
- console.error('[mbkauthe] Failed to copy text: ', err);
852
- });
853
- }
854
- </script>
855
- </body>
856
- </html>
857
- `);
424
+ res.render("info.handlebars", {
425
+ layout: false,
426
+ mbkautheVar: mbkautheVar,
427
+ version: packageJson.version,
428
+ latestVersion,
429
+ });
858
430
  } catch (err) {
859
431
  console.error("[mbkauthe] Error fetching version information:", err);
860
432
  res.status(500).send(`
@@ -870,6 +442,168 @@ router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (_, res) => {
870
442
  `);
871
443
  }
872
444
  });
445
+ /*
446
+ // Configure GitHub Strategy for login
447
+ passport.use('github-login', new GitHubStrategy({
448
+ clientID: process.env.GITHUB_CLIENT_ID,
449
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
450
+ callbackURL: '/mbkauthe/api/github/login/callback',
451
+ scope: ['user:email']
452
+ },
453
+ async (accessToken, refreshToken, profile, done) => {
454
+ try {
455
+ // Check if this GitHub account is linked to any user
456
+ const githubUser = await dblogin.query(
457
+ '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',
458
+ [profile.id]
459
+ );
460
+
461
+ if (githubUser.rows.length === 0) {
462
+ // GitHub account is not linked to any user
463
+ return done(new Error('GitHub account not linked to any user'));
464
+ }
465
+
466
+ const user = githubUser.rows[0];
467
+
468
+ // Check if the user account is active
469
+ if (!user.Active) {
470
+ return done(new Error('Account is inactive'));
471
+ }
472
+
473
+ // Check if user is authorized for this app (same logic as regular login)
474
+ if (user.Role !== "SuperAdmin") {
475
+ const allowedApps = user.AllowedApps;
476
+ if (!allowedApps || !allowedApps.some(app => app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
477
+ return done(new Error(`Not authorized to use ${mbkautheVar.APP_NAME}`));
478
+ }
479
+ }
480
+
481
+ // Return user data for login
482
+ return done(null, {
483
+ id: user.id, // This should be the user ID from the Users table
484
+ username: user.UserName,
485
+ role: user.Role,
486
+ githubId: user.github_id,
487
+ githubUsername: user.github_username
488
+ });
489
+ } catch (err) {
490
+ console.error('[mbkauthe] GitHub login error:', err);
491
+ return done(err);
492
+ }
493
+ }
494
+ ));
495
+
496
+ // Serialize/Deserialize user for GitHub login
497
+ passport.serializeUser((user, done) => {
498
+ done(null, user);
499
+ });
873
500
 
501
+ passport.deserializeUser((user, done) => {
502
+ done(null, user);
503
+ });
504
+
505
+ // Initialize passport
506
+ router.use(passport.initialize());
507
+ router.use(passport.session());
508
+
509
+ // GitHub login initiation
510
+ router.get('/mbkauthe/api/github/login', passport.authenticate('github-login'));
511
+
512
+ // GitHub login callback
513
+ router.get('/mbkauthe/api/github/login/callback',
514
+ passport.authenticate('github-login', {
515
+ failureRedirect: '/mbkauthe/login?error=github_auth_failed',
516
+ session: false // We'll handle session manually
517
+ }),
518
+ async (req, res) => {
519
+ try {
520
+ const githubUser = req.user;
521
+
522
+ // Find the actual user record
523
+ const userQuery = `SELECT * FROM "Users" WHERE "UserName" = $1`;
524
+ const userResult = await dblogin.query(userQuery, [githubUser.username]);
525
+
526
+ if (userResult.rows.length === 0) {
527
+ console.log(`[mbkauthe] GitHub login: User not found: ${githubUser.username}`);
528
+ return res.redirect('/mbkauthe/login?error=user_not_found');
529
+ }
530
+
531
+ const user = userResult.rows[0];
532
+
533
+ // Check 2FA if enabled
534
+ if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLowerCase() === "true") {
535
+ const twoFAQuery = `SELECT "TwoFAStatus" FROM "TwoFA" WHERE "UserName" = $1`;
536
+ const twoFAResult = await dblogin.query(twoFAQuery, [githubUser.username]);
537
+
538
+ if (twoFAResult.rows.length > 0 && twoFAResult.rows[0].TwoFAStatus) {
539
+ // 2FA is enabled, store pre-auth user and redirect to 2FA
540
+ req.session.preAuthUser = {
541
+ id: user.id,
542
+ username: user.UserName,
543
+ role: user.Role,
544
+ loginMethod: 'github'
545
+ };
546
+ console.log(`[mbkauthe] GitHub login: 2FA required for user: ${githubUser.username}`);
547
+ return res.redirect('/mbkauthe/2fa');
548
+ }
549
+ }
550
+
551
+ // Complete login process
552
+ const userForSession = {
553
+ id: user.id,
554
+ username: user.UserName,
555
+ role: user.Role,
556
+ };
557
+
558
+ // Generate session and complete login
559
+ const sessionId = crypto.randomBytes(256).toString("hex");
560
+ console.log(`[mbkauthe] GitHub login: Generated session ID for username: ${user.UserName}`);
561
+
562
+ // Delete old session record for this user
563
+ await dblogin.query('DELETE FROM "session" WHERE username = $1', [user.UserName]);
564
+
565
+ await dblogin.query(`UPDATE "Users" SET "SessionId" = $1 WHERE "id" = $2`, [
566
+ sessionId,
567
+ user.id,
568
+ ]);
569
+
570
+ req.session.user = {
571
+ id: user.id,
572
+ username: user.UserName,
573
+ role: user.Role,
574
+ sessionId,
575
+ };
576
+
577
+ req.session.save(async (err) => {
578
+ if (err) {
579
+ console.log("[mbkauthe] GitHub login session save error:", err);
580
+ return res.redirect('/mbkauthe/login?error=session_error');
581
+ }
582
+
583
+ try {
584
+ await dblogin.query(
585
+ 'UPDATE "session" SET username = $1 WHERE sid = $2',
586
+ [user.UserName, req.sessionID]
587
+ );
588
+ } catch (e) {
589
+ console.log("[mbkauthe] GitHub login: Failed to update username in session table:", e);
590
+ }
591
+
592
+ const cookieOptions = getCookieOptions();
593
+ res.cookie("sessionId", sessionId, cookieOptions);
594
+ console.log(`[mbkauthe] GitHub login: User "${user.UserName}" logged in successfully`);
595
+
596
+ // Redirect to the configured URL or home
597
+ const redirectUrl = mbkautheVar.loginRedirectURL || '/home';
598
+ res.redirect(redirectUrl);
599
+ });
600
+
601
+ } catch (err) {
602
+ console.error('[mbkauthe] GitHub login callback error:', err);
603
+ res.redirect('/mbkauthe/login?error=internal_error');
604
+ }
605
+ }
606
+ );
607
+ */
874
608
  export { getLatestVersion };
875
609
  export default router;
@@ -113,7 +113,7 @@ async function validateSession(req, res, next) {
113
113
  }
114
114
  }
115
115
 
116
- const checkRolePermission = (requiredRole) => {
116
+ const checkRolePermission = (requiredRole, notAllowed) => {
117
117
  return async (req, res, next) => {
118
118
  try {
119
119
  if (!req.session || !req.session.user || !req.session.user.id) {
@@ -143,6 +143,19 @@ const checkRolePermission = (requiredRole) => {
143
143
  }
144
144
 
145
145
  const userRole = result.rows[0].Role;
146
+
147
+ // Check notAllowed role
148
+ if (notAllowed && userRole === notAllowed) {
149
+ return res.render("Error/dError.handlebars", {
150
+ layout: false,
151
+ code: 403,
152
+ error: "Access Denied",
153
+ message: `You are not allowed to access this resource with role: ${notAllowed}`,
154
+ pagename: "Home",
155
+ page: `/${mbkautheVar.loginRedirectURL}`
156
+ });
157
+ }
158
+
146
159
  if (userRole !== requiredRole) {
147
160
  return res.render("Error/dError.handlebars", {
148
161
  layout: false,
@@ -162,64 +175,14 @@ const checkRolePermission = (requiredRole) => {
162
175
  };
163
176
  };
164
177
 
165
- const validateSessionAndRole = (requiredRole) => {
178
+ const validateSessionAndRole = (requiredRole, notAllowed) => {
166
179
  return async (req, res, next) => {
167
180
  await validateSession(req, res, async () => {
168
- await checkRolePermission(requiredRole)(req, res, next);
181
+ await checkRolePermission(requiredRole, notAllowed)(req, res, next);
169
182
  });
170
183
  };
171
184
  };
172
185
 
173
- async function getUserData(UserName, parameters) {
174
- try {
175
- if (!parameters || parameters.length === 0) {
176
- throw new Error("Parameters are required to fetch user data");
177
- }
178
-
179
- const userFields = [
180
- "Password", "UserName", "Role", "Active", "GuestRole", "HaveMailAccount", "AllowedApps",
181
- ];
182
- const profileFields = [
183
- "FullName", "email", "Image", "ProjectLinks", "SocialAccounts", "Bio", "Positions",
184
- ];
185
-
186
- let userParameters = [];
187
- let profileParameters = [];
188
-
189
- if (parameters === "profiledata") {
190
- userParameters = userFields.filter(field => field !== "Password");
191
- profileParameters = profileFields;
192
- } else {
193
- userParameters = userFields.filter(field => parameters.includes(field));
194
- profileParameters = profileFields.filter(field => parameters.includes(field));
195
- }
196
-
197
- let userResult = {};
198
- if (userParameters.length > 0) {
199
- const userQuery = `SELECT ${userParameters.map(field => `"${field}"`).join(", ")}
200
- FROM "Users" WHERE "UserName" = $1`;
201
- const userQueryResult = await dblogin.query(userQuery, [UserName]);
202
- if (userQueryResult.rows.length === 0) return { error: "User not found" };
203
- userResult = userQueryResult.rows[0];
204
- }
205
-
206
- let profileResult = {};
207
- if (profileParameters.length > 0) {
208
- const profileQuery = `SELECT ${profileParameters.map(field => `"${field}"`).join(", ")}
209
- FROM profiledata WHERE "UserName" = $1`;
210
- const profileQueryResult = await dblogin.query(profileQuery, [UserName]);
211
- if (profileQueryResult.rows.length === 0) return { error: "Profile data not found" };
212
- profileResult = profileQueryResult.rows[0];
213
- }
214
-
215
- const combinedResult = { ...userResult, ...profileResult };
216
- return combinedResult;
217
- } catch (err) {
218
- console.error("[mbkauthe] Error fetching user data:", err.message);
219
- throw err;
220
- }
221
- }
222
-
223
186
  const authenticate = (authentication) => {
224
187
  return (req, res, next) => {
225
188
  const token = req.headers["authorization"];
@@ -331,4 +294,4 @@ const authapi = (requiredRole = []) => {
331
294
  };
332
295
  };
333
296
 
334
- export { validateSession, checkRolePermission, validateSessionAndRole, getUserData, authenticate, authapi };
297
+ export { validateSession, checkRolePermission, validateSessionAndRole, authenticate, authapi };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mbkauthe",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "MBKTechStudio's reusable authentication system for Node.js applications.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -0,0 +1,236 @@
1
+ <html>
2
+
3
+ <head>
4
+ <title>Version and Configuration Information</title>
5
+ <link rel="icon" type="image/x-icon" href="https://mbktechstudio.com/Assets/Images/Icon/dgicon.svg">
6
+
7
+ <style>
8
+ :root {
9
+ --bg-color: #121212;
10
+ --card-bg: #1e1e1e;
11
+ --text-color: #e0e0e0;
12
+ --text-secondary: #a0a0a0;
13
+ --primary: #bb86fc;
14
+ --primary-dark: #3700b3;
15
+ --secondary: #03dac6;
16
+ --border-color: #333;
17
+ --success: #4caf50;
18
+ --warning: #ff9800;
19
+ --error: #f44336;
20
+ --key-color: #bb86fc;
21
+ --string-color: #03dac6;
22
+ --number-color: #ff7043;
23
+ --boolean-color: #7986cb;
24
+ }
25
+
26
+ body {
27
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
28
+ margin: 0;
29
+ padding: 20px;
30
+ background-color: var(--bg-color);
31
+ color: var(--text-color);
32
+ }
33
+
34
+ .container {
35
+ max-width: 1000px;
36
+ margin: 0 auto;
37
+ padding: 20px;
38
+ background: var(--card-bg);
39
+ border-radius: 8px;
40
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
41
+ }
42
+
43
+ h1 {
44
+ color: var(--primary);
45
+ text-align: center;
46
+ margin-bottom: 30px;
47
+ padding-bottom: 10px;
48
+ border-bottom: 1px solid var(--border-color);
49
+ font-weight: bold;
50
+ letter-spacing: 1px;
51
+ }
52
+
53
+ .info-section {
54
+ margin-bottom: 25px;
55
+ padding: 20px;
56
+ border: 1px solid var(--border-color);
57
+ border-radius: 8px;
58
+ background-color: rgba(30, 30, 30, 0.7);
59
+ transition: all 0.3s ease;
60
+ }
61
+
62
+ .info-section:hover {
63
+ border-color: var(--primary);
64
+ box-shadow: 0 0 0 1px var(--primary);
65
+ }
66
+
67
+ .info-section h2 {
68
+ color: var(--primary);
69
+ border-bottom: 2px solid var(--primary-dark);
70
+ padding-bottom: 8px;
71
+ margin-top: 0;
72
+ margin-bottom: 15px;
73
+ font-size: 1.2em;
74
+ display: flex;
75
+ justify-content: space-between;
76
+ align-items: center;
77
+ }
78
+
79
+ .info-row {
80
+ display: flex;
81
+ margin-bottom: 10px;
82
+ padding-bottom: 10px;
83
+ border-bottom: 1px solid var(--border-color);
84
+ }
85
+
86
+ .info-label {
87
+ font-weight: 600;
88
+ color: var(--text-secondary);
89
+ min-width: 220px;
90
+ font-size: 0.95em;
91
+ }
92
+
93
+ .info-value {
94
+ flex: 1;
95
+ word-break: break-word;
96
+ color: var(--text-color);
97
+ }
98
+
99
+ .version-status {
100
+ display: inline-block;
101
+ padding: 3px 10px;
102
+ border-radius: 12px;
103
+ font-size: 0.8em;
104
+ font-weight: 600;
105
+ margin-left: 10px;
106
+ }
107
+
108
+ .version-up-to-date {
109
+ background: rgba(76, 175, 80, 0.2);
110
+ color: var(--success);
111
+ border: 1px solid var(--success);
112
+ }
113
+
114
+ .version-outdated {
115
+ background: rgba(244, 67, 54, 0.2);
116
+ color: var(--error);
117
+ border: 1px solid var(--error);
118
+ }
119
+
120
+ .version-fetch-error {
121
+ background: rgba(255, 152, 0, 0.2);
122
+ color: var(--warning);
123
+ border: 1px solid var(--warning);
124
+ }
125
+
126
+ ::-webkit-scrollbar {
127
+ width: 8px;
128
+ height: 8px;
129
+ }
130
+
131
+ ::-webkit-scrollbar-track {
132
+ background: #2d2d2d;
133
+ border-radius: 4px;
134
+ }
135
+
136
+ ::-webkit-scrollbar-thumb {
137
+ background: #555;
138
+ border-radius: 4px;
139
+ }
140
+
141
+ ::-webkit-scrollbar-thumb:hover {
142
+ background: var(--primary);
143
+ }
144
+
145
+ /* Tooltip for copy button */
146
+ .tooltip {
147
+ position: relative;
148
+ display: inline-block;
149
+ }
150
+
151
+ .tooltip .tooltiptext {
152
+ visibility: hidden;
153
+ width: 120px;
154
+ background-color: #333;
155
+ color: #fff;
156
+ text-align: center;
157
+ border-radius: 6px;
158
+ padding: 5px;
159
+ position: absolute;
160
+ z-index: 1;
161
+ bottom: 125%;
162
+ left: 50%;
163
+ margin-left: -60px;
164
+ opacity: 0;
165
+ transition: opacity 0.3s;
166
+ font-size: 0.8em;
167
+ }
168
+
169
+ .tooltip:hover .tooltiptext {
170
+ visibility: visible;
171
+ opacity: 1;
172
+ }
173
+ </style>
174
+ <link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
175
+ </head>
176
+
177
+ <body>
178
+ <div class="container">
179
+ <h1>Version and Configuration Dashboard</h1>
180
+
181
+ <div class="info-section">
182
+ <h2>Version Information</h2>
183
+ <div class="info-row">
184
+ <div class="info-label">Current Version:</div>
185
+ <div class="info-value" id="CurrentVersion">{{version}}</div>
186
+ </div>
187
+ <div class="info-row">
188
+ <div class="info-label">Latest Version:</div>
189
+ <div class="info-value">
190
+ {{latestVersion}}
191
+
192
+ {{#if (eq latestVersion version)}}
193
+ <span class="version-status version-up-to-date">Up to date</span>
194
+ {{/if}}
195
+
196
+ </div>
197
+ </div>
198
+ </div>
199
+
200
+ <div class="info-section">
201
+ <h2>Configuration Information</h2>
202
+ <div class="info-row">
203
+ <div class="info-label">APP_NAME:</div>
204
+ <div class="info-value">{{mbkautheVar.APP_NAME}}</div>
205
+ </div>
206
+ <div class="info-row">
207
+ <div class="info-label">MBKAUTH_TWO_FA_ENABLE:</div>
208
+ <div class="info-value">{{mbkautheVar.MBKAUTH_TWO_FA_ENABLE}}</div>
209
+ </div>
210
+ <div class="info-row">
211
+ <div class="info-label">COOKIE_EXPIRE_TIME:</div>
212
+ <div class="info-value">{{mbkautheVar.COOKIE_EXPIRE_TIME}} Days</div>
213
+ </div>
214
+ <div class="info-row">
215
+ <div class="info-label">IS_DEPLOYED:</div>
216
+ <div class="info-value">{{mbkautheVar.IS_DEPLOYED}}</div>
217
+ </div>
218
+ <div class="info-row">
219
+ <div class="info-label">DOMAIN:</div>
220
+ <div class="info-value">{{mbkautheVar.DOMAIN}}</div>
221
+ </div>
222
+ <div class="info-row">
223
+ <div class="info-label">Login Redirect URL:</div>
224
+ <div class="info-value">{{mbkautheVar.loginRedirectURL}}</div>
225
+ </div>
226
+ <div class="info-row">
227
+ <div class="info-label">GitHub Login Enabled:</div>
228
+ <div class="info-value">{{mbkautheVar.GITHUB_LOGIN_ENABLED}}</div>
229
+ </div>
230
+ </div>
231
+
232
+ </div>
233
+
234
+ </body>
235
+
236
+ </html>
@@ -242,6 +242,60 @@
242
242
  box-shadow: none;
243
243
  }
244
244
 
245
+ .social-login {
246
+ margin-top: 1.5rem;
247
+ text-align: center;
248
+ }
249
+
250
+ .divider {
251
+ display: flex;
252
+ align-items: center;
253
+ justify-content: center;
254
+ margin: 1rem 0;
255
+ }
256
+
257
+ .divider::before,
258
+ .divider::after {
259
+ content: '';
260
+ flex: 1;
261
+ height: 1px;
262
+ background: var(--gray-dark);
263
+ }
264
+
265
+ .divider span {
266
+ background: var(--dark);
267
+ padding: 0 15px;
268
+ color: var(--gray);
269
+ font-size: 0.9rem;
270
+ }
271
+
272
+ .btn-github {
273
+ display: inline-flex;
274
+ align-items: center;
275
+ justify-content: center;
276
+ gap: 8px;
277
+ width: 100%;
278
+ padding: 12px 20px;
279
+ border-radius: var(--radius-sm);
280
+ background: #24292e;
281
+ color: white;
282
+ font-weight: 500;
283
+ font-size: 0.95rem;
284
+ text-decoration: none;
285
+ transition: var(--transition);
286
+ box-shadow: var(--shadow-sm);
287
+ }
288
+
289
+ .btn-github:hover {
290
+ background: #3b4045;
291
+ box-shadow: var(--shadow-md);
292
+ transform: translateY(-2px);
293
+ }
294
+
295
+ .btn-github i {
296
+ font-size: 1.1rem;
297
+ }
298
+
245
299
  .login-links {
246
300
  display: flex;
247
301
  justify-content: space-between;
@@ -511,6 +565,17 @@
511
565
  <button type="submit" class="btn-login" id="loginButton">
512
566
  <span id="loginButtonText">Login</span>
513
567
  </button>
568
+ {{#if githubLoginEnabled }}
569
+ <div class="social-login">
570
+ <div class="divider">
571
+ <span>or</span>
572
+ </div>
573
+ <a href="/mbkauthe/api/github/login" class="btn-github">
574
+ <i class="fab fa-github"></i>
575
+ <span>Continue with GitHub</span>
576
+ </a>
577
+ </div>
578
+ {{/if }}
514
579
 
515
580
  {{#if userLoggedIn }}
516
581
  <div class="WarningboxInfo">