mbkauthe 1.3.4 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,13 +11,27 @@ const getCookieOptions = () => ({
11
11
  httpOnly: true
12
12
  });
13
13
 
14
+ const getClearCookieOptions = () => ({
15
+ domain: mbkautheVar.IS_DEPLOYED === 'true' ? `.${mbkautheVar.DOMAIN}` : undefined,
16
+ secure: mbkautheVar.IS_DEPLOYED === 'true' ? 'auto' : false,
17
+ sameSite: 'lax',
18
+ path: '/',
19
+ httpOnly: true
20
+ });
21
+
14
22
  async function validateSession(req, res, next) {
15
23
  if (!req.session.user && req.cookies.sessionId) {
16
24
  try {
17
25
  const sessionId = req.cookies.sessionId;
18
- const query = `SELECT * FROM "Users" WHERE "SessionId" = $1`;
19
- const result = await dblogin.query(query, [sessionId]);
20
- const userResult = result.rows[0];
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] });
21
35
 
22
36
  if (result.rows.length > 0) {
23
37
  const user = result.rows[0];
@@ -25,7 +39,10 @@ async function validateSession(req, res, next) {
25
39
  id: user.id,
26
40
  username: user.UserName,
27
41
  UserName: user.UserName,
42
+ role: user.Role,
43
+ Role: user.Role,
28
44
  sessionId,
45
+ allowedApps: user.AllowedApps,
29
46
  };
30
47
  }
31
48
  } catch (err) {
@@ -48,15 +65,16 @@ async function validateSession(req, res, next) {
48
65
  }
49
66
 
50
67
  try {
51
- const { id, sessionId } = req.session.user;
52
- const query = `SELECT "SessionId", "Active", "Role", "AllowedApps" FROM "Users" WHERE "id" = $1`;
53
- const result = await dblogin.query(query, [id]);
54
- const userResult = result.rows[0];
68
+ const { id, sessionId, role, allowedApps } = req.session.user;
69
+
70
+ // Fetch only SessionId and Active status for validation (reduced query)
71
+ 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] });
55
73
 
56
- if (result.rows.length === 0 || userResult.SessionId !== sessionId) {
74
+ if (result.rows.length === 0 || result.rows[0].SessionId !== sessionId) {
57
75
  console.log(`[mbkauthe] Session invalidated for user "${req.session.user.username}"`);
58
76
  req.session.destroy();
59
- const cookieOptions = getCookieOptions();
77
+ const cookieOptions = getClearCookieOptions();
60
78
  res.clearCookie("mbkauthe.sid", cookieOptions);
61
79
  res.clearCookie("sessionId", cookieOptions);
62
80
  res.clearCookie("username", cookieOptions);
@@ -70,10 +88,10 @@ async function validateSession(req, res, next) {
70
88
  });
71
89
  }
72
90
 
73
- if (!userResult.Active) {
91
+ if (!result.rows[0].Active) {
74
92
  console.log(`[mbkauthe] Account is inactive for user "${req.session.user.username}"`);
75
93
  req.session.destroy();
76
- const cookieOptions = getCookieOptions();
94
+ const cookieOptions = getClearCookieOptions();
77
95
  res.clearCookie("mbkauthe.sid", cookieOptions);
78
96
  res.clearCookie("sessionId", cookieOptions);
79
97
  res.clearCookie("username", cookieOptions);
@@ -87,12 +105,11 @@ async function validateSession(req, res, next) {
87
105
  });
88
106
  }
89
107
 
90
- if (userResult.Role !== "SuperAdmin") {
91
- const allowedApps = userResult.AllowedApps;
108
+ if (role !== "SuperAdmin") {
92
109
  if (!allowedApps || !allowedApps.some(app => app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
93
110
  console.warn(`[mbkauthe] User \"${req.session.user.username}\" is not authorized to use the application \"${mbkautheVar.APP_NAME}\"`);
94
111
  req.session.destroy();
95
- const cookieOptions = getCookieOptions();
112
+ const cookieOptions = getClearCookieOptions();
96
113
  res.clearCookie("mbkauthe.sid", cookieOptions);
97
114
  res.clearCookie("sessionId", cookieOptions);
98
115
  res.clearCookie("username", cookieOptions);
@@ -133,7 +150,7 @@ const checkRolePermission = (requiredRole, notAllowed) => {
133
150
  const userId = req.session.user.id;
134
151
 
135
152
  const query = `SELECT "Role" FROM "Users" WHERE "id" = $1`;
136
- const result = await dblogin.query(query, [userId]);
153
+ const result = await dblogin.query({ name: 'get-role-by-id', text: query, values: [userId] });
137
154
 
138
155
  if (result.rows.length === 0) {
139
156
  return res.status(401).json({ success: false, message: "User not found" });
@@ -202,98 +219,73 @@ const authapi = (requiredRole = []) => {
202
219
  return (req, res, next) => {
203
220
  const token = req.headers["authorization"];
204
221
 
205
- if (typeof token === 'string') {
206
- console.log("[mbkauthe] [authapi] Received request with token:", token[0] + token[1] + token[2], ".....", token[63]);
207
- } else {
208
- console.log("[mbkauthe] [authapi] Token is not a valid string:", token);
209
- }
210
-
222
+ // Validate token
211
223
  if (!token) {
212
- console.log("[mbkauthe] [authapi] No token provided in the request headers");
224
+ console.warn("[mbkauthe] [authapi] No token provided in the request headers");
213
225
  return res.status(401).json({
214
226
  success: false,
215
227
  message: "Authorization token is required"
216
228
  });
217
229
  }
218
230
 
219
- console.log("[mbkauthe] [authapi] Querying database to validate token");
220
- const tokenQuery = 'SELECT * FROM "UserAuthApiKey" WHERE "key" = $1';
221
- pool.query(tokenQuery, [token], (err, result) => {
222
- if (err) {
223
- console.error("[mbkauthe] [authapi] Database query error while validating token:", err);
224
- return res.status(500).json({
225
- success: false,
226
- message: "Internal Server Error"
227
- });
228
- }
231
+ if (typeof token !== 'string' || token.length === 0 || token.length > 512) {
232
+ console.warn("[mbkauthe] [authapi] Invalid token format");
233
+ return res.status(401).json({
234
+ success: false,
235
+ message: "Invalid authorization token format"
236
+ });
237
+ }
229
238
 
230
- if (result.rows.length === 0) {
231
- console.log("[mbkauthe] [authapi] Invalid token provided:", token);
232
- return res.status(401).json({
233
- success: false,
234
- message: "The AuthApiToken Is Invalid"
235
- });
236
- }
239
+ if (typeof token === 'string' && token.length >= 64) {
240
+ console.log("[mbkauthe] [authapi] Received request with token:", token.substring(0, 3) + ".....", token.charAt(63));
241
+ } else {
242
+ console.log("[mbkauthe] [authapi] Received request with short token");
243
+ }
237
244
 
238
- const username = result.rows[0].username;
239
- console.log("[mbkauthe] [authapi] Token is valid. Associated username:", username);
240
-
241
- console.log("[mbkauthe] [authapi] Querying database to validate user and role");
242
- const userQuery = `
243
- SELECT id, "UserName", "Active", "Role" FROM "Users"
244
- WHERE "UserName" = $1 AND "Active" = true
245
- `;
246
-
247
- pool.query(userQuery, [username], (err, userResult) => {
248
- if (err) {
249
- console.error("[mbkauthe] [authapi] Database query error while validating user:", err);
250
- return res.status(500).json({
251
- success: false,
252
- message: "Internal Server Error"
253
- });
245
+ // Single query to validate API key and fetch user in one DB round trip.
246
+ (async () => {
247
+ try {
248
+ const jointQuery = `
249
+ SELECT u.id, u."UserName", u."Active", u."Role", k."key" as apikey
250
+ FROM "UserAuthApiKey" k
251
+ JOIN "Users" u ON u."UserName" = k.username
252
+ WHERE k."key" = $1 AND u."Active" = true
253
+ LIMIT 1
254
+ `;
255
+
256
+ const result = await pool.query(jointQuery, [token]);
257
+
258
+ if (result.rows.length === 0) {
259
+ console.warn("[mbkauthe] [authapi] Invalid token or associated user inactive");
260
+ return res.status(401).json({ success: false, message: "The AuthApiToken Is Invalid or user inactive" });
254
261
  }
255
262
 
256
- if (userResult.rows.length === 0) {
257
- console.log("[mbkauthe] [authapi] User does not exist or is not active. Username:", username);
258
- return res.status(401).json({
259
- success: false,
260
- message: "User does not exist or is not active",
261
- });
262
- }
263
+ const user = result.rows[0];
263
264
 
264
- if (username === "demo") {
265
- console.log("[mbkauthe] [authapi] Demo user attempted to access an endpoint. Access denied.");
266
- return res.status(401).json({
267
- success: false,
268
- message: "Demo user is not allowed to access endpoints",
269
- });
265
+ if (user.UserName === "demo") {
266
+ console.warn("[mbkauthe] [authapi] Demo user attempted to access an endpoint. Access denied.");
267
+ return res.status(401).json({ success: false, message: "Demo user is not allowed to access endpoints" });
270
268
  }
271
269
 
272
- const user = userResult.rows[0];
273
- console.log("[mbkauthe] [authapi] User is valid. User details:", user);
274
-
275
- // Check if role is required and if user has it
270
+ // role check
276
271
  if ((requiredRole && user.Role !== requiredRole) && user.Role !== "SuperAdmin") {
277
- console.log(`[mbkauthe] [authapi] User does not have the required role. Required: ${requiredRole}, User's role: ${user.Role}`);
278
- return res.status(403).json({
279
- success: false,
280
- message: `Access denied. Required role: ${requiredRole}`,
281
- });
272
+ console.warn(`[mbkauthe] [authapi] User does not have the required role. Required: ${requiredRole}, User's role: ${user.Role}`);
273
+ return res.status(403).json({ success: false, message: `Access denied. Required role: ${requiredRole}` });
282
274
  }
283
275
 
284
- console.log("[mbkauthe] [authapi] User has the required role or no specific role is required. Proceeding to next middleware.");
285
276
  req.user = {
286
277
  username: user.UserName,
287
278
  UserName: user.UserName,
288
279
  role: user.Role,
289
- Role: user.role,
290
- // Add other user properties you might need
280
+ Role: user.Role,
291
281
  };
292
282
 
293
- console.log("[mbkauthe] [authapi] Token and user validation successful. Passing control to next middleware.");
294
283
  next();
295
- });
296
- });
284
+ } catch (err) {
285
+ console.error("[mbkauthe] [authapi] Database error while validating token/user:", err);
286
+ return res.status(500).json({ success: false, message: "Internal Server Error" });
287
+ }
288
+ })();
297
289
  };
298
290
  };
299
291
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "mbkauthe",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "description": "MBKTechStudio's reusable authentication system for Node.js applications.",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "scripts": {
8
- "test": "set test=true&& nodemon index.js"
8
+ "dev": "set test=dev&& nodemon index.js"
9
9
  },
10
10
  "repository": {
11
11
  "type": "git",
@@ -26,11 +26,11 @@
26
26
  },
27
27
  "homepage": "https://github.com/MIbnEKhalid/mbkauthe#readme",
28
28
  "dependencies": {
29
- "bcrypt": "^5.1.1",
29
+ "bcrypt": "^6.0.0",
30
30
  "cheerio": "^1.0.0",
31
31
  "connect-pg-simple": "^10.0.0",
32
32
  "cookie-parser": "^1.4.7",
33
- "csurf": "^1.11.0",
33
+ "csurf": "^1.2.2",
34
34
  "dotenv": "^16.4.7",
35
35
  "express": "^5.1.0",
36
36
  "express-handlebars": "^8.0.1",
@@ -44,6 +44,6 @@
44
44
  "url": "^0.11.4"
45
45
  },
46
46
  "devDependencies": {
47
- "nodemon": "^2.0.22"
47
+ "nodemon": "^3.1.11"
48
48
  }
49
49
  }
package/public/main.js ADDED
@@ -0,0 +1,134 @@
1
+ async function logout() {
2
+ const confirmation = confirm("Are you sure you want to logout?");
3
+ if (!confirmation) {
4
+ return;
5
+ }
6
+
7
+ try {
8
+ // Clear all caches before logging out (except rememberedUsername)
9
+ await nuclearCacheClear();
10
+
11
+ const response = await fetch("/mbkauthe/api/logout", {
12
+ method: "POST",
13
+ headers: {
14
+ "Content-Type": "application/json",
15
+ "Cache-Control": "no-cache"
16
+ },
17
+ credentials: "include"
18
+ });
19
+
20
+ const result = await response.json();
21
+
22
+ if (response.ok) {
23
+ alert(result.message);
24
+ // Force a full page reload with cache bypass
25
+ window.location.href = window.location.origin;
26
+ } else {
27
+ alert(result.message);
28
+ }
29
+ } catch (error) {
30
+ console.error("Error during logout:", error);
31
+ alert("Logout failed");
32
+ }
33
+ }
34
+
35
+ async function nuclearCacheClear() {
36
+ try {
37
+ // Preserve rememberedUsername before clearing storage
38
+ const rememberedUsername = localStorage.getItem('rememberedUsername');
39
+
40
+ // 1. Clear all possible caches
41
+ if ('caches' in window) {
42
+ const cacheNames = await caches.keys();
43
+ await Promise.all(cacheNames.map(cacheName => caches.delete(cacheName)));
44
+ }
45
+
46
+ // 2. Clear service workers
47
+ if ('serviceWorker' in navigator) {
48
+ const registrations = await navigator.serviceWorker.getRegistrations();
49
+ await Promise.all(registrations.map(registration => registration.unregister()));
50
+ }
51
+
52
+ // 3. Clear all storage except rememberedUsername
53
+ localStorage.clear();
54
+ sessionStorage.clear();
55
+ if (rememberedUsername) {
56
+ localStorage.setItem('rememberedUsername', rememberedUsername);
57
+ }
58
+
59
+ if ('indexedDB' in window) {
60
+ try {
61
+ const dbs = await window.indexedDB.databases();
62
+ await Promise.all(dbs.map(db => {
63
+ if (db.name) {
64
+ return window.indexedDB.deleteDatabase(db.name);
65
+ }
66
+ return Promise.resolve();
67
+ }));
68
+ } catch (error) {
69
+ console.error("Error clearing IndexedDB:", error);
70
+ }
71
+ }
72
+
73
+ // 4. Clear cookies (except those you want to preserve)
74
+ document.cookie.split(';').forEach(cookie => {
75
+ const eqPos = cookie.indexOf('=');
76
+ const name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
77
+
78
+ // Skip cookies you want to preserve (add conditions as needed)
79
+ if (!name.startsWith('preserve_')) { // Example condition
80
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=${window.location.hostname}`;
81
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${window.location.hostname}`;
82
+ }
83
+ });
84
+
85
+ // 5. Force hard reload with cache busting
86
+ window.location.reload();
87
+
88
+ } catch (error) {
89
+ console.error('Nuclear cache clear failed:', error);
90
+ window.location.reload(true);
91
+ }
92
+ }
93
+
94
+ async function logoutuser() {
95
+ await logout();
96
+ }
97
+
98
+ const validateSessionInterval = 60000;
99
+ // 1 minutes in milliseconds Function to check session validity by sending a request to the server
100
+ function checkSession() {
101
+ fetch("/api/validate-session")
102
+ .then((response) => {
103
+ if (!response.ok) {
104
+ // Redirect or handle errors (session expired, user inactive, etc.)
105
+ window.location.reload(); // Reload the page to update the session status
106
+ }
107
+ })
108
+ .catch((error) => console.error("Error checking session:", error));
109
+ }
110
+ // Call validateSession every 2 minutes (120000 milliseconds)
111
+ // setInterval(checkSession, validateSessionInterval);
112
+
113
+ function getCookieValue(cookieName) {
114
+ const cookies = document.cookie.split('; ');
115
+ for (let cookie of cookies) {
116
+ const [name, value] = cookie.split('=');
117
+ if (name === cookieName) {
118
+ return decodeURIComponent(value);
119
+ }
120
+ }
121
+ return null; // Return null if the cookie is not found
122
+ }
123
+
124
+ function loadpage(url) {
125
+ window.location.href = url;
126
+ }
127
+
128
+ function formatDate(date) {
129
+ return new Date(date).toLocaleString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: true });
130
+ }
131
+
132
+ function reloadPage() {
133
+ window.location.reload();
134
+ }