mbkauthe 3.1.0 → 3.2.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/index.d.ts CHANGED
@@ -34,10 +34,11 @@ declare global {
34
34
  UserName?: string;
35
35
  role: 'SuperAdmin' | 'NormalUser' | 'Guest';
36
36
  Role?: 'SuperAdmin' | 'NormalUser' | 'Guest';
37
- loginMethod?: 'password' | 'github';
37
+ loginMethod?: 'password' | 'github' | 'google';
38
38
  redirectUrl?: string | null;
39
39
  };
40
40
  oauthRedirect?: string;
41
+ oauthCsrfToken?: string;
41
42
  }
42
43
  }
43
44
  }
@@ -57,10 +58,22 @@ declare module 'mbkauthe' {
57
58
  GITHUB_LOGIN_ENABLED?: 'true' | 'false' | 'f';
58
59
  GITHUB_CLIENT_ID?: string;
59
60
  GITHUB_CLIENT_SECRET?: string;
61
+ GOOGLE_LOGIN_ENABLED?: 'true' | 'false' | 'f';
62
+ GOOGLE_CLIENT_ID?: string;
63
+ GOOGLE_CLIENT_SECRET?: string;
60
64
  loginRedirectURL?: string;
61
65
  EncPass?: 'true' | 'false' | 'f';
62
66
  }
63
67
 
68
+ export interface OAuthConfig {
69
+ GITHUB_LOGIN_ENABLED?: 'true' | 'false' | 'f';
70
+ GITHUB_CLIENT_ID?: string;
71
+ GITHUB_CLIENT_SECRET?: string;
72
+ GOOGLE_LOGIN_ENABLED?: 'true' | 'false' | 'f';
73
+ GOOGLE_CLIENT_ID?: string;
74
+ GOOGLE_CLIENT_SECRET?: string;
75
+ }
76
+
64
77
  // User Types
65
78
  export type UserRole = 'SuperAdmin' | 'NormalUser' | 'Guest';
66
79
 
@@ -80,7 +93,7 @@ declare module 'mbkauthe' {
80
93
  UserName?: string;
81
94
  role: UserRole;
82
95
  Role?: UserRole;
83
- loginMethod?: 'password' | 'github';
96
+ loginMethod?: 'password' | 'github' | 'google';
84
97
  redirectUrl?: string | null;
85
98
  }
86
99
 
@@ -127,6 +140,16 @@ declare module 'mbkauthe' {
127
140
  updated_at: Date;
128
141
  }
129
142
 
143
+ export interface GoogleUser {
144
+ id: number;
145
+ user_name: string;
146
+ google_id: string;
147
+ google_email: string;
148
+ access_token: string;
149
+ created_at: Date;
150
+ updated_at: Date;
151
+ }
152
+
130
153
  // API Response Types
131
154
  export interface LoginResponse {
132
155
  success: boolean;
@@ -28,6 +28,38 @@ function validateConfiguration() {
28
28
  throw new Error(`[mbkauthe] Configuration Error:\n - ${errors.join('\n - ')}`);
29
29
  }
30
30
 
31
+ // Parse and validate oAuthVar (optional fallback for OAuth settings)
32
+ let oAuthVar = null;
33
+ try {
34
+ if (process.env.oAuthVar) {
35
+ oAuthVar = JSON.parse(process.env.oAuthVar);
36
+ if (oAuthVar && typeof oAuthVar !== 'object') {
37
+ console.warn('[mbkauthe] oAuthVar is not a valid object, ignoring it');
38
+ oAuthVar = null;
39
+ } else {
40
+ console.log('[mbkauthe] oAuthVar detected and parsed successfully');
41
+ }
42
+ }
43
+ } catch (error) {
44
+ console.warn('[mbkauthe] Invalid JSON in process.env.oAuthVar, ignoring it');
45
+ oAuthVar = null;
46
+ }
47
+
48
+ // Merge OAuth settings: use oAuthVar as fallback if values not in mbkautheVar
49
+ const oAuthKeys = [
50
+ 'GITHUB_LOGIN_ENABLED', 'GITHUB_CLIENT_ID', 'GITHUB_CLIENT_SECRET',
51
+ 'GOOGLE_LOGIN_ENABLED', 'GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET'
52
+ ];
53
+
54
+ if (oAuthVar) {
55
+ oAuthKeys.forEach(key => {
56
+ if ((!mbkautheVar[key] || (typeof mbkautheVar[key] === 'string' && mbkautheVar[key].trim() === '')) && oAuthVar[key]) {
57
+ mbkautheVar[key] = oAuthVar[key];
58
+ console.log(`[mbkauthe] Using ${key} from oAuthVar`);
59
+ }
60
+ });
61
+ }
62
+
31
63
  // Validate required keys
32
64
  // COOKIE_EXPIRE_TIME is not required but if provided must be valid, COOKIE_EXPIRE_TIME by default is 2 days
33
65
  // loginRedirectURL is not required but if provided must be valid, loginRedirectURL by default is /dashboard
@@ -55,6 +87,11 @@ function validateConfiguration() {
55
87
  errors.push("mbkautheVar.GITHUB_LOGIN_ENABLED must be either 'true' or 'false' or 'f'");
56
88
  }
57
89
 
90
+ // Validate GOOGLE_LOGIN_ENABLED value
91
+ if (mbkautheVar.GOOGLE_LOGIN_ENABLED && !['true', 'false', 'f'].includes(mbkautheVar.GOOGLE_LOGIN_ENABLED.toLowerCase())) {
92
+ errors.push("mbkautheVar.GOOGLE_LOGIN_ENABLED must be either 'true' or 'false' or 'f'");
93
+ }
94
+
58
95
  // Validate EncPass value if provided
59
96
  if (mbkautheVar.EncPass && !['true', 'false', 'f'].includes(mbkautheVar.EncPass.toLowerCase())) {
60
97
  errors.push("mbkautheVar.EncPass must be either 'true' or 'false' or 'f'");
@@ -70,6 +107,16 @@ function validateConfiguration() {
70
107
  }
71
108
  }
72
109
 
110
+ // Validate Google login configuration
111
+ if (mbkautheVar.GOOGLE_LOGIN_ENABLED === "true") {
112
+ if (!mbkautheVar.GOOGLE_CLIENT_ID || mbkautheVar.GOOGLE_CLIENT_ID.trim() === '') {
113
+ errors.push("mbkautheVar.GOOGLE_CLIENT_ID is required when GOOGLE_LOGIN_ENABLED is 'true'");
114
+ }
115
+ if (!mbkautheVar.GOOGLE_CLIENT_SECRET || mbkautheVar.GOOGLE_CLIENT_SECRET.trim() === '') {
116
+ errors.push("mbkautheVar.GOOGLE_CLIENT_SECRET is required when GOOGLE_LOGIN_ENABLED is 'true'");
117
+ }
118
+ }
119
+
73
120
  // Validate COOKIE_EXPIRE_TIME if provided
74
121
  if (mbkautheVar.COOKIE_EXPIRE_TIME !== undefined) {
75
122
  const expireTime = parseFloat(mbkautheVar.COOKIE_EXPIRE_TIME);
@@ -133,4 +180,4 @@ try {
133
180
  }
134
181
  }
135
182
 
136
- export { packageJson, appVersion };
183
+ export { packageJson, appVersion };
package/lib/main.js CHANGED
@@ -19,6 +19,18 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
19
 
20
20
  const router = express.Router();
21
21
 
22
+ // Configure Express to trust proxy headers for rate limiting in dev mode only
23
+ // This prevents conflicts with parent project proxy settings in production
24
+ if (process.env.test === "dev") {
25
+ router.use((req, res, next) => {
26
+ // Set trust proxy to true for the app instance if not already set
27
+ if (!req.app.get('trust proxy')) {
28
+ req.app.set('trust proxy', true);
29
+ }
30
+ next();
31
+ });
32
+ }
33
+
22
34
  // Basic middleware
23
35
  router.use(express.json());
24
36
  router.use(express.urlencoded({ extended: true }));
@@ -18,9 +18,11 @@ export const sessionConfig = {
18
18
  proxy: true,
19
19
  cookie: {
20
20
  maxAge: mbkautheVar.COOKIE_EXPIRE_TIME * 24 * 60 * 60 * 1000,
21
- domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
21
+ // Don't set domain in development/localhost to avoid cookie issues
22
+ domain: (mbkautheVar.IS_DEPLOYED === 'true' && process.env.test !== 'dev') ? `.${mbkautheVar.DOMAIN}` : undefined,
22
23
  httpOnly: true,
23
- secure: mbkautheVar.IS_DEPLOYED === 'true',
24
+ // Only use secure cookies in production with HTTPS
25
+ secure: mbkautheVar.IS_DEPLOYED === 'true' && process.env.test !== 'dev',
24
26
  sameSite: 'lax',
25
27
  path: '/'
26
28
  },
@@ -22,19 +22,31 @@ const LoginLimit = rateLimit({
22
22
  message: { success: false, message: "Too many attempts, please try again later" },
23
23
  skip: (req) => {
24
24
  return !!req.session.user;
25
+ },
26
+ validate: {
27
+ trustProxy: false,
28
+ xForwardedForHeader: false
25
29
  }
26
30
  });
27
31
 
28
32
  const LogoutLimit = rateLimit({
29
33
  windowMs: 1 * 60 * 1000,
30
34
  max: 10,
31
- message: { success: false, message: "Too many logout attempts, please try again later" }
35
+ message: { success: false, message: "Too many logout attempts, please try again later" },
36
+ validate: {
37
+ trustProxy: false,
38
+ xForwardedForHeader: false
39
+ }
32
40
  });
33
41
 
34
42
  const TwoFALimit = rateLimit({
35
43
  windowMs: 1 * 60 * 1000,
36
44
  max: 5,
37
- message: { success: false, message: "Too many 2FA attempts, please try again later" }
45
+ message: { success: false, message: "Too many 2FA attempts, please try again later" },
46
+ validate: {
47
+ trustProxy: false,
48
+ xForwardedForHeader: false
49
+ }
38
50
  });
39
51
 
40
52
  // CSRF protection middleware
@@ -117,8 +129,17 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
117
129
  const sessionId = crypto.randomBytes(32).toString("hex");
118
130
  console.log(`[mbkauthe] Generated session ID for username: ${username}`);
119
131
 
120
- // Regenerate session to prevent session fixation attacks
132
+ // Fix session fixation: Delete old session BEFORE regenerating to prevent timing window
121
133
  const oldSessionId = req.sessionID;
134
+
135
+ // Delete old session first to prevent session fixation attacks
136
+ await dblogin.query({
137
+ name: 'login-delete-old-session-before-regen',
138
+ text: 'DELETE FROM "session" WHERE sid = $1',
139
+ values: [oldSessionId]
140
+ });
141
+
142
+ // Now regenerate with new session ID (timing window closed)
122
143
  await new Promise((resolve, reject) => {
123
144
  req.session.regenerate((err) => {
124
145
  if (err) reject(err);
@@ -509,6 +530,7 @@ router.get("/login", LoginLimit, csrfProtection, (req, res) => {
509
530
  return res.render("loginmbkauthe.handlebars", {
510
531
  layout: false,
511
532
  githubLoginEnabled: mbkautheVar.GITHUB_LOGIN_ENABLED,
533
+ googleLoginEnabled: mbkautheVar.GOOGLE_LOGIN_ENABLED,
512
534
  customURL: mbkautheVar.loginRedirectURL || '/dashboard',
513
535
  userLoggedIn: !!req.session?.user,
514
536
  username: req.session?.user?.username || '',
@@ -22,6 +22,21 @@ const LoginLimit = rateLimit({
22
22
  message: { success: false, message: "Too many attempts, please try again later" },
23
23
  skip: (req) => {
24
24
  return !!req.session.user;
25
+ },
26
+ validate: {
27
+ trustProxy: false,
28
+ xForwardedForHeader: false
29
+ }
30
+ });
31
+
32
+ // Rate limiter for admin operations
33
+ const AdminOperationLimit = rateLimit({
34
+ windowMs: 5 * 60 * 1000,
35
+ max: 3,
36
+ message: { success: false, message: "Too many admin operations, please try again later" },
37
+ validate: {
38
+ trustProxy: false,
39
+ xForwardedForHeader: false
25
40
  }
26
41
  });
27
42
 
@@ -232,12 +247,18 @@ router.get(["/info", "/i"], LoginLimit, async (req, res) => {
232
247
  });
233
248
 
234
249
  // Terminate all sessions (admin endpoint)
235
- router.post("/api/terminateAllSessions", authenticate(mbkautheVar.Main_SECRET_TOKEN), async (req, res) => {
250
+ router.post("/api/terminateAllSessions", AdminOperationLimit, authenticate(mbkautheVar.Main_SECRET_TOKEN), async (req, res) => {
236
251
  try {
237
252
  // Run both operations in parallel for better performance
238
253
  await Promise.all([
239
- dblogin.query({ name: 'terminate-all-user-sessions', text: `UPDATE "Users" SET "SessionId" = NULL` }),
240
- dblogin.query({ name: 'terminate-all-db-sessions', text: 'DELETE FROM "session"' })
254
+ dblogin.query({
255
+ name: 'terminate-all-user-sessions',
256
+ text: 'UPDATE "Users" SET "SessionId" = NULL WHERE "SessionId" IS NOT NULL'
257
+ }),
258
+ dblogin.query({
259
+ name: 'terminate-all-db-sessions',
260
+ text: 'DELETE FROM "session" WHERE expire > NOW()'
261
+ })
241
262
  ]);
242
263
 
243
264
  req.session.destroy((err) => {