mbkauthe 4.6.1 → 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 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
- # OAuth Login Setup Guide
1
+ # Database Schema
2
2
 
3
- ## Overview
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
- ## Setup Instructions
5
+ ---
7
6
 
8
- ### 1. Environment Variables
9
- Add these to your `.env` file:
7
+ ## 1. Roles
10
8
 
11
- ```env
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
- # Google OAuth App Configuration
17
- GOOGLE_CLIENT_ID=your_google_client_id
18
- GOOGLE_CLIENT_SECRET=your_google_client_secret
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
- ### 2. GitHub OAuth App Setup
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
- ### 3. Google OAuth App Setup
28
- 1. Go to Google Cloud Console (https://console.cloud.google.com/)
29
- 2. Create a new project or select an existing one
30
- 3. Enable the Google+ API
31
- 4. Go to Credentials > Create Credentials > OAuth 2.0 Client ID
32
- 5. Set the application type to "Web application"
33
- 6. Add authorized redirect URI: `https://yourdomain.com/mbkauthe/api/google/login/callback`
34
- 7. Copy the Client ID and Client Secret to your `.env` file
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
- ### 4. Database Schema
37
- Ensure your OAuth tables exist with these columns:
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
- -- GitHub users table
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
- -- Google users table
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
- ### 5. API Tokens Table
72
- Used for storing persistent API keys.
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 "ApiTokens" (
76
- "id" SERIAL PRIMARY KEY,
77
- "UserName" VARCHAR(50) NOT NULL REFERENCES "Users"("UserName") ON DELETE CASCADE,
78
- "Name" VARCHAR(255) NOT NULL, -- User-provided friendly name
79
- "TokenHash" VARCHAR(128) NOT NULL UNIQUE, -- Hashed access token (SHA-256)
80
- "Prefix" VARCHAR(32) NOT NULL, -- First few chars of token for ID
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 idx_apitokens_tokenhash ON "ApiTokens" ("TokenHash");
89
- CREATE INDEX IF NOT EXISTS idx_apitokens_username ON "ApiTokens" ("UserName");
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
- **Token Permissions (JSONB):**
135
+ ### Express Session Store (`session`)
93
136
 
94
- The `Permissions` column stores both scope and allowed apps in a single JSONB structure for optimal performance:
137
+ Used by `express-session` when configured to store sessions in Postgres.
95
138
 
96
- ```json
97
- {
98
- "scope": "read-only" | "write",
99
- "allowedApps": null | ["app1", "app2"] | ["*"] | []
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
- **Scope Values:**
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
- **AllowedApps Values:**
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
- **Note**: SuperAdmin users bypass all app permission checks, so their tokens work on any app regardless of the `allowedApps` value.
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
- **Security:**
116
- - Tokens are stored as SHA-256 hashes (never plain text)
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
- The GitHub login feature is now fully integrated into your mbkauthe system and ready to use!
225
-
226
- ## Database structure
165
+ ---
227
166
 
228
- [<- Back](README.md)
167
+ ## 6. Trusted Devices
229
168
 
230
- ## Table of Contents
169
+ Stores trusted device tokens used to remember a device and bypass 2FA challenges.
231
170
 
232
- 1. [Users Table](#users-table)
233
- 2. [Session Table](#session-table)
234
- 3. [Two-Factor Authentication Table](#two-factor-authentication-table)
235
- 4. [Query to Add a User](#query-to-add-a-user)
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
- ### Users Table
189
+ ---
239
190
 
240
- - **Columns:**
191
+ ## 7. API Tokens
241
192
 
242
- - `id` (INTEGER, auto-increment, primary key): Unique identifier for each user.
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 TYPE role AS ENUM ('SuperAdmin', 'NormalUser', 'Guest');
259
-
260
- CREATE TABLE "Users" (
261
- id INTEGER PRIMARY KEY AUTOINCREMENT AS IDENTITY,
262
- "UserName" VARCHAR(50) NOT NULL UNIQUE,
263
- "Password" VARCHAR(255) NOT NULL,
264
- "Active" BOOLEAN DEFAULT FALSE,
265
- "Role" role DEFAULT 'NormalUser' NOT NULL,
266
- "HaveMailAccount" BOOLEAN DEFAULT FALSE,
267
- "AllowedApps" JSONB DEFAULT '["mbkauthe", "portal"]',
268
- "created_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
269
- "updated_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
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 idx_users_username ON "Users" USING BTREE ("UserName");
287
- CREATE INDEX IF NOT EXISTS idx_users_role ON "Users" USING BTREE ("Role");
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
- -- Application Sessions table (stores multiple concurrent sessions per user)
296
- -- Note: this is separate from the express-session store table named "session"
297
- CREATE TABLE "Sessions" (
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
- -- Indexes optimized by username instead of numeric user id
306
- CREATE INDEX IF NOT EXISTS idx_sessions_username ON "Sessions" ("UserName");
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
- **Multi-session behavior:** MBKAuthe supports multiple concurrent application sessions per user. The maximum number of concurrent sessions is controlled by `mbkautheVar.MAX_SESSIONS_PER_USER` (default: 5). When a new session would exceed that limit, the system prunes the oldest session(s) for that user (ordered by `created_at`) to keep the count within the configured maximum.
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
- **Password Storage Notes:**
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
- ### Session Table
236
+ The `Permissions` column stores both scope and allowed apps in a single JSONB structure:
319
237
 
320
- - **Columns:**
238
+ ```json
239
+ {
240
+ "scope": "read-only" | "write",
241
+ "allowedApps": null | ["app1", "app2"] | ["*"] | []
242
+ }
243
+ ```
321
244
 
322
- - `sid` (VARCHAR, primary key): Unique session identifier.
323
- - `sess` (JSON): Session data stored in JSON format.
324
- - `expire` (TIMESTAMP): Expiration timestamp for the session.
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
- - **Schema:**
327
- ```sql
328
- CREATE TABLE "session" (
329
- sid VARCHAR(33) PRIMARY KEY NOT NULL,
330
- sess JSONB NOT NULL,
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
- -- Add indexes for performance optimization
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
- ### Two-Factor Authentication Table
257
+ ---
340
258
 
341
- - **Columns:**
259
+ ## 8. Seed Data
342
260
 
343
- - `UserName` (TEXT): The username of the user.
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
- CREATE TABLE "TwoFA" (
350
- "UserName" VARCHAR(50) primary key REFERENCES "Users"("UserName"),
351
- "TwoFAStatus" boolean NOT NULL,
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
- -- Add indexes for performance optimization
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", "GuestRole")
401
- VALUES ('support', '12345678', 'SuperAdmin', true, false, '{"allowPages": [""], "NotallowPages": [""]}'::jsonb);
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", "GuestRole")
404
- VALUES ('test', '12345678', 'NormalUser', true, false, '{"allowPages": [""], "NotallowPages": [""]}'::jsonb);
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", "GuestRole")
412
- VALUES ('support', 'your_hashed_password_here', 'SuperAdmin', true, false, '{"allowPages": [""], "NotallowPages": [""]}'::jsonb);
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", "GuestRole")
415
- VALUES ('test', 'your_hashed_password_here', 'NormalUser', true, false, '{"allowPages": [""], "NotallowPages": [""]}'::jsonb);
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: