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 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(255) REFERENCES "Users"("UserName"),
32
- github_id VARCHAR(255) UNIQUE NOT NULL,
31
+ user_name VARCHAR(50) REFERENCES "Users"("UserName"),
32
+ github_id VARCHAR(255) UNIQUE,
33
33
  github_username VARCHAR(255),
34
- access_token TEXT,
35
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
36
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
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
- ```sql
153
- CREATE TABLE "Users" (
154
- id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
155
- "UserName" TEXT NOT NULL,
156
- "Password" TEXT NOT NULL,
157
- "Role" TEXT CHECK("Role" IN ('SuperAdmin', 'NormalUser', 'Guest')) NOT NULL DEFAULT 'NormalUser'::text,
158
- "Active" BOOLEAN NOT NULL DEFAULT true,
159
- "HaveMailAccount" BOOLEAN NOT NULL DEFAULT false,
160
- "SessionId" TEXT,
161
- "AllowedApps" JSONB DEFAULT '["mbkauthe"]'::jsonb
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
- ```sql
175
- CREATE TABLE session (
176
- sid VARCHAR PRIMARY KEY,
177
- sess JSON NOT NULL,
178
- expire TIMESTAMP NOT NULL
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
- ```sql
192
- CREATE TABLE "TwoFA" (
193
- "UserName" TEXT NOT NULL PRIMARY KEY,
194
- "TwoFAStatus" TEXT NOT NULL DEFAULT false,
195
- "TwoFASecret" TEXT NOT NULL
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
- const query = `SELECT * FROM "Users" WHERE "SessionId" = $1`;
86
- const result = await dblogin.query(query, [sessionId]);
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
- const sessionId = crypto.randomBytes(256).toString("hex");
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
- try {
145
- await dblogin.query(
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
- const userQuery = `SELECT * FROM "Users" WHERE "UserName" = $1`;
228
- const userResult = await dblogin.query(userQuery, [username]);
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
- const query = `SELECT "TwoFAStatus" FROM "TwoFA" WHERE "UserName" = $1`;
271
- const twoFAResult = await dblogin.query(query, [username]);
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
- const query = `SELECT * FROM "Users" WHERE "SessionId" = $1`;
19
- const result = await dblogin.query(query, [sessionId]);
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
- const query = `SELECT "SessionId", "Active", "Role", "AllowedApps" FROM "Users" WHERE "id" = $1`;
53
- const result = await dblogin.query(query, [id]);
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
- const query = `SELECT "Role" FROM "Users" WHERE "id" = $1`;
136
- const result = await dblogin.query(query, [userId]);
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
- console.log("[mbkauthe] [authapi] Querying database to validate token");
220
- const tokenQuery = 'SELECT * FROM "UserAuthApiKey" WHERE "key" = $1';
221
- pool.query(tokenQuery, [token], (err, result) => {
222
- if (err) {
223
- console.error("[mbkauthe] [authapi] Database query error while validating token:", err);
224
- return res.status(500).json({
225
- success: false,
226
- message: "Internal Server Error"
227
- });
228
- }
229
-
230
- if (result.rows.length === 0) {
231
- console.log("[mbkauthe] [authapi] Invalid token provided:", token);
232
- return res.status(401).json({
233
- success: false,
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
- if (userResult.rows.length === 0) {
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 (username === "demo") {
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
- const user = userResult.rows[0];
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.role,
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mbkauthe",
3
- "version": "1.3.4",
3
+ "version": "1.3.5",
4
4
  "description": "MBKTechStudio's reusable authentication system for Node.js applications.",
5
5
  "main": "index.js",
6
6
  "type": "module",