mbkauthe 4.8.2 → 4.8.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/README.md CHANGED
@@ -11,7 +11,9 @@
11
11
  <img height="64px" src="./public/logo.png" alt="MBKAuthe" />
12
12
  </p>
13
13
 
14
- **MBKAuthe** is a production-ready authentication system for Node.js with Express and PostgreSQL. Features include secure login, 2FA, role-based access, OAuth (GitHub & Google), multi-session support, and multi-app user management.
14
+ **MBKAuthe** is an open source package focused on login, authentication, and validating sessions for your desired apps in Node.js with Express and PostgreSQL. It provides secure login, session validation, 2FA, role/app access checks, OAuth (GitHub & Google), multi-session support, and related authentication flows.
15
+
16
+ > **Note:** MBKAuthe is intentionally limited to authentication and session validation. The full user/permission/dashboard management system is a separate product called **MBKCore**, developed by MBKTech and not currently open source. Access to MBKCore is currently available only to the MBKTech team, and we may refine it and consider open sourcing it in the future.
15
17
 
16
18
  ## Todo
17
19
  - Currently, for every request to a protected page, a database query is made to retrieve authentication information (allowed apps, username, session ID, role, etc.). We should implement a caching mechanism to reduce this overhead, but also find a way to allow administrators to log users out and update permissions in near real-time.
@@ -54,15 +56,15 @@ Run [docs/db.sql](docs/db.sql) to create tables and a default SuperAdmin (`suppo
54
56
 
55
57
  ```javascript
56
58
  import express from 'express';
57
- import mbkauthe, { validateSession, checkRolePermission } from 'mbkauthe';
59
+ import mbkauthe, { sessVal, roleChk } from 'mbkauthe';
58
60
  import dotenv from 'dotenv';
59
61
  dotenv.config();
60
62
 
61
63
  const app = express();
62
64
  app.use(mbkauthe);
63
65
 
64
- app.get('/dashboard', validateSession, (req, res) => res.send(`Welcome ${req.session.user.username}!`));
65
- app.get('/admin', validateSession, checkRolePermission(['SuperAdmin']), (req, res) => res.send('Admin Panel'));
66
+ app.get('/dashboard', sessVal, (req, res) => res.send(`Welcome ${req.session.user.username}!`));
67
+ app.get('/admin', sessVal, roleChk(['SuperAdmin']), (req, res) => res.send('Admin Panel'));
66
68
 
67
69
  app.listen(3000);
68
70
  ```
@@ -78,10 +80,27 @@ npm run dev
78
80
  ## 🔧 Core API
79
81
 
80
82
  - **Session Validation:** `validateSession`
81
- - **Role Check:** `checkRolePermission(['Role'])`
82
- - **Combined:** `validateSessionAndRole(['SuperAdmin', 'NormalUser'])`
83
+ - **Role Check:** `checkRolePermission(['Role'])`/`roleChk(['Role'])`
84
+ - **Combined:** `validateSessionAndRole(['SuperAdmin', 'NormalUser'])`/`sessRole(['SuperAdmin', 'NormalUser'])`
83
85
  - **API Token Auth:** `authenticate(process.env.API_TOKEN)`
84
86
 
87
+ ## 🧾 JSON Responses (HTML vs JSON)
88
+
89
+ Most browser page routes render HTML on auth errors, while API/AJAX-style requests receive JSON error responses.
90
+
91
+ MBKAuthe treats a request as **JSON** (and returns JSON errors) when any of the following apply:
92
+
93
+ - URL/path starts with `/mbkauthe/api/` or `/api/`
94
+ - `X-Requested-With: XMLHttpRequest`
95
+ - `Accept` indicates JSON (e.g., `application/json`) and does not explicitly prefer `text/html`
96
+ - `User-Agent` looks like a non-browser client (e.g., `curl`, `wget`, `Postman`)
97
+ - `User-Agent: json` (explicitly forces JSON responses)
98
+
99
+ **Example (force JSON errors):**
100
+ ```bash
101
+ curl -i -H "User-Agent: json" http://localhost:3000/mbkauthe/test
102
+ ```
103
+
85
104
  ## 🧰 Diagnostics (dev only)
86
105
 
87
106
  These are only mounted when `process.env.env === "dev"`:
package/docs/api.md CHANGED
@@ -57,12 +57,12 @@ Authorization: Bearer <your_api_token>
57
57
 
58
58
  **1. Backend Implementation (Express):**
59
59
 
60
- Even when using API tokens, the `validateSession` middleware hydrates `req.session.user` for consistency, allowing you to use the same route logic for both browser and API clients.
60
+ Even when using API tokens, the `validateSession`/`sessVal` middleware hydrates `req.session.user` for consistency, allowing you to use the same route logic for both browser and API clients.
61
61
 
62
62
  ```javascript
63
- import { validateSession } from 'mbkauthe';
63
+ import { sessVal } from 'mbkauthe';
64
64
 
65
- app.get('/api/protected-resource', validateSession, (req, res) => {
65
+ app.get('/api/protected-resource', sessVal, (req, res) => {
66
66
  // Access user info populated from the token
67
67
  const user = req.session.user; // { id, username, role, ... }
68
68
 
@@ -1398,15 +1398,15 @@ Both GitHub and Google OAuth implementations include:
1398
1398
 
1399
1399
  ## Middleware Reference
1400
1400
 
1401
- ### `validateSession`
1401
+ ### `validateSession`/`sessRole`
1402
1402
 
1403
1403
  Validates that the user has an active session.
1404
1404
 
1405
1405
  **Usage:**
1406
1406
  ```javascript
1407
- import { validateSession } from 'mbkauthe';
1407
+ import { sessRole } from 'mbkauthe';
1408
1408
 
1409
- app.get('/protected', validateSession, (req, res) => {
1409
+ app.get('/protected', sessRole, (req, res) => {
1410
1410
  // User is authenticated
1411
1411
  const user = req.session.user;
1412
1412
  // user contains: { id, username, UserName, role, Role, sessionId }
@@ -1422,6 +1422,21 @@ app.get('/protected', validateSession, (req, res) => {
1422
1422
  - Verifies user is authorized for the current application
1423
1423
  - Redirects to login page if validation fails
1424
1424
 
1425
+ **JSON vs HTML error responses:**
1426
+
1427
+ When `validateSession` fails, MBKAuthe will either render an HTML error/login page (browser flow) or return a JSON error response (API/AJAX flow). A request is treated as **JSON** when any of these are true:
1428
+
1429
+ - URL/path starts with `/mbkauthe/api/` or `/api/`
1430
+ - `X-Requested-With: XMLHttpRequest`
1431
+ - `Accept` indicates JSON (e.g., `application/json`) and does not explicitly prefer `text/html`
1432
+ - `User-Agent` matches a non-browser client (e.g., `curl`, `wget`, `Postman`, `Insomnia`)
1433
+ - `User-Agent: json` (explicitly forces JSON responses)
1434
+
1435
+ **Example (force JSON errors on a page route):**
1436
+ ```bash
1437
+ curl -i -H "User-Agent: json" http://localhost:3000/mbkauthe/test
1438
+ ```
1439
+
1425
1440
  ### reloadSessionUser(req, res)
1426
1441
 
1427
1442
  Use this helper when you need to refresh the values stored in `req.session.user` from the authoritative database record (for example, after a profile update that changes FullName, or when session expiration policies are updated).
@@ -1440,7 +1455,7 @@ Use this helper when you need to refresh the values stored in `req.session.user`
1440
1455
  import { reloadSessionUser } from 'mbkauthe';
1441
1456
 
1442
1457
  // After updating profile data
1443
- app.post('/mbkauthe/api/update-profile', validateSession, async (req, res) => {
1458
+ app.post('/mbkauthe/api/update-profile', sessRole, async (req, res) => {
1444
1459
  // ... update profiledata.FullName in DB ...
1445
1460
  const refreshed = await reloadSessionUser(req, res);
1446
1461
  if (!refreshed) {
@@ -1469,7 +1484,7 @@ req.session.user = {
1469
1484
  These cookies allow front-end UI to display a friendly name without making extra requests to the server.
1470
1485
  ---
1471
1486
 
1472
- ### `checkRolePermission(requiredRole, notAllowed)`
1487
+ ### `checkRolePermission(requiredRole, notAllowed)`/`roleChk `
1473
1488
 
1474
1489
  Checks if the authenticated user has the required role.
1475
1490
 
@@ -1479,15 +1494,15 @@ Checks if the authenticated user has the required role.
1479
1494
 
1480
1495
  **Usage:**
1481
1496
  ```javascript
1482
- import { validateSession, checkRolePermission } from 'mbkauthe';
1497
+ import { sessVal, roleChk } from 'mbkauthe';
1483
1498
 
1484
1499
  // Only SuperAdmin can access
1485
- app.get('/admin', validateSession, checkRolePermission('SuperAdmin'), (req, res) => {
1500
+ app.get('/admin', sessVal, roleChk('SuperAdmin'), (req, res) => {
1486
1501
  res.send('Admin panel');
1487
1502
  });
1488
1503
 
1489
1504
  // Any authenticated user except Guest
1490
- app.get('/content', validateSession, checkRolePermission('Any', 'Guest'), (req, res) => {
1505
+ app.get('/content', sessVal, roleChk('Any', 'Guest'), (req, res) => {
1491
1506
  res.send('Protected content');
1492
1507
  });
1493
1508
  ```
@@ -1501,7 +1516,7 @@ app.get('/content', validateSession, checkRolePermission('Any', 'Guest'), (req,
1501
1516
 
1502
1517
  ---
1503
1518
 
1504
- ### `validateSessionAndRole(requiredRole, notAllowed)`
1519
+ ### `validateSessionAndRole(requiredRole, notAllowed)`/`sessRole`
1505
1520
 
1506
1521
  Combined middleware for session validation and role checking.
1507
1522
 
@@ -1511,17 +1526,17 @@ Combined middleware for session validation and role checking.
1511
1526
 
1512
1527
  **Usage:**
1513
1528
  ```javascript
1514
- import { validateSessionAndRole } from 'mbkauthe';
1529
+ import { sessRole, roleChk } from 'mbkauthe';
1515
1530
 
1516
1531
  // Validate session AND check role in one middleware
1517
- app.get('/moderator', validateSessionAndRole('SuperAdmin'), (req, res) => {
1532
+ app.get('/moderator', sessRole('SuperAdmin'), (req, res) => {
1518
1533
  res.send('Moderator panel');
1519
1534
  });
1520
1535
  ```
1521
1536
 
1522
1537
  **Equivalent to:**
1523
1538
  ```javascript
1524
- app.get('/moderator', validateSession, checkRolePermission('SuperAdmin'), (req, res) => {
1539
+ app.get('/moderator', sessVal, roleChk('SuperAdmin'), (req, res) => {
1525
1540
  res.send('Moderator panel');
1526
1541
  });
1527
1542
  ```
@@ -1603,12 +1618,15 @@ app.post('/api/data', authenticate(process.env.API_TOKEN), (req, res) => {
1603
1618
 
1604
1619
  **Headers Required:**
1605
1620
  ```
1606
- Authorization: your-secret-token
1621
+ Authorization: Bearer your-secret-token
1607
1622
  ```
1608
1623
 
1624
+ You can also send the raw token without the `Bearer` prefix.
1625
+
1609
1626
  **Behavior:**
1610
1627
  - Checks `Authorization` header
1611
- - Compares with provided token
1628
+ - Extracts the token (strips optional `Bearer` prefix)
1629
+ - Compares the provided token to the expected token using a timing-safe SHA-256 hash comparison
1612
1630
  - Returns 401 if token doesn't match
1613
1631
 
1614
1632
  ---
@@ -1658,7 +1676,7 @@ const app = express();
1658
1676
  app.use(mbkauthe);
1659
1677
 
1660
1678
  // Protected route
1661
- app.get('/dashboard', validateSession, (req, res) => {
1679
+ app.get('/dashboard', sessVal, (req, res) => {
1662
1680
  res.send(`Welcome ${req.session.user.username}!`);
1663
1681
  });
1664
1682
 
@@ -1672,12 +1690,12 @@ app.listen(3000, () => {
1672
1690
  ### Role-Based Access Control
1673
1691
 
1674
1692
  ```javascript
1675
- import { validateSession, checkRolePermission, validateSessionAndRole } from 'mbkauthe';
1693
+ import { sessVal, roleChk, sessRole } from 'mbkauthe';
1676
1694
 
1677
1695
  // Method 1: Separate middleware
1678
1696
  app.get('/admin',
1679
- validateSession,
1680
- checkRolePermission('SuperAdmin'),
1697
+ sessVal,
1698
+ roleChk('SuperAdmin'),
1681
1699
  (req, res) => {
1682
1700
  res.send('Admin panel');
1683
1701
  }
@@ -1685,7 +1703,7 @@ app.get('/admin',
1685
1703
 
1686
1704
  // Method 2: Combined middleware
1687
1705
  app.get('/admin',
1688
- validateSessionAndRole('SuperAdmin'),
1706
+ sessRole('SuperAdmin'),
1689
1707
  (req, res) => {
1690
1708
  res.send('Admin panel');
1691
1709
  }
@@ -1693,8 +1711,8 @@ app.get('/admin',
1693
1711
 
1694
1712
  // Allow any role except Guest
1695
1713
  app.get('/content',
1696
- validateSession,
1697
- checkRolePermission('Any', 'Guest'),
1714
+ sessVal,
1715
+ roleChk('Any', 'Guest'),
1698
1716
  (req, res) => {
1699
1717
  res.send('Content for registered users');
1700
1718
  }
@@ -1702,7 +1720,7 @@ app.get('/content',
1702
1720
 
1703
1721
  // Multiple roles (using separate middleware)
1704
1722
  app.get('/moderator',
1705
- validateSession,
1723
+ sessVal,
1706
1724
  (req, res, next) => {
1707
1725
  if (['SuperAdmin', 'NormalUser'].includes(req.session.user.role)) {
1708
1726
  next();
@@ -1743,7 +1761,7 @@ app.post('/api/admin/terminate-sessions',
1743
1761
 
1744
1762
  // Protected API endpoint (requires session)
1745
1763
  app.get('/api/user/profile',
1746
- validateSession,
1764
+ sessVal,
1747
1765
  async (req, res) => {
1748
1766
  const { username } = req.session.user;
1749
1767
 
@@ -1835,7 +1853,7 @@ async function logout() {
1835
1853
  import { dblogin } from 'mbkauthe';
1836
1854
 
1837
1855
  // Custom query using the database pool
1838
- app.get('/api/users', validateSession, checkRolePermission('SuperAdmin'), async (req, res) => {
1856
+ app.get('/api/users', sessVal, roleChk('SuperAdmin'), async (req, res) => {
1839
1857
  try {
1840
1858
  const result = await dblogin.query(
1841
1859
  'SELECT id, "UserName", "Role", "Active" FROM "Users" ORDER BY id'
@@ -1,10 +1,10 @@
1
1
  %%{init: {"themeVariables": {"fontFamily": "Arial, Helvetica, sans-serif", "primaryTextColor": "#333333", "secondaryTextColor": "#333333", "background": "#ffffff"}}}%%
2
2
  flowchart TD
3
- A[Client POST /api/login - payload: username password redirect] --> B[Validate input length & format]
3
+ A[Client POST /mbkauthe/api/login - payload: username password redirect] --> B[Validate input length & format]
4
4
  B -->|invalid| Aerr[400 - missing/invalid]
5
5
  B --> C[Query Users and TwoFA status]
6
6
  C -->|no user| U404[401 - invalid credentials]
7
- C --> D[Compare password - EncPass or raw]
7
+ C --> D[Compare password hash ]
8
8
  D -->|fail| P401[401 - incorrect password]
9
9
  D -->|ok| E[Check account Active and app authorization]
10
10
  E -->|inactive| I403[403 - account inactive]
@@ -27,7 +27,7 @@ flowchart TD
27
27
  Gtrust --> RESP
28
28
 
29
29
  %% 2FA
30
- A2[Client POST /api/verify-2fa - payload: token trustDevice] --> B2[Ensure req.session.preAuthUser exists]
30
+ A2[Client POST /mbkauthe/api/verify-2fa - payload: token trustDevice] --> B2[Ensure req.session.preAuthUser exists]
31
31
  B2 -->|missing| Err401[401 - session not found]
32
32
  B2 --> C2[Validate token format - 6 digits]
33
33
  C2 --> D2[Fetch TwoFA secret from DB for username]
@@ -39,7 +39,7 @@ flowchart TD
39
39
  %% completeLoginProcess & validateSession kept separate for clarity
40
40
 
41
41
  %% Logout
42
- L1[Client POST /api/logout] --> L2{req.session.user exists?}
42
+ L1[Client POST /mbkauthe/api/logout] --> L2{req.session.user exists?}
43
43
  L2 -->|no| Lerr[400 - Not logged in]
44
44
  L2 -->|yes| L3[Delete app Sessions row and session store row]
45
45
  L3 --> L4[removeAccountFromCookie]
@@ -47,7 +47,7 @@ flowchart TD
47
47
  L5 --> LResp[200 - logout successful]
48
48
 
49
49
  %% Account-sessions
50
- AS1[Client GET /api/account-sessions] --> AS2[readAccountListFromCookie]
50
+ AS1[Client GET /mbkauthe/api/account-sessions] --> AS2[readAccountListFromCookie]
51
51
  AS2 -->|empty| ASresp[Return empty accounts and currentSessionId]
52
52
  AS2 --> AS3[For each stored account: validate each stored session via fetchActiveSession]
53
53
  AS3 -->|invalid| AS4[invalidate DB session and remove account from cookie]
@@ -55,7 +55,7 @@ flowchart TD
55
55
  AS5 --> ASresp2[Return accounts and currentSessionId]
56
56
 
57
57
  %% Switch session
58
- SS1[Client POST /api/switch-session] --> SS2[Validate sessionId format]
58
+ SS1[Client POST /mbkauthe/api/switch-session] --> SS2[Validate sessionId format]
59
59
  SS2 -->|invalid| SSerr400[400 - Invalid session id]
60
60
  SS2 --> SS3[Check sessionId in readAccountListFromCookie]
61
61
  SS3 -->|missing| SSerr403[403 - Account not available on this device]
@@ -66,7 +66,7 @@ flowchart TD
66
66
  SS6 --> SSresp[200 - success with username fullName redirect]
67
67
 
68
68
  %% Logout-all
69
- LA1[Client POST /api/logout-all] --> LA2[readAccountListFromCookie]
69
+ LA1[Client POST /mbkauthe/api/logout-all] --> LA2[readAccountListFromCookie]
70
70
  LA2 --> LA3[Collect sessionIds; add currentSession if present]
71
71
  LA3 --> LA4[Delete Sessions rows by ids]
72
72
  LA4 --> LA5[Delete session store row for current session]
@@ -74,9 +74,9 @@ flowchart TD
74
74
  LA6 --> LAresp[200 - All accounts logged out]
75
75
 
76
76
  %% OAuth
77
- O1[Client GET /api/provider/login?redirect=...] --> O2[Server create CSRF token and save to session.oauthCsrfToken and oauthRedirect]
77
+ O1[Client GET /mbkauthe/api/github/login or /mbkauthe/api/google/login?redirect=...] --> O2[Server create CSRF token and save to session.oauthCsrfToken and oauthRedirect]
78
78
  O2 --> O3[passport.authenticate -> redirect to provider auth page]
79
- O3 --> O4[Provider redirects back to /api/provider/login/callback with state and auth code]
79
+ O3 --> O4[Provider redirects back to /mbkauthe/api/github/login/callback or /mbkauthe/api/google/login/callback with state and auth code]
80
80
  O4 --> O5[validateOAuthCallback: compare state with session.oauthCsrfToken]
81
81
  O5 -->|invalid| Oerr[403 - CSRF mismatch]
82
82
  O5 -->|valid| O6[passport.authenticate -> locate linked user record]
@@ -0,0 +1,71 @@
1
+ sequenceDiagram
2
+ participant C as Client
3
+ participant S as Server
4
+ participant DB as Database
5
+
6
+ rect rgb(235, 245, 255)
7
+ Note over C, DB: Authentication Flow
8
+
9
+ C->>S: POST /login (username, password)
10
+ S->>S: Validate payload format
11
+ S->>DB: Query User & 2FA status
12
+ DB-->>S: User Data (Password Hash, 2FA Enabled)
13
+ S->>S: Compare Hash (PasswordEnc)
14
+
15
+ alt 2FA Enabled & Device Not Trusted
16
+ S-->>C: 200 (twoFactorRequired: true)
17
+
18
+ C->>S: POST /verify-2fa (TOTP Token)
19
+ S->>DB: Fetch TOTP Secret
20
+ DB-->>S: Secret Key
21
+ S->>S: Verify TOTP
22
+ end
23
+
24
+ Note over S, DB: completeLoginProcess
25
+
26
+ S->>DB: Delete old sessions & Prune max sessions
27
+ S->>S: Regenerate Session ID
28
+ S->>DB: Insert new Session row
29
+ S->>DB: Update Users.last_login
30
+
31
+ S-->>C: 200 Success (Set Encrypted Cookies)
32
+ end
33
+
34
+ rect rgb(245, 245, 245)
35
+ Note over C, DB: Request Authentication Middleware Flow
36
+
37
+ C->>S: Request + Auth (Cookie or Bearer Token)
38
+
39
+ rect rgb(240, 240, 240)
40
+ Note over S, DB: Token Path (API)
41
+
42
+ alt Header: Bearer mbk_token
43
+ S->>S: Hash Token
44
+ S->>DB: Query ApiTokens JOIN Users
45
+
46
+ alt Valid & Not Expired
47
+ S->>DB: Update last_used
48
+ S->>S: Authorize Scope
49
+ else Invalid/Expired
50
+ S-->>C: 401 Unauthorized
51
+ end
52
+ end
53
+ end
54
+
55
+ rect rgb(220, 230, 250)
56
+ Note over S, DB: Session Path (Web)
57
+
58
+ alt Cookie: sessionId exists
59
+ S->>DB: Query Sessions JOIN Users
60
+ DB-->>S: Session Data + User Status
61
+
62
+ alt Session Valid & User Active
63
+ S->>S: Sync Cookies & Update session.user
64
+ S->>S: Proceed to Route
65
+ else Session Expired/User Inactive
66
+ S->>S: Destroy Session & Clear Cookies
67
+ S-->>C: 403 Forbidden
68
+ end
69
+ end
70
+ end
71
+ end