CSP WIP add sessionid as well
This commit is contained in:
parent
74b610a7b8
commit
994aebf71d
136
Sean/inusers.js
136
Sean/inusers.js
@ -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.');
|
||||
}
|
||||
}
|
@ -37,6 +37,9 @@ module.exports = (sequelize) => {
|
||||
reset_token_expiry: {
|
||||
type: DataTypes.DATE,
|
||||
},
|
||||
sessionid: {
|
||||
type: DataTypes.STRING,
|
||||
},
|
||||
}, {
|
||||
hooks: {
|
||||
beforeCreate: async (user) => {
|
||||
|
@ -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
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
58
Sean/views/forgot-password.css
Normal file
58
Sean/views/forgot-password.css
Normal file
@ -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;
|
||||
}
|
@ -5,66 +5,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Forgot Password - Your Website</title>
|
||||
<style>
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="forgot-password.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="forgotPasswordForm">
|
||||
|
@ -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;
|
||||
}, {});
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
@ -7,7 +7,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>In-House Users</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap">
|
||||
</head>
|
||||
@ -173,19 +173,21 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="<%= nonce %>">
|
||||
const allUsers = <%- JSON.stringify(allUsers) %>;
|
||||
const currentUsername = '<%= currentUsername %>';
|
||||
const nonce = "<%= nonce %>"
|
||||
console.log('Nonce:', nonce);
|
||||
</script>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.4/xlsx.full.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.2.1/exceljs.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.js"></script>
|
||||
<script src="inusers.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.4.min.js" nonce="<%= nonce %>"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.3.0/js/bootstrap.bundle.min.js" nonce="<%= nonce %>" ></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.4/xlsx.full.min.js" nonce="<%= nonce %>"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js" nonce="<%= nonce %>"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.2.1/exceljs.min.js" nonce="<%= nonce %>"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" nonce="<%= nonce %>"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.js" nonce="<%= nonce %>"></script>
|
||||
<script src="inusers.js" nonce="<%= nonce %>"></script>
|
||||
|
||||
|
||||
</body>
|
||||
|
@ -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
|
||||
|
67
Sean/views/login.css
Normal file
67
Sean/views/login.css
Normal file
@ -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;
|
||||
}
|
||||
|
@ -4,75 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Login - Eco Saver</title>
|
||||
<style>
|
||||
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;
|
||||
}
|
||||
|
||||
</style>
|
||||
<link rel="stylesheet" href="login.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
|
@ -1,146 +0,0 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
|
||||
*{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Poppins',sans-serif;
|
||||
}
|
||||
body{
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
background: linear-gradient(135deg, #71b7e6, #9b59b6);
|
||||
}
|
||||
.container{
|
||||
max-width: 700px;
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
padding: 25px 30px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 5px 10px rgba(0,0,0,0.15);
|
||||
}
|
||||
.container .title{
|
||||
font-size: 25px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
}
|
||||
.container .title::before{
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
height: 3px;
|
||||
width: 30px;
|
||||
border-radius: 5px;
|
||||
background: linear-gradient(135deg, #71b7e6, #9b59b6);
|
||||
}
|
||||
.content form .user-details{
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin: 20px 0 12px 0;
|
||||
}
|
||||
form .user-details .input-box{
|
||||
margin-bottom: 15px;
|
||||
width: calc(100% / 2 - 20px);
|
||||
}
|
||||
form .input-box span.details{
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.user-details .input-box input{
|
||||
height: 45px;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
font-size: 16px;
|
||||
border-radius: 5px;
|
||||
padding-left: 15px;
|
||||
border: 1px solid #ccc;
|
||||
border-bottom-width: 2px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.user-details .input-box input:focus,
|
||||
.user-details .input-box input:valid{
|
||||
border-color: #9b59b6;
|
||||
}
|
||||
form .gender-details .gender-title{
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
}
|
||||
form .category{
|
||||
display: flex;
|
||||
width: 80%;
|
||||
margin: 14px 0 ;
|
||||
justify-content: space-between;
|
||||
}
|
||||
form .category label{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
form .category label .dot{
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
background: #d9d9d9;
|
||||
border: 5px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
#dot-1:checked ~ .category label .one,
|
||||
#dot-2:checked ~ .category label .two,
|
||||
#dot-3:checked ~ .category label .three{
|
||||
background: #9b59b6;
|
||||
border-color: #d9d9d9;
|
||||
}
|
||||
form input[type="radio"]{
|
||||
display: none;
|
||||
}
|
||||
form .button{
|
||||
height: 45px;
|
||||
margin: 35px 0
|
||||
}
|
||||
form .button input{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 1px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background: linear-gradient(135deg, #71b7e6, #9b59b6);
|
||||
}
|
||||
form .button input:hover{
|
||||
/* transform: scale(0.99); */
|
||||
background: linear-gradient(-135deg, #71b7e6, #9b59b6);
|
||||
}
|
||||
@media(max-width: 584px){
|
||||
.container{
|
||||
max-width: 100%;
|
||||
}
|
||||
form .user-details .input-box{
|
||||
margin-bottom: 15px;
|
||||
width: 100%;
|
||||
}
|
||||
form .category{
|
||||
width: 100%;
|
||||
}
|
||||
.content form .user-details{
|
||||
max-height: 300px;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.user-details::-webkit-scrollbar{
|
||||
width: 5px;
|
||||
}
|
||||
}
|
||||
@media(max-width: 459px){
|
||||
.container .content .category{
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
50
Sean/views/otp.css
Normal file
50
Sean/views/otp.css
Normal file
@ -0,0 +1,50 @@
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
text-align: center;
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 300px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
margin-top: 10px;
|
||||
}
|
@ -4,58 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Enter OTP</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f4f4f4;
|
||||
text-align: center;
|
||||
margin: 50px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 300px;
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
<link rel="stylesheet" href="otp.css">
|
||||
</head>
|
||||
<body>
|
||||
<h2>Enter OTP</h2>
|
||||
|
@ -1,162 +0,0 @@
|
||||
/* Responsive CSS Here */
|
||||
@media screen and (max-width: 950px) {
|
||||
.nav-img {
|
||||
height: 25px;
|
||||
}
|
||||
.nav-option {
|
||||
gap: 30px;
|
||||
}
|
||||
.nav-option h3 {
|
||||
font-size: 15px;
|
||||
}
|
||||
.report-topic-heading,
|
||||
.item1,
|
||||
.items {
|
||||
width: 800px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 850px) {
|
||||
.nav-img {
|
||||
height: 30px;
|
||||
}
|
||||
.nav-option {
|
||||
gap: 30px;
|
||||
}
|
||||
.nav-option h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
.report-topic-heading,
|
||||
.item1,
|
||||
.items {
|
||||
width: 700px;
|
||||
}
|
||||
.navcontainer {
|
||||
width: 100vw;
|
||||
position: absolute;
|
||||
transition: all 0.6s ease-in-out;
|
||||
top: 0;
|
||||
left: -100vw;
|
||||
}
|
||||
.nav {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
.navclose {
|
||||
left: 00px;
|
||||
}
|
||||
.searchbar {
|
||||
display: none;
|
||||
}
|
||||
.main {
|
||||
padding: 40px 30px 30px 30px;
|
||||
}
|
||||
.searchbar2 {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
margin: 0 0 40px 0;
|
||||
justify-content: center;
|
||||
}
|
||||
.searchbar2 input {
|
||||
width: 250px;
|
||||
height: 42px;
|
||||
border-radius: 50px 0 0 50px;
|
||||
background-color: var(--background-color3);
|
||||
padding: 0 20px;
|
||||
font-size: 15px;
|
||||
border: 2px solid var(--secondary-color);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 490px) {
|
||||
.message {
|
||||
display: none;
|
||||
}
|
||||
.logosec {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.logo {
|
||||
font-size: 20px;
|
||||
}
|
||||
.menuicn {
|
||||
height: 25px;
|
||||
}
|
||||
.nav-img {
|
||||
height: 25px;
|
||||
}
|
||||
.nav-option {
|
||||
gap: 25px;
|
||||
}
|
||||
.nav-option h3 {
|
||||
font-size: 12px;
|
||||
}
|
||||
.nav-upper-options {
|
||||
gap: 15px;
|
||||
}
|
||||
.recent-Articles {
|
||||
font-size: 20px;
|
||||
}
|
||||
.report-topic-heading,
|
||||
.item1,
|
||||
.items {
|
||||
width: 550px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
.recent-Articles {
|
||||
font-size: 17px;
|
||||
}
|
||||
.view {
|
||||
width: 60px;
|
||||
font-size: 10px;
|
||||
height: 27px;
|
||||
}
|
||||
.report-header {
|
||||
height: 60px;
|
||||
padding: 10px 10px 5px 10px;
|
||||
}
|
||||
.searchbtn img {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 320px) {
|
||||
.recent-Articles {
|
||||
font-size: 12px;
|
||||
}
|
||||
.view {
|
||||
width: 50px;
|
||||
font-size: 8px;
|
||||
height: 27px;
|
||||
}
|
||||
.report-header {
|
||||
height: 60px;
|
||||
padding: 10px 5px 5px 5px;
|
||||
}
|
||||
.t-op {
|
||||
font-size: 12px;
|
||||
}
|
||||
.t-op-nextlvl {
|
||||
font-size: 10px;
|
||||
}
|
||||
.report-topic-heading,
|
||||
.item1,
|
||||
.items {
|
||||
width: 300px;
|
||||
}
|
||||
.report-body {
|
||||
padding: 10px;
|
||||
}
|
||||
.label-tag {
|
||||
width: 70px;
|
||||
}
|
||||
.searchbtn {
|
||||
width: 40px;
|
||||
}
|
||||
.searchbar2 input {
|
||||
width: 180px;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
<!-- setup-mfa.ejs (or your template engine's file) -->
|
||||
<h2>Setup Multi-Factor Authentication</h2>
|
||||
<p>Scan the QR code below with your authenticator app:</p>
|
||||
<img src="<%= qrCodeDataUri %>" alt="QR Code">
|
||||
|
||||
<form action="/setup-mfa" method="post">
|
||||
<label for="mfaCode">Enter MFA Code:</label>
|
||||
<input type="text" id="mfaCode" name="mfaCode" required>
|
||||
<button type="submit">Verify and Save</button>
|
||||
</form>
|
5
package-lock.json
generated
5
package-lock.json
generated
@ -1156,6 +1156,11 @@
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"helmet": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/helmet/-/helmet-7.1.0.tgz",
|
||||
"integrity": "sha512-g+HZqgfbpXdCkme/Cd/mZkV0aV3BZZZSugecH03kl38m/Kmdx8jKjBikpDj2cr+Iynv4KpYEviojNdTJActJAg=="
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||
|
Loading…
x
Reference in New Issue
Block a user