mbkauthe 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -10
- package/docs/db.md +0 -6
- package/env.md +68 -0
- package/lib/main.js +289 -262
- package/lib/pool.js +2 -2
- package/package.json +1 -1
- package/views/loginmbkauthe.handlebars +5 -1
package/.env.example
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
mbkautheVar=
|
|
2
|
-
"APP_NAME": "MBKAUTH",
|
|
3
|
-
"SESSION_SECRET_KEY": "your-session-secret-key",
|
|
4
|
-
"IS_DEPLOYED": "true",
|
|
5
|
-
"LOGIN_DB": "postgres://username:password@host:port/database",
|
|
6
|
-
"MBKAUTH_TWO_FA_ENABLE": "false",
|
|
7
|
-
"COOKIE_EXPIRE_TIME": 2,
|
|
8
|
-
"DOMAIN": "yourdomain.com",
|
|
9
|
-
"loginRedirectURL": "/admin"
|
|
10
|
-
}'
|
|
1
|
+
mbkautheVar={"APP_NAME":"mbkauthe","Main_SECRET_TOKEN": 123,"SESSION_SECRET_KEY":"123","IS_DEPLOYED":"true","LOGIN_DB":"postgres://","MBKAUTH_TWO_FA_ENABLE":"true","COOKIE_EXPIRE_TIME":2,"DOMAIN":"mbktech.org","loginRedirectURL":"/mbkauthe/test","GITHUB_LOGIN_ENABLED":"true","GITHUB_CLIENT_ID":"","GITHUB_CLIENT_SECRET":""}
|
|
11
2
|
|
|
12
3
|
# See env.md for more details
|
package/docs/db.md
CHANGED
|
@@ -12,8 +12,6 @@ Add these to your `.env` file:
|
|
|
12
12
|
# GitHub OAuth App Configuration
|
|
13
13
|
GITHUB_CLIENT_ID=your_github_client_id
|
|
14
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
15
|
```
|
|
18
16
|
|
|
19
17
|
### 2. GitHub OAuth App Setup
|
|
@@ -109,10 +107,6 @@ The login page now includes:
|
|
|
109
107
|
# Required for GitHub Login
|
|
110
108
|
GITHUB_CLIENT_ID=your_github_client_id
|
|
111
109
|
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
110
|
```
|
|
117
111
|
|
|
118
112
|
The GitHub login feature is now fully integrated into your mbkauthe system and ready to use!
|
package/env.md
CHANGED
|
@@ -138,6 +138,74 @@ COOKIE_EXPIRE_TIME=30 # 1 month (convenience)
|
|
|
138
138
|
|
|
139
139
|
---
|
|
140
140
|
|
|
141
|
+
## 🐙 GitHub OAuth Authentication
|
|
142
|
+
|
|
143
|
+
### GitHub Login Configuration
|
|
144
|
+
```env
|
|
145
|
+
GITHUB_LOGIN_ENABLED=false
|
|
146
|
+
GITHUB_CLIENT_ID=your-github-client-id
|
|
147
|
+
GITHUB_CLIENT_SECRET=your-github-client-secret
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### GITHUB_LOGIN_ENABLED
|
|
151
|
+
**Description:** Enables or disables GitHub OAuth login functionality.
|
|
152
|
+
|
|
153
|
+
**Values:**
|
|
154
|
+
- `true` - Enable GitHub login (users can authenticate via GitHub)
|
|
155
|
+
- `false` - Disable GitHub login (default)
|
|
156
|
+
|
|
157
|
+
**Required:** Yes (if using GitHub authentication)
|
|
158
|
+
|
|
159
|
+
#### GITHUB_CLIENT_ID
|
|
160
|
+
**Description:** OAuth application client ID from GitHub.
|
|
161
|
+
|
|
162
|
+
- **Purpose:** Identifies your application to GitHub's OAuth service
|
|
163
|
+
- **Format:** Alphanumeric string provided by GitHub
|
|
164
|
+
- **Setup:** Obtain from [GitHub Developer Settings](https://github.com/settings/developers)
|
|
165
|
+
- **Required:** Yes (when `GITHUB_LOGIN_ENABLED=true`)
|
|
166
|
+
|
|
167
|
+
**Example:** `GITHUB_CLIENT_ID=Iv1.a1b2c3d4e5f6g7h8`
|
|
168
|
+
|
|
169
|
+
#### GITHUB_CLIENT_SECRET
|
|
170
|
+
**Description:** OAuth application client secret from GitHub.
|
|
171
|
+
|
|
172
|
+
- **Purpose:** Authenticates your application with GitHub's OAuth service
|
|
173
|
+
- **Security:** Keep this secret secure and never commit to version control
|
|
174
|
+
- **Format:** Alphanumeric string provided by GitHub
|
|
175
|
+
- **Setup:** Generated when creating OAuth app in GitHub Developer Settings
|
|
176
|
+
- **Required:** Yes (when `GITHUB_LOGIN_ENABLED=true`)
|
|
177
|
+
|
|
178
|
+
**Example:** `GITHUB_CLIENT_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0`
|
|
179
|
+
|
|
180
|
+
### Setting Up GitHub OAuth
|
|
181
|
+
|
|
182
|
+
1. **Create GitHub OAuth App:**
|
|
183
|
+
- Go to [GitHub Developer Settings](https://github.com/settings/developers)
|
|
184
|
+
- Click "New OAuth App"
|
|
185
|
+
- Fill in application details:
|
|
186
|
+
- **Application name:** Your app name
|
|
187
|
+
- **Homepage URL:** `https://yourdomain.com` (or `http://localhost:3000` for dev)
|
|
188
|
+
- **Authorization callback URL:** `https://yourdomain.com/auth/github/callback`
|
|
189
|
+
- Click "Register application"
|
|
190
|
+
|
|
191
|
+
2. **Copy Credentials:**
|
|
192
|
+
- Copy the **Client ID**
|
|
193
|
+
- Generate and copy the **Client Secret**
|
|
194
|
+
|
|
195
|
+
3. **Configure Environment:**
|
|
196
|
+
```env
|
|
197
|
+
GITHUB_LOGIN_ENABLED=true
|
|
198
|
+
GITHUB_CLIENT_ID=your-copied-client-id
|
|
199
|
+
GITHUB_CLIENT_SECRET=your-copied-client-secret
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Security Notes:**
|
|
203
|
+
- Use separate OAuth apps for development and production environments
|
|
204
|
+
- Rotate client secrets periodically
|
|
205
|
+
- Never expose client secrets in client-side code
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
141
209
|
## 🚀 Quick Setup Examples
|
|
142
210
|
|
|
143
211
|
### Development Environment
|
package/lib/main.js
CHANGED
|
@@ -15,6 +15,7 @@ import passport from 'passport';
|
|
|
15
15
|
import GitHubStrategy from 'passport-github2';
|
|
16
16
|
|
|
17
17
|
import { createRequire } from "module";
|
|
18
|
+
import { fileURLToPath } from "url";
|
|
18
19
|
import fs from "fs";
|
|
19
20
|
import path from "path";
|
|
20
21
|
|
|
@@ -31,8 +32,22 @@ router.use(express.json());
|
|
|
31
32
|
router.use(express.urlencoded({ extended: true }));
|
|
32
33
|
router.use(cookieParser());
|
|
33
34
|
|
|
35
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
|
|
34
37
|
router.get('/mbkauthe/main.js', (req, res) => {
|
|
35
|
-
res.sendFile(path.join(
|
|
38
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'main.js'));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
router.get("/mbkauthe/bg.avif", (req, res) => {
|
|
42
|
+
const imgPath = path.join(__dirname, "..", "public", "bg.avif");
|
|
43
|
+
res.setHeader('Content-Type', 'image/avif');
|
|
44
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
|
45
|
+
const stream = fs.createReadStream(imgPath);
|
|
46
|
+
stream.on('error', (err) => {
|
|
47
|
+
console.error('[mbkauthe] Error streaming bg.avif:', err);
|
|
48
|
+
res.status(404).send('Image not found');
|
|
49
|
+
});
|
|
50
|
+
stream.pipe(res);
|
|
36
51
|
});
|
|
37
52
|
|
|
38
53
|
// CSRF protection middleware
|
|
@@ -46,8 +61,8 @@ router.use((req, res, next) => {
|
|
|
46
61
|
const originUrl = new URL(origin);
|
|
47
62
|
const allowedDomain = `.${mbkautheVar.DOMAIN}`;
|
|
48
63
|
// Exact match or subdomain match (must end with .domain.com, not just domain.com)
|
|
49
|
-
if (originUrl.hostname === mbkautheVar.DOMAIN ||
|
|
50
|
-
|
|
64
|
+
if (originUrl.hostname === mbkautheVar.DOMAIN ||
|
|
65
|
+
(originUrl.hostname.endsWith(allowedDomain) && originUrl.hostname.charAt(originUrl.hostname.length - allowedDomain.length - 1) !== '.')) {
|
|
51
66
|
res.header('Access-Control-Allow-Origin', origin);
|
|
52
67
|
res.header('Access-Control-Allow-Credentials', 'true');
|
|
53
68
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
|
|
@@ -162,8 +177,8 @@ const getClearCookieOptions = () => ({
|
|
|
162
177
|
httpOnly: true
|
|
163
178
|
});
|
|
164
179
|
|
|
165
|
-
router.get('/mbkauthe/test', validateSession, LoginLimit, async(req, res) => {
|
|
166
|
-
if(req.session?.user) {
|
|
180
|
+
router.get('/mbkauthe/test', validateSession, LoginLimit, async (req, res) => {
|
|
181
|
+
if (req.session?.user) {
|
|
167
182
|
return res.send(`
|
|
168
183
|
<head> <script src="/mbkauthe/main.js"></script> </head>
|
|
169
184
|
<p>if you are seeing this page than User is logged in.</p>
|
|
@@ -197,16 +212,18 @@ async function completeLoginProcess(req, res, user, redirectUrl = null) {
|
|
|
197
212
|
});
|
|
198
213
|
|
|
199
214
|
// 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}"%`]
|
|
215
|
+
await dblogin.query({
|
|
216
|
+
name: 'login-delete-old-user-sessions',
|
|
217
|
+
text: 'DELETE FROM "session" WHERE sess::text LIKE $1',
|
|
218
|
+
values: [`%"username":"${username}"%`]
|
|
204
219
|
});
|
|
205
220
|
|
|
206
|
-
await dblogin.query({
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
221
|
+
await dblogin.query({
|
|
222
|
+
name: 'login-update-session-id', text: `UPDATE "Users" SET "SessionId" = $1 WHERE "id" = $2`, values: [
|
|
223
|
+
sessionId,
|
|
224
|
+
user.id,
|
|
225
|
+
]
|
|
226
|
+
});
|
|
210
227
|
|
|
211
228
|
req.session.user = {
|
|
212
229
|
id: user.id,
|
|
@@ -533,19 +550,15 @@ async function getLatestVersion() {
|
|
|
533
550
|
}
|
|
534
551
|
}
|
|
535
552
|
|
|
536
|
-
router.get("/mbkauthe/bg.avif", (req, res) => {
|
|
537
|
-
res.sendFile("bg.avif", { root: path.join(process.cwd(), "public") });
|
|
538
|
-
});
|
|
539
|
-
|
|
540
553
|
router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (req, res) => {
|
|
541
554
|
let latestVersion;
|
|
542
555
|
const parameters = req.query;
|
|
543
556
|
let authorized = false;
|
|
544
|
-
|
|
557
|
+
|
|
545
558
|
if (parameters.password && mbkautheVar.Main_SECRET_TOKEN) {
|
|
546
559
|
authorized = String(parameters.password) === String(mbkautheVar.Main_SECRET_TOKEN);
|
|
547
560
|
}
|
|
548
|
-
|
|
561
|
+
|
|
549
562
|
try {
|
|
550
563
|
latestVersion = await getLatestVersion();
|
|
551
564
|
//latestVersion = "Under Development"; // Placeholder for the latest version
|
|
@@ -579,69 +592,69 @@ router.get(["/mbkauthe/info", "/mbkauthe/i"], LoginLimit, async (req, res) => {
|
|
|
579
592
|
|
|
580
593
|
// Configure GitHub Strategy for login
|
|
581
594
|
passport.use('github-login', new GitHubStrategy({
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
595
|
+
clientID: mbkautheVar.GITHUB_CLIENT_ID,
|
|
596
|
+
clientSecret: mbkautheVar.GITHUB_CLIENT_SECRET,
|
|
597
|
+
callbackURL: '/mbkauthe/api/github/login/callback',
|
|
598
|
+
scope: ['user:email']
|
|
586
599
|
},
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
600
|
+
async (accessToken, refreshToken, profile, done) => {
|
|
601
|
+
try {
|
|
602
|
+
// Check if this GitHub account is linked to any user
|
|
603
|
+
const githubUser = await dblogin.query({
|
|
604
|
+
name: 'github-login-get-user',
|
|
605
|
+
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',
|
|
606
|
+
values: [profile.id]
|
|
607
|
+
});
|
|
595
608
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
// Return user data for login
|
|
623
|
-
return done(null, {
|
|
624
|
-
id: user.id, // This should be the user ID from the Users table
|
|
625
|
-
username: user.UserName,
|
|
626
|
-
role: user.Role,
|
|
627
|
-
githubId: user.github_id,
|
|
628
|
-
githubUsername: user.github_username
|
|
629
|
-
});
|
|
630
|
-
} catch (err) {
|
|
631
|
-
console.error('[mbkauthe] GitHub login error:', err);
|
|
632
|
-
err.code = err.code || 'GITHUB_AUTH_ERROR';
|
|
633
|
-
return done(err);
|
|
609
|
+
if (githubUser.rows.length === 0) {
|
|
610
|
+
// GitHub account is not linked to any user
|
|
611
|
+
const error = new Error('GitHub account not linked to any user');
|
|
612
|
+
error.code = 'GITHUB_NOT_LINKED';
|
|
613
|
+
return done(error);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const user = githubUser.rows[0];
|
|
617
|
+
|
|
618
|
+
// Check if the user account is active
|
|
619
|
+
if (!user.Active) {
|
|
620
|
+
const error = new Error('Account is inactive');
|
|
621
|
+
error.code = 'ACCOUNT_INACTIVE';
|
|
622
|
+
return done(error);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Check if user is authorized for this app (same logic as regular login)
|
|
626
|
+
if (user.Role !== "SuperAdmin") {
|
|
627
|
+
const allowedApps = user.AllowedApps;
|
|
628
|
+
if (!allowedApps || !allowedApps.some(app => app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
|
|
629
|
+
const error = new Error(`Not authorized to use ${mbkautheVar.APP_NAME}`);
|
|
630
|
+
error.code = 'NOT_AUTHORIZED';
|
|
631
|
+
return done(error);
|
|
634
632
|
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Return user data for login
|
|
636
|
+
return done(null, {
|
|
637
|
+
id: user.id, // This should be the user ID from the Users table
|
|
638
|
+
username: user.UserName,
|
|
639
|
+
role: user.Role,
|
|
640
|
+
githubId: user.github_id,
|
|
641
|
+
githubUsername: user.github_username
|
|
642
|
+
});
|
|
643
|
+
} catch (err) {
|
|
644
|
+
console.error('[mbkauthe] GitHub login error:', err);
|
|
645
|
+
err.code = err.code || 'GITHUB_AUTH_ERROR';
|
|
646
|
+
return done(err);
|
|
635
647
|
}
|
|
648
|
+
}
|
|
636
649
|
));
|
|
637
650
|
|
|
638
651
|
// Serialize/Deserialize user for GitHub login
|
|
639
652
|
passport.serializeUser((user, done) => {
|
|
640
|
-
|
|
653
|
+
done(null, user);
|
|
641
654
|
});
|
|
642
655
|
|
|
643
656
|
passport.deserializeUser((user, done) => {
|
|
644
|
-
|
|
657
|
+
done(null, user);
|
|
645
658
|
});
|
|
646
659
|
|
|
647
660
|
// Initialize passport
|
|
@@ -650,212 +663,226 @@ router.use(passport.session());
|
|
|
650
663
|
|
|
651
664
|
// GitHub login initiation
|
|
652
665
|
router.get('/mbkauthe/api/github/login', GitHubOAuthLimit, (req, res, next) => {
|
|
666
|
+
if (mbkautheVar.GITHUB_LOGIN_ENABLED) {
|
|
653
667
|
// Store redirect parameter in session before OAuth flow (validate to prevent open redirect)
|
|
654
668
|
const redirect = req.query.redirect;
|
|
655
669
|
if (redirect && typeof redirect === 'string') {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
670
|
+
// Only allow relative URLs or same-origin URLs to prevent open redirect attacks
|
|
671
|
+
if (redirect.startsWith('/') && !redirect.startsWith('//')) {
|
|
672
|
+
req.session.oauthRedirect = redirect;
|
|
673
|
+
} else {
|
|
674
|
+
console.warn(`[mbkauthe] Invalid redirect parameter rejected: ${redirect}`);
|
|
675
|
+
}
|
|
662
676
|
}
|
|
663
677
|
passport.authenticate('github-login')(req, res, next);
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
res.status(403).render('Error/dError.handlebars', {
|
|
681
|
+
layout: false,
|
|
682
|
+
code: '403',
|
|
683
|
+
error: 'GitHub Login Disabled',
|
|
684
|
+
message: 'GitHub login is currently disabled. Please use your username and password to log in.',
|
|
685
|
+
page: '/mbkauthe/login',
|
|
686
|
+
pagename: 'Login',
|
|
687
|
+
version: packageJson.version,
|
|
688
|
+
app: mbkautheVar.APP_NAME
|
|
689
|
+
});
|
|
690
|
+
}
|
|
664
691
|
});
|
|
665
692
|
|
|
666
693
|
// GitHub login callback
|
|
667
694
|
router.get('/mbkauthe/api/github/login/callback',
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
-
},
|
|
749
|
-
async (req, res) => {
|
|
750
|
-
try {
|
|
751
|
-
const githubUser = req.user;
|
|
752
|
-
|
|
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]
|
|
695
|
+
GitHubOAuthLimit,
|
|
696
|
+
(req, res, next) => {
|
|
697
|
+
passport.authenticate('github-login', {
|
|
698
|
+
session: false // We'll handle session manually
|
|
699
|
+
}, (err, user, info) => {
|
|
700
|
+
// Custom error handling for passport authentication
|
|
701
|
+
if (err) {
|
|
702
|
+
console.error('[mbkauthe] GitHub authentication error:', err);
|
|
703
|
+
|
|
704
|
+
// Map error codes to user-friendly messages
|
|
705
|
+
switch (err.code) {
|
|
706
|
+
case 'GITHUB_NOT_LINKED':
|
|
707
|
+
return res.status(403).render('Error/dError.handlebars', {
|
|
708
|
+
layout: false,
|
|
709
|
+
code: '403',
|
|
710
|
+
error: 'GitHub Account Not Linked',
|
|
711
|
+
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.',
|
|
712
|
+
page: '/mbkauthe/login',
|
|
713
|
+
pagename: 'Login',
|
|
714
|
+
version: packageJson.version,
|
|
715
|
+
app: mbkautheVar.APP_NAME
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
case 'ACCOUNT_INACTIVE':
|
|
719
|
+
return res.status(403).render('Error/dError.handlebars', {
|
|
720
|
+
layout: false,
|
|
721
|
+
code: '403',
|
|
722
|
+
error: 'Account Inactive',
|
|
723
|
+
message: 'Your account has been deactivated. Please contact your administrator.',
|
|
724
|
+
page: '/mbkauthe/login',
|
|
725
|
+
pagename: 'Login',
|
|
726
|
+
version: packageJson.version,
|
|
727
|
+
app: mbkautheVar.APP_NAME
|
|
759
728
|
});
|
|
760
729
|
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const user = userResult.rows[0];
|
|
777
|
-
|
|
778
|
-
// Check 2FA if enabled
|
|
779
|
-
if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLowerCase() === "true") {
|
|
780
|
-
const twoFAQuery = `SELECT "TwoFAStatus" FROM "TwoFA" WHERE "UserName" = $1`;
|
|
781
|
-
const twoFAResult = await dblogin.query({
|
|
782
|
-
name: 'github-check-2fa-status',
|
|
783
|
-
text: twoFAQuery,
|
|
784
|
-
values: [githubUser.username]
|
|
785
|
-
});
|
|
786
|
-
|
|
787
|
-
if (twoFAResult.rows.length > 0 && twoFAResult.rows[0].TwoFAStatus) {
|
|
788
|
-
// 2FA is enabled, store pre-auth user and redirect to 2FA
|
|
789
|
-
req.session.preAuthUser = {
|
|
790
|
-
id: user.id,
|
|
791
|
-
username: user.UserName,
|
|
792
|
-
UserName: user.UserName,
|
|
793
|
-
role: user.Role,
|
|
794
|
-
Role: user.Role,
|
|
795
|
-
loginMethod: 'github'
|
|
796
|
-
};
|
|
797
|
-
console.log(`[mbkauthe] GitHub login: 2FA required for user: ${githubUser.username}`);
|
|
798
|
-
return res.redirect('/mbkauthe/2fa');
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
// Complete login process using the shared function
|
|
803
|
-
const userForSession = {
|
|
804
|
-
id: user.id,
|
|
805
|
-
username: user.UserName,
|
|
806
|
-
UserName: user.UserName,
|
|
807
|
-
role: user.Role,
|
|
808
|
-
Role: user.Role
|
|
809
|
-
};
|
|
810
|
-
|
|
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;
|
|
815
|
-
|
|
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);
|
|
824
|
-
};
|
|
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);
|
|
835
|
-
}
|
|
836
|
-
// Restore original methods for error responses
|
|
837
|
-
res.json = originalJson;
|
|
838
|
-
res.status = originalStatus;
|
|
839
|
-
return originalJson(data);
|
|
840
|
-
};
|
|
841
|
-
|
|
842
|
-
await completeLoginProcess(req, res, userForSession);
|
|
843
|
-
|
|
844
|
-
} catch (err) {
|
|
845
|
-
console.error('[mbkauthe] GitHub login callback error:', err);
|
|
730
|
+
case 'NOT_AUTHORIZED':
|
|
731
|
+
return res.status(403).render('Error/dError.handlebars', {
|
|
732
|
+
layout: false,
|
|
733
|
+
code: '403',
|
|
734
|
+
error: 'Not Authorized',
|
|
735
|
+
message: `You are not authorized to access ${mbkautheVar.APP_NAME}. Please contact your administrator.`,
|
|
736
|
+
page: '/mbkauthe/login',
|
|
737
|
+
pagename: 'Login',
|
|
738
|
+
version: packageJson.version,
|
|
739
|
+
app: mbkautheVar.APP_NAME
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
default:
|
|
846
743
|
return res.status(500).render('Error/dError.handlebars', {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
744
|
+
layout: false,
|
|
745
|
+
code: '500',
|
|
746
|
+
error: 'Authentication Error',
|
|
747
|
+
message: 'An error occurred during GitHub authentication. Please try again.',
|
|
748
|
+
page: '/mbkauthe/login',
|
|
749
|
+
pagename: 'Login',
|
|
750
|
+
version: packageJson.version,
|
|
751
|
+
app: mbkautheVar.APP_NAME,
|
|
752
|
+
details: process.env.NODE_ENV === 'development' ? `${err.message}\n${err.stack}` : 'Error details hidden in production'
|
|
856
753
|
});
|
|
857
754
|
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
if (!user) {
|
|
758
|
+
console.error('[mbkauthe] GitHub callback: No user data received');
|
|
759
|
+
return res.status(401).render('Error/dError.handlebars', {
|
|
760
|
+
layout: false,
|
|
761
|
+
code: '401',
|
|
762
|
+
error: 'Authentication Failed',
|
|
763
|
+
message: 'GitHub authentication failed. Please try again.',
|
|
764
|
+
page: '/mbkauthe/login',
|
|
765
|
+
pagename: 'Login',
|
|
766
|
+
version: packageJson.version,
|
|
767
|
+
app: mbkautheVar.APP_NAME
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Authentication successful, attach user to request
|
|
772
|
+
req.user = user;
|
|
773
|
+
next();
|
|
774
|
+
})(req, res, next);
|
|
775
|
+
},
|
|
776
|
+
async (req, res) => {
|
|
777
|
+
try {
|
|
778
|
+
const githubUser = req.user;
|
|
779
|
+
|
|
780
|
+
// Find the actual user record with named query
|
|
781
|
+
const userQuery = `SELECT id, "UserName", "Active", "Role", "AllowedApps" FROM "Users" WHERE "UserName" = $1`;
|
|
782
|
+
const userResult = await dblogin.query({
|
|
783
|
+
name: 'github-callback-get-user',
|
|
784
|
+
text: userQuery,
|
|
785
|
+
values: [githubUser.username]
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
if (userResult.rows.length === 0) {
|
|
789
|
+
console.error(`[mbkauthe] GitHub login: User not found: ${githubUser.username}`);
|
|
790
|
+
return res.status(404).render('Error/dError.handlebars', {
|
|
791
|
+
layout: false,
|
|
792
|
+
code: '404',
|
|
793
|
+
error: 'User Not Found',
|
|
794
|
+
message: 'Your GitHub account is linked, but the user account no longer exists in our system.',
|
|
795
|
+
page: '/mbkauthe/login',
|
|
796
|
+
pagename: 'Login',
|
|
797
|
+
version: packageJson.version,
|
|
798
|
+
app: mbkautheVar.APP_NAME,
|
|
799
|
+
details: `GitHub username: ${githubUser.username}\nPlease contact your administrator.`
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
const user = userResult.rows[0];
|
|
804
|
+
|
|
805
|
+
// Check 2FA if enabled
|
|
806
|
+
if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLowerCase() === "true") {
|
|
807
|
+
const twoFAQuery = `SELECT "TwoFAStatus" FROM "TwoFA" WHERE "UserName" = $1`;
|
|
808
|
+
const twoFAResult = await dblogin.query({
|
|
809
|
+
name: 'github-check-2fa-status',
|
|
810
|
+
text: twoFAQuery,
|
|
811
|
+
values: [githubUser.username]
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
if (twoFAResult.rows.length > 0 && twoFAResult.rows[0].TwoFAStatus) {
|
|
815
|
+
// 2FA is enabled, store pre-auth user and redirect to 2FA
|
|
816
|
+
req.session.preAuthUser = {
|
|
817
|
+
id: user.id,
|
|
818
|
+
username: user.UserName,
|
|
819
|
+
UserName: user.UserName,
|
|
820
|
+
role: user.Role,
|
|
821
|
+
Role: user.Role,
|
|
822
|
+
loginMethod: 'github'
|
|
823
|
+
};
|
|
824
|
+
console.log(`[mbkauthe] GitHub login: 2FA required for user: ${githubUser.username}`);
|
|
825
|
+
return res.redirect('/mbkauthe/2fa');
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Complete login process using the shared function
|
|
830
|
+
const userForSession = {
|
|
831
|
+
id: user.id,
|
|
832
|
+
username: user.UserName,
|
|
833
|
+
UserName: user.UserName,
|
|
834
|
+
role: user.Role,
|
|
835
|
+
Role: user.Role
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
// For OAuth redirect flow, we need to handle redirect differently
|
|
839
|
+
// Store the redirect URL before calling completeLoginProcess
|
|
840
|
+
const oauthRedirect = req.session.oauthRedirect;
|
|
841
|
+
delete req.session.oauthRedirect;
|
|
842
|
+
|
|
843
|
+
// Custom response handler for OAuth flow - wrap the response object
|
|
844
|
+
const originalJson = res.json.bind(res);
|
|
845
|
+
const originalStatus = res.status.bind(res);
|
|
846
|
+
let statusCode = 200;
|
|
847
|
+
|
|
848
|
+
res.status = function (code) {
|
|
849
|
+
statusCode = code;
|
|
850
|
+
return originalStatus(code);
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
res.json = function (data) {
|
|
854
|
+
if (data.success && statusCode === 200) {
|
|
855
|
+
// If login successful, redirect instead of sending JSON
|
|
856
|
+
const redirectUrl = oauthRedirect || mbkautheVar.loginRedirectURL || '/home';
|
|
857
|
+
console.log(`[mbkauthe] GitHub login: Redirecting to ${redirectUrl}`);
|
|
858
|
+
// Restore original methods before redirect
|
|
859
|
+
res.json = originalJson;
|
|
860
|
+
res.status = originalStatus;
|
|
861
|
+
return res.redirect(redirectUrl);
|
|
862
|
+
}
|
|
863
|
+
// Restore original methods for error responses
|
|
864
|
+
res.json = originalJson;
|
|
865
|
+
res.status = originalStatus;
|
|
866
|
+
return originalJson(data);
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
await completeLoginProcess(req, res, userForSession);
|
|
870
|
+
|
|
871
|
+
} catch (err) {
|
|
872
|
+
console.error('[mbkauthe] GitHub login callback error:', err);
|
|
873
|
+
return res.status(500).render('Error/dError.handlebars', {
|
|
874
|
+
layout: false,
|
|
875
|
+
code: '500',
|
|
876
|
+
error: 'Internal Server Error',
|
|
877
|
+
message: 'An error occurred during GitHub authentication. Please try again.',
|
|
878
|
+
page: '/mbkauthe/login',
|
|
879
|
+
pagename: 'Login',
|
|
880
|
+
version: packageJson.version,
|
|
881
|
+
app: mbkautheVar.APP_NAME,
|
|
882
|
+
details: process.env.NODE_ENV === 'development' ? `${err.message}\n${err.stack}` : 'Error details hidden in production'
|
|
883
|
+
});
|
|
858
884
|
}
|
|
885
|
+
}
|
|
859
886
|
);
|
|
860
887
|
|
|
861
888
|
export { getLatestVersion };
|
package/lib/pool.js
CHANGED
|
@@ -36,9 +36,9 @@ 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:
|
|
39
|
+
max: 10,
|
|
40
40
|
idleTimeoutMillis: 10000,
|
|
41
|
-
connectionTimeoutMillis:
|
|
41
|
+
connectionTimeoutMillis: 25000,
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
export const dblogin = new Pool(poolConfig);
|
package/package.json
CHANGED
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
<button type="submit" class="btn-login" id="loginButton">
|
|
69
69
|
<span id="loginButtonText">Login</span>
|
|
70
70
|
</button>
|
|
71
|
+
{{#if githubLoginEnabled }}
|
|
71
72
|
<div class="social-login">
|
|
72
73
|
<div class="divider">
|
|
73
74
|
<span>or</span>
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
<span>Continue with GitHub</span>
|
|
79
80
|
</a>
|
|
80
81
|
</div>
|
|
81
|
-
|
|
82
|
+
{{/if }}
|
|
82
83
|
<div class="remember-me">
|
|
83
84
|
<input type="checkbox" id="rememberMe" name="rememberMe">
|
|
84
85
|
<label for="rememberMe">Remember me</label>
|
|
@@ -272,6 +273,8 @@
|
|
|
272
273
|
}
|
|
273
274
|
});
|
|
274
275
|
|
|
276
|
+
{{#if githubLoginEnabled }}
|
|
277
|
+
|
|
275
278
|
// GitHub login: Attempt to POST redirect to backend, fallback to direct navigation
|
|
276
279
|
async function startGithubLogin() {
|
|
277
280
|
const urlParams = new URLSearchParams(window.location.search);
|
|
@@ -310,6 +313,7 @@
|
|
|
310
313
|
|
|
311
314
|
const githubBtn = document.getElementById('githubLoginBtn');
|
|
312
315
|
if (githubBtn) githubBtn.addEventListener('click', startGithubLogin);
|
|
316
|
+
{{/if }}
|
|
313
317
|
</script>
|
|
314
318
|
</body>
|
|
315
319
|
|