mbkauthe 1.3.4 → 1.3.5
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/docs/db.md +54 -31
- package/lib/main.js +11 -16
- package/lib/pool.js +7 -0
- package/lib/validateSessionAndRole.js +33 -67
- package/package.json +1 -1
package/docs/db.md
CHANGED
|
@@ -28,12 +28,12 @@ Ensure your `user_github` table exists with these columns:
|
|
|
28
28
|
```sql
|
|
29
29
|
CREATE TABLE user_github (
|
|
30
30
|
id SERIAL PRIMARY KEY,
|
|
31
|
-
user_name VARCHAR(
|
|
32
|
-
github_id VARCHAR(255) UNIQUE
|
|
31
|
+
user_name VARCHAR(50) REFERENCES "Users"("UserName"),
|
|
32
|
+
github_id VARCHAR(255) UNIQUE,
|
|
33
33
|
github_username VARCHAR(255),
|
|
34
|
-
access_token
|
|
35
|
-
created_at
|
|
36
|
-
updated_at
|
|
34
|
+
access_token VARCHAR(255),
|
|
35
|
+
created_at TimeStamp WITH TIME ZONE DEFAULT NOW(),
|
|
36
|
+
updated_at TimeStamp WITH TIME ZONE DEFAULT NOW()
|
|
37
37
|
);
|
|
38
38
|
```
|
|
39
39
|
|
|
@@ -149,18 +149,32 @@ The GitHub login feature is now fully integrated into your mbkauthe system and r
|
|
|
149
149
|
- `AllowedApps`(JSONB):
|
|
150
150
|
|
|
151
151
|
- **Schema:**
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
152
|
+
```sql
|
|
153
|
+
CREATE TYPE role AS ENUM ('SuperAdmin', 'NormalUser', 'Guest');
|
|
154
|
+
|
|
155
|
+
CREATE TABLE "Users" (
|
|
156
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
157
|
+
"UserName" VARCHAR(50) NOT NULL UNIQUE,
|
|
158
|
+
"Password" VARCHAR(61) NOT NULL, -- For bcrypt hash
|
|
159
|
+
"Role" role DEFAULT 'NormalUser' NOT NULL,
|
|
160
|
+
"Active" BOOLEAN DEFAULT FALSE,
|
|
161
|
+
"HaveMailAccount" BOOLEAN DEFAULT FALSE,
|
|
162
|
+
"AllowedApps" JSONB DEFAULT '["mbkauthe", "portal"]',
|
|
163
|
+
"SessionId" VARCHAR(213),
|
|
164
|
+
"IsOnline" BOOLEAN DEFAULT FALSE,
|
|
165
|
+
"created_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
166
|
+
"updated_at" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
167
|
+
"last_login" TIMESTAMP WITH TIME ZONE
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
-- Add indexes for Users table
|
|
171
|
+
CREATE INDEX IF NOT EXISTS idx_users_session_id ON "Users" ("SessionId")
|
|
172
|
+
CREATE INDEX idx_users_username ON "Users" USING BTREE ("UserName");
|
|
173
|
+
CREATE INDEX idx_users_role ON "Users" USING BTREE ("Role");
|
|
174
|
+
CREATE INDEX idx_users_active ON "Users" USING BTREE ("Active");
|
|
175
|
+
CREATE INDEX idx_users_isonline ON "Users" USING BTREE ("IsOnline");
|
|
176
|
+
CREATE INDEX idx_users_last_login ON "Users" USING BTREE (last_login);
|
|
177
|
+
```
|
|
164
178
|
|
|
165
179
|
### Session Table
|
|
166
180
|
|
|
@@ -171,13 +185,20 @@ The GitHub login feature is now fully integrated into your mbkauthe system and r
|
|
|
171
185
|
- `expire` (TIMESTAMP): Expiration timestamp for the session.
|
|
172
186
|
|
|
173
187
|
- **Schema:**
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
188
|
+
```sql
|
|
189
|
+
CREATE TABLE "session" (
|
|
190
|
+
sid VARCHAR(33) PRIMARY KEY NOT NULL,
|
|
191
|
+
sess JSONB NOT NULL,
|
|
192
|
+
expire TimeStamp WITH TIME ZONE Not Null,
|
|
193
|
+
"UserName" VARCHAR(50) REFERENCES "Users"("UserName"),
|
|
194
|
+
last_activity TimeStamp WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
-- Add indexes for session table
|
|
198
|
+
CREATE INDEX idx_session_expire ON "session" USING BTREE (expire);
|
|
199
|
+
CREATE INDEX idx_session_username ON "session" USING BTREE ("UserName");
|
|
200
|
+
CREATE INDEX idx_session_last_activity ON "session" USING BTREE (last_activity);
|
|
201
|
+
```
|
|
181
202
|
|
|
182
203
|
### Two-Factor Authentication Table
|
|
183
204
|
|
|
@@ -188,13 +209,15 @@ The GitHub login feature is now fully integrated into your mbkauthe system and r
|
|
|
188
209
|
- `TwoFASecret` (TEXT): The secret key used for two-factor authentication.
|
|
189
210
|
|
|
190
211
|
- **Schema:**
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
212
|
+
```sql
|
|
213
|
+
CREATE TABLE "TwoFA" (
|
|
214
|
+
"UserName" VARCHAR(50) primary key REFERENCES "Users"("UserName"),
|
|
215
|
+
"TwoFAStatus" boolean NOT NULL,
|
|
216
|
+
"TwoFASecret" TEXT
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
CREATE INDEX IF NOT EXISTS idx_twofa_username ON "TwoFA" ("UserName")
|
|
220
|
+
```
|
|
198
221
|
|
|
199
222
|
### Query to Add a User
|
|
200
223
|
|
package/lib/main.js
CHANGED
|
@@ -82,8 +82,8 @@ router.use(async (req, res, next) => {
|
|
|
82
82
|
if (!req.session.user && req.cookies.sessionId) {
|
|
83
83
|
try {
|
|
84
84
|
const sessionId = req.cookies.sessionId;
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
const query = `SELECT id, "UserName", "Active", "Role", "SessionId" FROM "Users" WHERE "SessionId" = $1`;
|
|
86
|
+
const result = await dblogin.query({ name: 'get-user-by-sessionid', text: query, values: [sessionId] });
|
|
87
87
|
|
|
88
88
|
if (result.rows.length > 0) {
|
|
89
89
|
const user = result.rows[0];
|
|
@@ -112,9 +112,10 @@ const getCookieOptions = () => ({
|
|
|
112
112
|
|
|
113
113
|
async function completeLoginProcess(req, res, user, redirectUrl = null) {
|
|
114
114
|
try {
|
|
115
|
-
|
|
115
|
+
// smaller session id is sufficient and faster to generate/serialize
|
|
116
|
+
const sessionId = crypto.randomBytes(32).toString("hex");
|
|
116
117
|
console.log(`[mbkauthe] Generated session ID for username: ${user.username}`);
|
|
117
|
-
|
|
118
|
+
|
|
118
119
|
// Delete old session record for this user
|
|
119
120
|
await dblogin.query('DELETE FROM "session" WHERE username = $1', [user.username]);
|
|
120
121
|
|
|
@@ -141,14 +142,8 @@ async function completeLoginProcess(req, res, user, redirectUrl = null) {
|
|
|
141
142
|
console.log("[mbkauthe] Session save error:", err);
|
|
142
143
|
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
143
144
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
'UPDATE "session" SET username = $1 WHERE sid = $2',
|
|
147
|
-
[user.username, req.sessionID]
|
|
148
|
-
);
|
|
149
|
-
} catch (e) {
|
|
150
|
-
console.log("[mbkauthe] Failed to update username in session table:", e);
|
|
151
|
-
}
|
|
145
|
+
// avoid writing back into the session table here to reduce DB writes;
|
|
146
|
+
// the pg session store will already persist the session data.
|
|
152
147
|
|
|
153
148
|
const cookieOptions = getCookieOptions();
|
|
154
149
|
res.cookie("sessionId", sessionId, cookieOptions);
|
|
@@ -224,8 +219,8 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
224
219
|
}
|
|
225
220
|
|
|
226
221
|
try {
|
|
227
|
-
|
|
228
|
-
|
|
222
|
+
const userQuery = `SELECT id, "UserName", "Password", "Active", "Role", "AllowedApps" FROM "Users" WHERE "UserName" = $1`;
|
|
223
|
+
const userResult = await dblogin.query({ name: 'get-user-by-username', text: userQuery, values: [username] });
|
|
229
224
|
|
|
230
225
|
if (userResult.rows.length === 0) {
|
|
231
226
|
console.log(`[mbkauthe] Username does not exist: ${username}`);
|
|
@@ -267,8 +262,8 @@ router.post("/mbkauthe/api/login", LoginLimit, async (req, res) => {
|
|
|
267
262
|
}
|
|
268
263
|
|
|
269
264
|
if ((mbkautheVar.MBKAUTH_TWO_FA_ENABLE || "").toLocaleLowerCase() === "true") {
|
|
270
|
-
|
|
271
|
-
|
|
265
|
+
const query = `SELECT "TwoFAStatus" FROM "TwoFA" WHERE "UserName" = $1`;
|
|
266
|
+
const twoFAResult = await dblogin.query({ name: 'get-2fa-status', text: query, values: [username] });
|
|
272
267
|
|
|
273
268
|
if (twoFAResult.rows.length > 0 && twoFAResult.rows[0].TwoFAStatus) {
|
|
274
269
|
// 2FA is enabled, prompt for token on a separate page
|
package/lib/pool.js
CHANGED
|
@@ -32,6 +32,13 @@ const poolConfig = {
|
|
|
32
32
|
rejectUnauthorized: true,
|
|
33
33
|
},
|
|
34
34
|
|
|
35
|
+
// Connection pool tuning for serverless/ephemeral environments (Vercel)
|
|
36
|
+
// - keep max small to avoid exhausting DB connections
|
|
37
|
+
// - reduce idle time so connections are returned sooner
|
|
38
|
+
// - set a short connection timeout to fail fast
|
|
39
|
+
max: 6,
|
|
40
|
+
idleTimeoutMillis: 50000,
|
|
41
|
+
connectionTimeoutMillis: 5000,
|
|
35
42
|
};
|
|
36
43
|
|
|
37
44
|
export const dblogin = new Pool(poolConfig);
|
|
@@ -15,8 +15,8 @@ async function validateSession(req, res, next) {
|
|
|
15
15
|
if (!req.session.user && req.cookies.sessionId) {
|
|
16
16
|
try {
|
|
17
17
|
const sessionId = req.cookies.sessionId;
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const query = `SELECT id, "UserName", "Active", "Role", "SessionId" FROM "Users" WHERE "SessionId" = $1`;
|
|
19
|
+
const result = await dblogin.query({ name: 'get-user-by-sessionid', text: query, values: [sessionId] });
|
|
20
20
|
const userResult = result.rows[0];
|
|
21
21
|
|
|
22
22
|
if (result.rows.length > 0) {
|
|
@@ -49,8 +49,8 @@ async function validateSession(req, res, next) {
|
|
|
49
49
|
|
|
50
50
|
try {
|
|
51
51
|
const { id, sessionId } = req.session.user;
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
const query = `SELECT "SessionId", "Active", "Role", "AllowedApps" FROM "Users" WHERE "id" = $1`;
|
|
53
|
+
const result = await dblogin.query({ name: 'get-user-by-id', text: query, values: [id] });
|
|
54
54
|
const userResult = result.rows[0];
|
|
55
55
|
|
|
56
56
|
if (result.rows.length === 0 || userResult.SessionId !== sessionId) {
|
|
@@ -132,8 +132,8 @@ const checkRolePermission = (requiredRole, notAllowed) => {
|
|
|
132
132
|
|
|
133
133
|
const userId = req.session.user.id;
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
const query = `SELECT "Role" FROM "Users" WHERE "id" = $1`;
|
|
136
|
+
const result = await dblogin.query({ name: 'get-role-by-id', text: query, values: [userId] });
|
|
137
137
|
|
|
138
138
|
if (result.rows.length === 0) {
|
|
139
139
|
return res.status(401).json({ success: false, message: "User not found" });
|
|
@@ -216,84 +216,50 @@ const authapi = (requiredRole = []) => {
|
|
|
216
216
|
});
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
message: "The AuthApiToken Is Invalid"
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const username = result.rows[0].username;
|
|
239
|
-
console.log("[mbkauthe] [authapi] Token is valid. Associated username:", username);
|
|
240
|
-
|
|
241
|
-
console.log("[mbkauthe] [authapi] Querying database to validate user and role");
|
|
242
|
-
const userQuery = `
|
|
243
|
-
SELECT id, "UserName", "Active", "Role" FROM "Users"
|
|
244
|
-
WHERE "UserName" = $1 AND "Active" = true
|
|
245
|
-
`;
|
|
246
|
-
|
|
247
|
-
pool.query(userQuery, [username], (err, userResult) => {
|
|
248
|
-
if (err) {
|
|
249
|
-
console.error("[mbkauthe] [authapi] Database query error while validating user:", err);
|
|
250
|
-
return res.status(500).json({
|
|
251
|
-
success: false,
|
|
252
|
-
message: "Internal Server Error"
|
|
253
|
-
});
|
|
219
|
+
// Single query to validate API key and fetch user in one DB round trip.
|
|
220
|
+
(async () => {
|
|
221
|
+
try {
|
|
222
|
+
const jointQuery = `
|
|
223
|
+
SELECT u.id, u."UserName", u."Active", u."Role", k."key" as apikey
|
|
224
|
+
FROM "UserAuthApiKey" k
|
|
225
|
+
JOIN "Users" u ON u."UserName" = k.username
|
|
226
|
+
+ WHERE k."key" = $1 AND u."Active" = true
|
|
227
|
+
+ LIMIT 1
|
|
228
|
+
+ `;
|
|
229
|
+
|
|
230
|
+
const result = await pool.query(jointQuery, [token]);
|
|
231
|
+
|
|
232
|
+
if (result.rows.length === 0) {
|
|
233
|
+
console.log("[mbkauthe] [authapi] Invalid token or associated user inactive:", token);
|
|
234
|
+
return res.status(401).json({ success: false, message: "The AuthApiToken Is Invalid or user inactive" });
|
|
254
235
|
}
|
|
255
236
|
|
|
256
|
-
|
|
257
|
-
console.log("[mbkauthe] [authapi] User does not exist or is not active. Username:", username);
|
|
258
|
-
return res.status(401).json({
|
|
259
|
-
success: false,
|
|
260
|
-
message: "User does not exist or is not active",
|
|
261
|
-
});
|
|
262
|
-
}
|
|
237
|
+
const user = result.rows[0];
|
|
263
238
|
|
|
264
|
-
if (
|
|
239
|
+
if (user.UserName === "demo") {
|
|
265
240
|
console.log("[mbkauthe] [authapi] Demo user attempted to access an endpoint. Access denied.");
|
|
266
|
-
return res.status(401).json({
|
|
267
|
-
success: false,
|
|
268
|
-
message: "Demo user is not allowed to access endpoints",
|
|
269
|
-
});
|
|
241
|
+
return res.status(401).json({ success: false, message: "Demo user is not allowed to access endpoints" });
|
|
270
242
|
}
|
|
271
243
|
|
|
272
|
-
|
|
273
|
-
console.log("[mbkauthe] [authapi] User is valid. User details:", user);
|
|
274
|
-
|
|
275
|
-
// Check if role is required and if user has it
|
|
244
|
+
// role check
|
|
276
245
|
if ((requiredRole && user.Role !== requiredRole) && user.Role !== "SuperAdmin") {
|
|
277
246
|
console.log(`[mbkauthe] [authapi] User does not have the required role. Required: ${requiredRole}, User's role: ${user.Role}`);
|
|
278
|
-
return res.status(403).json({
|
|
279
|
-
success: false,
|
|
280
|
-
message: `Access denied. Required role: ${requiredRole}`,
|
|
281
|
-
});
|
|
247
|
+
return res.status(403).json({ success: false, message: `Access denied. Required role: ${requiredRole}` });
|
|
282
248
|
}
|
|
283
249
|
|
|
284
|
-
console.log("[mbkauthe] [authapi] User has the required role or no specific role is required. Proceeding to next middleware.");
|
|
285
250
|
req.user = {
|
|
286
251
|
username: user.UserName,
|
|
287
252
|
UserName: user.UserName,
|
|
288
253
|
role: user.Role,
|
|
289
|
-
Role: user.
|
|
290
|
-
// Add other user properties you might need
|
|
254
|
+
Role: user.Role,
|
|
291
255
|
};
|
|
292
256
|
|
|
293
|
-
console.log("[mbkauthe] [authapi] Token and user validation successful. Passing control to next middleware.");
|
|
294
257
|
next();
|
|
295
|
-
})
|
|
296
|
-
|
|
258
|
+
} catch (err) {
|
|
259
|
+
console.error("[mbkauthe] [authapi] Database error while validating token/user:", err);
|
|
260
|
+
return res.status(500).json({ success: false, message: "Internal Server Error" });
|
|
261
|
+
}
|
|
262
|
+
})();
|
|
297
263
|
};
|
|
298
264
|
};
|
|
299
265
|
|