mbkauthe 3.3.0 → 3.5.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/README.md +15 -2
- package/docs/api.md +69 -1
- package/docs/env.md +89 -434
- package/index.d.ts +7 -0
- package/index.js +4 -1
- package/lib/config/cookies.js +1 -0
- package/lib/config/index.js +68 -24
- package/lib/middleware/auth.js +156 -3
- package/lib/middleware/index.js +22 -1
- package/lib/routes/auth.js +16 -0
- package/lib/routes/misc.js +40 -5
- package/lib/routes/oauth.js +37 -19
- package/package.json +4 -4
- package/test.spec.js +15 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# MBKAuthe
|
|
1
|
+
# MBKAuthe - Authentication System for Node.js
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/mbkauthe)
|
|
4
4
|
[](LICENSE)
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
<img height="48px" src="https://handlebarsjs.com/handlebars-icon.svg" alt="Handlebars" />
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
|
-
**
|
|
19
|
+
**MBKAuthe** is a production-ready authentication system for Node.js applications. Built with Express and PostgreSQL, it provides secure authentication, 2FA, role-based access, and OAuth integration (GitHub & Google) out of the box.
|
|
20
20
|
|
|
21
21
|
## ✨ Key Features
|
|
22
22
|
|
|
@@ -92,6 +92,7 @@ import dotenv from 'dotenv';
|
|
|
92
92
|
|
|
93
93
|
dotenv.config();
|
|
94
94
|
|
|
95
|
+
// App-specific configuration (as JSON string)
|
|
95
96
|
process.env.mbkautheVar = JSON.stringify({
|
|
96
97
|
APP_NAME: process.env.APP_NAME,
|
|
97
98
|
SESSION_SECRET_KEY: process.env.SESSION_SECRET_KEY,
|
|
@@ -102,6 +103,16 @@ process.env.mbkautheVar = JSON.stringify({
|
|
|
102
103
|
loginRedirectURL: '/dashboard'
|
|
103
104
|
});
|
|
104
105
|
|
|
106
|
+
// Optional shared configuration (useful for shared OAuth credentials across multiple projects)
|
|
107
|
+
process.env.mbkauthShared = JSON.stringify({
|
|
108
|
+
GITHUB_CLIENT_ID: process.env.GITHUB_CLIENT_ID,
|
|
109
|
+
GITHUB_CLIENT_SECRET: process.env.GITHUB_CLIENT_SECRET,
|
|
110
|
+
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
|
|
111
|
+
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// MBKAuth prioritizes values in mbkautheVar, then mbkauthShared, then built-in defaults.
|
|
115
|
+
|
|
105
116
|
const app = express();
|
|
106
117
|
|
|
107
118
|
// Mount authentication routes
|
|
@@ -335,6 +346,8 @@ const result = await dblogin.query('SELECT * FROM "Users"');
|
|
|
335
346
|
- ✅ Use environment variables for all secrets
|
|
336
347
|
|
|
337
348
|
**Vercel:**
|
|
349
|
+
|
|
350
|
+
Tip: On Vercel you can set `mbkauthShared` at the project or team level to share common OAuth credentials across multiple deployments. MBKAuth will use values from `mbkautheVar` first and fall back to `mbkauthShared`.
|
|
338
351
|
```json
|
|
339
352
|
{
|
|
340
353
|
"version": 2,
|
package/docs/api.md
CHANGED
|
@@ -194,6 +194,39 @@ fetch('/mbkauthe/api/login', {
|
|
|
194
194
|
|
|
195
195
|
---
|
|
196
196
|
|
|
197
|
+
#### `GET /mbkauthe/api/checkSession`
|
|
198
|
+
|
|
199
|
+
Checks whether the current session (cookie-based) is valid. Returns a JSON response suitable for AJAX/SPA checks.
|
|
200
|
+
|
|
201
|
+
**Authentication:** Requires a valid session cookie set by `/mbkauthe/api/login`.
|
|
202
|
+
|
|
203
|
+
**Success Response (200 OK):**
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"sessionValid": true,
|
|
207
|
+
"expiry": "2025-12-27T12:34:56.000Z"
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Error Responses (examples):**
|
|
212
|
+
- 200 Session invalid ( { "sessionValid": false, "expiry": null } )
|
|
213
|
+
- 500 Internal Server Error (rare)
|
|
214
|
+
|
|
215
|
+
**Example Request:**
|
|
216
|
+
```javascript
|
|
217
|
+
fetch('/mbkauthe/api/checkSession')
|
|
218
|
+
.then(res => res.json())
|
|
219
|
+
.then(data => {
|
|
220
|
+
if (data.sessionValid) {
|
|
221
|
+
// session active, expiry available in data.expiry
|
|
222
|
+
} else {
|
|
223
|
+
// not authenticated
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
197
230
|
#### `GET /mbkauthe/2fa`
|
|
198
231
|
|
|
199
232
|
Renders the Two-Factor Authentication verification page.
|
|
@@ -759,16 +792,51 @@ app.get('/protected', validateSession, (req, res) => {
|
|
|
759
792
|
- Verifies user is authorized for the current application
|
|
760
793
|
- Redirects to login page if validation fails
|
|
761
794
|
|
|
795
|
+
### reloadSessionUser(req, res)
|
|
796
|
+
|
|
797
|
+
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).
|
|
798
|
+
|
|
799
|
+
- Behavior:
|
|
800
|
+
- Validates the session against the database (sessionId, active)
|
|
801
|
+
- Updates `req.session.user` fields: `username`, `role`, `allowedApps`, `fullname`
|
|
802
|
+
- Uses cached `fullName` cookie if available; falls back to querying `profiledata`
|
|
803
|
+
- Syncs `username`, `fullName`, and `sessionId` cookies for client display
|
|
804
|
+
- If the session is invalid (sessionId mismatch, inactive account, or unauthorized), it destroys the session and clears cookies
|
|
805
|
+
|
|
806
|
+
- Returns: `Promise<boolean>` — `true` if session was refreshed and still valid, `false` if session was invalidated or reload failed.
|
|
807
|
+
|
|
808
|
+
- Example:
|
|
809
|
+
```javascript
|
|
810
|
+
import { reloadSessionUser } from 'mbkauthe';
|
|
811
|
+
|
|
812
|
+
// After updating profile data
|
|
813
|
+
app.post('/mbkauthe/api/update-profile', validateSession, async (req, res) => {
|
|
814
|
+
// ... update profiledata.FullName in DB ...
|
|
815
|
+
const refreshed = await reloadSessionUser(req, res);
|
|
816
|
+
if (!refreshed) {
|
|
817
|
+
return res.status(401).json({ success: false, message: 'Session invalidated' });
|
|
818
|
+
}
|
|
819
|
+
res.json({ success: true, fullname: req.session.user.fullname });
|
|
820
|
+
});
|
|
821
|
+
```
|
|
822
|
+
|
|
762
823
|
**Session Object:**
|
|
763
824
|
```javascript
|
|
764
825
|
req.session.user = {
|
|
765
826
|
id: 1, // User ID
|
|
766
|
-
username: "john.doe", // Username
|
|
827
|
+
username: "john.doe", // Username (login name)
|
|
828
|
+
fullname: "John Doe", // Optional display name fetched from profiledata
|
|
767
829
|
role: "NormalUser", // User role
|
|
768
830
|
sessionId: "abc123...", // 64-char hex session ID
|
|
769
831
|
}
|
|
770
832
|
```
|
|
771
833
|
|
|
834
|
+
**Session Cookie Sync:**
|
|
835
|
+
- The middleware sets non-httpOnly cookies for client display:
|
|
836
|
+
- `username` — the login username (exposed for UI)
|
|
837
|
+
- `fullName` — the display name (falls back to username if not available)
|
|
838
|
+
|
|
839
|
+
These cookies allow front-end UI to display a friendly name without making extra requests to the server.
|
|
772
840
|
---
|
|
773
841
|
|
|
774
842
|
### `checkRolePermission(requiredRole, notAllowed)`
|
package/docs/env.md
CHANGED
|
@@ -1,455 +1,110 @@
|
|
|
1
1
|
# Environment Configuration Guide
|
|
2
2
|
|
|
3
3
|
[← Back to README](README.md)
|
|
4
|
+
This document describes the environment variables MBKAuth expects and keeps brief usage notes for each parameter. Validation and defaults are implemented in `lib/config/index.js` (it parses `mbkautheVar`, applies optional `mbkauthShared` fallbacks, normalizes values, and throws on validation failures).
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
## How configuration is provided
|
|
7
|
+
- Primary payload: `mbkautheVar` — a JSON string with app-specific keys.
|
|
8
|
+
- Optional shared defaults: `mbkauthShared` — a JSON string used only for missing or empty keys.
|
|
9
|
+
- Example: `mbkautheVar={"APP_NAME":"mbkauthe", ...}`
|
|
6
10
|
|
|
7
11
|
---
|
|
8
12
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
APP_NAME
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
- `
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
DOMAIN=localhost
|
|
81
|
-
IS_DEPLOYED=false
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
#### EncPass
|
|
85
|
-
**Description:** Controls whether passwords are stored and validated in encrypted format.
|
|
86
|
-
|
|
87
|
-
**Values:**
|
|
88
|
-
- `true` - Use encrypted password validation
|
|
89
|
-
- Passwords are hashed using PBKDF2 with the username as salt
|
|
90
|
-
- Compares against `PasswordEnc` column in Users table
|
|
91
|
-
- `false` - Use raw password validation (default)
|
|
92
|
-
- Passwords are stored and compared in plain text
|
|
93
|
-
- Compares against `Password` column in Users table
|
|
94
|
-
|
|
95
|
-
**Default:** `false`
|
|
96
|
-
|
|
97
|
-
**Security Note:** Setting `EncPass=true` is recommended for production environments as it provides better security by storing hashed passwords instead of plain text.
|
|
98
|
-
|
|
99
|
-
**Examples:**
|
|
100
|
-
```env
|
|
101
|
-
# Production (recommended)
|
|
102
|
-
EncPass=true
|
|
103
|
-
|
|
104
|
-
# Development
|
|
105
|
-
EncPass=false
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**Database Implications:**
|
|
109
|
-
- When `EncPass=true`: The system uses the `PasswordEnc` column
|
|
110
|
-
- When `EncPass=false`: The system uses the `Password` column
|
|
111
|
-
- Ensure your database schema includes the appropriate column based on your configuration
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
## 🗄️ Database Configuration
|
|
116
|
-
|
|
117
|
-
### PostgreSQL Connection
|
|
118
|
-
```env
|
|
119
|
-
LOGIN_DB=postgresql://username:password@host:port/database_name
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
**Description:** PostgreSQL database connection string for user authentication.
|
|
123
|
-
|
|
124
|
-
**Format:** `postgresql://[username]:[password]@[host]:[port]/[database]`
|
|
125
|
-
|
|
126
|
-
**Examples:**
|
|
127
|
-
```env
|
|
128
|
-
# Local database
|
|
129
|
-
LOGIN_DB=postgresql://admin:password123@localhost:5432/mbkauth_db
|
|
130
|
-
|
|
131
|
-
# Remote database
|
|
132
|
-
LOGIN_DB=postgresql://user:pass@db.example.com:5432/production_db
|
|
133
|
-
|
|
134
|
-
# With SSL (recommended for production)
|
|
135
|
-
LOGIN_DB=postgresql://user:pass@host:5432/db?sslmode=require
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
**Required:** Yes
|
|
139
|
-
|
|
140
|
-
---
|
|
141
|
-
|
|
142
|
-
## 🔒 Two-Factor Authentication (2FA)
|
|
143
|
-
|
|
144
|
-
### 2FA Configuration
|
|
145
|
-
```env
|
|
146
|
-
MBKAUTH_TWO_FA_ENABLE=false
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
**Description:** Enables or disables Two-Factor Authentication for enhanced security.
|
|
150
|
-
|
|
151
|
-
**Values:**
|
|
152
|
-
- `true` - Enable 2FA (recommended for production)
|
|
153
|
-
- `false` - Disable 2FA (default)
|
|
154
|
-
|
|
155
|
-
**Note:** When enabled, users will need to configure an authenticator app (Google Authenticator, Authy, etc.) for login.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
## 🔄 Redirect Configuration
|
|
160
|
-
|
|
161
|
-
### Login Redirect URL
|
|
162
|
-
```env
|
|
163
|
-
loginRedirectURL=/mbkauthe/test
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
**Description:** Specifies the URL path where users are redirected after successful authentication.
|
|
167
|
-
|
|
168
|
-
- **Purpose:** Controls post-login navigation flow
|
|
169
|
-
- **Format:** URL path (relative or absolute)
|
|
170
|
-
- **Default:** `/` (root path if not specified)
|
|
171
|
-
- **Required:** No (optional configuration)
|
|
172
|
-
|
|
173
|
-
**Examples:**
|
|
174
|
-
```env
|
|
175
|
-
# Redirect to dashboard
|
|
176
|
-
loginRedirectURL=/dashboard
|
|
177
|
-
|
|
178
|
-
# Redirect to specific app section
|
|
179
|
-
loginRedirectURL=/mbkauthe/test
|
|
180
|
-
|
|
181
|
-
# Redirect to home page
|
|
182
|
-
loginRedirectURL=/
|
|
183
|
-
|
|
184
|
-
# Redirect to external URL (if supported)
|
|
185
|
-
loginRedirectURL=https://example.com/app
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
---
|
|
189
|
-
|
|
190
|
-
## 🍪 Cookie Settings
|
|
191
|
-
|
|
192
|
-
### Cookie Expiration
|
|
193
|
-
```env
|
|
194
|
-
COOKIE_EXPIRE_TIME=2
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
**Description:** Sets how long authentication cookies remain valid.
|
|
198
|
-
|
|
199
|
-
- **Unit:** Days
|
|
200
|
-
- **Default:** `2` days
|
|
201
|
-
- **Range:** 1-30 days (recommended)
|
|
202
|
-
- **Security:** Shorter periods are more secure but require more frequent logins
|
|
203
|
-
|
|
204
|
-
**Examples:**
|
|
205
|
-
```env
|
|
206
|
-
COOKIE_EXPIRE_TIME=1 # 1 day (high security)
|
|
207
|
-
COOKIE_EXPIRE_TIME=7 # 7 days (balanced)
|
|
208
|
-
COOKIE_EXPIRE_TIME=30 # 30 days (convenience)
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Device Trust Duration
|
|
212
|
-
```env
|
|
213
|
-
DEVICE_TRUST_DURATION_DAYS=7
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
**Description:** Sets how long a device remains trusted after successful authentication.
|
|
217
|
-
|
|
218
|
-
- **Unit:** Days
|
|
219
|
-
- **Default:** `7` days
|
|
220
|
-
- **Purpose:** Controls device recognition and trust persistence
|
|
221
|
-
- **Range:** 1-365 days (recommended)
|
|
222
|
-
- **Behavior:** Trusted devices may skip certain authentication steps (like 2FA) during this period
|
|
223
|
-
|
|
224
|
-
**Examples:**
|
|
225
|
-
```env
|
|
226
|
-
DEVICE_TRUST_DURATION_DAYS=1 # 1 day (high security)
|
|
227
|
-
DEVICE_TRUST_DURATION_DAYS=7 # 1 week (balanced)
|
|
228
|
-
DEVICE_TRUST_DURATION_DAYS=30 # 30 days (convenience)
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
---
|
|
232
|
-
|
|
233
|
-
## 🔐 OAuth Authentication
|
|
234
|
-
|
|
235
|
-
### GitHub OAuth Authentication
|
|
236
|
-
|
|
237
|
-
#### GitHub Login Configuration
|
|
238
|
-
```env
|
|
239
|
-
GITHUB_LOGIN_ENABLED=false
|
|
240
|
-
GITHUB_CLIENT_ID=your-github-client-id
|
|
241
|
-
GITHUB_CLIENT_SECRET=your-github-client-secret
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
##### GITHUB_LOGIN_ENABLED
|
|
245
|
-
**Description:** Enables or disables GitHub OAuth login functionality.
|
|
246
|
-
|
|
247
|
-
**Values:**
|
|
248
|
-
- `true` - Enable GitHub login (users can authenticate via GitHub)
|
|
249
|
-
- `false` - Disable GitHub login (default)
|
|
250
|
-
|
|
251
|
-
**Required:** Yes (if using GitHub authentication)
|
|
252
|
-
|
|
253
|
-
##### GITHUB_CLIENT_ID
|
|
254
|
-
**Description:** OAuth application client ID from GitHub.
|
|
255
|
-
|
|
256
|
-
- **Purpose:** Identifies your application to GitHub's OAuth service
|
|
257
|
-
- **Format:** Alphanumeric string provided by GitHub
|
|
258
|
-
- **Setup:** Obtain from [GitHub Developer Settings](https://github.com/settings/developers)
|
|
259
|
-
- **Required:** Yes (when `GITHUB_LOGIN_ENABLED=true`)
|
|
260
|
-
|
|
261
|
-
**Example:** `GITHUB_CLIENT_ID=Iv1.a1b2c3d4e5f6g7h8`
|
|
262
|
-
|
|
263
|
-
##### GITHUB_CLIENT_SECRET
|
|
264
|
-
**Description:** OAuth application client secret from GitHub.
|
|
265
|
-
|
|
266
|
-
- **Purpose:** Authenticates your application with GitHub's OAuth service
|
|
267
|
-
- **Security:** Keep this secret secure and never commit to version control
|
|
268
|
-
- **Format:** Alphanumeric string provided by GitHub
|
|
269
|
-
- **Setup:** Generated when creating OAuth app in GitHub Developer Settings
|
|
270
|
-
- **Required:** Yes (when `GITHUB_LOGIN_ENABLED=true`)
|
|
271
|
-
|
|
272
|
-
**Example:** `GITHUB_CLIENT_SECRET=a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0`
|
|
273
|
-
|
|
274
|
-
#### Setting Up GitHub OAuth
|
|
275
|
-
|
|
276
|
-
1. **Create GitHub OAuth App:**
|
|
277
|
-
- Go to [GitHub Developer Settings](https://github.com/settings/developers)
|
|
278
|
-
- Click "New OAuth App"
|
|
279
|
-
- Fill in application details:
|
|
280
|
-
- **Application name:** Your app name
|
|
281
|
-
- **Homepage URL:** `https://yourdomain.com` (or `http://localhost:3000` for dev)
|
|
282
|
-
- **Authorization callback URL:** `https://yourdomain.com/mbkauthe/api/github/login/callback`
|
|
283
|
-
- Click "Register application"
|
|
284
|
-
|
|
285
|
-
2. **Copy Credentials:**
|
|
286
|
-
- Copy the **Client ID**
|
|
287
|
-
- Generate and copy the **Client Secret**
|
|
288
|
-
|
|
289
|
-
3. **Configure Environment:**
|
|
290
|
-
```env
|
|
291
|
-
GITHUB_LOGIN_ENABLED=true
|
|
292
|
-
GITHUB_CLIENT_ID=your-copied-client-id
|
|
293
|
-
GITHUB_CLIENT_SECRET=your-copied-client-secret
|
|
294
|
-
```
|
|
13
|
+
## Parameters (short descriptions)
|
|
14
|
+
|
|
15
|
+
- APP_NAME
|
|
16
|
+
- Description: Application identifier used for access control.
|
|
17
|
+
- Example: `"APP_NAME":"mbkauthe"`
|
|
18
|
+
- Required: Yes
|
|
19
|
+
|
|
20
|
+
- Main_SECRET_TOKEN
|
|
21
|
+
- Description: Primary token used for internal auth and validations.
|
|
22
|
+
- Example: `"Main_SECRET_TOKEN":"my-secret-token"`
|
|
23
|
+
- Required: Yes
|
|
24
|
+
|
|
25
|
+
- SESSION_SECRET_KEY
|
|
26
|
+
- Description: Cryptographic key for sessions/cookies. Use a long random string.
|
|
27
|
+
- Example: `"SESSION_SECRET_KEY":"<32+ random chars>"`
|
|
28
|
+
- Required: Yes
|
|
29
|
+
|
|
30
|
+
- IS_DEPLOYED
|
|
31
|
+
- Description: Deployment mode flag; affects cookie domain and localhost behavior.
|
|
32
|
+
- Values: `true` / `false` / `f` (normalized to strings)
|
|
33
|
+
- Example: `"IS_DEPLOYED":"false"`
|
|
34
|
+
- Required: Yes
|
|
35
|
+
|
|
36
|
+
- DOMAIN
|
|
37
|
+
- Description: App domain (e.g., `localhost` or `yourdomain.com`). Required when deployed.
|
|
38
|
+
- Example: `"DOMAIN":"localhost"`
|
|
39
|
+
- Required: Yes
|
|
40
|
+
|
|
41
|
+
- LOGIN_DB
|
|
42
|
+
- Description: PostgreSQL connection string for auth (must start with `postgresql://` or `postgres://`).
|
|
43
|
+
- Example: `"LOGIN_DB":"postgresql://user:pass@localhost:5432/mbkauth"`
|
|
44
|
+
- Required: Yes
|
|
45
|
+
|
|
46
|
+
- MBKAUTH_TWO_FA_ENABLE
|
|
47
|
+
- Description: Enable Two-Factor Authentication.
|
|
48
|
+
- Values: `true` / `false` / `f`
|
|
49
|
+
- Example: `"MBKAUTH_TWO_FA_ENABLE":"true"`
|
|
50
|
+
- Required: Yes
|
|
51
|
+
|
|
52
|
+
- EncPass
|
|
53
|
+
- Description: When `true`, use hashed password column (`PasswordEnc`) instead of plain `Password`.
|
|
54
|
+
- Default: `false` (recommended `true` in production)
|
|
55
|
+
- Example: `"EncPass":"true"`
|
|
56
|
+
- Required: No
|
|
57
|
+
|
|
58
|
+
- COOKIE_EXPIRE_TIME
|
|
59
|
+
- Description: Session cookie lifetime (days).
|
|
60
|
+
- Default: `2`
|
|
61
|
+
- Example: `"COOKIE_EXPIRE_TIME":7`
|
|
62
|
+
- Required: No
|
|
63
|
+
|
|
64
|
+
- DEVICE_TRUST_DURATION_DAYS
|
|
65
|
+
- Description: Days a device remains trusted (skips some auth steps).
|
|
66
|
+
- Default: `7`
|
|
67
|
+
- Example: `"DEVICE_TRUST_DURATION_DAYS":30`
|
|
68
|
+
- Required: No
|
|
69
|
+
|
|
70
|
+
- loginRedirectURL
|
|
71
|
+
- Description: Post-login redirect path.
|
|
72
|
+
- Default: `/dashboard`
|
|
73
|
+
- Example: `"loginRedirectURL":"/dashboard"`
|
|
74
|
+
- Required: No
|
|
75
|
+
|
|
76
|
+
- GITHUB_LOGIN_ENABLED / GOOGLE_LOGIN_ENABLED
|
|
77
|
+
- Description: Enable OAuth providers.
|
|
78
|
+
- Default: `false`
|
|
79
|
+
- If `true`, corresponding `*_CLIENT_ID` and `*_CLIENT_SECRET` are required.
|
|
80
|
+
|
|
81
|
+
- GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET / GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET
|
|
82
|
+
- Description: OAuth credentials (put in `mbkautheVar` preferred, or `mbkauthShared`).
|
|
83
|
+
- Required when provider enabled.
|
|
295
84
|
|
|
296
85
|
---
|
|
297
86
|
|
|
298
|
-
|
|
87
|
+
## Quick examples
|
|
88
|
+
Development (.env):
|
|
299
89
|
|
|
300
|
-
#### Google Login Configuration
|
|
301
90
|
```env
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
|
91
|
+
mbkautheVar={"APP_NAME":"mbkauthe","Main_SECRET_TOKEN":"dev-token","SESSION_SECRET_KEY":"dev-secret","IS_DEPLOYED":"false","DOMAIN":"localhost","EncPass":"false","LOGIN_DB":"postgresql://user:pass@localhost:5432/mbkauth_dev","MBKAUTH_TWO_FA_ENABLE":"false"}
|
|
92
|
+
mbkauthShared={"GITHUB_LOGIN_ENABLED":"false"}
|
|
305
93
|
```
|
|
306
94
|
|
|
307
|
-
|
|
308
|
-
**Description:** Enables or disables Google OAuth login functionality.
|
|
309
|
-
|
|
310
|
-
**Values:**
|
|
311
|
-
- `true` - Enable Google login (users can authenticate via Google)
|
|
312
|
-
- `false` - Disable Google login (default)
|
|
313
|
-
|
|
314
|
-
**Required:** Yes (if using Google authentication)
|
|
315
|
-
|
|
316
|
-
##### GOOGLE_CLIENT_ID
|
|
317
|
-
**Description:** OAuth 2.0 client ID from Google Cloud Console.
|
|
318
|
-
|
|
319
|
-
- **Purpose:** Identifies your application to Google's OAuth service
|
|
320
|
-
- **Format:** String ending in `.apps.googleusercontent.com`
|
|
321
|
-
- **Setup:** Obtain from [Google Cloud Console](https://console.cloud.google.com/)
|
|
322
|
-
- **Required:** Yes (when `GOOGLE_LOGIN_ENABLED=true`)
|
|
323
|
-
|
|
324
|
-
**Example:** `GOOGLE_CLIENT_ID=123456789-abc123def456.apps.googleusercontent.com`
|
|
325
|
-
|
|
326
|
-
##### GOOGLE_CLIENT_SECRET
|
|
327
|
-
**Description:** OAuth 2.0 client secret from Google Cloud Console.
|
|
328
|
-
|
|
329
|
-
- **Purpose:** Authenticates your application with Google's OAuth service
|
|
330
|
-
- **Security:** Keep this secret secure and never commit to version control
|
|
331
|
-
- **Format:** Alphanumeric string provided by Google
|
|
332
|
-
- **Setup:** Generated when creating OAuth credentials in Google Cloud Console
|
|
333
|
-
- **Required:** Yes (when `GOOGLE_LOGIN_ENABLED=true`)
|
|
334
|
-
|
|
335
|
-
**Example:** `GOOGLE_CLIENT_SECRET=GOCSPX-abc123def456ghi789jkl012mno`
|
|
336
|
-
|
|
337
|
-
#### Setting Up Google OAuth
|
|
338
|
-
|
|
339
|
-
1. **Create Google Cloud Project:**
|
|
340
|
-
- Go to [Google Cloud Console](https://console.cloud.google.com/)
|
|
341
|
-
- Create a new project or select an existing one
|
|
342
|
-
- Enable the Google+ API (or People API)
|
|
343
|
-
|
|
344
|
-
2. **Configure OAuth Consent Screen:**
|
|
345
|
-
- Navigate to "OAuth consent screen" in the sidebar
|
|
346
|
-
- Choose "External" user type (or "Internal" for Google Workspace)
|
|
347
|
-
- Fill in required app information
|
|
348
|
-
- Add your domain to authorized domains
|
|
349
|
-
- Save and continue
|
|
350
|
-
|
|
351
|
-
3. **Create OAuth Credentials:**
|
|
352
|
-
- Navigate to "Credentials" in the sidebar
|
|
353
|
-
- Click "Create Credentials" > "OAuth 2.0 Client ID"
|
|
354
|
-
- Choose "Web application" as application type
|
|
355
|
-
- Add authorized JavaScript origins:
|
|
356
|
-
- `https://yourdomain.com`
|
|
357
|
-
- `http://localhost:3000` (for development)
|
|
358
|
-
- Add authorized redirect URIs:
|
|
359
|
-
- `https://yourdomain.com/mbkauthe/api/google/login/callback`
|
|
360
|
-
- `http://localhost:3000/mbkauthe/api/google/login/callback` (for development)
|
|
361
|
-
- Click "Create"
|
|
362
|
-
|
|
363
|
-
4. **Copy Credentials:**
|
|
364
|
-
- Copy the **Client ID**
|
|
365
|
-
- Copy the **Client Secret**
|
|
95
|
+
Production (short):
|
|
366
96
|
|
|
367
|
-
5. **Configure Environment:**
|
|
368
|
-
```env
|
|
369
|
-
GOOGLE_LOGIN_ENABLED=true
|
|
370
|
-
GOOGLE_CLIENT_ID=your-copied-client-id
|
|
371
|
-
GOOGLE_CLIENT_SECRET=your-copied-client-secret
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
### OAuth Security Notes
|
|
375
|
-
|
|
376
|
-
- Use separate OAuth apps for development and production environments
|
|
377
|
-
- Rotate client secrets periodically
|
|
378
|
-
- Never expose client secrets in client-side code
|
|
379
|
-
- Ensure callback URLs match exactly with OAuth provider configuration
|
|
380
|
-
- Users must link their OAuth accounts before they can use OAuth login
|
|
381
|
-
|
|
382
|
-
---
|
|
383
|
-
|
|
384
|
-
## 🚀 Quick Setup Examples
|
|
385
|
-
|
|
386
|
-
### Development Environment
|
|
387
|
-
```env
|
|
388
|
-
# .env file for local development
|
|
389
|
-
APP_NAME=mbkauthe
|
|
390
|
-
Main_SECRET_TOKEN=dev-token-123
|
|
391
|
-
SESSION_SECRET_KEY=dev-secret-key-change-in-production
|
|
392
|
-
IS_DEPLOYED=false
|
|
393
|
-
DOMAIN=localhost
|
|
394
|
-
EncPass=false
|
|
395
|
-
LOGIN_DB=postgresql://admin:password@localhost:5432/mbkauth_dev
|
|
396
|
-
MBKAUTH_TWO_FA_ENABLE=false
|
|
397
|
-
COOKIE_EXPIRE_TIME=7
|
|
398
|
-
DEVICE_TRUST_DURATION_DAYS=7
|
|
399
|
-
loginRedirectURL=/dashboard
|
|
400
|
-
GITHUB_LOGIN_ENABLED=false
|
|
401
|
-
GITHUB_CLIENT_ID=
|
|
402
|
-
GITHUB_CLIENT_SECRET=
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
### Production Environment
|
|
406
97
|
```env
|
|
407
|
-
|
|
408
|
-
APP_NAME=mbkauthe
|
|
409
|
-
Main_SECRET_TOKEN=your-secure-production-token
|
|
410
|
-
SESSION_SECRET_KEY=your-super-secure-production-key-here
|
|
411
|
-
IS_DEPLOYED=true
|
|
412
|
-
DOMAIN=yourdomain.com
|
|
413
|
-
EncPass=true
|
|
414
|
-
LOGIN_DB=postgresql://dbuser:securepass@prod-db.example.com:5432/mbkauth_prod
|
|
415
|
-
MBKAUTH_TWO_FA_ENABLE=true
|
|
416
|
-
COOKIE_EXPIRE_TIME=2
|
|
417
|
-
DEVICE_TRUST_DURATION_DAYS=7
|
|
418
|
-
loginRedirectURL=/dashboard
|
|
419
|
-
GITHUB_LOGIN_ENABLED=false
|
|
420
|
-
GITHUB_CLIENT_ID=
|
|
421
|
-
GITHUB_CLIENT_SECRET=
|
|
98
|
+
mbkautheVar={"APP_NAME":"mbkauthe","Main_SECRET_TOKEN":"prod-token","SESSION_SECRET_KEY":"prod-secret","IS_DEPLOYED":"true","DOMAIN":"yourdomain.com","EncPass":"true","LOGIN_DB":"postgresql://dbuser:secure@db:5432/mbkauth_prod","MBKAUTH_TWO_FA_ENABLE":"true"}
|
|
422
99
|
```
|
|
423
100
|
|
|
424
101
|
---
|
|
425
102
|
|
|
426
|
-
##
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
5. **Use environment-specific databases** (separate dev/prod databases)
|
|
433
|
-
6. **Enable 2FA** for production environments
|
|
434
|
-
|
|
435
|
-
---
|
|
436
|
-
|
|
437
|
-
## 🔧 Troubleshooting
|
|
438
|
-
|
|
439
|
-
### Common Issues
|
|
440
|
-
|
|
441
|
-
**Login not working on localhost:**
|
|
442
|
-
- Ensure `IS_DEPLOYED=false` for local development
|
|
443
|
-
- Check that `DOMAIN=localhost`
|
|
444
|
-
|
|
445
|
-
**Session not persisting:**
|
|
446
|
-
- Verify `SESSION_SECRET_KEY` is set and consistent
|
|
447
|
-
- Check cookie settings in your browser
|
|
448
|
-
|
|
449
|
-
**Database connection errors:**
|
|
450
|
-
- Verify database credentials and connection string format
|
|
451
|
-
- Ensure database server is running and accessible
|
|
103
|
+
## Rules & best practices
|
|
104
|
+
- Boolean-like fields: use `"true"`, `"false"`, or `"f"` (the parser accepts booleans too and normalizes to strings).
|
|
105
|
+
- Numeric fields: must be positive numbers (e.g., `COOKIE_EXPIRE_TIME`, `DEVICE_TRUST_DURATION_DAYS`).
|
|
106
|
+
- `LOGIN_DB` must start with `postgresql://` or `postgres://`.
|
|
107
|
+
- Never commit `.env` to source control and use HTTPS in production (when `IS_DEPLOYED=true`).
|
|
108
|
+
- Use a >=32-char `SESSION_SECRET_KEY` and rotate secrets regularly.
|
|
452
109
|
|
|
453
|
-
|
|
454
|
-
- Confirm authenticator app time is synchronized
|
|
455
|
-
- Verify `MBKAUTH_TWO_FA_ENABLE` setting matches your setup
|
|
110
|
+
For the exact validation messages and default application, consult `lib/config/index.js` (it will throw a comprehensive error if validation fails at startup).
|
package/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ declare global {
|
|
|
12
12
|
user?: {
|
|
13
13
|
username: string;
|
|
14
14
|
role: 'SuperAdmin' | 'NormalUser' | 'Guest';
|
|
15
|
+
fullname?: string;
|
|
15
16
|
};
|
|
16
17
|
userRole?: 'SuperAdmin' | 'NormalUser' | 'Guest';
|
|
17
18
|
}
|
|
@@ -20,6 +21,7 @@ declare global {
|
|
|
20
21
|
user?: {
|
|
21
22
|
id: number;
|
|
22
23
|
username: string;
|
|
24
|
+
fullname?: string;
|
|
23
25
|
role: 'SuperAdmin' | 'NormalUser' | 'Guest';
|
|
24
26
|
sessionId: string;
|
|
25
27
|
allowedApps?: string[];
|
|
@@ -75,6 +77,7 @@ declare module 'mbkauthe' {
|
|
|
75
77
|
export interface SessionUser {
|
|
76
78
|
id: number;
|
|
77
79
|
username: string;
|
|
80
|
+
fullname?: string;
|
|
78
81
|
role: UserRole;
|
|
79
82
|
sessionId: string;
|
|
80
83
|
allowedApps?: string[];
|
|
@@ -209,6 +212,10 @@ declare module 'mbkauthe' {
|
|
|
209
212
|
|
|
210
213
|
export function authenticate(token: string): AuthMiddleware;
|
|
211
214
|
|
|
215
|
+
// Reload session user values from DB and refresh cookies.
|
|
216
|
+
// Returns true when session is refreshed and valid, false if session invalidated.
|
|
217
|
+
export function reloadSessionUser(req: Request, res: Response): Promise<boolean>;
|
|
218
|
+
|
|
212
219
|
// Utility Functions
|
|
213
220
|
export function renderError(
|
|
214
221
|
res: Response,
|
package/index.js
CHANGED
|
@@ -89,7 +89,10 @@ if (process.env.test !== "dev") {
|
|
|
89
89
|
await checkVersion();
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
export {
|
|
92
|
+
export {
|
|
93
|
+
validateSession, validateApiSession, checkRolePermission,
|
|
94
|
+
validateSessionAndRole, authenticate, reloadSessionUser
|
|
95
|
+
} from "./lib/middleware/auth.js";
|
|
93
96
|
export { renderError } from "./lib/utils/response.js";
|
|
94
97
|
export { dblogin } from "./lib/database/pool.js";
|
|
95
98
|
export { ErrorCodes, ErrorMessages, getErrorByCode, createErrorResponse, logError } from "./lib/utils/errors.js";
|
package/lib/config/cookies.js
CHANGED
|
@@ -46,6 +46,7 @@ export const clearSessionCookies = (res) => {
|
|
|
46
46
|
res.clearCookie("mbkauthe.sid", cachedClearCookieOptions);
|
|
47
47
|
res.clearCookie("sessionId", cachedClearCookieOptions);
|
|
48
48
|
res.clearCookie("username", cachedClearCookieOptions);
|
|
49
|
+
res.clearCookie("fullName", cachedClearCookieOptions);
|
|
49
50
|
res.clearCookie("device_token", cachedClearCookieOptions);
|
|
50
51
|
};
|
|
51
52
|
|
package/lib/config/index.js
CHANGED
|
@@ -28,37 +28,81 @@ function validateConfiguration() {
|
|
|
28
28
|
throw new Error(`[mbkauthe] Configuration Error:\n - ${errors.join('\n - ')}`);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
// Parse and validate
|
|
32
|
-
let
|
|
31
|
+
// Parse and validate mbkauthShared (optional fallback for shared settings)
|
|
32
|
+
let mbkauthShared = null;
|
|
33
33
|
try {
|
|
34
|
-
if (process.env.
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
console.warn('[mbkauthe]
|
|
38
|
-
|
|
34
|
+
if (process.env.mbkauthShared) {
|
|
35
|
+
mbkauthShared = JSON.parse(process.env.mbkauthShared);
|
|
36
|
+
if (mbkauthShared && typeof mbkauthShared !== 'object') {
|
|
37
|
+
console.warn('[mbkauthe] mbkauthShared is not a valid object, ignoring it');
|
|
38
|
+
mbkauthShared = null;
|
|
39
39
|
} else {
|
|
40
|
-
console.log('[mbkauthe]
|
|
40
|
+
console.log('[mbkauthe] mbkauthShared detected and parsed successfully');
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
} catch (error) {
|
|
44
|
-
console.warn('[mbkauthe] Invalid JSON in process.env.
|
|
45
|
-
|
|
46
|
-
}
|
|
44
|
+
console.warn('[mbkauthe] Invalid JSON in process.env.mbkauthShared, ignoring it');
|
|
45
|
+
mbkauthShared = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Merge fallback settings: for any key missing or empty in mbkautheVar, check mbkauthShared
|
|
49
|
+
const applyFallback = (source, sourceName) => {
|
|
50
|
+
if (!source) return;
|
|
51
|
+
Object.keys(source).forEach(key => {
|
|
52
|
+
const val = source[key];
|
|
53
|
+
if ((mbkautheVar[key] === undefined || (typeof mbkautheVar[key] === 'string' && mbkautheVar[key].trim() === '')) &&
|
|
54
|
+
val !== undefined && !(typeof val === 'string' && val.trim() === '')) {
|
|
55
|
+
mbkautheVar[key] = val;
|
|
56
|
+
console.log(`[mbkauthe] Using ${key} from ${sourceName}`);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
applyFallback(mbkauthShared, 'mbkauthShared');
|
|
47
62
|
|
|
48
|
-
//
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
63
|
+
// Ensure specific keys are checked in mbkautheVar first, then mbkauthShared, then apply config defaults
|
|
64
|
+
const keysToCheck = [
|
|
65
|
+
"APP_NAME","DEVICE_TRUST_DURATION_DAYS","EncPass","Main_SECRET_TOKEN","SESSION_SECRET_KEY","IS_DEPLOYED",
|
|
66
|
+
"LOGIN_DB","MBKAUTH_TWO_FA_ENABLE","COOKIE_EXPIRE_TIME","DOMAIN","loginRedirectURL",
|
|
67
|
+
"GITHUB_LOGIN_ENABLED","GITHUB_CLIENT_ID","GITHUB_CLIENT_SECRET","GOOGLE_LOGIN_ENABLED","GOOGLE_CLIENT_ID","GOOGLE_CLIENT_SECRET"
|
|
52
68
|
];
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
|
|
70
|
+
const defaults = {
|
|
71
|
+
DEVICE_TRUST_DURATION_DAYS: 7,
|
|
72
|
+
EncPass: 'false',
|
|
73
|
+
IS_DEPLOYED: 'false',
|
|
74
|
+
MBKAUTH_TWO_FA_ENABLE: 'false',
|
|
75
|
+
COOKIE_EXPIRE_TIME: 2,
|
|
76
|
+
loginRedirectURL: '/dashboard',
|
|
77
|
+
GITHUB_LOGIN_ENABLED: 'false',
|
|
78
|
+
GOOGLE_LOGIN_ENABLED: 'false'
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
keysToCheck.forEach(key => {
|
|
82
|
+
const current = mbkautheVar[key];
|
|
83
|
+
const isEmpty = current === undefined || (typeof current === 'string' && current.trim() === '');
|
|
84
|
+
if (isEmpty) {
|
|
85
|
+
if (mbkauthShared && mbkauthShared[key] !== undefined && !(typeof mbkauthShared[key] === 'string' && mbkauthShared[key].trim() === '')) {
|
|
86
|
+
mbkautheVar[key] = mbkauthShared[key];
|
|
87
|
+
console.log(`[mbkauthe] Using ${key} from mbkauthShared`);
|
|
88
|
+
} else if (defaults[key] !== undefined) {
|
|
89
|
+
mbkautheVar[key] = defaults[key];
|
|
90
|
+
console.log(`[mbkauthe] Using default value for ${key}`);
|
|
59
91
|
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Normalize boolean-like values to consistent lowercase 'true'/'false' strings
|
|
96
|
+
['GITHUB_LOGIN_ENABLED','GOOGLE_LOGIN_ENABLED','MBKAUTH_TWO_FA_ENABLE','IS_DEPLOYED','EncPass'].forEach(k => {
|
|
97
|
+
const val = mbkautheVar[k];
|
|
98
|
+
if (typeof val === 'boolean') {
|
|
99
|
+
mbkautheVar[k] = val ? 'true' : 'false';
|
|
100
|
+
} else if (typeof val === 'string') {
|
|
101
|
+
const norm = val.trim().toLowerCase();
|
|
102
|
+
// Accept 'f' as shorthand for false but normalize it to 'false'
|
|
103
|
+
mbkautheVar[k] = (norm === 'f') ? 'false' : norm;
|
|
104
|
+
}
|
|
105
|
+
});
|
|
62
106
|
|
|
63
107
|
// Validate required keys
|
|
64
108
|
// COOKIE_EXPIRE_TIME is not required but if provided must be valid, COOKIE_EXPIRE_TIME by default is 2 days
|
|
@@ -73,7 +117,7 @@ function validateConfiguration() {
|
|
|
73
117
|
});
|
|
74
118
|
|
|
75
119
|
// Validate IS_DEPLOYED value
|
|
76
|
-
if (mbkautheVar.IS_DEPLOYED && !['true', 'false', 'f'].includes(mbkautheVar.IS_DEPLOYED)) {
|
|
120
|
+
if (mbkautheVar.IS_DEPLOYED && !['true', 'false', 'f'].includes((mbkautheVar.IS_DEPLOYED + '').toLowerCase())) {
|
|
77
121
|
errors.push("mbkautheVar.IS_DEPLOYED must be either 'true' or 'false' or 'f'");
|
|
78
122
|
}
|
|
79
123
|
|
package/lib/middleware/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dblogin } from "../database/pool.js";
|
|
2
2
|
import { mbkautheVar } from "../config/index.js";
|
|
3
3
|
import { renderError } from "../utils/response.js";
|
|
4
|
-
import { clearSessionCookies } from "../config/cookies.js";
|
|
4
|
+
import { clearSessionCookies, cachedCookieOptions } from "../config/cookies.js";
|
|
5
5
|
|
|
6
6
|
async function validateSession(req, res, next) {
|
|
7
7
|
if (!req.session.user) {
|
|
@@ -97,6 +97,160 @@ async function validateSession(req, res, next) {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* API-friendly session validation middleware
|
|
102
|
+
* Returns JSON error responses instead of rendering pages
|
|
103
|
+
*/
|
|
104
|
+
async function validateApiSession(req, res, next) {
|
|
105
|
+
if (!req.session.user) {
|
|
106
|
+
return res.status(401).json(createErrorResponse(401, ErrorCodes.SESSION_NOT_FOUND));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const { id, sessionId, role, allowedApps } = req.session.user;
|
|
111
|
+
|
|
112
|
+
// Defensive checks for sessionId and allowedApps
|
|
113
|
+
if (!sessionId) {
|
|
114
|
+
console.warn(`[mbkauthe] Missing sessionId for user "${req.session.user.username}"`);
|
|
115
|
+
req.session.destroy();
|
|
116
|
+
clearSessionCookies(res);
|
|
117
|
+
return res.status(401).json(createErrorResponse(401, ErrorCodes.SESSION_EXPIRED));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Normalize sessionId to lowercase for consistent comparison
|
|
121
|
+
const normalizedSessionId = sessionId.toLowerCase();
|
|
122
|
+
|
|
123
|
+
// Single optimized query to validate session and get role
|
|
124
|
+
const query = `SELECT "SessionId", "Active", "Role" FROM "Users" WHERE "id" = $1`;
|
|
125
|
+
const result = await dblogin.query({ name: 'validate-user-session-for-api', text: query, values: [id] });
|
|
126
|
+
|
|
127
|
+
const dbSessionId = result.rows.length > 0 && result.rows[0].SessionId ? String(result.rows[0].SessionId).toLowerCase() : null;
|
|
128
|
+
if (!dbSessionId || dbSessionId !== normalizedSessionId) {
|
|
129
|
+
if (result.rows.length > 0 && !result.rows[0].SessionId) {
|
|
130
|
+
console.warn(`[mbkauthe] DB sessionId is null for user "${req.session.user.username}"`);
|
|
131
|
+
}
|
|
132
|
+
console.log(`[mbkauthe] Session invalidated for user "${req.session.user.username}"`);
|
|
133
|
+
req.session.destroy();
|
|
134
|
+
clearSessionCookies(res);
|
|
135
|
+
return res.status(401).json(createErrorResponse(401, ErrorCodes.SESSION_INVALID));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!result.rows[0].Active) {
|
|
139
|
+
console.log(`[mbkauthe] Account is inactive for user "${req.session.user.username}"`);
|
|
140
|
+
req.session.destroy();
|
|
141
|
+
clearSessionCookies(res);
|
|
142
|
+
return res.status(401).json(createErrorResponse(401, ErrorCodes.ACCOUNT_INACTIVE));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (role !== "SuperAdmin") {
|
|
146
|
+
// If allowedApps is not provided or not an array, treat as no access
|
|
147
|
+
const hasAllowedApps = Array.isArray(allowedApps) && allowedApps.length > 0;
|
|
148
|
+
if (!hasAllowedApps || !allowedApps.some(app => app && app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
|
|
149
|
+
console.warn(`[mbkauthe] User \"${req.session.user.username}\" is not authorized to use the application \"${mbkautheVar.APP_NAME}\"`);
|
|
150
|
+
req.session.destroy();
|
|
151
|
+
clearSessionCookies(res);
|
|
152
|
+
return res.status(401).json(createErrorResponse(401, ErrorCodes.APP_NOT_AUTHORIZED));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Store user role in request for checkRolePermission to use
|
|
157
|
+
req.userRole = result.rows[0].Role;
|
|
158
|
+
|
|
159
|
+
next();
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.error("[mbkauthe] API session validation error:", err);
|
|
162
|
+
return res.status(500).json(createErrorResponse(500, ErrorCodes.INTERNAL_SERVER_ERROR));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Reload session user values from the database and refresh cookies.
|
|
168
|
+
* - Validates sessionId and active status
|
|
169
|
+
* - Updates `req.session.user` fields (username, role, allowedApps, fullname)
|
|
170
|
+
* - Uses cached `fullName` cookie when available, otherwise queries `profiledata`
|
|
171
|
+
* - Syncs `username`, `fullName` and `sessionId` cookies
|
|
172
|
+
* Returns: true if session refreshed and valid, false if session invalidated
|
|
173
|
+
*/
|
|
174
|
+
export async function reloadSessionUser(req, res) {
|
|
175
|
+
if (!req.session || !req.session.user || !req.session.user.id) return false;
|
|
176
|
+
try {
|
|
177
|
+
const { id, sessionId: currentSessionId } = req.session.user;
|
|
178
|
+
|
|
179
|
+
// Fetch fresh user record
|
|
180
|
+
const query = `SELECT id, "UserName", "Active", "Role", "AllowedApps", "SessionId" FROM "Users" WHERE id = $1`;
|
|
181
|
+
const result = await dblogin.query({ name: 'reload-session-user', text: query, values: [id] });
|
|
182
|
+
|
|
183
|
+
if (result.rows.length === 0) {
|
|
184
|
+
// User not found — invalidate session
|
|
185
|
+
req.session.destroy(() => {});
|
|
186
|
+
clearSessionCookies(res);
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const row = result.rows[0];
|
|
191
|
+
const dbSessionId = row.SessionId ? String(row.SessionId).toLowerCase() : null;
|
|
192
|
+
if (!dbSessionId || dbSessionId !== String(currentSessionId).toLowerCase()) {
|
|
193
|
+
// Session invalidated in DB
|
|
194
|
+
req.session.destroy(() => {});
|
|
195
|
+
clearSessionCookies(res);
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!row.Active) {
|
|
200
|
+
// Account is inactive
|
|
201
|
+
req.session.destroy(() => {});
|
|
202
|
+
clearSessionCookies(res);
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Authorization: ensure allowed for current app unless SuperAdmin
|
|
207
|
+
if (row.Role !== 'SuperAdmin') {
|
|
208
|
+
const allowedApps = row.AllowedApps;
|
|
209
|
+
const hasAllowedApps = Array.isArray(allowedApps) && allowedApps.length > 0;
|
|
210
|
+
if (!hasAllowedApps || !allowedApps.some(app => app && app.toLowerCase() === mbkautheVar.APP_NAME.toLowerCase())) {
|
|
211
|
+
req.session.destroy(() => {});
|
|
212
|
+
clearSessionCookies(res);
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Update session fields
|
|
218
|
+
req.session.user.username = row.UserName;
|
|
219
|
+
req.session.user.role = row.Role;
|
|
220
|
+
req.session.user.allowedApps = row.AllowedApps;
|
|
221
|
+
|
|
222
|
+
// Obtain fullname from client cookie cache when present else DB
|
|
223
|
+
if (req.cookies && req.cookies.fullName && typeof req.cookies.fullName === 'string') {
|
|
224
|
+
req.session.user.fullname = req.cookies.fullName;
|
|
225
|
+
} else {
|
|
226
|
+
try {
|
|
227
|
+
const prof = await dblogin.query({ name: 'reload-get-fullname', text: 'SELECT "FullName" FROM "profiledata" WHERE "UserName" = $1 LIMIT 1', values: [row.UserName] });
|
|
228
|
+
if (prof.rows.length > 0 && prof.rows[0].FullName) req.session.user.fullname = prof.rows[0].FullName;
|
|
229
|
+
} catch (profileErr) {
|
|
230
|
+
console.error('[mbkauthe] Error fetching fullname during reload:', profileErr);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Persist session changes
|
|
235
|
+
await new Promise((resolve, reject) => req.session.save(err => err ? reject(err) : resolve()));
|
|
236
|
+
|
|
237
|
+
// Sync cookies for client UI (non-httpOnly)
|
|
238
|
+
try {
|
|
239
|
+
res.cookie('username', req.session.user.username, { ...cachedCookieOptions, httpOnly: false });
|
|
240
|
+
res.cookie('fullName', req.session.user.fullname || req.session.user.username, { ...cachedCookieOptions, httpOnly: false });
|
|
241
|
+
res.cookie('sessionId', req.session.user.sessionId, cachedCookieOptions);
|
|
242
|
+
} catch (cookieErr) {
|
|
243
|
+
// Ignore cookie setting errors, session is still refreshed
|
|
244
|
+
console.error('[mbkauthe] Error syncing cookies during reload:', cookieErr);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return true;
|
|
248
|
+
} catch (err) {
|
|
249
|
+
console.error('[mbkauthe] reloadSessionUser error:', err);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
100
254
|
const checkRolePermission = (requiredRoles, notAllowed) => {
|
|
101
255
|
return async (req, res, next) => {
|
|
102
256
|
try {
|
|
@@ -173,5 +327,4 @@ const authenticate = (authentication) => {
|
|
|
173
327
|
};
|
|
174
328
|
};
|
|
175
329
|
|
|
176
|
-
|
|
177
|
-
export { validateSession, checkRolePermission, validateSessionAndRole, authenticate };
|
|
330
|
+
export { validateSession, validateApiSession, checkRolePermission, validateSessionAndRole, authenticate };
|
package/lib/middleware/index.js
CHANGED
|
@@ -85,6 +85,25 @@ export async function sessionRestorationMiddleware(req, res, next) {
|
|
|
85
85
|
sessionId: normalizedSessionId,
|
|
86
86
|
allowedApps: user.AllowedApps,
|
|
87
87
|
};
|
|
88
|
+
|
|
89
|
+
// Use cached FullName from client cookie when available to avoid extra DB queries
|
|
90
|
+
if (req.cookies.fullName && typeof req.cookies.fullName === 'string') {
|
|
91
|
+
req.session.user.fullname = req.cookies.fullName;
|
|
92
|
+
} else {
|
|
93
|
+
// Fallback: attempt to fetch FullName from profiledata to populate session
|
|
94
|
+
try {
|
|
95
|
+
const profileRes = await dblogin.query({
|
|
96
|
+
name: 'restore-get-fullname',
|
|
97
|
+
text: 'SELECT "FullName" FROM "profiledata" WHERE "UserName" = $1 LIMIT 1',
|
|
98
|
+
values: [user.UserName]
|
|
99
|
+
});
|
|
100
|
+
if (profileRes.rows.length > 0 && profileRes.rows[0].FullName) {
|
|
101
|
+
req.session.user.fullname = profileRes.rows[0].FullName;
|
|
102
|
+
}
|
|
103
|
+
} catch (profileErr) {
|
|
104
|
+
console.error("[mbkauthe] Error fetching FullName during session restore:", profileErr);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
88
107
|
}
|
|
89
108
|
} catch (err) {
|
|
90
109
|
console.error("[mbkauthe] Session restoration error:", err);
|
|
@@ -99,8 +118,10 @@ export function sessionCookieSyncMiddleware(req, res, next) {
|
|
|
99
118
|
// Only set cookies if they're missing or different
|
|
100
119
|
if (req.cookies.sessionId !== req.session.user.sessionId) {
|
|
101
120
|
res.cookie("username", req.session.user.username, { ...cachedCookieOptions, httpOnly: false });
|
|
121
|
+
// Also expose FullName (fallback to username) for display in client-side UI
|
|
122
|
+
res.cookie("fullName", req.session.user.fullname || req.session.user.username, { ...cachedCookieOptions, httpOnly: false });
|
|
102
123
|
res.cookie("sessionId", req.session.user.sessionId, cachedCookieOptions);
|
|
103
124
|
}
|
|
104
125
|
}
|
|
105
126
|
next();
|
|
106
|
-
}
|
|
127
|
+
}
|
package/lib/routes/auth.js
CHANGED
|
@@ -170,6 +170,20 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
|
|
|
170
170
|
allowedApps: user.allowedApps || user.AllowedApps,
|
|
171
171
|
};
|
|
172
172
|
|
|
173
|
+
// Attempt to fetch FullName from profiledata and store it in session for display purposes
|
|
174
|
+
try {
|
|
175
|
+
const profileResult = await dblogin.query({
|
|
176
|
+
name: 'login-get-fullname',
|
|
177
|
+
text: 'SELECT "FullName" FROM "profiledata" WHERE "UserName" = $1 LIMIT 1',
|
|
178
|
+
values: [username]
|
|
179
|
+
});
|
|
180
|
+
if (profileResult.rows.length > 0 && profileResult.rows[0].FullName) {
|
|
181
|
+
req.session.user.fullname = profileResult.rows[0].FullName;
|
|
182
|
+
}
|
|
183
|
+
} catch (profileErr) {
|
|
184
|
+
console.error("[mbkauthe] Error fetching FullName for user:", profileErr);
|
|
185
|
+
}
|
|
186
|
+
|
|
173
187
|
if (req.session.preAuthUser) {
|
|
174
188
|
delete req.session.preAuthUser;
|
|
175
189
|
}
|
|
@@ -180,7 +194,9 @@ export async function completeLoginProcess(req, res, user, redirectUrl = null, t
|
|
|
180
194
|
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
181
195
|
}
|
|
182
196
|
|
|
197
|
+
// Expose sessionId and display name to client for UI (fullName falls back to username)
|
|
183
198
|
res.cookie("sessionId", sessionId, cachedCookieOptions);
|
|
199
|
+
res.cookie("fullName", req.session.user.fullname || username, { ...cachedCookieOptions, httpOnly: false });
|
|
184
200
|
|
|
185
201
|
// Handle trusted device if requested
|
|
186
202
|
if (trustDevice) {
|
package/lib/routes/misc.js
CHANGED
|
@@ -3,7 +3,7 @@ import fetch from 'node-fetch';
|
|
|
3
3
|
import rateLimit from 'express-rate-limit';
|
|
4
4
|
import { mbkautheVar, packageJson, appVersion } from "../config/index.js";
|
|
5
5
|
import { renderError } from "../utils/response.js";
|
|
6
|
-
import { authenticate, validateSession } from "../middleware/auth.js";
|
|
6
|
+
import { authenticate, validateSession, validateApiSession } from "../middleware/auth.js";
|
|
7
7
|
import { ErrorCodes, ErrorMessages } from "../utils/errors.js";
|
|
8
8
|
import { dblogin } from "../database/pool.js";
|
|
9
9
|
import { clearSessionCookies } from "../config/cookies.js";
|
|
@@ -90,6 +90,41 @@ router.get('/test', validateSession, LoginLimit, async (req, res) => {
|
|
|
90
90
|
}
|
|
91
91
|
});
|
|
92
92
|
|
|
93
|
+
// API: check current session validity (JSON) — minimal response
|
|
94
|
+
router.get('/api/checkSession', LoginLimit, async (req, res) => {
|
|
95
|
+
try {
|
|
96
|
+
if (!req.session?.user) {
|
|
97
|
+
return res.status(200).json({ sessionValid: false, expiry: null });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const { id, sessionId } = req.session.user;
|
|
101
|
+
if (!sessionId) {
|
|
102
|
+
req.session.destroy(() => { });
|
|
103
|
+
clearSessionCookies(res);
|
|
104
|
+
return res.status(200).json({ sessionValid: false, expiry: null });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const normalizedSessionId = String(sessionId).toLowerCase();
|
|
108
|
+
const result = await dblogin.query({ name: 'check-session-validity', text: 'SELECT "SessionId", "Active" FROM "Users" WHERE id = $1', values: [id] });
|
|
109
|
+
|
|
110
|
+
const dbSessionId = result.rows.length > 0 && result.rows[0].SessionId ? String(result.rows[0].SessionId).toLowerCase() : null;
|
|
111
|
+
if (!dbSessionId || dbSessionId !== normalizedSessionId || !result.rows[0].Active) {
|
|
112
|
+
req.session.destroy(() => { });
|
|
113
|
+
clearSessionCookies(res);
|
|
114
|
+
return res.status(200).json({ sessionValid: false, expiry: null });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Fetch expiry timestamp from session table (connect-pg-simple)
|
|
118
|
+
const sessResult = await dblogin.query({ name: 'get-session-expiry', text: 'SELECT expire FROM "session" WHERE sid = $1', values: [req.sessionID] });
|
|
119
|
+
const expiry = sessResult.rows.length > 0 && sessResult.rows[0].expire ? new Date(sessResult.rows[0].expire).toISOString() : null;
|
|
120
|
+
|
|
121
|
+
return res.status(200).json({ sessionValid: true, expiry });
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.error('[mbkauthe] checkSession error:', err);
|
|
124
|
+
return res.status(200).json({ sessionValid: false, expiry: null });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
93
128
|
// Error codes page
|
|
94
129
|
router.get("/ErrorCode", (req, res) => {
|
|
95
130
|
try {
|
|
@@ -251,12 +286,12 @@ router.post("/api/terminateAllSessions", AdminOperationLimit, authenticate(mbkau
|
|
|
251
286
|
try {
|
|
252
287
|
// Run both operations in parallel for better performance
|
|
253
288
|
await Promise.all([
|
|
254
|
-
dblogin.query({
|
|
255
|
-
name: 'terminate-all-user-sessions',
|
|
289
|
+
dblogin.query({
|
|
290
|
+
name: 'terminate-all-user-sessions',
|
|
256
291
|
text: 'UPDATE "Users" SET "SessionId" = NULL WHERE "SessionId" IS NOT NULL'
|
|
257
292
|
}),
|
|
258
|
-
dblogin.query({
|
|
259
|
-
name: 'terminate-all-db-sessions',
|
|
293
|
+
dblogin.query({
|
|
294
|
+
name: 'terminate-all-db-sessions',
|
|
260
295
|
text: 'DELETE FROM "session" WHERE expire > NOW()'
|
|
261
296
|
})
|
|
262
297
|
]);
|
package/lib/routes/oauth.js
CHANGED
|
@@ -108,25 +108,43 @@ const createOAuthStrategy = async (provider, profile, done) => {
|
|
|
108
108
|
}
|
|
109
109
|
};
|
|
110
110
|
|
|
111
|
-
// Configure GitHub Strategy for login
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
111
|
+
// Configure GitHub Strategy for login (only if enabled and configured)
|
|
112
|
+
if ((mbkautheVar.GITHUB_LOGIN_ENABLED || "").toLowerCase() === "true") {
|
|
113
|
+
if (mbkautheVar.GITHUB_CLIENT_ID && mbkautheVar.GITHUB_CLIENT_SECRET) {
|
|
114
|
+
passport.use('github-login', new GitHubStrategy({
|
|
115
|
+
clientID: mbkautheVar.GITHUB_CLIENT_ID,
|
|
116
|
+
clientSecret: mbkautheVar.GITHUB_CLIENT_SECRET,
|
|
117
|
+
callbackURL: '/mbkauthe/api/github/login/callback',
|
|
118
|
+
scope: ['user:email']
|
|
119
|
+
}, (accessToken, refreshToken, profile, done) =>
|
|
120
|
+
createOAuthStrategy('GitHub', profile, done)
|
|
121
|
+
));
|
|
122
|
+
console.log('[mbkauthe] GitHub OAuth strategy registered');
|
|
123
|
+
} else {
|
|
124
|
+
console.warn('[mbkauthe] GITHUB_LOGIN_ENABLED is true but GITHUB_CLIENT_ID/SECRET missing; skipping GitHub strategy registration');
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
console.log('[mbkauthe] GitHub OAuth not enabled; skipping GitHub strategy registration');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Configure Google Strategy for login (only if enabled and configured)
|
|
131
|
+
if ((mbkautheVar.GOOGLE_LOGIN_ENABLED || "").toLowerCase() === "true") {
|
|
132
|
+
if (mbkautheVar.GOOGLE_CLIENT_ID && mbkautheVar.GOOGLE_CLIENT_SECRET) {
|
|
133
|
+
passport.use('google-login', new GoogleStrategy({
|
|
134
|
+
clientID: mbkautheVar.GOOGLE_CLIENT_ID,
|
|
135
|
+
clientSecret: mbkautheVar.GOOGLE_CLIENT_SECRET,
|
|
136
|
+
callbackURL: '/mbkauthe/api/google/login/callback',
|
|
137
|
+
scope: ['profile', 'email']
|
|
138
|
+
}, (accessToken, refreshToken, profile, done) =>
|
|
139
|
+
createOAuthStrategy('Google', profile, done)
|
|
140
|
+
));
|
|
141
|
+
console.log('[mbkauthe] Google OAuth strategy registered');
|
|
142
|
+
} else {
|
|
143
|
+
console.warn('[mbkauthe] GOOGLE_LOGIN_ENABLED is true but GOOGLE_CLIENT_ID/SECRET missing; skipping Google strategy registration');
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
console.log('[mbkauthe] Google OAuth not enabled; skipping Google strategy registration');
|
|
147
|
+
}
|
|
130
148
|
|
|
131
149
|
// Serialize/Deserialize user for OAuth login
|
|
132
150
|
passport.serializeUser((user, done) => {
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mbkauthe",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "MBKTech's reusable authentication system for Node.js applications.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"types": "index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"dev": "
|
|
10
|
-
"test": "node
|
|
11
|
-
"test:watch": "node
|
|
9
|
+
"dev": "cross-env test=dev nodemon index.js",
|
|
10
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
11
|
+
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch"
|
|
12
12
|
},
|
|
13
13
|
"repository": {
|
|
14
14
|
"type": "git",
|
package/test.spec.js
CHANGED
|
@@ -192,5 +192,20 @@ describe('mbkauthe Routes', () => {
|
|
|
192
192
|
expect(response.headers['content-type']).toContain('application/json');
|
|
193
193
|
}
|
|
194
194
|
});
|
|
195
|
+
|
|
196
|
+
test('GET /mbkauthe/api/checkSession handles session check', async () => {
|
|
197
|
+
const response = await request(BASE_URL).get('/mbkauthe/api/checkSession');
|
|
198
|
+
expect(response.status).toBe(200);
|
|
199
|
+
expect(response.headers['content-type']).toContain('application/json');
|
|
200
|
+
expect(response.body).toHaveProperty('sessionValid');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('POST /mbkauthe/api/logout handles logout', async () => {
|
|
204
|
+
const response = await request(BASE_URL).post('/mbkauthe/api/logout').send();
|
|
205
|
+
expect([200, 400, 401, 403, 429]).toContain(response.status);
|
|
206
|
+
if (response.status === 200) {
|
|
207
|
+
expect(response.headers['content-type']).toContain('application/json');
|
|
208
|
+
}
|
|
209
|
+
});
|
|
195
210
|
});
|
|
196
211
|
});
|