mbkauthe 3.0.0 โ 3.1.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 +49 -7
- package/docs/api.md +100 -110
- package/index.d.ts +37 -22
- package/lib/main.js +15 -1
- package/lib/routes/misc.js +5 -14
- package/package.json +18 -11
- package/test.spec.js +178 -0
package/README.md
CHANGED
|
@@ -110,6 +110,30 @@ app.get('/admin', validateSession, checkRolePermission(['SuperAdmin']), (req, re
|
|
|
110
110
|
app.listen(3000);
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
## ๐งช Testing
|
|
114
|
+
|
|
115
|
+
MBKAuthe includes comprehensive test coverage for all authentication features:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Run all tests
|
|
119
|
+
npm test
|
|
120
|
+
|
|
121
|
+
# Run tests in watch mode (auto-rerun on changes)
|
|
122
|
+
npm run test:watch
|
|
123
|
+
|
|
124
|
+
# Run with development flags
|
|
125
|
+
npm run dev
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Test Coverage:**
|
|
129
|
+
- โ
Authentication flows (login, 2FA, logout)
|
|
130
|
+
- โ
OAuth integration (GitHub)
|
|
131
|
+
- โ
Session management and security
|
|
132
|
+
- โ
Role-based access control
|
|
133
|
+
- โ
API endpoints and error handling
|
|
134
|
+
- โ
CSRF protection and rate limiting
|
|
135
|
+
- โ
Static asset serving
|
|
136
|
+
|
|
113
137
|
## ๐ Architecture (v3.0)
|
|
114
138
|
|
|
115
139
|
```
|
|
@@ -148,14 +172,31 @@ app.post('/api/data', authenticate(process.env.API_TOKEN), handler);
|
|
|
148
172
|
|
|
149
173
|
### Built-in Routes
|
|
150
174
|
|
|
151
|
-
|
|
175
|
+
**Authentication Routes:**
|
|
176
|
+
- `GET /login`, `/signin` - Redirect to main login page
|
|
177
|
+
- `GET /mbkauthe/login` - Login page (8/min rate limit)
|
|
152
178
|
- `POST /mbkauthe/api/login` - Login endpoint (8/min rate limit)
|
|
153
179
|
- `POST /mbkauthe/api/logout` - Logout endpoint (10/min rate limit)
|
|
154
|
-
- `GET /mbkauthe/2fa` - 2FA page (if enabled)
|
|
155
|
-
- `POST /mbkauthe/api/verify-2fa` - 2FA verification (5/min rate limit)
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
- `GET /mbkauthe/
|
|
180
|
+
- `GET /mbkauthe/2fa` - 2FA verification page (if enabled)
|
|
181
|
+
- `POST /mbkauthe/api/verify-2fa` - 2FA verification API (5/min rate limit)
|
|
182
|
+
|
|
183
|
+
**OAuth Routes:**
|
|
184
|
+
- `GET /mbkauthe/api/github/login` - GitHub OAuth initiation (10/5min rate limit)
|
|
185
|
+
- `GET /mbkauthe/api/github/login/callback` - GitHub OAuth callback
|
|
186
|
+
|
|
187
|
+
**Information & Utility Routes:**
|
|
188
|
+
- `GET /mbkauthe/info`, `/mbkauthe/i` - Version & config info (8/min rate limit)
|
|
189
|
+
- `GET /mbkauthe/ErrorCode` - Error code documentation
|
|
190
|
+
- `GET /mbkauthe/test` - Test authentication status (8/min rate limit)
|
|
191
|
+
|
|
192
|
+
**Static Asset Routes:**
|
|
193
|
+
- `GET /mbkauthe/main.js` - Client-side JavaScript utilities
|
|
194
|
+
- `GET /mbkauthe/bg.webp` - Background image for auth pages
|
|
195
|
+
- `GET /icon.svg` - Application SVG icon (root level)
|
|
196
|
+
- `GET /favicon.ico`, `/icon.ico` - Application favicon
|
|
197
|
+
|
|
198
|
+
**Admin API Routes:**
|
|
199
|
+
- `POST /mbkauthe/api/terminateAllSessions` - Terminate all sessions (admin only)
|
|
159
200
|
|
|
160
201
|
## ๐ Security Features
|
|
161
202
|
|
|
@@ -245,8 +286,9 @@ const result = await dblogin.query('SELECT * FROM "Users"');
|
|
|
245
286
|
## ๐ Documentation
|
|
246
287
|
|
|
247
288
|
- [API Documentation](docs/api.md) - Complete API reference
|
|
248
|
-
- [Database Guide](docs/db.md) - Schema details
|
|
289
|
+
- [Database Guide](docs/db.md) - Schema details
|
|
249
290
|
- [Environment Config](docs/env.md) - Configuration options
|
|
291
|
+
- [Error Messages](docs/error-messages.md) - Error code reference
|
|
250
292
|
|
|
251
293
|
## ๐ License
|
|
252
294
|
|
package/docs/api.md
CHANGED
|
@@ -55,14 +55,65 @@ When a user logs in, MBKAuthe creates a session and sets the following cookies:
|
|
|
55
55
|
|
|
56
56
|
### Public Endpoints
|
|
57
57
|
|
|
58
|
+
#### `GET /login`
|
|
59
|
+
|
|
60
|
+
Redirect route that forwards to the main login page.
|
|
61
|
+
|
|
62
|
+
**Rate Limit:** No rate limiting applied
|
|
63
|
+
|
|
64
|
+
**Query Parameters:**
|
|
65
|
+
- All query parameters are preserved and forwarded
|
|
66
|
+
|
|
67
|
+
**Response:** 302 redirect to `/mbkauthe/login`
|
|
68
|
+
|
|
69
|
+
**Example:**
|
|
70
|
+
```
|
|
71
|
+
GET /login?redirect=/dashboard
|
|
72
|
+
โ Redirects to: /mbkauthe/login?redirect=/dashboard
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
#### `GET /signin`
|
|
78
|
+
|
|
79
|
+
Alias redirect route that forwards to the main login page.
|
|
80
|
+
|
|
81
|
+
**Rate Limit:** No rate limiting applied
|
|
82
|
+
|
|
83
|
+
**Query Parameters:**
|
|
84
|
+
- All query parameters are preserved and forwarded
|
|
85
|
+
|
|
86
|
+
**Response:** 302 redirect to `/mbkauthe/login`
|
|
87
|
+
|
|
88
|
+
**Example:**
|
|
89
|
+
```
|
|
90
|
+
GET /signin
|
|
91
|
+
โ Redirects to: /mbkauthe/login
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
58
96
|
#### `GET /mbkauthe/login`
|
|
59
97
|
|
|
60
|
-
Renders the login page.
|
|
98
|
+
Renders the main login page.
|
|
99
|
+
|
|
100
|
+
**Rate Limit:** 8 requests per minute (exempt for logged-in users)
|
|
101
|
+
|
|
102
|
+
**CSRF Protection:** Required (token included in form)
|
|
61
103
|
|
|
62
104
|
**Query Parameters:**
|
|
63
105
|
- `redirect` (optional) - URL to redirect after successful login
|
|
64
106
|
|
|
65
|
-
**Response:** HTML page
|
|
107
|
+
**Response:** HTML page with login form
|
|
108
|
+
|
|
109
|
+
**Template Variables:**
|
|
110
|
+
- `githubLoginEnabled` - Whether GitHub OAuth is enabled
|
|
111
|
+
- `customURL` - Redirect URL after login
|
|
112
|
+
- `userLoggedIn` - Whether user is already authenticated
|
|
113
|
+
- `username` - Current username if logged in
|
|
114
|
+
- `version` - MBKAuthe version
|
|
115
|
+
- `appName` - Application name
|
|
116
|
+
- `csrfToken` - CSRF protection token
|
|
66
117
|
|
|
67
118
|
**Example:**
|
|
68
119
|
```
|
|
@@ -275,13 +326,17 @@ fetch('/mbkauthe/api/logout', {
|
|
|
275
326
|
|
|
276
327
|
Terminates all active sessions across all users (admin only).
|
|
277
328
|
|
|
278
|
-
**Authentication:** API token required
|
|
329
|
+
**Authentication:** API token required (via `authenticate` middleware)
|
|
330
|
+
|
|
331
|
+
**Rate Limit:** No explicit rate limiting applied
|
|
279
332
|
|
|
280
333
|
**Headers:**
|
|
281
334
|
```
|
|
282
|
-
Authorization: your-
|
|
335
|
+
Authorization: your-main-secret-token
|
|
283
336
|
```
|
|
284
337
|
|
|
338
|
+
**Request Body:** None required
|
|
339
|
+
|
|
285
340
|
**Success Response (200 OK):**
|
|
286
341
|
```json
|
|
287
342
|
{
|
|
@@ -298,16 +353,28 @@ Authorization: your-api-token
|
|
|
298
353
|
| 500 | Failed to terminate sessions |
|
|
299
354
|
| 500 | Internal Server Error |
|
|
300
355
|
|
|
356
|
+
**Implementation Details:**
|
|
357
|
+
- Clears all user session IDs in the database (`Users` table)
|
|
358
|
+
- Deletes all session records from the `session` table
|
|
359
|
+
- Destroys the current request session
|
|
360
|
+
- Clears session cookies
|
|
361
|
+
- Runs database operations in parallel for better performance
|
|
362
|
+
|
|
301
363
|
**Example Request:**
|
|
302
364
|
```javascript
|
|
303
365
|
fetch('/mbkauthe/api/terminateAllSessions', {
|
|
304
366
|
method: 'POST',
|
|
305
367
|
headers: {
|
|
306
|
-
'Authorization':
|
|
368
|
+
'Authorization': process.env.MAIN_SECRET_TOKEN,
|
|
369
|
+
'Content-Type': 'application/json'
|
|
307
370
|
}
|
|
308
371
|
})
|
|
309
372
|
.then(response => response.json())
|
|
310
|
-
.then(data =>
|
|
373
|
+
.then(data => {
|
|
374
|
+
if (data.success) {
|
|
375
|
+
console.log('All sessions terminated successfully');
|
|
376
|
+
}
|
|
377
|
+
});
|
|
311
378
|
```
|
|
312
379
|
|
|
313
380
|
---
|
|
@@ -363,6 +430,10 @@ GET /mbkauthe/ErrorCode
|
|
|
363
430
|
|
|
364
431
|
Serves the client-side JavaScript file containing helper functions for authentication operations.
|
|
365
432
|
|
|
433
|
+
**Rate Limit:** No rate limiting applied
|
|
434
|
+
|
|
435
|
+
**Cache:** Cached for 1 year (max-age=31536000)
|
|
436
|
+
|
|
366
437
|
**Purpose:** Provides frontend JavaScript utilities including:
|
|
367
438
|
- `logout()` - Logout function with confirmation dialog and cache clearing
|
|
368
439
|
- `logoutuser()` - Alias for logout function
|
|
@@ -396,46 +467,20 @@ Serves the client-side JavaScript file containing helper functions for authentic
|
|
|
396
467
|
- Clears cookies
|
|
397
468
|
- Forces page reload
|
|
398
469
|
|
|
399
|
-
|
|
400
|
-
---
|
|
401
|
-
|
|
402
|
-
#### `GET /mbkauthe/ErrorCode`
|
|
403
|
-
|
|
404
|
-
Displays comprehensive error code documentation with descriptions and user messages.
|
|
405
|
-
|
|
406
|
-
**Response:** HTML page showing:
|
|
407
|
-
- All error codes organized by category
|
|
408
|
-
- Error code ranges (Authentication, 2FA, Session, Authorization, etc.)
|
|
409
|
-
- User-friendly messages and hints for each error
|
|
410
|
-
- Dynamically generated from `lib/utils/errors.js`
|
|
411
|
-
|
|
412
|
-
**Categories:**
|
|
413
|
-
- **600-699**: Authentication Errors
|
|
414
|
-
- **700-799**: Two-Factor Authentication Errors
|
|
415
|
-
- **800-899**: Session Management Errors
|
|
416
|
-
- **900-999**: Authorization Errors
|
|
417
|
-
- **1000-1099**: Input Validation Errors
|
|
418
|
-
- **1100-1199**: Rate Limiting Errors
|
|
419
|
-
- **1200-1299**: Server Errors
|
|
420
|
-
- **1300-1399**: OAuth Errors
|
|
421
|
-
|
|
422
|
-
**Note:** This page is automatically synchronized with the error definitions in the codebase. Adding new errors only requires updating `lib/utils/errors.js` and the category code array.
|
|
423
|
-
|
|
424
|
-
**Usage:**
|
|
425
|
-
```
|
|
426
|
-
GET /mbkauthe/ErrorCode
|
|
427
|
-
```
|
|
428
|
-
|
|
429
470
|
---
|
|
430
471
|
|
|
431
472
|
#### `GET /icon.svg`
|
|
432
473
|
|
|
433
|
-
Serves the application's SVG icon file.
|
|
474
|
+
Serves the application's SVG icon file from the root level.
|
|
475
|
+
|
|
476
|
+
**Rate Limit:** No rate limiting applied
|
|
434
477
|
|
|
435
478
|
**Response:** SVG image file (Content-Type: image/svg+xml)
|
|
436
479
|
|
|
437
480
|
**Cache:** Cached for 1 year (max-age=31536000)
|
|
438
481
|
|
|
482
|
+
**Note:** This route is mounted at the root level (not under `/mbkauthe`)
|
|
483
|
+
|
|
439
484
|
**Usage:**
|
|
440
485
|
```html
|
|
441
486
|
<img src="/icon.svg" alt="App Icon">
|
|
@@ -710,64 +755,6 @@ Authorization: your-secret-token
|
|
|
710
755
|
|
|
711
756
|
---
|
|
712
757
|
|
|
713
|
-
### `authapi(requiredRole)`
|
|
714
|
-
|
|
715
|
-
Advanced API authentication with role-based access control.
|
|
716
|
-
|
|
717
|
-
**Parameters:**
|
|
718
|
-
- `requiredRole` (array, optional) - Array of allowed roles
|
|
719
|
-
|
|
720
|
-
**Usage:**
|
|
721
|
-
```javascript
|
|
722
|
-
import { authapi } from 'mbkauthe';
|
|
723
|
-
|
|
724
|
-
// Any authenticated API user
|
|
725
|
-
app.get('/api/data', authapi(), (req, res) => {
|
|
726
|
-
console.log(req.user); // { username, UserName, role, Role }
|
|
727
|
-
res.json({ data: 'API data' });
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
// Only SuperAdmin via API
|
|
731
|
-
app.post('/api/admin/action', authapi(['SuperAdmin']), (req, res) => {
|
|
732
|
-
res.json({ success: true });
|
|
733
|
-
});
|
|
734
|
-
```
|
|
735
|
-
|
|
736
|
-
**Headers Required:**
|
|
737
|
-
```
|
|
738
|
-
Authorization: user-api-key-64-char-hex
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
**Behavior:**
|
|
742
|
-
- Validates API key from `Authorization` header
|
|
743
|
-
- Looks up user associated with API key
|
|
744
|
-
- Checks if user account is active
|
|
745
|
-
- Verifies user role if `requiredRole` is specified
|
|
746
|
-
- Blocks demo users from accessing endpoints
|
|
747
|
-
- Populates `req.user` with user information
|
|
748
|
-
- Returns 401 if unauthorized, 403 if insufficient permissions
|
|
749
|
-
|
|
750
|
-
**req.user Object:**
|
|
751
|
-
```javascript
|
|
752
|
-
req.user = {
|
|
753
|
-
username: "john.doe",
|
|
754
|
-
UserName: "john.doe",
|
|
755
|
-
role: "NormalUser",
|
|
756
|
-
Role: "NormalUser"
|
|
757
|
-
}
|
|
758
|
-
```
|
|
759
|
-
|
|
760
|
-
**Database Requirement:**
|
|
761
|
-
Requires `UserAuthApiKey` table:
|
|
762
|
-
```sql
|
|
763
|
-
CREATE TABLE "UserAuthApiKey" (
|
|
764
|
-
username VARCHAR(50) PRIMARY KEY REFERENCES "Users"("UserName"),
|
|
765
|
-
"key" VARCHAR(64) UNIQUE NOT NULL
|
|
766
|
-
);
|
|
767
|
-
```
|
|
768
|
-
|
|
769
|
-
---
|
|
770
|
-
|
|
771
758
|
## Error Codes
|
|
772
759
|
|
|
773
760
|
### HTTP Status Codes
|
|
@@ -876,7 +863,7 @@ app.get('/moderator',
|
|
|
876
863
|
### API Authentication
|
|
877
864
|
|
|
878
865
|
```javascript
|
|
879
|
-
import { authenticate
|
|
866
|
+
import { authenticate } from 'mbkauthe';
|
|
880
867
|
|
|
881
868
|
// Simple token authentication
|
|
882
869
|
app.post('/api/webhook',
|
|
@@ -887,24 +874,27 @@ app.post('/api/webhook',
|
|
|
887
874
|
}
|
|
888
875
|
);
|
|
889
876
|
|
|
890
|
-
// API
|
|
891
|
-
app.
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
// Fetch user profile
|
|
895
|
-
const profile = await getUserProfile(username);
|
|
896
|
-
|
|
897
|
-
res.json({ success: true, profile });
|
|
898
|
-
});
|
|
899
|
-
|
|
900
|
-
// API key with role requirement
|
|
901
|
-
app.delete('/api/admin/users/:id',
|
|
902
|
-
authapi(['SuperAdmin']),
|
|
877
|
+
// Admin API with token authentication
|
|
878
|
+
app.post('/api/admin/terminate-sessions',
|
|
879
|
+
authenticate(process.env.MAIN_SECRET_TOKEN),
|
|
903
880
|
async (req, res) => {
|
|
904
|
-
|
|
881
|
+
// Terminate all sessions
|
|
905
882
|
res.json({ success: true });
|
|
906
883
|
}
|
|
907
884
|
);
|
|
885
|
+
|
|
886
|
+
// Protected API endpoint (requires session)
|
|
887
|
+
app.get('/api/user/profile',
|
|
888
|
+
validateSession,
|
|
889
|
+
async (req, res) => {
|
|
890
|
+
const { username } = req.session.user;
|
|
891
|
+
|
|
892
|
+
// Fetch user profile
|
|
893
|
+
const profile = await getUserProfile(username);
|
|
894
|
+
|
|
895
|
+
res.json({ success: true, profile });
|
|
896
|
+
}
|
|
897
|
+
);
|
|
908
898
|
```
|
|
909
899
|
|
|
910
900
|
---
|
package/index.d.ts
CHANGED
|
@@ -5,6 +5,43 @@
|
|
|
5
5
|
import { Request, Response, NextFunction, Router } from 'express';
|
|
6
6
|
import { Pool } from 'pg';
|
|
7
7
|
|
|
8
|
+
// Global augmentations must be at the top level, outside any declare module blocks
|
|
9
|
+
declare global {
|
|
10
|
+
namespace Express {
|
|
11
|
+
interface Request {
|
|
12
|
+
user?: {
|
|
13
|
+
username: string;
|
|
14
|
+
UserName: string;
|
|
15
|
+
role: 'SuperAdmin' | 'NormalUser' | 'Guest';
|
|
16
|
+
Role: 'SuperAdmin' | 'NormalUser' | 'Guest';
|
|
17
|
+
};
|
|
18
|
+
userRole?: 'SuperAdmin' | 'NormalUser' | 'Guest';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface Session {
|
|
22
|
+
user?: {
|
|
23
|
+
id: number;
|
|
24
|
+
username: string;
|
|
25
|
+
UserName: string;
|
|
26
|
+
role: 'SuperAdmin' | 'NormalUser' | 'Guest';
|
|
27
|
+
Role: 'SuperAdmin' | 'NormalUser' | 'Guest';
|
|
28
|
+
sessionId: string;
|
|
29
|
+
allowedApps?: string[];
|
|
30
|
+
};
|
|
31
|
+
preAuthUser?: {
|
|
32
|
+
id: number;
|
|
33
|
+
username: string;
|
|
34
|
+
UserName?: string;
|
|
35
|
+
role: 'SuperAdmin' | 'NormalUser' | 'Guest';
|
|
36
|
+
Role?: 'SuperAdmin' | 'NormalUser' | 'Guest';
|
|
37
|
+
loginMethod?: 'password' | 'github';
|
|
38
|
+
redirectUrl?: string | null;
|
|
39
|
+
};
|
|
40
|
+
oauthRedirect?: string;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
8
45
|
declare module 'mbkauthe' {
|
|
9
46
|
// Configuration Types
|
|
10
47
|
export interface MBKAuthConfig {
|
|
@@ -90,26 +127,6 @@ declare module 'mbkauthe' {
|
|
|
90
127
|
updated_at: Date;
|
|
91
128
|
}
|
|
92
129
|
|
|
93
|
-
// Request Extensions
|
|
94
|
-
declare global {
|
|
95
|
-
namespace Express {
|
|
96
|
-
interface Request {
|
|
97
|
-
user?: {
|
|
98
|
-
username: string;
|
|
99
|
-
UserName: string;
|
|
100
|
-
role: UserRole;
|
|
101
|
-
Role: UserRole;
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
interface Session {
|
|
106
|
-
user?: SessionUser;
|
|
107
|
-
preAuthUser?: PreAuthUser;
|
|
108
|
-
oauthRedirect?: string;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
130
|
// API Response Types
|
|
114
131
|
export interface LoginResponse {
|
|
115
132
|
success: boolean;
|
|
@@ -177,8 +194,6 @@ declare module 'mbkauthe' {
|
|
|
177
194
|
|
|
178
195
|
export function authenticate(token: string): AuthMiddleware;
|
|
179
196
|
|
|
180
|
-
export function authapi(requiredRole?: UserRole[]): AuthMiddleware;
|
|
181
|
-
|
|
182
197
|
// Utility Functions
|
|
183
198
|
export function renderError(
|
|
184
199
|
res: Response,
|
package/lib/main.js
CHANGED
|
@@ -2,7 +2,6 @@ import express from "express";
|
|
|
2
2
|
import session from "express-session";
|
|
3
3
|
import cookieParser from "cookie-parser";
|
|
4
4
|
import passport from 'passport';
|
|
5
|
-
import { packageJson } from "./config/index.js";
|
|
6
5
|
import {
|
|
7
6
|
sessionConfig,
|
|
8
7
|
corsMiddleware,
|
|
@@ -12,6 +11,11 @@ import {
|
|
|
12
11
|
import authRoutes from "./routes/auth.js";
|
|
13
12
|
import oauthRoutes from "./routes/oauth.js";
|
|
14
13
|
import miscRoutes from "./routes/misc.js";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import path from "path";
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
|
|
15
19
|
|
|
16
20
|
const router = express.Router();
|
|
17
21
|
|
|
@@ -48,5 +52,15 @@ router.get(["/login", "/signin"], async (req, res) => {
|
|
|
48
52
|
return res.redirect(redirectUrl);
|
|
49
53
|
});
|
|
50
54
|
|
|
55
|
+
router.get('/icon.svg', (req, res) => {
|
|
56
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
|
57
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'icon.svg'));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
router.get(['/favicon.ico', '/icon.ico'], (req, res) => {
|
|
61
|
+
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
|
62
|
+
res.sendFile(path.join(__dirname, '..', 'public', 'icon.ico'));
|
|
63
|
+
});
|
|
64
|
+
|
|
51
65
|
export { getLatestVersion } from "./routes/misc.js";
|
|
52
66
|
export default router;
|
package/lib/routes/misc.js
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import express from "express";
|
|
2
2
|
import fetch from 'node-fetch';
|
|
3
3
|
import rateLimit from 'express-rate-limit';
|
|
4
|
-
import { fileURLToPath } from "url";
|
|
5
|
-
import fs from "fs";
|
|
6
|
-
import path from "path";
|
|
7
4
|
import { mbkautheVar, packageJson, appVersion } from "../config/index.js";
|
|
8
5
|
import { renderError } from "../utils/response.js";
|
|
9
6
|
import { authenticate, validateSession } from "../middleware/auth.js";
|
|
10
7
|
import { ErrorCodes, ErrorMessages } from "../utils/errors.js";
|
|
11
8
|
import { dblogin } from "../database/pool.js";
|
|
12
9
|
import { clearSessionCookies } from "../config/cookies.js";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import path from "path";
|
|
12
|
+
import fs from "fs";
|
|
13
13
|
|
|
14
|
-
const router = express.Router();
|
|
15
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
15
|
|
|
16
|
+
|
|
17
|
+
const router = express.Router();
|
|
17
18
|
// Rate limiter for info/test routes
|
|
18
19
|
const LoginLimit = rateLimit({
|
|
19
20
|
windowMs: 1 * 60 * 1000,
|
|
@@ -30,16 +31,6 @@ router.get('/main.js', (req, res) => {
|
|
|
30
31
|
res.sendFile(path.join(__dirname, '..', '..', 'public', 'main.js'));
|
|
31
32
|
});
|
|
32
33
|
|
|
33
|
-
router.get('/icon.svg', (req, res) => {
|
|
34
|
-
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
|
35
|
-
res.sendFile(path.join(__dirname, '..', '..', 'public', 'icon.svg'));
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
router.get(['/favicon.ico', '/icon.ico'], (req, res) => {
|
|
39
|
-
res.setHeader('Cache-Control', 'public, max-age=31536000');
|
|
40
|
-
res.sendFile(path.join(__dirname, '..', '..', 'public', 'icon.ico'));
|
|
41
|
-
});
|
|
42
|
-
|
|
43
34
|
router.get("/bg.webp", (req, res) => {
|
|
44
35
|
const imgPath = path.join(__dirname, "..", "..", "public", "bg.webp");
|
|
45
36
|
res.setHeader('Content-Type', 'image/webp');
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mbkauthe",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.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": "cross-env test=dev nodemon index.js"
|
|
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"
|
|
10
12
|
},
|
|
11
13
|
"repository": {
|
|
12
14
|
"type": "git",
|
|
@@ -30,8 +32,6 @@
|
|
|
30
32
|
"registry": "https://registry.npmjs.org/"
|
|
31
33
|
},
|
|
32
34
|
"dependencies": {
|
|
33
|
-
"bcrypt": "^6.0.0",
|
|
34
|
-
"cheerio": "^1.0.0",
|
|
35
35
|
"connect-pg-simple": "^10.0.0",
|
|
36
36
|
"cookie-parser": "^1.4.7",
|
|
37
37
|
"csurf": "^1.2.2",
|
|
@@ -40,19 +40,26 @@
|
|
|
40
40
|
"express-handlebars": "^8.0.1",
|
|
41
41
|
"express-rate-limit": "^7.5.0",
|
|
42
42
|
"express-session": "^1.18.1",
|
|
43
|
-
"marked": "^15.0.11",
|
|
44
43
|
"node-fetch": "^3.3.2",
|
|
45
44
|
"passport": "^0.7.0",
|
|
46
45
|
"passport-github2": "^0.1.12",
|
|
47
|
-
"path": "^0.12.7",
|
|
48
46
|
"pg": "^8.14.1",
|
|
49
|
-
"speakeasy": "^2.0.0"
|
|
50
|
-
"url": "^0.11.4"
|
|
47
|
+
"speakeasy": "^2.0.0"
|
|
51
48
|
},
|
|
52
49
|
"devDependencies": {
|
|
53
50
|
"@types/express": "^5.0.6",
|
|
54
|
-
"@types/node": "^22.19.1",
|
|
55
51
|
"cross-env": "^7.0.3",
|
|
56
|
-
"
|
|
52
|
+
"jest": "^30.2.0",
|
|
53
|
+
"nodemon": "^3.1.11",
|
|
54
|
+
"supertest": "^7.1.4"
|
|
55
|
+
},
|
|
56
|
+
"jest": {
|
|
57
|
+
"testEnvironment": "node",
|
|
58
|
+
"testMatch": [
|
|
59
|
+
"**/*.spec.js",
|
|
60
|
+
"**/*.test.js"
|
|
61
|
+
],
|
|
62
|
+
"testTimeout": 10000,
|
|
63
|
+
"transform": {}
|
|
57
64
|
}
|
|
58
|
-
}
|
|
65
|
+
}
|
package/test.spec.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import request from 'supertest';
|
|
2
|
+
|
|
3
|
+
const BASE_URL = "http://localhost:5555";
|
|
4
|
+
|
|
5
|
+
// Helper to get CSRF token and cookies
|
|
6
|
+
const getCSRFTokenAndCookies = async () => {
|
|
7
|
+
const response = await request(BASE_URL).get('/mbkauthe/login');
|
|
8
|
+
const html = response.text;
|
|
9
|
+
const csrfMatch = html.match(/name="_csrf".*?value="([^"]+)"/i) ||
|
|
10
|
+
html.match(/content="([^"]+)".*?name="_csrf"/i);
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
csrfToken: csrfMatch?.[1] || '',
|
|
14
|
+
cookies: response.headers['set-cookie'] || []
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('mbkauthe Routes', () => {
|
|
19
|
+
|
|
20
|
+
describe('Redirect Routes', () => {
|
|
21
|
+
test('GET /login redirects to /mbkauthe/login', async () => {
|
|
22
|
+
const response = await request(BASE_URL)
|
|
23
|
+
.get('/login')
|
|
24
|
+
.redirects(0);
|
|
25
|
+
|
|
26
|
+
expect(response.status).toBe(302);
|
|
27
|
+
expect(response.headers.location).toContain('/mbkauthe/login');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('GET /signin redirects to /mbkauthe/login', async () => {
|
|
31
|
+
const response = await request(BASE_URL)
|
|
32
|
+
.get('/signin')
|
|
33
|
+
.redirects(0);
|
|
34
|
+
|
|
35
|
+
expect(response.status).toBe(302);
|
|
36
|
+
expect(response.headers.location).toContain('/mbkauthe/login');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('Authentication Pages', () => {
|
|
41
|
+
test('GET /mbkauthe/login contains loginUsername input', async () => {
|
|
42
|
+
const response = await request(BASE_URL).get('/mbkauthe/login');
|
|
43
|
+
|
|
44
|
+
expect(response.status).toBe(200);
|
|
45
|
+
expect(response.text).toMatch(/id\s*=\s*["']loginUsername["']/i);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('GET /mbkauthe/2fa contains token input or redirects', async () => {
|
|
49
|
+
const response = await request(BASE_URL)
|
|
50
|
+
.get('/mbkauthe/2fa')
|
|
51
|
+
.redirects(0);
|
|
52
|
+
|
|
53
|
+
if (response.status === 302) {
|
|
54
|
+
expect(response.headers.location).toContain('/mbkauthe/login');
|
|
55
|
+
} else {
|
|
56
|
+
expect(response.status).toBe(200);
|
|
57
|
+
expect(response.text).toMatch(/id\s*=\s*["']token["']/i);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe('Info Pages', () => {
|
|
63
|
+
test.each([
|
|
64
|
+
['/mbkauthe/info', 'info page'],
|
|
65
|
+
['/mbkauthe/i', 'info short page'],
|
|
66
|
+
])('GET %s contains CurrentVersion div', async (path, desc) => {
|
|
67
|
+
const response = await request(BASE_URL).get(path);
|
|
68
|
+
|
|
69
|
+
expect(response.status).toBe(200);
|
|
70
|
+
expect(response.text).toMatch(/id\s*=\s*["']CurrentVersion["']/i);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('GET /mbkauthe/ErrorCode contains error-603 div', async () => {
|
|
74
|
+
const response = await request(BASE_URL).get('/mbkauthe/ErrorCode');
|
|
75
|
+
|
|
76
|
+
expect(response.status).toBe(200);
|
|
77
|
+
expect(response.text).toMatch(/id\s*=\s*["']error-603["']/i);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('Static Assets', () => {
|
|
82
|
+
test('GET /mbkauthe/main.js returns JavaScript', async () => {
|
|
83
|
+
const response = await request(BASE_URL).get('/mbkauthe/main.js');
|
|
84
|
+
|
|
85
|
+
expect(response.status).toBe(200);
|
|
86
|
+
expect(response.headers['content-type']).toContain('javascript');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('GET /icon.svg returns SVG content', async () => {
|
|
90
|
+
const response = await request(BASE_URL).get('/icon.svg');
|
|
91
|
+
|
|
92
|
+
expect(response.status).toBe(200);
|
|
93
|
+
expect(response.text || response.body.toString()).toMatch(/<svg|SVG/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test.each([
|
|
97
|
+
['/favicon.ico', 'favicon'],
|
|
98
|
+
['/icon.ico', 'icon'],
|
|
99
|
+
])('GET %s returns ICO content', async (path, desc) => {
|
|
100
|
+
const response = await request(BASE_URL).get(path);
|
|
101
|
+
expect(response.status).toBe(200);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('GET /mbkauthe/bg.webp returns WEBP content', async () => {
|
|
105
|
+
const response = await request(BASE_URL).get('/mbkauthe/bg.webp');
|
|
106
|
+
|
|
107
|
+
expect(response.status).toBe(200);
|
|
108
|
+
expect(response.headers['content-type']).toContain('image/webp');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('Protected Routes', () => {
|
|
113
|
+
test('GET /mbkauthe/test responds appropriately', async () => {
|
|
114
|
+
const response = await request(BASE_URL).get('/mbkauthe/test');
|
|
115
|
+
expect([200, 302, 401, 403, 429]).toContain(response.status);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('OAuth Routes', () => {
|
|
120
|
+
test('GET /mbkauthe/api/github/login handles GitHub OAuth', async () => {
|
|
121
|
+
const response = await request(BASE_URL)
|
|
122
|
+
.get('/mbkauthe/api/github/login')
|
|
123
|
+
.redirects(0);
|
|
124
|
+
|
|
125
|
+
expect([200, 302, 500, 429]).toContain(response.status);
|
|
126
|
+
|
|
127
|
+
if (response.status === 302) {
|
|
128
|
+
const location = response.headers.location;
|
|
129
|
+
expect(location).toMatch(/github\.com|login/);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('GET /mbkauthe/api/github/login/callback handles callback', async () => {
|
|
134
|
+
const response = await request(BASE_URL).get('/mbkauthe/api/github/login/callback');
|
|
135
|
+
expect([200, 302, 400, 401, 429]).toContain(response.status);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('API Endpoints', () => {
|
|
140
|
+
test('POST /mbkauthe/api/login handles login API', async () => {
|
|
141
|
+
const response = await request(BASE_URL)
|
|
142
|
+
.post('/mbkauthe/api/login')
|
|
143
|
+
.send({ username: 'test', password: 'test' });
|
|
144
|
+
|
|
145
|
+
expect([200, 400, 401, 403, 429]).toContain(response.status);
|
|
146
|
+
expect(response.headers['content-type']).toContain('application/json');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('POST /mbkauthe/api/verify-2fa handles 2FA API', async () => {
|
|
150
|
+
const { csrfToken, cookies } = await getCSRFTokenAndCookies();
|
|
151
|
+
|
|
152
|
+
const response = await request(BASE_URL)
|
|
153
|
+
.post('/mbkauthe/api/verify-2fa')
|
|
154
|
+
.set('Cookie', cookies)
|
|
155
|
+
.send({ token: '123456', _csrf: csrfToken });
|
|
156
|
+
|
|
157
|
+
expect([200, 400, 401, 403, 429]).toContain(response.status);
|
|
158
|
+
expect(response.headers['content-type']).toContain('application/json');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test('POST /mbkauthe/api/terminateAllSessions handles session termination', async () => {
|
|
162
|
+
const { csrfToken, cookies } = await getCSRFTokenAndCookies();
|
|
163
|
+
|
|
164
|
+
const response = await request(BASE_URL)
|
|
165
|
+
.post('/mbkauthe/api/terminateAllSessions')
|
|
166
|
+
.set('Cookie', cookies)
|
|
167
|
+
.send({ _csrf: csrfToken });
|
|
168
|
+
|
|
169
|
+
expect([200, 400, 401, 403, 429]).toContain(response.status);
|
|
170
|
+
|
|
171
|
+
if (response.status === 401) {
|
|
172
|
+
expect(response.headers['content-type']).not.toContain('application/json');
|
|
173
|
+
} else {
|
|
174
|
+
expect(response.headers['content-type']).toContain('application/json');
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|