mbkauthe 1.0.2 → 1.0.4

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/index.js ADDED
@@ -0,0 +1,23 @@
1
+ import dotenv from "dotenv";
2
+ import Joi from "joi";
3
+ import router from "./lib/main.js";
4
+ dotenv.config();
5
+
6
+ const envSchema = Joi.object({
7
+ RECAPTCHA_SECRET_KEY: Joi.string().required(),
8
+ SESSION_SECRET_KEY: Joi.string().required(),
9
+ IS_DEPLOYED: Joi.string().valid("true", "false").required(),
10
+ LOGIN_DB: Joi.string().uri().required(),
11
+ MBKAUTH_TWO_FA_ENABLE: Joi.string().valid("true", "false").required(),
12
+ COOKIE_EXPIRE_TIME: Joi.number().integer().positive(),
13
+ DOMAIN: Joi.string().required(),
14
+ }).unknown(true);
15
+
16
+ const { error } = envSchema.validate(process.env);
17
+ if (error) {
18
+ throw new Error(`Environment variable validation error: ${error.message}`);
19
+ }
20
+ export { validateSession, checkRolePermission, validateSessionAndRole, getUserData } from "./lib/validateSessionAndRole.js";
21
+ export { authenticate } from "./lib/auth.js";
22
+ export { dblogin } from "./lib/pool.js";
23
+ export default router;
package/lib/auth.js ADDED
@@ -0,0 +1,13 @@
1
+ export const authenticate = (authentication) => {
2
+ return (req, res, next) => {
3
+ const token = req.headers["authorization"];
4
+ console.log(`Received token: ${token}`);
5
+ if (token === authentication) {
6
+ console.log("Authentication successful");
7
+ next();
8
+ } else {
9
+ console.log("Authentication failed");
10
+ res.status(401).send("Unauthorized");
11
+ }
12
+ };
13
+ };
package/lib/main.js ADDED
@@ -0,0 +1,333 @@
1
+ import express from "express";
2
+ import crypto from "crypto";
3
+ import session from "express-session";
4
+ import pgSession from "connect-pg-simple";
5
+ const PgSession = pgSession(session);
6
+ import dotenv from "dotenv";
7
+ import { dblogin } from "./pool.js";
8
+ import { authenticate } from "./auth.js";
9
+ import fetch from 'node-fetch';
10
+ import cookieParser from "cookie-parser"; // Import cookie-parser
11
+
12
+ dotenv.config();
13
+ const router = express.Router();
14
+ let COOKIE_EXPIRE_TIME = 2 * 24 * 60 * 60 * 1000; //2 days
15
+
16
+ try {
17
+ const parsedExpireTime = parseInt(process.env.COOKIE_EXPIRE_TIME, 10);
18
+ if (!isNaN(parsedExpireTime) && parsedExpireTime > 0) {
19
+ COOKIE_EXPIRE_TIME = parsedExpireTime * 24 * 60 * 60 * 1000; // Convert days to milliseconds
20
+ } else {
21
+ console.warn("Invalid COOKIE_EXPIRE_TIME in environment variables, using default value");
22
+ }
23
+ WriteConsoleLogs(`Cookie expiration time set to ${COOKIE_EXPIRE_TIME} days for deployed environment`);
24
+ } catch (error) {
25
+ WriteConsoleLogs("Error parsing COOKIE_EXPIRE_TIME:", error);
26
+ }
27
+
28
+ async function WriteConsoleLogs(message) {
29
+ const appName = process.env.AppName;
30
+ try {
31
+ const query = `
32
+ INSERT INTO mbkauthlogs (app_name, message)
33
+ VALUES ($1, $2)
34
+ `;
35
+ await dblogin.query(query, [appName, message]);
36
+ console.log(`Logged message: ${message}`);
37
+ } catch (error) {
38
+ console.error("Error logging message:", error.message);
39
+ }
40
+ }
41
+
42
+ router.use(express.json());
43
+ router.use(express.urlencoded({ extended: true }));
44
+
45
+ router.use(
46
+ session({
47
+ store: new PgSession({
48
+ pool: dblogin, // Connection pool
49
+ tableName: "session", // Use another table-name than the default "session" one
50
+ }),
51
+ secret: process.env.SESSION_SECRET_KEY, // Replace with your secret key
52
+ resave: false,
53
+ saveUninitialized: false,
54
+ cookie: {
55
+ maxAge: COOKIE_EXPIRE_TIME,
56
+ DOMAIN: process.env.IS_DEPLOYED === 'true' ? `.${process.env.DOMAIN}` : undefined, // Use root DOMAIN for subDOMAIN sharing
57
+ httpOnly: true,
58
+ secure: process.env.IS_DEPLOYED === 'true', // Use secure cookies in production
59
+ },
60
+ })
61
+ );
62
+
63
+
64
+
65
+ router.use(cookieParser()); // Use cookie-parser middleware
66
+
67
+ router.use((req, res, next) => {
68
+ if (req.session && req.session.user) {
69
+ const userAgent = req.headers["user-agent"];
70
+ const userIp =
71
+ req.headers["x-forwarded-for"] || req.connection.remoteAddress;
72
+ const formattedIp = userIp === "::1" ? "127.0.0.1" : userIp;
73
+
74
+ req.session.otherInfo = {
75
+ ip: formattedIp,
76
+ browser: userAgent,
77
+ };
78
+
79
+ next();
80
+ } else {
81
+ next();
82
+ }
83
+ });
84
+
85
+ // Save the username in a cookie, the cookie user name is use
86
+ // for displaying user name in profile menu. This cookie is not use anyelse where.
87
+ // So it is safe to use.
88
+ router.use(async (req, res, next) => {
89
+ if (req.session && req.session.user) {
90
+ try {
91
+
92
+ res.cookie("username", req.session.user.username, {
93
+ maxAge: COOKIE_EXPIRE_TIME,
94
+ });
95
+
96
+ const query = `SELECT "Role" FROM "Users" WHERE "UserName" = $1`;
97
+ const result = await dblogin.query(query, [req.session.user.username]);
98
+
99
+ if (result.rows.length > 0) {
100
+ req.session.user.role = result.rows[0].Role;
101
+ res.cookie("userRole", req.session.user.role, {
102
+ maxAge: COOKIE_EXPIRE_TIME,
103
+ });
104
+ } else {
105
+ req.session.user.role = null;
106
+ }
107
+ } catch (error) {
108
+ WriteConsoleLogs("Error fetching user role:", error.message);
109
+ req.session.user.role = null; // Fallback to null role
110
+ }
111
+ }
112
+ next();
113
+ });
114
+
115
+ router.use(async (req, res, next) => {
116
+ // Check for sessionId cookie if session is not initialized
117
+ if (!req.session.user && req.cookies && req.cookies.sessionId) {
118
+ WriteConsoleLogs("Restoring session from sessionId cookie"); // Log session restoration
119
+ const sessionId = req.cookies.sessionId;
120
+ const query = `SELECT * FROM "Users" WHERE "SessionId" = $1`;
121
+ const result = await dblogin.query(query, [sessionId]);
122
+
123
+ if (result.rows.length > 0) {
124
+ const user = result.rows[0];
125
+ req.session.user = {
126
+ id: user.id,
127
+ username: user.UserName,
128
+ sessionId,
129
+ };
130
+ WriteConsoleLogs(`Session restored for user: ${user.UserName}`); // Log successful session restoration
131
+ } else {
132
+ console.warn("No matching session found for sessionId"); // Log if no session is found
133
+ }
134
+ }
135
+ next();
136
+ });
137
+
138
+ //Invoke-RestMethod -Uri http://localhost:3030/terminateAllSessions -Method POST
139
+ // Terminate all sessions route
140
+ router.post("/mbkauthe/api/terminateAllSessions", authenticate(process.env.Main_SECRET_TOKEN), async (req, res) => {
141
+ try {
142
+ await dblogin.query(`UPDATE "Users" SET "SessionId" = NULL`);
143
+
144
+ // Clear the session table
145
+ await dblogin.query('DELETE FROM "session"');
146
+
147
+ // Destroy all sessions on the server
148
+ req.session.destroy((err) => {
149
+ if (err) {
150
+ WriteConsoleLogs("Error destroying session:", err);
151
+ return res
152
+ .status(500)
153
+ .json({ success: false, message: "Failed to terminate sessions" });
154
+ }
155
+ WriteConsoleLogs("All sessions terminated successfully");
156
+ res.status(200).json({
157
+ success: true,
158
+ message: "All sessions terminated successfully",
159
+ });
160
+ });
161
+ } catch (err) {
162
+ WriteConsoleLogs("Database query error during session termination:", err);
163
+ res
164
+ .status(500)
165
+ .json({ success: false, message: "Internal Server Error" });
166
+ }
167
+ }
168
+ );
169
+
170
+ router.post("/mbkauthe/api/login", async (req, res) => {
171
+ WriteConsoleLogs("Login request received"); // Log when login is initiated
172
+
173
+ const { username, password, token, recaptcha } = req.body;
174
+ WriteConsoleLogs(`Login attempt for username: ${username}`); // Log username
175
+
176
+ const secretKey = process.env.RECAPTCHA_SECRET_KEY;
177
+ const verificationUrl = `https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${recaptcha}`;
178
+
179
+ // Bypass recaptcha for specific users
180
+ if (username !== "ibnekhalid" && username !== "maaz.waheed" && username !== "support") {
181
+ try {
182
+ const response = await fetch(verificationUrl, { method: 'POST' });
183
+ const body = await response.json();
184
+ WriteConsoleLogs("reCAPTCHA verification response:", body); // Log reCAPTCHA response
185
+
186
+ if (!body.success) {
187
+ WriteConsoleLogs("Failed reCAPTCHA verification");
188
+ return res.status(400).json({ success: false, message: "Failed reCAPTCHA verification" });
189
+ }
190
+ } catch (err) {
191
+ WriteConsoleLogs("Error during reCAPTCHA verification:", err);
192
+ return res.status(500).json({ success: false, message: "Internal Server Error" });
193
+ }
194
+ }
195
+
196
+ if (!username || !password) {
197
+ WriteConsoleLogs("Missing username or password");
198
+ return res.status(400).json({
199
+ success: false,
200
+ message: "Username and password are required",
201
+ });
202
+ }
203
+
204
+ WriteConsoleLogs("RECAPTCHA_SECRET_KEY:", process.env.RECAPTCHA_SECRET_KEY); // Log reCAPTCHA secret key
205
+ WriteConsoleLogs("SESSION_SECRET_KEY:", process.env.SESSION_SECRET_KEY); // Log reCAPTCHA secret key
206
+ WriteConsoleLogs("LOGIN_DB:", process.env.LOGIN_DB); // Log reCAPTCHA secret key
207
+ WriteConsoleLogs("COOKIE_EXPIRE_TIME:", process.env.COOKIE_EXPIRE_TIME); // Log reCAPTCHA secret key
208
+ WriteConsoleLogs("DOMAIN:", process.env.DOMAIN); // Log reCAPTCHA secret key
209
+ WriteConsoleLogs("IS_DEPLOYED:", process.env.IS_DEPLOYED); // Log reCAPTCHA secret key
210
+ WriteConsoleLogs("MBKAUTH_TWO_FA_ENABLE:", process.env.MBKAUTH_TWO_FA_ENABLE); // Log reCAPTCHA secret key
211
+
212
+ try {
213
+ // Query to check if the username exists
214
+ const userQuery = `SELECT * FROM "Users" WHERE "UserName" = $1`;
215
+ const userResult = await dblogin.query(userQuery, [username]);
216
+ WriteConsoleLogs("User query result:", userResult.rows); // Log user query result
217
+
218
+ if (userResult.rows.length === 0) {
219
+ WriteConsoleLogs(`Username does not exist: ${username}`);
220
+ return res.status(404).json({ success: false, message: "Username does not exist" });
221
+ }
222
+
223
+ const user = userResult.rows[0];
224
+
225
+ // Check if the password matches
226
+ if (user.Password !== password) {
227
+ WriteConsoleLogs(`Incorrect password for username: ${username}`);
228
+ return res.status(401).json({ success: false, message: "Incorrect password" });
229
+ }
230
+
231
+ // Check if the account is inactive
232
+ if (!user.Active) {
233
+ WriteConsoleLogs(`Inactive account for username: ${username}`);
234
+ return res.status(403).json({ success: false, message: "Account is inactive" });
235
+ }
236
+
237
+ if ((process.env.MBKAUTH_TWO_FA_ENABLE || "").toLocaleLowerCase() === "true") {
238
+ let sharedSecret;
239
+ const query = `SELECT "TwoFAStatus", "TwoFASecret" FROM "TwoFA" WHERE "UserName" = $1`;
240
+ const twoFAResult = await dblogin.query(query, [username]);
241
+ WriteConsoleLogs("TwoFA query result:", twoFAResult.rows); // Log TwoFA query result
242
+
243
+ sharedSecret = twoFAResult.rows[0]?.TwoFASecret;
244
+ if (twoFAResult.rows.length > 0 && twoFAResult.rows[0].TwoFAStatus && !token) {
245
+ WriteConsoleLogs("2FA code required but not provided");
246
+ return res.status(401).json({ success: false, message: "Please Enter 2FA code" });
247
+ }
248
+
249
+ if (token && twoFAResult.rows[0]?.TwoFAStatus) {
250
+ const tokenValidates = speakeasy.totp.verify({
251
+ secret: sharedSecret,
252
+ encoding: "base32",
253
+ token: token,
254
+ window: 1, // Allows a margin for clock drift, optional
255
+ });
256
+
257
+ if (!tokenValidates) {
258
+ WriteConsoleLogs(`Invalid 2FA code for username: ${username}`);
259
+ return res.status(401).json({ success: false, message: "Invalid 2FA code" });
260
+ }
261
+ }
262
+ }
263
+
264
+ // Generate session ID
265
+ const sessionId = crypto.randomBytes(256).toString("hex");
266
+ WriteConsoleLogs(`Generated session ID for username: ${username}`); // Log session ID
267
+
268
+ await dblogin.query(`UPDATE "Users" SET "SessionId" = $1 WHERE "id" = $2`, [
269
+ sessionId,
270
+ user.id,
271
+ ]);
272
+
273
+ // Store session ID in session
274
+ req.session.user = {
275
+ id: user.id,
276
+ username: user.UserName,
277
+ sessionId,
278
+ };
279
+ WriteConsoleLogs(`Session stored for user: ${user.UserName}, sessionId: ${sessionId}`); // Log session storage
280
+
281
+ // Set a cookie accessible across subDOMAINs
282
+ res.cookie("sessionId", sessionId, {
283
+ maxAge: COOKIE_EXPIRE_TIME,
284
+ DOMAIN: process.env.IS_DEPLOYED === 'true' ? `.${process.env.DOMAIN}` : undefined, // Use DOMAIN only in production
285
+ httpOnly: true,
286
+ secure: process.env.IS_DEPLOYED === 'true', // Use secure cookies in production
287
+ });
288
+ WriteConsoleLogs(`Cookie set for user: ${user.UserName}, sessionId: ${sessionId}`); // Log cookie setting
289
+
290
+ WriteConsoleLogs(`User "${username}" logged in successfully`);
291
+ res.status(200).json({
292
+ success: true,
293
+ message: "Login successful",
294
+ sessionId,
295
+ });
296
+ } catch (err) {
297
+ WriteConsoleLogs("Error during login process:", err);
298
+ res.status(500).json({ success: false, message: "Internal Server Error" });
299
+ }
300
+ });
301
+
302
+ router.post("/mbkauthe/api/logout", async (req, res) => {
303
+ if (req.session.user) {
304
+ try {
305
+ const { id, username } = req.session.user;
306
+ const query = `SELECT "Active" FROM "Users" WHERE "id" = $1`;
307
+ const result = await dblogin.query(query, [id]);
308
+
309
+ if (result.rows.length > 0 && !result.rows[0].Active) {
310
+ WriteConsoleLogs("Account is inactive during logout");
311
+ }
312
+
313
+ req.session.destroy((err) => {
314
+ if (err) {
315
+ WriteConsoleLogs("Error destroying session:", err);
316
+ return res.status(500).json({ success: false, message: "Logout failed" });
317
+ }
318
+ // Clear both session cookies
319
+ res.clearCookie("connect.sid");
320
+ res.clearCookie("sessionId"); // Clear the sessionId cookie used for restoration
321
+ WriteConsoleLogs(`User "${username}" logged out successfully`);
322
+ res.status(200).json({ success: true, message: "Logout successful" });
323
+ });
324
+ } catch (err) {
325
+ WriteConsoleLogs("Database query error during logout:", err);
326
+ res.status(500).json({ success: false, message: "Internal Server Error" });
327
+ }
328
+ } else {
329
+ res.status(400).json({ success: false, message: "Not logged in" });
330
+ }
331
+ });
332
+
333
+ export default router;
package/lib/pool.js ADDED
@@ -0,0 +1,27 @@
1
+ import pkg from "pg";
2
+ const { Pool } = pkg;
3
+ import dotenv from "dotenv";
4
+
5
+ dotenv.config();
6
+
7
+ // PostgreSQL connection pool for pool
8
+ const poolConfig = {
9
+ connectionString: process.env.LOGIN_DB,
10
+ ssl: {
11
+ rejectUnauthorized: true,
12
+ },
13
+
14
+ };
15
+
16
+ export const dblogin = new Pool(poolConfig);
17
+
18
+ // Test connection for pool
19
+ (async () => {
20
+ try {
21
+ const client = await dblogin.connect();
22
+ console.log("Connected to PostgreSQL database (pool)!");
23
+ client.release();
24
+ } catch (err) {
25
+ console.error("Database connection error (pool):", err);
26
+ }
27
+ })();
@@ -0,0 +1,152 @@
1
+ import { dblogin } from "./pool.js";
2
+
3
+ async function validateSession(req, res, next) {
4
+ if (!req.session.user) {
5
+ return res.render("templates/Error/NotLoggedIn.handlebars", {
6
+ currentUrl: req.originalUrl,
7
+ });
8
+ }
9
+
10
+ try {
11
+ const { id, sessionId } = req.session.user;
12
+ const query = `SELECT "SessionId", "Active" FROM "Users" WHERE "id" = $1`;
13
+ const result = await dblogin.query(query, [id]);
14
+
15
+ // Check if user exists and session ID matches
16
+ if (result.rows.length === 0 || result.rows[0].SessionId !== sessionId) {
17
+ console.log(
18
+ `Session invalidated for user \"${req.session.user.username}\"`
19
+ );
20
+ req.session.destroy();
21
+ // ...existing code...
22
+ return res.render("templates/Error/SessionExpire.handlebars", {
23
+ currentUrl: req.originalUrl,
24
+ });
25
+ // ...existing code...
26
+ }
27
+
28
+ // Check if the user account is inactive
29
+ if (!result.rows[0].Active) {
30
+ console.log(
31
+ `Account is inactive for user \"${req.session.user.username}\"`
32
+ );
33
+ req.session.destroy();
34
+ res.clearCookie("connect.sid");
35
+ return res.render("templates/Error/AccountInactive.handlebars", {
36
+ currentUrl: req.originalUrl,
37
+ });
38
+ }
39
+
40
+ next(); // Proceed if everything is valid
41
+ } catch (err) {
42
+ console.error("Session validation error:", err);
43
+ res.status(500).json({ success: false, message: "Internal Server Error" });
44
+ }
45
+ }
46
+
47
+ const checkRolePermission = (requiredRole) => {
48
+ return async (req, res, next) => {
49
+ try {
50
+ if (!req.session || !req.session.user || !req.session.user.id) {
51
+ console.log("User not authenticated");
52
+ console.log(req.session);
53
+ return res.render("templates/Error/NotLoggedIn.handlebars", {
54
+ currentUrl: req.originalUrl,
55
+ });
56
+ }
57
+
58
+ if (requiredRole === "Any" || requiredRole === "any") {
59
+ return next();
60
+ }
61
+
62
+ const userId = req.session.user.id;
63
+
64
+ const query = `SELECT "Role" FROM "Users" WHERE "id" = $1`;
65
+ const result = await dblogin.query(query, [userId]);
66
+
67
+ if (result.rows.length === 0) {
68
+ return res
69
+ .status(401)
70
+ .json({ success: false, message: "User not found" });
71
+ }
72
+
73
+ const userRole = result.rows[0].Role;
74
+ if (userRole !== requiredRole) {
75
+ return res.render("templates/Error/AccessDenied.handlebars", {
76
+ currentRole: userRole,
77
+ requiredRole: requiredRole,
78
+ });
79
+ }
80
+
81
+ next();
82
+ } catch (err) {
83
+ console.error("Permission check error:", err);
84
+ res
85
+ .status(500)
86
+ .json({ success: false, message: "Internal Server Error" });
87
+ }
88
+ };
89
+ };
90
+
91
+ const validateSessionAndRole = (requiredRole) => {
92
+ return async (req, res, next) => {
93
+ await validateSession(req, res, async () => {
94
+ await checkRolePermission(requiredRole)(req, res, next);
95
+ });
96
+ };
97
+ };
98
+
99
+ async function getUserData(UserName, parameters) {
100
+ try {
101
+ if (!parameters || parameters.length === 0) {
102
+ throw new Error("Parameters are required to fetch user data");
103
+ }
104
+
105
+ // Dynamically select fields from Users table based on `parameters`
106
+ const userFields = [
107
+ "Password", "UserName", "Role", "Active", "GuestRole", "HaveMailAccount",
108
+ ];
109
+ const profileFields = [
110
+ "FullName", "email", "Image", "ProjectLinks", "SocialAccounts", "Bio",
111
+ ];
112
+
113
+ let userParameters = [];
114
+ let profileParameters = [];
115
+
116
+ if (parameters === "profiledata") {
117
+ userParameters = userFields.filter(field => field !== "Password");
118
+ profileParameters = profileFields;
119
+ } else {
120
+ userParameters = userFields.filter(field => parameters.includes(field));
121
+ profileParameters = profileFields.filter(field => parameters.includes(field));
122
+ }
123
+
124
+ // Prepare queries based on required fields
125
+ let userResult = {};
126
+ if (userParameters.length > 0) {
127
+ const userQuery = `SELECT ${userParameters.map(field => `"${field}"`).join(", ")}
128
+ FROM "Users" WHERE "UserName" = $1`;
129
+ const userQueryResult = await dblogin.query(userQuery, [UserName]);
130
+ if (userQueryResult.rows.length === 0) return { error: "User not found" };
131
+ userResult = userQueryResult.rows[0];
132
+ }
133
+
134
+ let profileResult = {};
135
+ if (profileParameters.length > 0) {
136
+ const profileQuery = `SELECT ${profileParameters.map(field => `"${field}"`).join(", ")}
137
+ FROM profiledata WHERE "UserName" = $1`;
138
+ const profileQueryResult = await dblogin.query(profileQuery, [UserName]);
139
+ if (profileQueryResult.rows.length === 0) return { error: "Profile data not found" };
140
+ profileResult = profileQueryResult.rows[0];
141
+ }
142
+
143
+ // Combine results
144
+ const combinedResult = { ...userResult, ...profileResult };
145
+ return combinedResult;
146
+ } catch (err) {
147
+ console.error("Error fetching user data:", err.message);
148
+ throw err;
149
+ }
150
+ }
151
+
152
+ export { validateSession, checkRolePermission, validateSessionAndRole, getUserData };
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "mbkauthe",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "MBKTechStudio's reusable authentication system for Node.js applications.",
5
5
  "main": "index.js",
6
+ "type": "module",
6
7
  "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
+ "test": "node index.js"
8
9
  },
9
10
  "repository": {
10
11
  "type": "git",
package/index,js DELETED
@@ -1,6 +0,0 @@
1
- import router from "./routes/main";
2
-
3
-
4
- console.log("Hello, World!");
5
-
6
- export default router;
package/routes/main.js DELETED
@@ -1,9 +0,0 @@
1
- import express from 'express';
2
-
3
- const router = express.Router();
4
-
5
- router.get('/mbkauthe/', (req, res) => {
6
- res.send('Welcome to the main page!');
7
- });
8
-
9
- export default router;