From 994aebf71d357b26168b2b8e825b117013ac7282 Mon Sep 17 00:00:00 2001 From: BIG2EYEZ Date: Sun, 21 Jan 2024 16:07:27 +0800 Subject: [PATCH] CSP WIP add sessionid as well --- Sean/inusers.js | 136 --------------------------- Sean/models/User.js | 3 + Sean/server.js | 72 +++++++++++++-- Sean/views/LOGO.PNG | Bin 10490 -> 0 bytes Sean/views/forgot-password.css | 58 ++++++++++++ Sean/views/forgot-password.ejs | 61 +------------ Sean/views/home.js | 107 ---------------------- Sean/views/index.js | 64 ------------- Sean/views/inusers.ejs | 22 +++-- Sean/views/inusers.js | 8 +- Sean/views/login.css | 67 ++++++++++++++ Sean/views/login.ejs | 70 +------------- Sean/views/loginstyle.css | 146 ----------------------------- Sean/views/otp.css | 50 ++++++++++ Sean/views/otp.ejs | 53 +---------- Sean/views/responsive.css | 162 --------------------------------- Sean/views/setup-mfa.ejs | 10 -- package-lock.json | 5 + 18 files changed, 264 insertions(+), 830 deletions(-) delete mode 100644 Sean/inusers.js delete mode 100644 Sean/views/LOGO.PNG create mode 100644 Sean/views/forgot-password.css delete mode 100644 Sean/views/home.js delete mode 100644 Sean/views/index.js create mode 100644 Sean/views/login.css delete mode 100644 Sean/views/loginstyle.css create mode 100644 Sean/views/otp.css delete mode 100644 Sean/views/responsive.css delete mode 100644 Sean/views/setup-mfa.ejs diff --git a/Sean/inusers.js b/Sean/inusers.js deleted file mode 100644 index e29bcd2..0000000 --- a/Sean/inusers.js +++ /dev/null @@ -1,136 +0,0 @@ -const allUsers = <%- JSON.stringify(allUsers) %>; - -document.getElementById('downloadButton').addEventListener('click', function () { - console.log('Download button clicked'); - downloadExcel(allUsers); -}); - -document.getElementById('addUserLink').addEventListener('click', function () { - document.getElementById('downloadButtonContainer').style.display = 'none'; - document.getElementById('userDataContainer').style.display = 'none'; - document.getElementById('createUserForm').style.display = 'block'; -}); - -document.getElementById('userDataLink').addEventListener('click', function () { - document.getElementById('downloadButtonContainer').style.display = 'block'; - document.getElementById('userDataContainer').style.display = 'block'; - document.getElementById('createUserForm').style.display = 'none'; -}); - -document.getElementById('userForm').addEventListener('submit', function (event) { - event.preventDefault(); - - // Use FormData directly - const formData = new FormData(document.getElementById('userForm')); - - // Check password complexity - const password = formData.get('password'); - const confirmPassword = formData.get('confirmPassword'); - - if (!isStrongPassword(password)) { - alert('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.'); - return; - } - - // Check if passwords match - if (password !== confirmPassword) { - alert('Passwords do not match. Please enter the same password in both fields.'); - return; - } - - // Make a fetch request - fetch('/createUser', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - name: formData.get('name'), - username: formData.get('username'), - email: formData.get('email'), - password: password, // Use the validated password - jobTitle: formData.get('jobTitle'), - }), - }) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - return response.json(); - }) - .then(data => { - console.log('Success:', data); - - // Show an alert with the received data - alert(`User Registered!`); - - // Optionally, you can clear the form or take other actions after registration - document.getElementById('userForm').reset(); - }) - .catch(error => { - console.error('Fetch Error:', error); - }); - }); - - // Function to validate password complexity - 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; - } - - - function downloadExcel(allUsers) { - if (allUsers && allUsers.length > 0) { - const workbook = new ExcelJS.Workbook(); - const worksheet = workbook.addWorksheet('All Users'); - const headers = ['Name', 'Username', 'Email', 'Password', 'Last Login', 'Job Title']; - worksheet.addRow(headers); - allUsers.forEach(user => { - const rowData = [ - user.name || '', - user.username || '', - user.email || '', - user.password || '', - user.lastLogin ? new Date(user.lastLogin).toLocaleString('en-US', { timeZone: 'Asia/Singapore' }) : '', - user.jobTitle || '' - ]; - worksheet.addRow(rowData); - }); - workbook.xlsx.writeBuffer().then(buffer => { - const currentDate = new Date(); - const formattedDate = currentDate.toISOString().split('T')[0]; - const formattedTime = currentDate.toTimeString().split(' ')[0].replace(/:/g, '-'); - const fileName = `user_data_${formattedDate}_${formattedTime}.xlsx`; - const blob = new Blob([buffer], { - type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - }); - saveAs(blob, fileName); - }); - } else { - console.error('No data available for download.'); - } - } diff --git a/Sean/models/User.js b/Sean/models/User.js index 8cda275..d4c831b 100644 --- a/Sean/models/User.js +++ b/Sean/models/User.js @@ -37,6 +37,9 @@ module.exports = (sequelize) => { reset_token_expiry: { type: DataTypes.DATE, }, + sessionid: { + type: DataTypes.STRING, + }, }, { hooks: { beforeCreate: async (user) => { diff --git a/Sean/server.js b/Sean/server.js index ea3ead6..278a374 100644 --- a/Sean/server.js +++ b/Sean/server.js @@ -1,7 +1,7 @@ const express = require("express"); const session = require("express-session"); const rateLimit = require('express-rate-limit'); - +const cookieParser = require('cookie-parser'); const bodyParser = require("body-parser"); const bcrypt = require("bcrypt"); const crypto = require("crypto"); @@ -10,21 +10,37 @@ const otpGenerator = require('otp-generator'); const { body, validationResult } = require('express-validator'); const validator = require('validator'); const { format } = require('date-fns'); - +const helmet = require('helmet'); const { Sequelize } = require('sequelize'); const { transporter } = require("./modules/nodeMailer"); - const { sequelize, User } = require("./modules/mysql"); const userLogs= require('./models/userLogs')(sequelize); // Adjust the path based on your project structure const app = express(); + +const nonce = crypto.randomBytes(16).toString('base64'); + +console.log('Nonce:', nonce); + app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); +app.use(cookieParser()); const PORT = process.env.PORT || 3000; require("dotenv").config(); app.use(bodyParser.urlencoded({ extended: true })); app.set("view engine", "ejs"); +app.use( + helmet.contentSecurityPolicy({ + directives: { + defaultSrc: ["'self'",`'nonce-${nonce}'`], + scriptSrc: ["'self'",`'nonce-${nonce}'`,"'strict-dynamic'", 'cdn.jsdelivr.net', 'fonts.googleapis.com', 'stackpath.bootstrapcdn.com', 'code.jquery.com', 'cdnjs.cloudflare.com'], + styleSrc: ["'self'",`'nonce-${nonce}'`, 'cdn.jsdelivr.net', 'fonts.googleapis.com'], + imgSrc: ["'self'"], + fontSrc: ["'self'", 'fonts.gstatic.com'], + }, + }) + ); app.use(session({ secret: process.env.key, @@ -189,7 +205,20 @@ app.post("/verify-otp", [ } const sessionToken = crypto.randomBytes(32).toString('hex'); - + const username = req.body.username; // Replace with the actual username + + User.update({ sessionid: sessionToken }, { where: { username } }) + .then(([rowsUpdated]) => { + if (rowsUpdated > 0) { + console.log(`SessionId updated for user: ${username}`); + } else { + console.error('User not found.'); + } + }) + .catch(error => { + console.error('Error updating sessionId:', error); + }); + req.session.authenticated = true; req.session.username = req.body.username; req.session.sessionToken = sessionToken; @@ -200,7 +229,6 @@ app.post("/verify-otp", [ // Log anti-CSRF token console.log(`Generated Anti-CSRF Token: ${csrfTokenSession}`); - // Set CSRF token as a cookie 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}`); @@ -232,6 +260,7 @@ app.post("/verify-otp", [ } else { console.log("Session destroyed."); // Log the logout activity using Sequelize + await User.update({ sessionid: null }, { where: { username } }) await userLogs.create({ username, activity: "User logged out. Session destroyed." }); // Clear the session token cookie res.clearCookie('sessionToken'); @@ -267,7 +296,7 @@ app.post("/verify-otp", [ const currentUsername = req.session.username; // Render the inusers page with JSON data - res.render("inusers", { allUsers, csrfToken: csrfTokenSession, currentUsername }); + res.render("inusers", { nonce: nonce, allUsers, csrfToken: csrfTokenSession, currentUsername }); } catch (error) { console.error("Error fetching all users:", error); res.status(500).send("Internal Server Error"); @@ -324,6 +353,14 @@ app.post( return res.status(400).json({ errors: errors.array() }); } + const sessionTokencookie = req.cookies['sessionToken']; + + // Verify sessionToken with the one stored in the database + const user = await User.findOne({ where: { sessionid: sessionTokencookie } }); + + if (!user) { + return res.status(403).json({ error: 'Invalid sessionToken' }); + } // Validate the anti-CSRF token const submittedCSRFToken = req.body.csrf_token; @@ -595,7 +632,14 @@ app.post("/reset-password", async (req, res) => { if (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) { return res.status(403).json({ error: 'CSRF token mismatch' }); } + const sessionTokencookie = req.cookies['sessionToken']; + // Verify sessionToken with the one stored in the database + const user = await User.findOne({ where: { sessionid: sessionTokencookie } }); + + if (!user) { + return res.status(403).json({ error: 'Invalid sessionToken' }); + } // Sanitize the inputs const sanitizedUsername = validator.escape(username); const sanitizedPassword = validator.escape(password); @@ -686,7 +730,6 @@ app.get('/api/users', async (req, res) => { app.get('/api/searchUser', async (req, res) => { const { username } = req.query; - console.log(username); try { // Find the user in the database by username const user = await User.findOne({ where: { username } }); @@ -709,13 +752,22 @@ app.delete('/api/deleteUser/:username', async (req, res) => { const { username } = req.params; const creatorUsername = req.session.username; - try { - // Extract CSRF token from the request body + try { + // Retrieve sessionToken from cookies + const sessionTokencoookie = req.cookies['sessionToken']; + // Retrieve CSRF token from the request body const { csrfToken } = req.body; - + console.log(csrfToken); // Compare CSRF token with the one stored in the session if (csrfToken !== csrfTokenSession) { return res.status(403).json({ success: false, error: 'CSRF token mismatch' }); + } + + // Verify sessionToken with the one stored in the database + const user = await User.findOne({ where: { sessionid: sessionTokencoookie } }); + + if (!user) { + return res.status(403).json({ success: false, error: 'Invalid sessionToken or user not found' }); } // Log deletion activity to UserLogs model diff --git a/Sean/views/LOGO.PNG b/Sean/views/LOGO.PNG deleted file mode 100644 index eeecb45a91bbe17407338a081413c8f28dd71f09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10490 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 diff --git a/Sean/views/forgot-password.css b/Sean/views/forgot-password.css new file mode 100644 index 0000000..12183d1 --- /dev/null +++ b/Sean/views/forgot-password.css @@ -0,0 +1,58 @@ +body { + font-family: 'Arial', sans-serif; + background-color: #f4f4f4; + margin: 0; + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + } + + h1 { + text-align: center; + color: #333; + } + + form { + background-color: #fff; + padding: 20px; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + width: 300px; + } + + label { + display: block; + margin-bottom: 8px; + color: #555; + } + + input { + width: 100%; + padding: 8px; + margin-bottom: 16px; + border: 1px solid #ccc; + border-radius: 4px; + } + + button { + background-color: #4caf50; + color: #fff; + padding: 10px; + border: none; + border-radius: 4px; + cursor: pointer; + width: 100%; + } + + button:hover { + background-color: #45a049; + } + .error-message { + color: red; + margin-top: 10px; + } + .success-message { + color: green; + margin-top: 10px; + } \ No newline at end of file diff --git a/Sean/views/forgot-password.ejs b/Sean/views/forgot-password.ejs index af82f99..b13f3bb 100644 --- a/Sean/views/forgot-password.ejs +++ b/Sean/views/forgot-password.ejs @@ -5,66 +5,7 @@ Forgot Password - Your Website - +
diff --git a/Sean/views/home.js b/Sean/views/home.js deleted file mode 100644 index 883f205..0000000 --- a/Sean/views/home.js +++ /dev/null @@ -1,107 +0,0 @@ -document.addEventListener("DOMContentLoaded", async function () { - console.log("DOM Loaded"); - - // Extract data from sensorData - const sensorData = JSON.parse('<%- JSON.stringify(sensorData) %>'); - console.log("Sensor Data:", sensorData); - - // Fetch location names from the server - const locationNames = await fetch('/api/locations') // Adjust the API endpoint - .then(response => response.json()) - .then(data => data.map(location => ({ id: location.id, name: location.name }))) - .catch(error => console.error('Error fetching location names:', error)); - - - - // Group sensorData by locationid - const groupedData = groupBy(sensorData, 'locationid'); - - // Get the content div - const contentDiv = document.getElementById('content'); - - // Create a chart for each location - Object.keys(groupedData).forEach(locationId => { - const locationData = groupedData[locationId]; - - // Find the corresponding location name - const locationName = locationNames.find(location => location.id === parseInt(locationId, 10))?.name || `Unknown Location ${locationId}`; - - // Create a container for the chart - const container = document.createElement('div'); - container.className = 'chart-container'; - - // Create a title for the container with location name - const title = document.createElement('h4'); - title.textContent = `Location: ${locationName}`; - container.appendChild(title); - - // Get labels (Location IDs) - const labels = locationData.map(data => new Date(data.createdAt).toLocaleString('en-US', { timeZone: 'Asia/Singapore' })); - - // Create datasets for each measurement - const datasets = [ - { - label: 'CO', - data: locationData.map(data => data.measurement.co), - backgroundColor: 'rgba(255, 99, 132, 0.5)', // Red color - }, - { - label: 'O3', - data: locationData.map(data => data.measurement.o3), - backgroundColor: 'rgba(54, 162, 235, 0.5)', // Blue color - }, - { - label: 'NO2', - data: locationData.map(data => data.measurement.no2), - backgroundColor: 'rgba(255, 206, 86, 0.5)', // Yellow color - }, - { - label: 'SO2', - data: locationData.map(data => data.measurement.so2), - backgroundColor: 'rgba(75, 192, 192, 0.5)', // Green color - }, - ]; - - // Create a canvas element for each location - const canvas = document.createElement('canvas'); - canvas.width = 400; - canvas.height = 200; - - // Append canvas to the container - container.appendChild(canvas); - - // Append container to the content div - contentDiv.appendChild(container); - - // Create a bar chart for each location - const ctx = canvas.getContext('2d'); - new Chart(ctx, { - type: 'bar', - data: { - labels: labels, - datasets: datasets, - }, - options: { - scales: { - x: { - beginAtZero: true, - }, - y: { - beginAtZero: true, - }, - }, - }, - }); - }); - - // Helper function to group data by a specified key - function groupBy(arr, key) { - return arr.reduce((acc, obj) => { - const groupKey = obj[key]; - acc[groupKey] = acc[groupKey] || []; - acc[groupKey].push(obj); - return acc; - }, {}); - } - }); - \ No newline at end of file diff --git a/Sean/views/index.js b/Sean/views/index.js deleted file mode 100644 index 5ee6953..0000000 --- a/Sean/views/index.js +++ /dev/null @@ -1,64 +0,0 @@ -const express = require('express'); -const router = express.Router(); -const mysql = require('mysql'); - -// Replace with your MySQL connection details -const mysqlConfig = { - host: process.env.host, - user: process.env.user, - password: process.env.password, - database: process.env.database, - timezone: 'Z', // Set the timezone to UTC -}; - -const mysqlConnection = mysql.createConnection(mysqlConfig); - -// Middleware to check if the user is authenticated -function isAuthenticated(req, res, next) { - if (req.session && req.session.authenticated) { - return next(); - } else { - res.redirect('/login'); - } -} - -// InUsers route (renders the InUsers tab) -router.get('/', isAuthenticated, (req, res) => { - // Fetch all user data from the database - const userDataQuery = 'SELECT * FROM users'; - - mysqlConnection.query(userDataQuery, (error, userData) => { - if (error) { - console.error('Error fetching user data:', error); - res.status(500).send('Internal Server Error'); - return; - } - - // Render the inusers page with user data - res.render('inusers', { userData: userData }); - }); -}); - -// User Data route -router.get('/userdata', isAuthenticated, (req, res) => { - // Fetch all user data from the database - const userDataQuery = 'SELECT * FROM users'; - - mysqlConnection.query(userDataQuery, (error, userData) => { - if (error) { - console.error('Error fetching user data:', error); - res.status(500).send('Internal Server Error'); - return; - } - - // Render the user-data page with user data - res.render('user-data', { userData: userData }); - }); -}); - -// Edit User Data route -router.get('/edituserdata', isAuthenticated, (req, res) => { - res.render('edit-user-data'); -}); - -module.exports = router; diff --git a/Sean/views/inusers.ejs b/Sean/views/inusers.ejs index 10ade93..1975b70 100644 --- a/Sean/views/inusers.ejs +++ b/Sean/views/inusers.ejs @@ -7,7 +7,7 @@ In-House Users - + @@ -173,19 +173,21 @@
- - - - - - - - - + + + + + + + + diff --git a/Sean/views/inusers.js b/Sean/views/inusers.js index ad9f951..6ca1f73 100644 --- a/Sean/views/inusers.js +++ b/Sean/views/inusers.js @@ -33,7 +33,7 @@ $(document).ready(function () { $('#logsContainer').hide(); $('#additional-text').hide(); $('#additional-text2').hide(); - $('#additional-text2').hide(); + $('#additional-text3').hide(); }); $('#searchUserButton').on('click', function () { @@ -124,6 +124,7 @@ $('#searchResultsList').on('click', '.deleteUserButton', function () { headers: { 'Content-Type': 'application/json', }, + credentials: 'include', // Include cookies in the request body: JSON.stringify({ csrfToken }), // Include CSRF token in the request body }) .then(response => { @@ -243,8 +244,8 @@ function resetFormFields() { method: 'POST', headers: { 'Content-Type': 'application/json', - }, + credentials: 'include', // Include cookies in the request body: JSON.stringify({ name: name, username: username, @@ -330,6 +331,7 @@ $('#resetPasswordForm').on('submit', function (e) { headers: { 'Content-Type': 'application/json', }, + credentials: 'include', // Include cookies in the request body: JSON.stringify({ username: username, password: password, @@ -356,8 +358,6 @@ $('#resetPasswordForm').on('submit', function (e) { $('#resetPassword').val(''); $('#resetConfirmPassword').val(''); - // You might want to hide the reset password form after submission - $('#resetPasswordFormContainer').hide(); }) .catch(error => { // Handle 404 error separately to show an alert diff --git a/Sean/views/login.css b/Sean/views/login.css new file mode 100644 index 0000000..65e544c --- /dev/null +++ b/Sean/views/login.css @@ -0,0 +1,67 @@ +body { + font-family: 'Arial', sans-serif; + background-color: #f4f4f4; + margin: 0; + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + } + + .login-container { + background-color: #fff; + padding: 20px; + border-radius: 8px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + width: 300px; + margin: auto; /* Center the container horizontally */ + } + + h1 { + text-align: center; + color: #333; + } + + form { + margin-top: 20px; + } + + label { + display: block; + margin-bottom: 8px; + color: #555; + } + + input { + width: calc(100% - 16px); /* Adjust the width and center the input */ + padding: 8px; + margin-bottom: 16px; + border: 1px solid #ccc; + border-radius: 4px; + } + + button { + background-color: #4caf50; + color: #fff; + padding: 10px; + border: none; + border-radius: 4px; + cursor: pointer; + width: 100%; + } + + button:hover { + background-color: #45a049; + } + + .reset-link-container { + text-align: center; + margin-top: 10px; + } + + .reset-link { + color: #4caf50; + text-decoration: underline; + cursor: pointer; + } + \ No newline at end of file diff --git a/Sean/views/login.ejs b/Sean/views/login.ejs index ec63272..8d73368 100644 --- a/Sean/views/login.ejs +++ b/Sean/views/login.ejs @@ -4,75 +4,7 @@ Login - Eco Saver - +