const express = require("express"); const session = require("express-session"); const rateLimit = require('express-rate-limit'); const mysql2 = require('mysql2'); const bodyParser = require("body-parser"); const bcrypt = require("bcrypt"); const crypto = require("crypto"); const nodemailer = require("nodemailer"); const otpGenerator = require('otp-generator'); const { body, validationResult } = require('express-validator'); const validator = require('validator'); const { transporter } = require("./modules/nodeMailer"); const { connection } = require("./modules/mysql"); const app = express(); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); const PORT = process.env.PORT || 3000; require("dotenv").config(); app.use(bodyParser.urlencoded({ extended: true })); app.set("view engine", "ejs"); app.use(session({ secret: process.env.key, resave: false, saveUninitialized: true, cookie: { secure: false, // Make sure to set this to true in a production environment with HTTPS httpOnly: true, maxAge: 24 * 60 * 60 * 1000, // Session duration in milliseconds (here set to 1 day) }, })); function isAuthenticated(req, res, next) { if (req.session && req.session.authenticated) { return next(); } else { res.redirect("/login"); } } const generateOTP = () => { const otp = otpGenerator.generate(6, { upperCase: false, specialChars: false }); const expirationTime = Date.now() + 5 * 60 * 1000; // 5 minutes expiration return { otp, expirationTime }; }; const sendOTPByEmail = async (email, otp) => { try { const transporter = nodemailer.createTransport({ service: 'gmail', auth: { user: process.env.euser, // replace with your email pass: process.env.epass // replace with your email password } }); const mailOptions = { from: process.env.euser, to: email, subject: 'Login OTP', text: `Your OTP for login is: ${otp}` }; await transporter.sendMail(mailOptions); console.log('OTP sent successfully to', email); } catch (error) { console.error('Error sending OTP:', error); throw error; } }; app.get("/login", (req, res) => { res.render("login", { error: null }); }); const logActivity = async (username, success, message) => { try { if (!username) { console.error("Error logging activity: Username is null or undefined"); return; } const activity = success ? `successful login: ${message}` : `unsuccessful login: ${message}`; const logSql = "INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP)"; const logParams = [username, activity]; connection.query(logSql, logParams, (error, results) => { if (error) { console.error("Error executing logSql:", error); // Handle error (you may want to log it or take other appropriate actions) } else { console.log("Activity logged successfully"); } }); } catch (error) { console.error("Error in logActivity function:", error); // Handle error (you may want to log it or take other appropriate actions) } }; const logLogoutActivity = async (username, success, message) => { try { if (!username) { console.error("Error logging activity: Username is null or undefined"); return; } const activity = success ? `successful logout: ${message}` : `unsuccessful logout: ${message}`; const logSql = "INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP)"; const logParams = [username, activity]; connection.query(logSql, logParams, (error, results) => { if (error) { console.error("Error executing logSql:", error); // Handle error (you may want to log it or take other appropriate actions) } else { console.log("Activity logged successfully"); } }); } catch (error) { console.error("Error in logActivity function:", error); // Handle error (you may want to log it or take other appropriate actions) } }; const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // limit each IP to 3 requests per windowMs message: 'Too many login attempts from this IP, please try again later.', }); app.use('/login', limiter); app.post('/login', [ body('username').escape().trim().isLength({ min: 1 }).withMessage('Username must not be empty'), body('password').escape().trim().isLength({ min: 1 }).withMessage('Password must not be empty'), ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { // Handle validation errors, e.g., return an error message to the client return res.render('login', { error: 'Invalid input. Please check your credentials.', csrfToken: req.session.csrfToken }); } let { username, password } = req.body; username = username.trim(); const loginSql = "SELECT * FROM users WHERE username = ?"; connection.query(loginSql, [username], async (error, results) => { if (error) { console.error("Error executing login query:", error); res.status(500).send("Internal Server Error"); return; } if (results.length > 0) { const isLoginSuccessful = await bcrypt.compare(password, results[0].password); if (isLoginSuccessful) { // Log successful login attempt await logActivity(username, true, "Credentials entered correctly"); const user = results[0]; const { otp, expirationTime } = generateOTP(); // Store the OTP and expiration time in the session for verification req.session.otp = otp; req.session.otpExpiration = expirationTime; req.session.save(); // Send OTP via email try { await sendOTPByEmail(user.email, otp); // Log successful OTP sending await logActivity(username, true, "OTP successfully sent to user"); } catch (sendOTPError) { // Log unsuccessful OTP sending await logActivity(username, false, "OTP failed to send to user"); console.error("Error sending OTP:", sendOTPError); res.status(500).send("Internal Server Error"); return; } // Render OTP input page res.render("otp", { error: null, username: user.username, csrfToken: req.session.csrfToken }); } else { // Log unsuccessful login attempt await logActivity(username, false, "Incorrect password"); res.render("login", { error: "Invalid username or password", csrfToken: req.session.csrfToken }); } } else { // Log unsuccessful login attempt await logActivity(username, false, "User not found"); res.render("login", { error: "Invalid username or password", csrfToken: req.session.csrfToken }); } }); } catch (error) { console.error("Error in login route:", error); res.status(500).send("Internal Server Error"); } }); // OTP verification route app.post("/verify-otp", [ body('otp').escape().trim().isLength({ min: 1 }).withMessage('OTP must not be empty'), ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { // Handle validation errors, e.g., return an error message to the client return res.render('otp', { error: 'Invalid OTP. Please try again.', username: req.body.username, csrfToken: req.session.csrfToken }); } const enteredOTP = req.body.otp; if (!req.session) { // If session is not defined, handle accordingly console.error("Session is not defined."); return res.status(500).send("Internal Server Error"); } if (enteredOTP === req.session.otp) { // Log successful OTP entry if (req.body.username) { await logActivity(req.body.username, true, "OTP entered correctly"); } // Correct OTP, generate a session token const sessionToken = crypto.randomBytes(32).toString('hex'); // Store the session token in the session req.session.authenticated = true; req.session.username = req.body.username; req.session.sessionToken = sessionToken; // Generate and store anti-CSRF token in the session csrfTokensession = crypto.randomBytes(32).toString('hex'); // Set anti-CSRF token in res.locals // Log anti-CSRF token console.log(`Generated Anti-CSRF Token: ${csrfTokensession}`); // Set CSRF token as a cookie // Implement secure session handling: // 1. Set secure, HttpOnly, and SameSite flags // 2. Set an expiration time for the session token // 3. Regenerate the session after authentication res.cookie('sessionToken', sessionToken, { secure: true, httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000) }); // Expires in 1 day console.log(`Generated Session Token: ${sessionToken}`); // Redirect to home page with session token res.redirect("/home"); } else { // Log unsuccessful OTP entry if (req.body.username) { await logActivity(req.body.username, false, "Incorrect OTP entered"); } // Incorrect OTP, render login page with error res.render("login", { error: "Incorrect OTP. Please try again.", csrfToken: req.session.csrfToken }); } } catch (error) { console.error("Error in OTP verification route:", error); res.status(500).send("Internal Server Error"); } }); app.get("/logout", (req, res) => { try { const username = req.session.username || "Unknown User"; // Log the user out by clearing the session req.session.destroy(async (err) => { if (err) { console.error("Error destroying session:", err); await logLogoutActivity(username, false, "User logged out unsucessfully. Session not destroyed."); } else { console.log(`Session destroyed.`); res.clearCookie('sessionToken'); // Log the logout activity using a separate async function await logLogoutActivity(username, true, "User logged out. Session destroyed."); } // Redirect to the login page after logout res.redirect("/login"); }); } catch (error) { console.error("Error in logout route:", error); res.status(500).send("Internal Server Error"); } }); app.get("/home", isAuthenticated, (req, res) => { // Render the home page with sensor data res.render("home", { username: req.session.username, }); }); app.get("/inusers", isAuthenticated, (req, res) => { // Fetch all user data from the database const allUsersQuery = "SELECT * FROM users"; connection.query(allUsersQuery, (error, allUsers) => { if (error) { console.error("Error fetching all users:", error); res.status(500).send("Internal Server Error"); return; } const currentUsername = req.session.username; // Render the inusers page with JSON data res.render("inusers", { allUsers ,csrfToken: csrfTokensession, currentUsername:currentUsername }); }); }); function isStrongPassword(password) { // Password must be at least 10 characters long if (password.length < 10) { return false; } // Password must contain at least one uppercase letter if (!/[A-Z]/.test(password)) { return false; } // Password must contain at least one lowercase letter if (!/[a-z]/.test(password)) { return false; } // Password must contain at least one digit if (!/\d/.test(password)) { return false; } // Password must contain at least one symbol if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) { return false; } return true; } const logUserCreationActivity = async (creatorUsername, success, message) => { try { const activity = success ? "successful user creation" : `unsuccessful user creation: ${message}`; const logSql = "INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP)"; const logParams = [creatorUsername, activity]; connection.query(logSql, logParams, (error, results) => { if (error) { console.error("Error logging user creation activity:", error); // Handle error (you may want to log it or take other appropriate actions) } else { console.log("User creation activity logged successfully"); } }); } catch (error) { console.error("Error in logUserCreationActivity function:", error); // Handle error (you may want to log it or take other appropriate actions) } }; app.post( '/createUser', [ body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(), body('username').trim().isLength({ min: 1 }).withMessage('Username must not be empty').escape(), body('email').isEmail().withMessage('Invalid email address').normalizeEmail(), body('password').custom((value) => { if (!isStrongPassword(value)) { throw new Error('Password does not meet complexity requirements'); } return true; }), body('jobTitle').trim().isLength({ min: 1 }).withMessage('Job title must not be empty').escape(), ], async (req, res) => { try { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } // Validate the anti-CSRF token const submittedCSRFToken = req.body.csrf_token; if (!csrfTokensession || submittedCSRFToken !== csrfTokensession) { return res.status(403).json({ error: 'CSRF token mismatch' }); } // Extract user input const { name, username, email, password, jobTitle } = req.body; console.log(submittedCSRFToken); // Extract the username of the user creating a new user const creatorUsername = req.session.username; // Adjust this based on how you store the creator's username in your session // Additional password complexity check if (!isStrongPassword(password)) { return res.status(400).json({ error: "Password does not meet complexity requirements" }); } // Check if the username is already taken const checkUsernameQuery = "SELECT * FROM users WHERE username = ?"; connection.query(checkUsernameQuery, [username], (usernameQueryErr, usernameResults) => { if (usernameQueryErr) { console.error("Error checking username:", usernameQueryErr); return res.status(500).json({ error: "Internal Server Error" }); } if (usernameResults.length > 0) { // Log unsuccessful user creation due to username taken logUserCreationActivity(creatorUsername, false, "username taken"); return res.status(400).json({ error: "Username is already taken", message: "Username is already taken. Please choose a different username." }); } // Check if the email is already taken const checkEmailQuery = "SELECT * FROM users WHERE email = ?"; connection.query(checkEmailQuery, [email], (emailQueryErr, emailResults) => { if (emailQueryErr) { console.error("Error checking email:", emailQueryErr); return res.status(500).json({ error: "Internal Server Error" }); } if (emailResults.length > 0) { // Log unsuccessful user creation due to email taken logUserCreationActivity(creatorUsername, false, "email taken"); return res.status(400).json({ error: "Email is already in use", message: "Email is already in use. Please choose another email." }); } bcrypt.genSalt(10, (saltError, salt) => { if (saltError) { console.error("Error generating salt:", saltError); return res.status(500).json({ error: "Internal Server Error" }); } bcrypt.hash(req.body.password, salt, (hashError, hashedPassword) => { if (hashError) { console.error("Error hashing password:", hashError); return res.status(500).json({ error: "Internal Server Error" }); } connection.beginTransaction((transactionErr) => { if (transactionErr) { console.error("Error starting transaction:", transactionErr); return res.status(500).json({ error: "Internal Server Error" }); } // Define the insert query const insertUserQuery = "INSERT INTO users (name, username, email, password, lastLogin, jobTitle) VALUES (?, ?, ?, ?, NULL, ?)"; // Log the query and its parameters console.log("Insert Query:", insertUserQuery); console.log("Query Parameters:", [name, username, email, hashedPassword, jobTitle]); // Execute the query with user data connection.query(insertUserQuery, [name, username, email, hashedPassword, jobTitle], (queryErr, results) => { if (queryErr) { console.error("Error executing query:", queryErr); // Rollback the transaction in case of an error connection.rollback((rollbackErr) => { if (rollbackErr) { console.error("Error rolling back transaction:", rollbackErr); } // Log unsuccessful user creation due to an error logUserCreationActivity(creatorUsername, false, "internal error"); return res.status(500).json({ error: "Internal Server Error" }); }); return; } // Commit the transaction connection.commit((commitErr) => { if (commitErr) { console.error("Error committing transaction:", commitErr); return res.status(500).json({ error: "Internal Server Error" }); } res.status(200).json({ message: "User created successfully" }); logUserCreationActivity(creatorUsername, true, "user created successfully"); }); }); }); }); }); }); }); } catch (error) { console.error("Error creating user:", error); return res.status(500).json({ error: "Internal Server Error" }); } } ); app.get("/forgot-password", (req, res) => { res.render("forgot-password", { error: null, success: null }); }); // Handle the submission of the forgot password form app.post("/forgot-password", (req, res) => { const { usernameOrEmail } = req.body; // Sanitize the input const sanitizedUsernameOrEmail = validator.escape(usernameOrEmail); // Perform the logic for sending the reset password email // Check if the username or email exists in the database const checkUserQuery = "SELECT * FROM users WHERE username = ? OR email = ?"; connection.query( checkUserQuery, [sanitizedUsernameOrEmail, sanitizedUsernameOrEmail], (checkError, checkResults) => { if (checkError) { console.error("Error checking user:", checkError); const error = "An error occurred during the password reset process."; res.render("forgot-password", { error, success: null }); } else if (checkResults.length === 0) { const error = "Username or email not found."; res.render("forgot-password", { error, success: null }); } else { // Assuming the user exists, generate a reset token and send an email const user = checkResults[0]; const resetToken = crypto.randomBytes(20).toString("hex"); const resetTokenExpiry = new Date(Date.now() + 3600000); // Token expires in 1 hour // Update user with reset token and expiry const updateQuery = "UPDATE users SET reset_token = ?, reset_token_expiry = ? WHERE id = ?"; connection.query( updateQuery, [resetToken, resetTokenExpiry, user.id], (updateError) => { if (updateError) { console.error("Error updating reset token:", updateError); const error = "An error occurred during the password reset process."; res.render("forgot-password", { error, success: null }); } else { // Send email with reset link const resetLink = `http://localhost:3000/reset-password/${resetToken}`; const mailOptions = { to: user.email, subject: "Password Reset", text: `Click on the following link to reset your password: ${resetLink}`, }; transporter.sendMail(mailOptions, (emailError, info) => { if (emailError) { console.error("Error sending email:", emailError); const error = "An error occurred during the password reset process."; res.render("forgot-password", { error, success: null }); } else { console.log("Email sent: " + info.response); const success = "Password reset email sent successfully. Check your inbox."; res.render("forgot-password", { error: null, success }); // Log the successful sending of the reset link in the database logPasswordResetActivity(user.username,"link sent successfully"); } }); } } ); } } ); }); // Function to log the password reset activity in the database function logPasswordResetActivity(username, activity) { const logSql = "INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, CURRENT_TIMESTAMP)"; connection.query(logSql, [username, activity], (error) => { if (error) { console.error("Error logging password reset activity:", error); } else { console.log("Password reset activity logged successfully"); } }); } // Handle Reset Password request app.post("/reset-password/:token", async (req, res) => { const { token } = req.params; const { password, confirmPassword } = req.body; // Sanitize the inputs const sanitizedToken = validator.escape(token); const sanitizedPassword = validator.escape(password); const sanitizedConfirmPassword = validator.escape(confirmPassword); // Find user with matching reset token and not expired const selectQuery = "SELECT * FROM users WHERE reset_token = ? AND reset_token_expiry > NOW()"; connection.query(selectQuery, [sanitizedToken], async (selectErr, selectResults) => { if (selectErr) { console.error("Error querying reset token:", selectErr); return res.status(500).json({ error: "Error querying reset token" }); } if (selectResults.length === 0) { // Pass the error to the template when rendering the reset-password page return res.render("reset-password", { token, resetError: "Invalid or expired reset token", }); } // Check if passwords match if (sanitizedPassword !== sanitizedConfirmPassword) { // Pass the error to the template when rendering the reset-password page return res.render("reset-password", { token, resetError: "Passwords do not match", }); } // Check if the new password meets complexity requirements if (!isStrongPassword(sanitizedPassword)) { // Pass the error to the template when rendering the reset-password page return res.render("reset-password", { token, resetError: "Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.", }); } // Hash the new password const hashedPassword = await new Promise((resolve, reject) => { bcrypt.genSalt(10, (saltError, salt) => { if (saltError) { console.error("Error generating salt:", saltError); reject("Internal Server Error"); } // Use the generated salt to hash the password bcrypt.hash(sanitizedPassword, salt, (hashError, hashed) => { if (hashError) { console.error("Error hashing password:", hashError); reject("Internal Server Error"); } resolve(hashed); }); }); }); // Update user's password and clear reset token const updateQuery = "UPDATE users SET password = ?, reset_token = NULL, reset_token_expiry = NULL WHERE reset_token = ?"; connection.query(updateQuery, [hashedPassword, sanitizedToken], async (updateErr, updateResults) => { if (updateErr) { console.error("Error updating password:", updateErr); // Pass the error to the template when rendering the reset-password page res.render("reset-password", { token, resetError: "Error updating password", }); } else { // Log password reset activity const username = selectResults[0].username; // Assuming 'username' is the column name const logQuery = "INSERT INTO user_logs (username, activity, timestamp) VALUES (?, 'Password Reseted successfully', NOW())"; connection.query(logQuery, [username], (logErr) => { if (logErr) { console.error("Error logging password reset:", logErr); // You might want to handle the logging error here } }); // Redirect to the success page upon successful password reset res.redirect("/success"); } }); }); }); app.get("/success", (req, res) => { res.render("success"); }); app.get("/reset-password/:token", (req, res) => { const { token } = req.params; const error = req.query.error || null; // Get error from query parameter // Assuming you have this line in your server code where you render the reset-password view res.render("reset-password", { token, passwordValidationError: null, resetError: null, success: null, }); }); app.post("/reset-password", async (req, res) => { const { username, password, confirmPassword, csrf_token } = req.body; const creatorUsername = req.session.username; const submittedCSRFToken = req.body.csrf_token; if (!csrfTokensession|| submittedCSRFToken !== csrfTokensession) { return res.status(403).json({ error: 'CSRF token mismatch' }); } // Sanitize the inputs const sanitizedUsername = validator.escape(username); const sanitizedPassword = validator.escape(password); const sanitizedConfirmPassword = validator.escape(confirmPassword); // Check if passwords match if (sanitizedPassword !== sanitizedConfirmPassword) { return res.status(400).json({ error: "Passwords do not match" }); } // Check if the new password meets complexity requirements if (!isStrongPassword(sanitizedPassword)) { return res.status(400).json({ error: "Password does not meet complexity requirements. It must be at least 10 characters long and include at least one uppercase letter, one lowercase letter, one digit, and one symbol.", }); } // Generate a random salt const saltRounds = 10; // You can adjust the number of rounds based on your security requirements const salt = await bcrypt.genSalt(saltRounds); // Hash the new password with the generated salt const hashedPassword = await bcrypt.hash(sanitizedPassword, salt); // Check if the user exists in the database before updating the password const userExists = await checkIfUserExists(sanitizedUsername); if (!userExists) { return res.status(404).json({ error: "User does not exist" }); } // Update user's password based on the username const updateQuery = "UPDATE users SET password = ? WHERE username = ?"; connection.query(updateQuery, [hashedPassword, sanitizedUsername], async (updateErr, updateResults) => { if (updateErr) { console.error("Error updating password:", updateErr); return res.status(500).json({ error: "Error updating password" }); } // Check if the update affected any rows if (updateResults.affectedRows > 0) { // Log password reset activity const logQuery = "INSERT INTO user_logs (username, activity, timestamp) VALUES (?, ?, NOW())"; const logActivity = `Password has been reset for ${sanitizedUsername}`; connection.query(logQuery, [creatorUsername, logActivity], (logErr) => { if (logErr) { console.error("Error logging password reset:", logErr); // You might want to handle the logging error here } // Password update successful return res.status(200).json({ success: "Password updated successfully" }); }); } else { return res.status(404).json({ error: "User not found or password not updated.", }); } }); }); async function checkIfUserExists(username) { return new Promise((resolve, reject) => { const query = "SELECT * FROM users WHERE username = ?"; connection.query(query, [username], (err, results) => { if (err) { reject(err); } else { resolve(results.length > 0); } }); }); } app.get('/searchUser', (req, res) => { const { username } = req.query; // Sanitize the input const sanitizedUsername = validator.escape(username); const query = 'SELECT * FROM users WHERE username = ?'; connection.query(query, [sanitizedUsername], (err, results) => { if (err) { console.error('MySQL query error:', err); res.status(500).json({ success: false, error: 'Internal Server Error' }); } else if (results.length === 0) { // No user found with the given username res.status(404).json({ success: false, error: 'User not found' }); } else { res.json(results); } }); }); app.get('/api/users', (req, res) => { const query = 'SELECT * FROM users'; connection.query(query, (err, results) => { if (err) { console.error('MySQL query error:', err); res.status(500).json({ success: false, error: 'Internal Server Error' }); } else { res.json(results); } }); }); // Route to search for a user by username app.get('/api/searchUser', (req, res) => { const { username } = req.query; const query = 'SELECT * FROM users WHERE username = ?'; connection.query(query, [username], (err, results) => { if (err) { console.error('MySQL query error:', err); res.status(500).json({ success: false, error: 'Internal Server Error' }); } else { res.json(results); } }); }); app.delete('/api/deleteUser/:username', async (req, res) => { const { username } = req.params; const query = 'DELETE FROM users WHERE username = ?'; const creatorUsername = req.session.username; try { // Extract CSRF token from the request body const { csrfToken } = req.body; console.log(csrfToken); console.log(csrfTokensession); // Compare CSRF token with the one stored in the session if (csrfToken !== csrfTokensession) { return res.status(403).json({ success: false, error: 'CSRF token mismatch' }); } // Log deletion activity to USER_LOGS const deletionActivity = `User ${username} has been successfully deleted`; const logQuery = 'INSERT INTO USER_LOGS (USERNAME, ACTIVITY, TIMESTAMP) VALUES (?, ?, CURRENT_TIMESTAMP)'; await executeQuery(logQuery, [creatorUsername, deletionActivity]); // Perform user deletion const results = await executeQuery(query, [username]); if (results.affectedRows === 0) { res.status(404).json({ success: false, error: 'User not found' }); } else { res.json({ success: true, message: 'User deleted successfully' }); } } catch (error) { console.error('MySQL query error:', error); res.status(500).json({ success: false, error: 'Internal Server Error', details: error.message }); } }); async function executeQuery(sql, values) { return new Promise((resolve, reject) => { connection.query(sql, values, (err, results) => { if (err) { reject(err); } else { resolve(results); } }); }); } app.get('/api/getLogs', (req, res) => { // Query the database to fetch logs const query = 'SELECT id, username, activity, timestamp FROM user_logs'; connection.query(query, (err, results) => { if (err) { console.error('Error fetching logs from MySQL:', err); res.status(500).json({ error: 'Error fetching logs from MySQL' }); return; } // Format timestamps to a more readable format const formattedLogs = results.map((log) => ({ id: log.id, username: log.username, activity: log.activity, timestamp: new Date(log.timestamp).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }), })); // Send the formatted logs as a JSON response res.json(formattedLogs); }); }); app.use(express.static("views")); app.listen(PORT, () => { console.log(`Server is running on port ${PORT}`); });