mbkauthe 4.6.0 → 4.6.2
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 +3 -0
- package/docs/db.md +197 -300
- package/docs/db.sql +146 -59
- package/index.js +10 -17
- package/lib/config/cookies.js +1 -1
- package/lib/createTable.js +34 -0
- package/lib/main.js +2 -2
- package/lib/middleware/index.js +5 -1
- package/lib/pool.js +62 -0
- package/lib/routes/misc.js +144 -60
- package/lib/routes/oauth.js +5 -3
- package/package.json +3 -2
- package/public/main.css +25 -0
- package/public/main.js +29 -50
- package/test.spec.js +2 -1
- package/views/2fa.handlebars +1 -15
- package/views/head.handlebars +11 -5
- package/views/loginmbkauthe.handlebars +0 -6
- package/views/profilemenu.handlebars +3 -10
- package/views/sharedStyles.handlebars +50 -8
- package/public/icon.svg +0 -5
package/README.md
CHANGED
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
|
|
14
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.
|
|
15
15
|
|
|
16
|
+
## Todo
|
|
17
|
+
- 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.
|
|
18
|
+
|
|
16
19
|
## ✨ Key Features
|
|
17
20
|
|
|
18
21
|
- Secure password authentication (PBKDF2)
|
package/docs/db.md
CHANGED
|
@@ -1,44 +1,86 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Database Schema
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
This OAuth login feature allows users to authenticate using their GitHub or Google account if it's already linked to their account in the system. Users must first connect their OAuth account through the regular account linking process, then they can use it to log in directly.
|
|
3
|
+
This document describes the database schema used by **mbkauthe**. The schema is defined in `docs/db.sql` and is expected to match the database structure used by the application.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
---
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
Add these to your `.env` file:
|
|
7
|
+
## 1. Roles
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
# GitHub OAuth App Configuration
|
|
13
|
-
GITHUB_CLIENT_ID=your_github_client_id
|
|
14
|
-
GITHUB_CLIENT_SECRET=your_github_client_secret
|
|
9
|
+
The project uses a Postgres `ENUM` type for user roles:
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
```sql
|
|
12
|
+
DO $$
|
|
13
|
+
BEGIN
|
|
14
|
+
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'role') THEN
|
|
15
|
+
CREATE TYPE role AS ENUM ('SuperAdmin', 'NormalUser', 'Guest');
|
|
16
|
+
END IF;
|
|
17
|
+
END
|
|
18
|
+
$$;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 2. Users Table
|
|
24
|
+
|
|
25
|
+
Stores user accounts and profile metadata.
|
|
26
|
+
|
|
27
|
+
```sql
|
|
28
|
+
CREATE TABLE IF NOT EXISTS "Users" (
|
|
29
|
+
id SERIAL PRIMARY KEY,
|
|
30
|
+
"UserName" VARCHAR(50) NOT NULL UNIQUE,
|
|
31
|
+
"Password" VARCHAR(255) NOT NULL,
|
|
32
|
+
"Active" BOOLEAN DEFAULT FALSE,
|
|
33
|
+
"Role" role DEFAULT 'NormalUser' NOT NULL,
|
|
34
|
+
"HaveMailAccount" BOOLEAN DEFAULT FALSE,
|
|
35
|
+
"AllowedApps" JSONB DEFAULT '["mbkauthe", "portal"]',
|
|
36
|
+
"created_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
37
|
+
"updated_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
38
|
+
"last_login" TIMESTAMP WITH TIME ZONE,
|
|
39
|
+
"PasswordEnc" VARCHAR(128),
|
|
40
|
+
|
|
41
|
+
"FullName" VARCHAR(255),
|
|
42
|
+
"email" TEXT DEFAULT 'support@mbktech.org',
|
|
43
|
+
"Image" TEXT DEFAULT 'https://portal.mbktech.org/Assets/Images/M.png',
|
|
44
|
+
"Bio" TEXT DEFAULT 'I am ....',
|
|
45
|
+
"SocialAccounts" TEXT DEFAULT '{}',
|
|
46
|
+
"Positions" jsonb DEFAULT '{"Not_Permanent":"Member Is Not Permanent"}',
|
|
47
|
+
"resetToken" TEXT,
|
|
48
|
+
"resetTokenExpires" TimeStamp,
|
|
49
|
+
"resetAttempts" INTEGER DEFAULT '0',
|
|
50
|
+
"lastResetAttempt" TimeStamp WITH TIME ZONE
|
|
51
|
+
);
|
|
19
52
|
```
|
|
20
53
|
|
|
21
|
-
###
|
|
22
|
-
1. Go to GitHub Settings > Developer settings > OAuth Apps
|
|
23
|
-
2. Create a new OAuth App
|
|
24
|
-
3. Set the Authorization callback URL to: `https://yourdomain.com/mbkauthe/api/github/login/callback`
|
|
25
|
-
4. Copy the Client ID and Client Secret to your `.env` file
|
|
54
|
+
### Indexes
|
|
26
55
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
```sql
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_users_username ON "Users" USING BTREE ("UserName");
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_users_role ON "Users" USING BTREE ("Role");
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_users_active ON "Users" USING BTREE ("Active");
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON "Users" USING BTREE ("email");
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_users_last_login ON "Users" USING BTREE (last_login);
|
|
62
|
+
-- JSONB GIN indexes for common filters/queries on JSON fields
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_users_allowedapps_gin ON "Users" USING GIN ("AllowedApps");
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_users_positions_gin ON "Users" USING GIN ("Positions");
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Password Storage
|
|
68
|
+
|
|
69
|
+
- `Password` is used when `EncPass=false` (plain text / legacy).
|
|
70
|
+
- `PasswordEnc` is used when `EncPass=true` (PBKDF2 hashed, stored as a 128-character hex string).
|
|
71
|
+
- Only one of the two columns should be populated depending on the configuration.
|
|
35
72
|
|
|
36
|
-
|
|
37
|
-
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 3. OAuth Link Tables (GitHub / Google)
|
|
77
|
+
|
|
78
|
+
OAuth link tables store associations between an existing user account and an OAuth provider.
|
|
79
|
+
|
|
80
|
+
### GitHub
|
|
38
81
|
|
|
39
82
|
```sql
|
|
40
|
-
|
|
41
|
-
CREATE TABLE user_github (
|
|
83
|
+
CREATE TABLE IF NOT EXISTS user_github (
|
|
42
84
|
id SERIAL PRIMARY KEY,
|
|
43
85
|
user_name VARCHAR(50) REFERENCES "Users"("UserName"),
|
|
44
86
|
github_id VARCHAR(255) UNIQUE,
|
|
@@ -48,12 +90,14 @@ CREATE TABLE user_github (
|
|
|
48
90
|
updated_at TimeStamp WITH TIME ZONE DEFAULT NOW()
|
|
49
91
|
);
|
|
50
92
|
|
|
51
|
-
-- Add indexes for performance optimization
|
|
52
93
|
CREATE INDEX IF NOT EXISTS idx_user_github_github_id ON user_github (github_id);
|
|
53
94
|
CREATE INDEX IF NOT EXISTS idx_user_github_user_name ON user_github (user_name);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Google
|
|
54
98
|
|
|
55
|
-
|
|
56
|
-
CREATE TABLE user_google (
|
|
99
|
+
```sql
|
|
100
|
+
CREATE TABLE IF NOT EXISTS user_google (
|
|
57
101
|
id SERIAL PRIMARY KEY,
|
|
58
102
|
user_name VARCHAR(50) REFERENCES "Users"("UserName"),
|
|
59
103
|
google_id VARCHAR(255) UNIQUE,
|
|
@@ -63,313 +107,167 @@ CREATE TABLE user_google (
|
|
|
63
107
|
updated_at TimeStamp WITH TIME ZONE DEFAULT NOW()
|
|
64
108
|
);
|
|
65
109
|
|
|
66
|
-
-- Add indexes for performance optimization
|
|
67
110
|
CREATE INDEX IF NOT EXISTS idx_user_google_google_id ON user_google (google_id);
|
|
68
111
|
CREATE INDEX IF NOT EXISTS idx_user_google_user_name ON user_google (user_name);
|
|
69
112
|
```
|
|
70
113
|
|
|
71
|
-
|
|
72
|
-
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 4. Session Tables
|
|
117
|
+
|
|
118
|
+
### Application Sessions (`Sessions`)
|
|
119
|
+
|
|
120
|
+
Stores application sessions and supports multiple concurrent sessions per user.
|
|
73
121
|
|
|
74
122
|
```sql
|
|
75
|
-
CREATE TABLE "
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"Scope" VARCHAR(20) DEFAULT 'read-only', -- Token scope: 'read-only' or 'write'
|
|
82
|
-
"AllowedApps" JSONB DEFAULT NULL, -- Apps this token can access
|
|
83
|
-
"LastUsed" TIMESTAMP WITH TIME ZONE,
|
|
84
|
-
"CreatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
85
|
-
"ExpiresAt" TIMESTAMP WITH TIME ZONE -- Optional expiration
|
|
123
|
+
CREATE TABLE IF NOT EXISTS "Sessions" (
|
|
124
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- requires pgcrypto or uuid-ossp
|
|
125
|
+
"UserName" VARCHAR(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
|
|
126
|
+
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
127
|
+
expires_at TIMESTAMP WITH TIME ZONE,
|
|
128
|
+
meta JSONB
|
|
86
129
|
);
|
|
87
130
|
|
|
88
|
-
CREATE INDEX IF NOT EXISTS
|
|
89
|
-
CREATE INDEX IF NOT EXISTS
|
|
131
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_username ON "Sessions" ("UserName");
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_sessions_user_created ON "Sessions" ("UserName", created_at);
|
|
90
133
|
```
|
|
91
134
|
|
|
92
|
-
|
|
135
|
+
### Express Session Store (`session`)
|
|
93
136
|
|
|
94
|
-
|
|
137
|
+
Used by `express-session` when configured to store sessions in Postgres.
|
|
95
138
|
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
139
|
+
```sql
|
|
140
|
+
CREATE TABLE IF NOT EXISTS "session" (
|
|
141
|
+
sid VARCHAR(33) PRIMARY KEY NOT NULL,
|
|
142
|
+
sess JSONB NOT NULL,
|
|
143
|
+
expire TimeStamp WITH TIME ZONE NOT NULL
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_session_expire ON "session" ("expire");
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_session_user_id ON "session" ((sess->'user'->>'id'));
|
|
101
148
|
```
|
|
102
149
|
|
|
103
|
-
|
|
104
|
-
- `read-only`: Allows only safe, read-only HTTP methods (GET, HEAD, OPTIONS)
|
|
105
|
-
- `write`: Allows all HTTP methods (GET, POST, PUT, DELETE, PATCH, etc.)
|
|
150
|
+
---
|
|
106
151
|
|
|
107
|
-
|
|
108
|
-
- `null` (default): Token inherits allowed apps from user's `AllowedApps` in Users table
|
|
109
|
-
- `["app1", "app2"]`: Token is restricted to specific apps (must be subset of user's apps)
|
|
110
|
-
- `["*"]`: Token has access to all user's apps (for non-SuperAdmin) or all apps in system (SuperAdmin only)
|
|
111
|
-
- `[]` (empty array): Token has no app access (effectively disabled)
|
|
152
|
+
## 5. Two-Factor Authentication (2FA)
|
|
112
153
|
|
|
113
|
-
|
|
154
|
+
```sql
|
|
155
|
+
CREATE TABLE IF NOT EXISTS "TwoFA" (
|
|
156
|
+
"UserName" VARCHAR(50) primary key REFERENCES "Users"("UserName"),
|
|
157
|
+
"TwoFAStatus" boolean NOT NULL,
|
|
158
|
+
"TwoFASecret" TEXT
|
|
159
|
+
);
|
|
114
160
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
- Only the prefix (first 11 characters) is stored for identification
|
|
118
|
-
- The full token is only shown once during creation
|
|
119
|
-
- Scope enforcement is applied at the middleware level before route processing
|
|
120
|
-
- App access is validated against both token restrictions and user permissions
|
|
121
|
-
- **SuperAdmin exception**: SuperAdmin users bypass app permission checks - their tokens work on any app regardless of `allowedApps` configuration
|
|
122
|
-
- Wildcard `["*"]` for non-SuperAdmin means "all apps the user has access to", not "all apps in system"
|
|
123
|
-
|
|
124
|
-
## How It Works
|
|
125
|
-
|
|
126
|
-
### Login Flow (GitHub/Google)
|
|
127
|
-
1. User clicks "Login with GitHub" or "Login with Google" on the login page
|
|
128
|
-
2. User is redirected to the OAuth provider for authentication
|
|
129
|
-
3. Provider redirects back to `/mbkauthe/api/{provider}/login/callback`
|
|
130
|
-
4. System checks if the OAuth ID exists in the respective `user_{provider}` table
|
|
131
|
-
5. If found and user is active/authorized:
|
|
132
|
-
- If 2FA is enabled, redirect to 2FA page
|
|
133
|
-
- If no 2FA, complete login and redirect to home
|
|
134
|
-
6. If not found, redirect to login page with error
|
|
135
|
-
|
|
136
|
-
### Account Linking
|
|
137
|
-
Users must first link their OAuth account through your existing connection system (likely in user settings) before they can use OAuth login.
|
|
138
|
-
|
|
139
|
-
## API Routes Added
|
|
140
|
-
|
|
141
|
-
### GitHub Routes
|
|
142
|
-
|
|
143
|
-
#### `/mbkauthe/api/github/login`
|
|
144
|
-
- **Method**: GET
|
|
145
|
-
- **Description**: Initiates GitHub OAuth flow
|
|
146
|
-
- **Redirects to**: GitHub authorization page
|
|
147
|
-
|
|
148
|
-
#### `/mbkauthe/api/github/login/callback`
|
|
149
|
-
- **Method**: GET
|
|
150
|
-
- **Description**: Handles GitHub OAuth callback
|
|
151
|
-
- **Parameters**: `code` (from GitHub), `state` (optional)
|
|
152
|
-
- **Success**: Redirects to home page or configured redirect URL
|
|
153
|
-
- **Error**: Redirects to login page with error parameter
|
|
154
|
-
|
|
155
|
-
### Google Routes
|
|
156
|
-
|
|
157
|
-
#### `/mbkauthe/api/google/login`
|
|
158
|
-
- **Method**: GET
|
|
159
|
-
- **Description**: Initiates Google OAuth flow
|
|
160
|
-
- **Redirects to**: Google authorization page
|
|
161
|
-
|
|
162
|
-
#### `/mbkauthe/api/google/login/callback`
|
|
163
|
-
- **Method**: GET
|
|
164
|
-
- **Description**: Handles Google OAuth callback
|
|
165
|
-
- **Parameters**: `code` (from Google), `state` (optional)
|
|
166
|
-
- **Success**: Redirects to home page or configured redirect URL
|
|
167
|
-
- **Error**: Redirects to login page with error parameter
|
|
168
|
-
|
|
169
|
-
## Error Handling
|
|
170
|
-
|
|
171
|
-
The system handles various error cases:
|
|
172
|
-
- `github_auth_failed` / `google_auth_failed`: OAuth authentication failed
|
|
173
|
-
- `user_not_found`: OAuth account not linked to any user
|
|
174
|
-
- `account_inactive`: User account is deactivated
|
|
175
|
-
- `not_authorized`: User not authorized for this app
|
|
176
|
-
- `session_error`: Session save failed
|
|
177
|
-
- `internal_error`: General server error
|
|
178
|
-
|
|
179
|
-
## Testing
|
|
180
|
-
|
|
181
|
-
### GitHub Login
|
|
182
|
-
1. Create a test user in your `Users` table
|
|
183
|
-
2. Link a GitHub account to that user using your existing connection system
|
|
184
|
-
3. Try logging in with GitHub using the new login button
|
|
185
|
-
4. Check console logs for debugging information
|
|
186
|
-
|
|
187
|
-
### Google Login
|
|
188
|
-
1. Create a test user in your `Users` table
|
|
189
|
-
2. Link a Google account to that user using your existing connection system
|
|
190
|
-
3. Try logging in with Google using the new login button
|
|
191
|
-
4. Check console logs for debugging information
|
|
192
|
-
|
|
193
|
-
## Login Page Updates
|
|
194
|
-
|
|
195
|
-
The login page now includes:
|
|
196
|
-
- A "Login with GitHub" button
|
|
197
|
-
- A "Login with Google" button
|
|
198
|
-
- A divider ("or") between regular and OAuth login
|
|
199
|
-
- Proper styling that matches your existing design
|
|
200
|
-
|
|
201
|
-
## Security Notes
|
|
202
|
-
|
|
203
|
-
- Only users with active accounts can log in
|
|
204
|
-
- App authorization is checked (same as regular login)
|
|
205
|
-
- 2FA is respected if enabled
|
|
206
|
-
- Session management is handled the same way as regular login
|
|
207
|
-
- OAuth access tokens are stored securely
|
|
208
|
-
|
|
209
|
-
## Troubleshooting
|
|
210
|
-
|
|
211
|
-
1. **GitHub OAuth errors**: Check your GitHub OAuth app configuration
|
|
212
|
-
2. **Database errors**: Ensure `user_github` table exists and has proper relationships
|
|
213
|
-
3. **Session errors**: Check your session configuration
|
|
214
|
-
4. **2FA issues**: Verify 2FA table structure and configuration
|
|
215
|
-
|
|
216
|
-
## Environment Variables Summary
|
|
217
|
-
|
|
218
|
-
```env
|
|
219
|
-
# Required for GitHub Login
|
|
220
|
-
GITHUB_CLIENT_ID=your_github_client_id
|
|
221
|
-
GITHUB_CLIENT_SECRET=your_github_client_secret
|
|
161
|
+
CREATE INDEX IF NOT EXISTS idx_twofa_username ON "TwoFA" ("UserName");
|
|
162
|
+
CREATE INDEX IF NOT EXISTS idx_twofa_username_status ON "TwoFA" ("UserName", "TwoFAStatus");
|
|
222
163
|
```
|
|
223
164
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
## Database structure
|
|
165
|
+
---
|
|
227
166
|
|
|
228
|
-
|
|
167
|
+
## 6. Trusted Devices
|
|
229
168
|
|
|
230
|
-
|
|
169
|
+
Stores trusted device tokens used to remember a device and bypass 2FA challenges.
|
|
231
170
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
171
|
+
```sql
|
|
172
|
+
CREATE TABLE IF NOT EXISTS "TrustedDevices" (
|
|
173
|
+
"id" SERIAL PRIMARY KEY,
|
|
174
|
+
"UserName" VARCHAR(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
|
|
175
|
+
"DeviceToken" VARCHAR(64) UNIQUE NOT NULL,
|
|
176
|
+
"DeviceName" VARCHAR(255),
|
|
177
|
+
"UserAgent" TEXT,
|
|
178
|
+
"IpAddress" VARCHAR(45),
|
|
179
|
+
"CreatedAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
180
|
+
"ExpiresAt" TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
181
|
+
"LastUsed" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
182
|
+
);
|
|
236
183
|
|
|
184
|
+
CREATE INDEX IF NOT EXISTS idx_trusted_devices_token ON "TrustedDevices"("DeviceToken");
|
|
185
|
+
CREATE INDEX IF NOT EXISTS idx_trusted_devices_username ON "TrustedDevices"("UserName");
|
|
186
|
+
CREATE INDEX IF NOT EXISTS idx_trusted_devices_expires ON "TrustedDevices"("ExpiresAt");
|
|
187
|
+
```
|
|
237
188
|
|
|
238
|
-
|
|
189
|
+
---
|
|
239
190
|
|
|
240
|
-
|
|
191
|
+
## 7. API Tokens
|
|
241
192
|
|
|
242
|
-
|
|
243
|
-
- `UserName` (TEXT): The username of the user.
|
|
244
|
-
- `Password` (TEXT): The raw password of the user (used when EncPass=false).
|
|
245
|
-
- `PasswordEnc` (TEXT): The encrypted/hashed password of the user (used when EncPass=true).
|
|
246
|
-
- `Role` (ENUM): The role of the user. Possible values: `SuperAdmin`, `NormalUser`, `Guest`.
|
|
247
|
-
- `Active` (BOOLEAN): Indicates whether the user account is active.
|
|
248
|
-
- `HaveMailAccount` (BOOLEAN)(optional): Indicates if the user has a linked mail account.
|
|
249
|
-
- (SessionId removed) The application now stores multiple concurrent sessions in the `Sessions` table.
|
|
250
|
-
- `GuestRole` (JSONB): Stores additional guest-specific role information in binary JSON format.
|
|
251
|
-
- `AllowedApps`(JSONB): Array of applications the user is authorized to access.
|
|
252
|
-
- `Image` (TEXT): URL to the user's profile picture. Used by the `/mbkauthe/user/profilepic` route to serve profile images. If empty, the route returns the default icon.svg. The URL is cached in the session for performance and automatically refreshed on login/logout/account switch.
|
|
253
|
-
- `FullName` (VARCHAR): The full name/display name of the user.
|
|
254
|
-
- `email` (TEXT): The user's email address.
|
|
193
|
+
Stores long-lived API tokens used for programmatic access.
|
|
255
194
|
|
|
256
|
-
- **Schema:**
|
|
257
195
|
```sql
|
|
258
|
-
CREATE
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
"
|
|
263
|
-
"
|
|
264
|
-
"
|
|
265
|
-
|
|
266
|
-
"
|
|
267
|
-
"
|
|
268
|
-
"
|
|
269
|
-
|
|
270
|
-
"last_login" TIMESTAMP WITH TIME ZONE,
|
|
271
|
-
"PasswordEnc" VARCHAR(128),
|
|
272
|
-
|
|
273
|
-
"FullName" VARCHAR(255),
|
|
274
|
-
"email" TEXT DEFAULT 'support@mbktech.org',
|
|
275
|
-
"Image" TEXT DEFAULT 'https://portal.mbktech.org/icon.svg', -- Profile picture URL (used by /mbkauthe/user/profilepic route)
|
|
276
|
-
"Bio" TEXT DEFAULT 'I am ....',
|
|
277
|
-
"SocialAccounts" TEXT DEFAULT '{}',
|
|
278
|
-
"Positions" jsonb DEFAULT '{"Not_Permanent":"Member Is Not Permanent"}',
|
|
279
|
-
"resetToken" TEXT,
|
|
280
|
-
"resetTokenExpires" TimeStamp,
|
|
281
|
-
"resetAttempts" INTEGER DEFAULT '0',
|
|
282
|
-
"lastResetAttempt" TimeStamp WITH TIME ZONE
|
|
196
|
+
CREATE TABLE IF NOT EXISTS "ApiTokens" (
|
|
197
|
+
"id" SERIAL PRIMARY KEY,
|
|
198
|
+
"UserName" VARCHAR(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
|
|
199
|
+
"Name" VARCHAR(255) NOT NULL CHECK (LENGTH(TRIM("Name")) > 0),
|
|
200
|
+
"TokenHash" VARCHAR(128) NOT NULL UNIQUE,
|
|
201
|
+
"Prefix" VARCHAR(32) NOT NULL,
|
|
202
|
+
"Permissions" JSONB NOT NULL DEFAULT '{"scope":"read-only","allowedApps":null}'::jsonb
|
|
203
|
+
CHECK ("Permissions"->>'scope' IN ('read-only', 'write')),
|
|
204
|
+
"LastUsed" TIMESTAMP WITH TIME ZONE,
|
|
205
|
+
"CreatedAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
206
|
+
"ExpiresAt" TIMESTAMP WITH TIME ZONE
|
|
207
|
+
CHECK ("ExpiresAt" IS NULL OR "ExpiresAt" > "CreatedAt")
|
|
283
208
|
);
|
|
284
209
|
|
|
210
|
+
CREATE INDEX IF NOT EXISTS idx_apitokens_tokenhash
|
|
211
|
+
ON "ApiTokens" ("TokenHash");
|
|
285
212
|
|
|
286
|
-
CREATE INDEX IF NOT EXISTS
|
|
287
|
-
|
|
288
|
-
CREATE INDEX IF NOT EXISTS idx_users_active ON "Users" USING BTREE ("Active");
|
|
289
|
-
CREATE INDEX IF NOT EXISTS idx_users_email ON "Users" USING BTREE ("email");
|
|
290
|
-
CREATE INDEX IF NOT EXISTS idx_users_last_login ON "Users" USING BTREE (last_login);
|
|
291
|
-
-- JSONB GIN indexes for common filters/queries on JSON fields
|
|
292
|
-
CREATE INDEX IF NOT EXISTS idx_users_allowedapps_gin ON "Users" USING GIN ("AllowedApps");
|
|
293
|
-
CREATE INDEX IF NOT EXISTS idx_users_positions_gin ON "Users" USING GIN ("Positions");
|
|
213
|
+
CREATE INDEX IF NOT EXISTS idx_apitokens_username
|
|
214
|
+
ON "ApiTokens" ("UserName");
|
|
294
215
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- requires pgcrypto or uuid-ossp
|
|
299
|
-
"UserName" VARCHAR(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
|
|
300
|
-
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
|
301
|
-
expires_at TIMESTAMP WITH TIME ZONE,
|
|
302
|
-
meta JSONB
|
|
303
|
-
);
|
|
216
|
+
CREATE INDEX IF NOT EXISTS idx_apitokens_tokenhash_expires
|
|
217
|
+
ON "ApiTokens" ("TokenHash", "ExpiresAt")
|
|
218
|
+
WHERE "ExpiresAt" IS NOT NULL;
|
|
304
219
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_user_created ON "Sessions" ("UserName", created_at);
|
|
220
|
+
CREATE INDEX IF NOT EXISTS idx_apitokens_username_created
|
|
221
|
+
ON "ApiTokens" ("UserName", "CreatedAt" DESC);
|
|
308
222
|
|
|
309
|
-
|
|
223
|
+
CREATE INDEX IF NOT EXISTS idx_apitokens_expires
|
|
224
|
+
ON "ApiTokens" ("ExpiresAt")
|
|
225
|
+
WHERE "ExpiresAt" IS NOT NULL;
|
|
226
|
+
|
|
227
|
+
CREATE INDEX IF NOT EXISTS idx_apitokens_permissions_gin
|
|
228
|
+
ON "ApiTokens" USING GIN ("Permissions");
|
|
229
|
+
|
|
230
|
+
CREATE INDEX IF NOT EXISTS idx_apitokens_permissions_scope
|
|
231
|
+
ON "ApiTokens" (("Permissions"->>'scope'));
|
|
310
232
|
```
|
|
311
233
|
|
|
312
|
-
|
|
313
|
-
- When `EncPass=false` (default): The system uses the `Password` column to store and validate raw passwords
|
|
314
|
-
- When `EncPass=true` (recommended for production): The system uses the `PasswordEnc` column to store hashed passwords using PBKDF2 with the username as salt
|
|
315
|
-
- Only one password column should be populated based on your EncPass configuration
|
|
316
|
-
- The PasswordEnc field stores 128-character hex strings when using PBKDF2 hashing
|
|
234
|
+
### Token Permissions (JSONB)
|
|
317
235
|
|
|
318
|
-
|
|
236
|
+
The `Permissions` column stores both scope and allowed apps in a single JSONB structure:
|
|
319
237
|
|
|
320
|
-
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"scope": "read-only" | "write",
|
|
241
|
+
"allowedApps": null | ["app1", "app2"] | ["*"] | []
|
|
242
|
+
}
|
|
243
|
+
```
|
|
321
244
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
245
|
+
**Scope Values:**
|
|
246
|
+
- `read-only`: Allows only safe, read-only HTTP methods (GET, HEAD, OPTIONS)
|
|
247
|
+
- `write`: Allows all HTTP methods (GET, POST, PUT, DELETE, PATCH, etc.)
|
|
325
248
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
expire TimeStamp WITH TIME ZONE Not Null,
|
|
332
|
-
);
|
|
249
|
+
**AllowedApps Values:**
|
|
250
|
+
- `null` (default): Token inherits allowed apps from user's `AllowedApps` in Users table
|
|
251
|
+
- `["app1", "app2"]`: Token is restricted to specific apps (must be subset of user's apps)
|
|
252
|
+
- `["*"]`: Token has access to all user's apps (for non-SuperAdmin) or all apps in system (SuperAdmin only)
|
|
253
|
+
- `[]` (empty array): Token has no app access (effectively disabled)
|
|
333
254
|
|
|
334
|
-
|
|
335
|
-
CREATE INDEX IF NOT EXISTS idx_session_expire ON "session" ("expire");
|
|
336
|
-
CREATE INDEX IF NOT EXISTS idx_session_user_id ON "session" ((sess->'user'->>'id'));
|
|
337
|
-
```
|
|
255
|
+
**Note**: SuperAdmin users bypass all app permission checks, so their tokens work on any app regardless of the `allowedApps` value.
|
|
338
256
|
|
|
339
|
-
|
|
257
|
+
---
|
|
340
258
|
|
|
341
|
-
|
|
259
|
+
## 8. Seed Data
|
|
342
260
|
|
|
343
|
-
|
|
344
|
-
- `TwoFAStatus` (TEXT): The status of two-factor authentication (e.g., enabled, disabled).
|
|
345
|
-
- `TwoFASecret` (TEXT): The secret key used for two-factor authentication.
|
|
261
|
+
The schema includes a default `support` user (no encrypted password) to ensure at least one SuperAdmin account exists:
|
|
346
262
|
|
|
347
|
-
- **Schema:**
|
|
348
263
|
```sql
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
"TwoFASecret" TEXT
|
|
353
|
-
);
|
|
264
|
+
INSERT INTO "Users" ("UserName", "Password", "Role", "Active", "HaveMailAccount", "FullName")
|
|
265
|
+
VALUES ('support', '12345678', 'SuperAdmin', true, false, 'Support User')
|
|
266
|
+
ON CONFLICT ("UserName") DO NOTHING;
|
|
354
267
|
|
|
355
|
-
|
|
356
|
-
CREATE INDEX IF NOT EXISTS idx_twofa_username ON "TwoFA" ("UserName");
|
|
357
|
-
CREATE INDEX IF NOT EXISTS idx_twofa_username_status ON "TwoFA" ("UserName", "TwoFAStatus");
|
|
268
|
+
SELECT * FROM "Users" WHERE "UserName" = 'support';
|
|
358
269
|
```
|
|
359
270
|
|
|
360
|
-
### Trusted Devices Table (Remember 2FA Device)
|
|
361
|
-
|
|
362
|
-
- **Columns:**
|
|
363
|
-
|
|
364
|
-
- `id` (INTEGER, auto-increment, primary key): Unique identifier for each trusted device.
|
|
365
|
-
- `UserName` (VARCHAR): The username of the device owner (foreign key to Users).
|
|
366
|
-
- `DeviceToken` (VARCHAR): **HMAC-SHA256 hash** of the device token (raw token is only sent to the client in an httpOnly cookie and **not** stored in plaintext).
|
|
367
|
-
- `DeviceName` (VARCHAR): Optional friendly name for the device.
|
|
368
|
-
- `UserAgent` (TEXT): Browser/client user agent string.
|
|
369
|
-
- `IpAddress` (VARCHAR): IP address when device was trusted.
|
|
370
|
-
- `CreatedAt` (TIMESTAMP): When the device was first trusted.
|
|
371
|
-
- `ExpiresAt` (TIMESTAMP): When the device trust expires.
|
|
372
|
-
- `LastUsed` (TIMESTAMP): Last time this device was used for login.
|
|
373
271
|
|
|
374
272
|
- **Schema:**
|
|
375
273
|
```sql
|
|
@@ -397,22 +295,22 @@ To add new users to the `Users` table, use the following SQL queries:
|
|
|
397
295
|
|
|
398
296
|
**For Raw Password Storage (EncPass=false):**
|
|
399
297
|
```sql
|
|
400
|
-
INSERT INTO "Users" ("UserName", "Password", "Role", "Active", "HaveMailAccount"
|
|
401
|
-
VALUES ('support', '12345678', 'SuperAdmin', true, false
|
|
298
|
+
INSERT INTO "Users" ("UserName", "Password", "Role", "Active", "HaveMailAccount")
|
|
299
|
+
VALUES ('support', '12345678', 'SuperAdmin', true, false);
|
|
402
300
|
|
|
403
|
-
INSERT INTO "Users" ("UserName", "Password", "Role", "Active", "HaveMailAccount"
|
|
404
|
-
VALUES ('test', '12345678', 'NormalUser', true, false
|
|
301
|
+
INSERT INTO "Users" ("UserName", "Password", "Role", "Active", "HaveMailAccount")
|
|
302
|
+
VALUES ('test', '12345678', 'NormalUser', true, false);
|
|
405
303
|
```
|
|
406
304
|
|
|
407
305
|
**For Encrypted Password Storage (EncPass=true):**
|
|
408
306
|
```sql
|
|
409
307
|
-- Note: You'll need to hash the password using the hashPassword function
|
|
410
308
|
-- Example with pre-hashed password (PBKDF2 with username as salt)
|
|
411
|
-
INSERT INTO "Users" ("UserName", "PasswordEnc", "Role", "Active", "HaveMailAccount"
|
|
412
|
-
VALUES ('support', 'your_hashed_password_here', 'SuperAdmin', true, false
|
|
309
|
+
INSERT INTO "Users" ("UserName", "PasswordEnc", "Role", "Active", "HaveMailAccount")
|
|
310
|
+
VALUES ('support', 'your_hashed_password_here', 'SuperAdmin', true, false);
|
|
413
311
|
|
|
414
|
-
INSERT INTO "Users" ("UserName", "PasswordEnc", "Role", "Active", "HaveMailAccount"
|
|
415
|
-
VALUES ('test', 'your_hashed_password_here', 'NormalUser', true, false
|
|
312
|
+
INSERT INTO "Users" ("UserName", "PasswordEnc", "Role", "Active", "HaveMailAccount")
|
|
313
|
+
VALUES ('test', 'your_hashed_password_here', 'NormalUser', true, false);
|
|
416
314
|
```
|
|
417
315
|
|
|
418
316
|
**Configuration Notes:**
|
|
@@ -421,7 +319,6 @@ To add new users to the `Users` table, use the following SQL queries:
|
|
|
421
319
|
- For encrypted passwords: Use the hashPassword function to generate the hash before inserting.
|
|
422
320
|
- Adjust the `Role` values as needed (`SuperAdmin`, `NormalUser`, or `Guest`).
|
|
423
321
|
- Modify the `Active` and `HaveMailAccount` values as required.
|
|
424
|
-
- Update the `GuestRole` JSON object if specific permissions are required (this functionality is under construction).
|
|
425
322
|
|
|
426
323
|
**Generating Encrypted Passwords:**
|
|
427
324
|
If you're using `EncPass=true`, you can generate encrypted passwords using the hashPassword function:
|