mbkauthe 5.0.1 → 5.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mbkauthe",
3
- "version": "5.0.1",
3
+ "version": "5.0.4",
4
4
  "description": "MBKTech's reusable authentication system for Node.js applications.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -10,8 +10,10 @@
10
10
  "create-tables": "node lib/createTable.js",
11
11
  "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
12
12
  "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
13
+ "tets": "npm run test",
13
14
  "render:mermaid": "npx @mermaid-js/mermaid-cli -i docs/diagrams/auth-flows.mmd -o docs/images/auth-flows.svg",
14
- "render:mermaid:png": "npx @mermaid-js/mermaid-cli -i docs/diagrams/auth-flows.mmd -o docs/images/auth-flows.png -s 20"
15
+ "render:mermaid:png": "npx @mermaid-js/mermaid-cli -i docs/diagrams/auth-flows.mmd -o docs/images/auth-flows.png -s 20",
16
+ "install-mermaid": "npm i @mermaid-js/mermaid-cli@11.15.0 --save-dev"
15
17
  },
16
18
  "imports": {
17
19
  "#pool.js": "./lib/pool.js",
@@ -29,10 +31,13 @@
29
31
  "nodejs",
30
32
  "express",
31
33
  "session",
32
- "security"
34
+ "security",
35
+ "oauth",
36
+ "2fa",
37
+ "csrf"
33
38
  ],
34
- "author": "Muhammad Bin Khalid <support@mbktech.org>",
35
- "license": "GPL-2.0",
39
+ "author": "Muhammad Bin Khalid <support@mbktech.org> <chmuhammadbinkhalid28@gmail.com>",
40
+ "license": "LGPL-3.0-only",
36
41
  "bugs": {
37
42
  "url": "https://github.com/MIbnEKhalid/mbkauthe/issues"
38
43
  },
@@ -53,7 +58,6 @@
53
58
  "registry": "https://registry.npmjs.org/"
54
59
  },
55
60
  "dependencies": {
56
- "@mermaid-js/mermaid-cli": "^11.15.0",
57
61
  "bytes": "^3.1.2",
58
62
  "connect-pg-simple": "^10.0.0",
59
63
  "content-type": "^1.0.5",
@@ -99,7 +103,6 @@
99
103
  }
100
104
  },
101
105
  "devDependencies": {
102
- "@types/express": "^5.0.6",
103
106
  "cross-env": "^7.0.3",
104
107
  "jest": "^30.2.0",
105
108
  "nodemon": "^3.1.11",
package/public/main.js CHANGED
@@ -1,115 +1,162 @@
1
- async function logout() {
2
- const confirmation = confirm("Are you sure you want to logout?");
3
- if (!confirmation) {
4
- return;
5
- }
1
+ (() => {
2
+ const SESSION_KEYS = [
3
+ 'sessionId',
4
+ 'mbkauthe.sid',
5
+ 'fullName',
6
+ '_csrf',
7
+ 'profileImageUser',
8
+ 'profileImageUrl'
9
+ ];
10
+ const LOG_PREFIX = '[mbkauthe]';
11
+ const EXPIRED_COOKIE = 'expires=Thu, 01 Jan 1970 00:00:00 GMT';
12
+ const dateFormatter = new Intl.DateTimeFormat('en-GB', {
13
+ day: '2-digit',
14
+ month: '2-digit',
15
+ year: 'numeric',
16
+ hour: 'numeric',
17
+ minute: 'numeric',
18
+ second: 'numeric',
19
+ hour12: true
20
+ });
21
+
22
+ const reloadPage = () => window.location.reload();
23
+
24
+ const getCookieDomains = () => {
25
+ const hostname = window.location.hostname;
26
+
27
+ if (!hostname) {
28
+ return [];
29
+ }
30
+
31
+ return [...new Set([hostname, hostname.includes('.') ? `.${hostname}` : null].filter(Boolean))];
32
+ };
33
+
34
+ const clearCookie = (name) => {
35
+ document.cookie = `${name}=; ${EXPIRED_COOKIE}; path=/`;
6
36
 
7
- try {
8
- // First, logout from server
9
- const response = await fetch("/mbkauthe/api/logout", {
10
- method: "POST",
11
- headers: {
12
- "Content-Type": "application/json",
13
- "Cache-Control": "no-cache"
14
- },
15
- credentials: "include"
37
+ getCookieDomains().forEach((domain) => {
38
+ document.cookie = `${name}=; ${EXPIRED_COOKIE}; path=/; domain=${domain}`;
16
39
  });
40
+ };
17
41
 
18
- const result = await response.json();
42
+ const parseJson = async (response) => {
43
+ try {
44
+ return await response.json();
45
+ } catch {
46
+ return {};
47
+ }
48
+ };
19
49
 
20
- if (response.ok) {
21
- // Then clear all caches after successful logout (except rememberedUsername)
22
- await selectiveCacheClear();
23
- // selectiveCacheClear already redirects, so no need for additional redirect
24
- } else {
25
- alert(result.message);
50
+ async function logout({ confirmLogout = true } = {}) {
51
+ if (confirmLogout && !confirm('Are you sure you want to logout?')) {
52
+ return false;
26
53
  }
27
- } catch (error) {
28
- console.error(`[mbkauthe] Error during logout:`, error);
29
- alert(`Logout failed: ${error.message}`);
30
- }
31
- }
32
-
33
- async function selectiveCacheClear() {
34
- try {
35
-
36
- const cookiesToClear = [
37
- 'sessionId',
38
- 'mbkauthe.sid',
39
- 'fullName',
40
- '_csrf',
41
- 'profileImageUser',
42
- 'profileImageUrl'
43
- ];
44
-
45
- const localStorageToClear = [
46
- 'sessionId',
47
- 'mbkauthe.sid',
48
- 'fullName',
49
- '_csrf',
50
- 'profileImageUser',
51
- 'profileImageUrl'
52
- ];
53
-
54
- // 1. Clear selected localStorage keys
55
- localStorageToClear.forEach(key => {
56
- localStorage.removeItem(key);
57
- });
58
54
 
59
- // 2. Clear selected cookies
60
- cookiesToClear.forEach(name => {
61
- document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/`;
62
- document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=${window.location.hostname}`;
63
- document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${window.location.hostname}`;
64
- });
55
+ try {
56
+ const response = await fetch('/mbkauthe/api/logout', {
57
+ method: 'POST',
58
+ headers: {
59
+ 'Content-Type': 'application/json',
60
+ 'Cache-Control': 'no-cache'
61
+ },
62
+ credentials: 'include',
63
+ cache: 'no-store'
64
+ });
65
+ const result = await parseJson(response);
66
+
67
+ if (response.ok) {
68
+ selectiveCacheClear();
69
+ return true;
70
+ }
65
71
 
66
- // 3. Optional reload
67
- window.location.reload();
72
+ alert(result.message || 'Logout failed. Please try again.');
73
+ } catch (error) {
74
+ console.error(`${LOG_PREFIX} Error during logout:`, error);
75
+ alert(`Logout failed: ${error.message}`);
76
+ }
68
77
 
69
- } catch (error) {
70
- console.error(`[mbkauthe] selective cache clear failed:`, error);
71
- window.location.reload();
78
+ return false;
72
79
  }
73
- }
74
-
75
- async function logoutuser() {
76
- await logout();
77
- }
78
-
79
- const validateSessionInterval = 60000;
80
- // 1 minutes in milliseconds Function to check session validity by sending a request to the server
81
- function checkSession() {
82
- fetch("/api/validate-session")
83
- .then((response) => {
84
- if (!response.ok) {
85
- // Redirect or handle errors (session expired, user inactive, etc.)
86
- window.location.reload(); // Reload the page to update the session status
87
- }
80
+
81
+ function selectiveCacheClear() {
82
+ try {
83
+ SESSION_KEYS.forEach((key) => localStorage.removeItem(key));
84
+ SESSION_KEYS.forEach(clearCookie);
85
+ } catch (error) {
86
+ console.error(`${LOG_PREFIX} selective cache clear failed:`, error);
87
+ } finally {
88
+ reloadPage();
89
+ }
90
+ }
91
+
92
+ async function logoutuser() {
93
+ return logout();
94
+ }
95
+
96
+ function checkSession() {
97
+ return fetch('/mbkauthe/api/checkSession', {
98
+ credentials: 'include',
99
+ cache: 'no-store'
88
100
  })
89
- .catch((error) => console.error(`[mbkauthe] Error checking session:`, error));
90
- }
91
- // Call validateSession every 2 minutes (120000 milliseconds)
92
- // setInterval(checkSession, validateSessionInterval);
93
-
94
- function getCookieValue(cookieName) {
95
- const cookies = document.cookie.split('; ');
96
- for (let cookie of cookies) {
97
- const [name, value] = cookie.split('=');
98
- if (name === cookieName) {
101
+ .then(async (response) => {
102
+ if (!response.ok) {
103
+ reloadPage();
104
+ return;
105
+ }
106
+
107
+ const session = await parseJson(response);
108
+ if (session.sessionValid === false) {
109
+ reloadPage();
110
+ }
111
+ })
112
+ .catch((error) => console.error(`${LOG_PREFIX} Error checking session:`, error));
113
+ }
114
+
115
+ function getCookieValue(cookieName) {
116
+ if (!cookieName) {
117
+ return null;
118
+ }
119
+
120
+ const prefix = `${cookieName}=`;
121
+ const cookie = document.cookie
122
+ .split('; ')
123
+ .find((entry) => entry.startsWith(prefix));
124
+
125
+ if (!cookie) {
126
+ return null;
127
+ }
128
+
129
+ const value = cookie.slice(prefix.length);
130
+
131
+ try {
99
132
  return decodeURIComponent(value);
133
+ } catch {
134
+ return value;
100
135
  }
101
136
  }
102
- return null; // Return null if the cookie is not found
103
- }
104
137
 
105
- function loadpage(url) {
106
- window.location.href = url;
107
- }
138
+ function loadpage(url) {
139
+ if (url) {
140
+ window.location.href = url;
141
+ }
142
+ }
108
143
 
109
- function formatDate(date) {
110
- return new Date(date).toLocaleString('en-GB', { day: '2-digit', month: '2-digit', year: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric', hour12: true });
111
- }
144
+ function formatDate(date) {
145
+ const parsedDate = new Date(date);
146
+ return Number.isNaN(parsedDate.getTime()) ? 'Invalid Date' : dateFormatter.format(parsedDate);
147
+ }
112
148
 
113
- function reloadPage() {
114
- window.location.reload();
115
- }
149
+ const api = {
150
+ checkSession,
151
+ formatDate,
152
+ getCookieValue,
153
+ loadpage,
154
+ logout,
155
+ logoutuser,
156
+ reloadPage,
157
+ selectiveCacheClear
158
+ };
159
+
160
+ window.mbkauthe = Object.assign(window.mbkauthe || {}, api);
161
+ Object.assign(window, api);
162
+ })();
@@ -29,4 +29,5 @@
29
29
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
30
30
  {{> sharedStyles}}
31
31
  {{#if extraStyles}}{{{extraStyles}}}{{/if}}
32
+ <script src="/mbkauthe/main.js{{cacheBuster}}" defer></script>
32
33
  </head>
@@ -205,7 +205,7 @@
205
205
  </div>
206
206
 
207
207
  <div class="session-actions">
208
- <button class="btn btn-danger" onclick="logout()" aria-label="Log out">
208
+ <button class="btn btn-danger" onclick="mbkauthe.logout()" aria-label="Log out">
209
209
  <i class="fas fa-sign-out-alt"></i> Logout
210
210
  </button>
211
211
  <a class="btn btn-outline" href="https://portal.mbktech.org/">
@@ -233,7 +233,6 @@
233
233
  </div>
234
234
  </section>
235
235
 
236
- <script src="/mbkauthe/main.js{{cacheBuster}}"></script>
237
236
  {{#if sessionExpiry}}
238
237
  <script>
239
238
  (function () {
@@ -261,21 +261,7 @@
261
261
  logoutButton.textContent = 'Logging out...';
262
262
 
263
263
  try {
264
- const response = await fetch('/mbkauthe/api/logout', {
265
- method: 'POST',
266
- headers: {
267
- 'Content-Type': 'application/json'
268
- },
269
- credentials: 'include'
270
- });
271
-
272
- const result = await response.json().catch(() => ({}));
273
-
274
- if (response.ok) {
275
- window.location.reload();
276
- } else {
277
- alert(result.message || 'Logout failed. Please try again.');
278
- }
264
+ await mbkauthe.logout();
279
265
  } catch (error) {
280
266
  console.error(`[mbkauthe] Error during logout:`, error);
281
267
  alert('Logout failed. Please try again.');
@@ -1,122 +0,0 @@
1
- ```mermaid
2
- sequenceDiagram
3
- autonumber
4
-
5
- participant R as Protected Route
6
- participant VS as validateSession
7
- participant JT as isJsonRequest
8
- participant VT as validateTokenAuthentication
9
- participant CS as validateCookieSession
10
- participant DB as AuthRepository / DB
11
- participant RES as Response
12
-
13
- R->>VS: validateSession(req, res, next, strictTokenValidation?)
14
-
15
- alt Authorization header exists
16
- alt strictTokenValidation is true
17
- VS-->>RES: 401 INVALID_AUTH_TOKEN
18
- Note over VS,RES: Token auth is rejected for strict cookie-only middleware.
19
- else token auth allowed
20
- VS->>VT: validateTokenAuthentication(req)
21
-
22
- alt Header is not "Bearer <token>"
23
- VT-->>VS: null
24
- VS-->>RES: 401 INVALID_AUTH_TOKEN
25
- else Token does not start with mbk_ or is too long
26
- VT-->>VS: { error: "INVALID_TOKEN" }
27
- VS-->>RES: 401 INVALID_AUTH_TOKEN
28
- else Token format is accepted
29
- VT->>VT: hashApiToken(token)
30
- VT->>DB: getApiTokenByHash(tokenHash)
31
-
32
- alt API token row not found
33
- DB-->>VT: null
34
- VT-->>VS: { error: "INVALID_TOKEN" }
35
- VS-->>RES: 401 INVALID_AUTH_TOKEN
36
- else API token expired
37
- DB-->>VT: row with ExpiresAt <= now
38
- VT-->>VS: { error: "TOKEN_EXPIRED" }
39
- VS-->>RES: 401 API_TOKEN_EXPIRED
40
- else API token found
41
- DB-->>VT: token row joined to user
42
- VT->>VT: Resolve token scope and allowedApps
43
- VT->>DB: updateApiTokenLastUsed(row.id) async
44
- VT-->>VS: tokenUser
45
-
46
- alt tokenUser.active is false
47
- VS-->>RES: 401 ACCOUNT_INACTIVE
48
- else Non-SuperAdmin has no allowed apps
49
- VS-->>RES: 401 APP_NOT_AUTHORIZED
50
- else Token allowedApps has wildcard but user apps do not include APP_NAME
51
- VS-->>RES: 401 APP_NOT_AUTHORIZED
52
- else Token allowedApps does not include APP_NAME
53
- VS-->>RES: 401 APP_NOT_AUTHORIZED
54
- else App access allowed
55
- VS->>VS: attachApiTokenUser(req, res, tokenUser)
56
- Note over VS: Sets req.auth, req.user, req.userRole,<br/>and request-local req.session.user.
57
-
58
- alt tokenScope does not allow req.method
59
- VS-->>RES: 403 TOKEN_SCOPE_INSUFFICIENT
60
- else token scope allows method
61
- VS-->>R: next()
62
- end
63
- end
64
- end
65
- end
66
- end
67
- else No Authorization header
68
- VS->>JT: isJsonRequest(req)
69
- JT-->>VS: prefersJson
70
- VS->>CS: validateCookieSession(req, res, next, prefersJson)
71
-
72
- alt req.session.user is missing
73
- alt prefersJson
74
- CS-->>RES: 401 SESSION_NOT_FOUND
75
- else browser/page request
76
- CS-->>RES: 302 /mbkauthe/login?redirect=...&reason=logged_out
77
- end
78
- else req.session.user exists
79
- CS->>CS: Read req.session.user.sessionId
80
-
81
- alt sessionId missing or not UUID
82
- CS->>CS: destroy session and clear cookies
83
- alt prefersJson
84
- CS-->>RES: 401 SESSION_EXPIRED
85
- else browser/page request
86
- CS-->>RES: Render "Session Expired" error page
87
- end
88
- else sessionId is UUID
89
- CS->>DB: getSessionAuthData(sessionId)
90
-
91
- alt DB session row not found
92
- CS->>CS: destroy session and clear cookies
93
- alt prefersJson
94
- CS-->>RES: 401 SESSION_INVALID
95
- else browser/page request
96
- CS-->>RES: Render "Session Expired" error page
97
- end
98
- else DB session row found
99
- DB-->>CS: session row joined to user
100
-
101
- alt session expired
102
- CS->>CS: destroy session and clear cookies
103
- CS-->>RES: 401 SESSION_EXPIRED or rendered error page
104
- else user account inactive
105
- CS->>CS: destroy session and clear cookies
106
- CS-->>RES: 401 ACCOUNT_INACTIVE or rendered support page
107
- else user not allowed for APP_NAME
108
- CS->>CS: destroy session and clear cookies
109
- CS-->>RES: 401 APP_NOT_AUTHORIZED or rendered unauthorized page
110
- else session valid and app access allowed
111
- CS->>CS: req.userRole = sessionRow.Role
112
- CS-->>R: next()
113
- end
114
- end
115
- end
116
- end
117
- end
118
-
119
- opt Token or session validation throws
120
- VS-->>RES: 500 INTERNAL_SERVER_ERROR
121
- end
122
- ```