sensor and location done and some cleanupd
testing will be required
This commit is contained in:
		
							
								
								
									
										42
									
								
								Sean/modules/otpUtils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								Sean/modules/otpUtils.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| const nodemailer = require("nodemailer"); | ||||
| const otpGenerator = require('otp-generator'); | ||||
| const path = require('path') | ||||
| require('dotenv').config({ path: path.resolve(__dirname, '../.env') }) | ||||
|  | ||||
| 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', | ||||
| 		host: 'smtp.gmail.com', | ||||
|         port: 587, // use the appropriate port for your SMTP server | ||||
|         secure: false, // true for 465, false for other ports | ||||
| 		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; | ||||
| 	} | ||||
|   }; | ||||
|  | ||||
|   module.exports = { | ||||
|     generateOTP, | ||||
|     sendOTPByEmail | ||||
|   }; | ||||
							
								
								
									
										9
									
								
								Sean/modules/rateLimitMiddleware.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								Sean/modules/rateLimitMiddleware.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | ||||
| const rateLimit = require('express-rate-limit'); | ||||
|  | ||||
| const limiter = rateLimit({ | ||||
|   windowMs: 15 * 60 * 1000, // 15 minutes | ||||
|   max: 5, // limit each IP to 5 requests per windowMs | ||||
|   message: 'Too many login attempts from this IP, please try again later.', | ||||
| }); | ||||
|  | ||||
| module.exports = limiter; | ||||
							
								
								
									
										77
									
								
								Sean/modules/validationMiddleware.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								Sean/modules/validationMiddleware.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,77 @@ | ||||
| const { body } = require('express-validator'); | ||||
|  | ||||
| const locationValidation = [ | ||||
|   body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(), | ||||
|   body('added_by').trim().isLength({ min: 1 }).withMessage('Added by must not be empty').escape(), | ||||
|   body('description').trim().escape(), | ||||
| ]; | ||||
|  | ||||
| const locationValidationUpdate = [ | ||||
| 	body('id').trim().escape(), | ||||
| 	body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(), | ||||
| 	body('added_by').trim().isLength({ min: 1 }).withMessage('Added by must not be empty').escape(), | ||||
| 	body('description').trim().escape(), | ||||
|   ]; | ||||
|  | ||||
| const locationdeleteValidation = [ | ||||
| 	body('id').trim().escape() | ||||
|   ]; | ||||
|  | ||||
| const sensorValidation = [ | ||||
| 	body('sensorname').trim().isLength({ min: 1 }).withMessage('Sensor Name must not be empty').escape(), | ||||
| 	body('added_by').trim().isLength({ min: 1 }).withMessage('Added by must not be empty').escape(), | ||||
| 	body('macAddress').custom(value => { | ||||
| 	  const macAddressRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; | ||||
| 	  if (!macAddressRegex.test(value)) { | ||||
| 		throw new Error('Invalid MAC address format'); | ||||
| 	  } | ||||
| 	  return true; | ||||
| 	}).withMessage('Invalid MAC address format').escape(), | ||||
| 	body('description').trim().escape(), | ||||
| 	body('location').trim().escape() | ||||
|   ]; | ||||
|  | ||||
| const sensorupdateValidation = [ | ||||
| 	body('id').trim().escape(), | ||||
| 	body('sensorname').trim().isLength({ min: 1 }).withMessage('Sensor Name must not be empty').escape(), | ||||
| 	body('added_by').trim().isLength({ min: 1 }).withMessage('Added by must not be empty').escape(), | ||||
| 	body('macAddress').custom(value => { | ||||
| 	  const macAddressRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; | ||||
| 	  if (!macAddressRegex.test(value)) { | ||||
| 		throw new Error('Invalid MAC address format'); | ||||
| 	  } | ||||
| 	  return true; | ||||
| 	}).withMessage('Invalid MAC address format').escape(), | ||||
| 	body('description').trim().escape(), | ||||
| 	body('location').trim().escape() | ||||
|   ]; | ||||
|  | ||||
| const sensordeleteValidation = [ | ||||
| 	body('id').trim().escape() | ||||
|   ]; | ||||
|  | ||||
| const loginValidation = [ | ||||
| 	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'), | ||||
|   ]; | ||||
|  | ||||
| const otpValidation = [ | ||||
| 	body('otp').escape().trim().isLength({ min: 1 }).withMessage('OTP must not be empty'), | ||||
|   ]; | ||||
|  | ||||
| const createValidation = [ | ||||
| 	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(), | ||||
|   ];  | ||||
|    | ||||
|    | ||||
| module.exports = { | ||||
|   locationValidation,locationValidationUpdate,locationdeleteValidation | ||||
|   ,sensorValidation,sensorupdateValidation,sensordeleteValidation,loginValidation,otpValidation | ||||
|   ,createValidation | ||||
| }; | ||||
							
								
								
									
										280
									
								
								Sean/server.js
									
									
									
									
									
								
							
							
						
						
									
										280
									
								
								Sean/server.js
									
									
									
									
									
								
							| @ -1,21 +1,23 @@ | ||||
| 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"); | ||||
| const nodemailer = require("nodemailer"); | ||||
| const otpGenerator = require('otp-generator'); | ||||
| const { body, validationResult } = require('express-validator'); | ||||
| const validator = require('validator'); | ||||
| const axios = require('axios'); | ||||
|  | ||||
| const {validationResult } = require('express-validator'); | ||||
| const {locationValidation, locationValidationUpdate, locationdeleteValidation | ||||
| ,sensorValidation, sensorupdateValidation, sensordeleteValidation, loginValidation | ||||
| ,otpValidation, createValidation} = require('./modules/validationMiddleware'); | ||||
| const rateLimit = require('./modules/rateLimitMiddleware'); | ||||
| const { generateOTP, sendOTPByEmail } = require('./modules/otpUtils'); | ||||
| const { format } = require('date-fns'); | ||||
| 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 userLogs= require('./models/userLogs')(sequelize);  | ||||
| const app = express(); | ||||
|  | ||||
| app.use(bodyParser.urlencoded({ extended: true })); | ||||
| @ -28,7 +30,6 @@ app.use(bodyParser.urlencoded({ extended: true })); | ||||
|  | ||||
| app.set("view engine", "ejs"); | ||||
|  | ||||
|  | ||||
| app.use(session({ | ||||
|     secret: process.env.key, | ||||
|     resave: false, | ||||
| @ -46,76 +47,27 @@ function isAuthenticated(req, res, next) { | ||||
| 		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 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); | ||||
| app.use('/login', rateLimit); | ||||
|    | ||||
| app.post('/login', loginValidation, async (req, res) => { | ||||
|   try {const errors = validationResult(req); | ||||
|     if (!errors.isEmpty()) { | ||||
|       return res.render('login', { error: 'Invalid input. Please check your credentials.', csrfToken: req.session.csrfToken }); | ||||
|     } | ||||
|  | ||||
|     let { username, password } = req.body; | ||||
|     username = username.trim(); | ||||
|  | ||||
|     const user = await User.findOne({ where: { username } }); | ||||
|  | ||||
|     if (user) { | ||||
|       const isLoginSuccessful = await bcrypt.compare(password, user.password); | ||||
|  | ||||
|       if (isLoginSuccessful) { | ||||
| 		await userLogs.create({ username, success: true, activity: "Credentials entered correctly" }); | ||||
|  | ||||
|  | ||||
|         const { otp, expirationTime } = generateOTP(); | ||||
|  | ||||
|         req.session.otp = otp; | ||||
| @ -161,17 +113,12 @@ async (req, res) => { | ||||
|  | ||||
|  | ||||
| // OTP verification route | ||||
| app.post("/verify-otp", [ | ||||
| 	body('otp').escape().trim().isLength({ min: 1 }).withMessage('OTP must not be empty'), | ||||
|   ], | ||||
|   async (req, res) => { | ||||
| app.post("/verify-otp", otpValidation ,async (req, res) => { | ||||
| 	try { | ||||
| 	  const errors = validationResult(req); | ||||
|    | ||||
| 	  if (!errors.isEmpty()) { | ||||
| 		return res.render('otp', { error: 'Invalid OTP. Please try again.'}); | ||||
| 	  } | ||||
|    | ||||
| 	  const enteredOTP = req.body.otp; | ||||
|    | ||||
| 	  if (!req.session) { | ||||
| @ -209,17 +156,10 @@ app.post("/verify-otp", [ | ||||
| 		req.session.authenticated = true; | ||||
| 		req.session.username = req.body.username; | ||||
| 		req.session.sessionToken = sessionToken; | ||||
|    | ||||
| 		 | ||||
| 		csrfTokenSession = crypto.randomBytes(32).toString('hex'); | ||||
|    | ||||
| 		// Log anti-CSRF token | ||||
| 		console.log(`Generated Anti-CSRF Token: ${csrfTokenSession}`); | ||||
|    | ||||
| 		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}`); | ||||
|    | ||||
| 		res.redirect("/home"); | ||||
| 	  } else { | ||||
| 		if (req.body.username) { | ||||
| @ -262,17 +202,14 @@ app.post("/verify-otp", [ | ||||
| 	} | ||||
|   }); | ||||
|    | ||||
|   app.get("/home", isAuthenticated, async (req, res) => { | ||||
| 	   | ||||
|  | ||||
|   app.get("/home", isAuthenticated, (req, res) => { | ||||
| 	  // Render the home page with sensor data | ||||
| 	  res.render("home", { | ||||
| 		username: req.session.username, | ||||
| 	  }); | ||||
| 	  const response = await axios.get(process.env.API_ALLLOCATION); | ||||
| 	  const valueData = response.data; | ||||
| 	  console.log = (valueData); | ||||
| 	  res.render("home", { username: req.session.username, valueData}); | ||||
| 	}); | ||||
|  | ||||
|  | ||||
|    | ||||
| 	app.get("/inusers", isAuthenticated, async (req, res) => { | ||||
| 		try { | ||||
| 			// Fetch all user data from the database using Sequelize | ||||
| @ -318,20 +255,8 @@ function isStrongPassword(password) { | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
|  | ||||
|  | ||||
| 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) => { | ||||
|     '/createUser', createValidation, async (req, res) => { | ||||
|         try { | ||||
|             const errors = validationResult(req); | ||||
|  | ||||
| @ -463,24 +388,13 @@ app.post("/forgot-password", async (req, res) => { | ||||
| 		const error = "Username or email not found."; | ||||
| 		return res.render("forgot-password", { error, success: null }); | ||||
| 	  } | ||||
|    | ||||
| 	  // Generate reset token and update the user | ||||
| 	  const reset_token = crypto.randomBytes(20).toString("hex"); | ||||
| 	  const reset_token_expiry = new Date(Date.now() + 3600000); // Token expires in 1 hour | ||||
|    | ||||
| 	  // Update the user with the reset token and expiry | ||||
| 	  await User.update( | ||||
| 		{ | ||||
| 		  reset_token, | ||||
| 		  reset_token_expiry, | ||||
| 		}, | ||||
| 		{ | ||||
| 		  where: { | ||||
| 			id: user.id, // Replace 'id' with the actual primary key field of your User model | ||||
| 		  }, | ||||
| 		} | ||||
| 	  await User.update({reset_token,reset_token_expiry,}, | ||||
| 		{where: {id: user.id},} | ||||
| 	  ); | ||||
|    | ||||
| 	  // Send email with reset link | ||||
| 	  const resetLink = `http://localhost:3000/reset-password/${reset_token}`; | ||||
| 	  const mailOptions = { | ||||
| @ -488,12 +402,9 @@ app.post("/forgot-password", async (req, res) => { | ||||
| 		subject: "Password Reset", | ||||
| 		text: `Click on the following link to reset your password: ${resetLink}`, | ||||
| 	  }; | ||||
|    | ||||
| 	  await transporter.sendMail(mailOptions); | ||||
|    | ||||
| 	  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 | ||||
| 	  await userLogs.create({ | ||||
| 		username: user.username, | ||||
| @ -509,29 +420,22 @@ app.post("/forgot-password", async (req, res) => { | ||||
| 	  console.error("Error during password reset:", error); | ||||
| 	  const errorMessage = "An error occurred during the password reset process."; | ||||
| 	  res.render("forgot-password", { error: errorMessage, success: null }); | ||||
| 	} | ||||
|   }); | ||||
|    | ||||
|    | ||||
| 	}}); | ||||
|    | ||||
|   app.post("/reset-password/:token", async (req, res) => { | ||||
| 	try { | ||||
| 	  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 user = await User.findOne({ | ||||
| 		where: { | ||||
| 		  reset_token: sanitizedToken, | ||||
| 		  reset_token_expiry: { [Sequelize.Op.gt]: new Date() }, | ||||
| 		where: {reset_token: sanitizedToken, | ||||
| 		reset_token_expiry: { [Sequelize.Op.gt]: new Date() }, | ||||
| 		}, | ||||
| 	  }); | ||||
|    | ||||
| 	  if (!user) { | ||||
| 		// Pass the error to the template when rendering the reset-password page | ||||
| 		return res.render("reset-password", { | ||||
| @ -539,7 +443,6 @@ app.post("/forgot-password", async (req, res) => { | ||||
| 		  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 | ||||
| @ -548,31 +451,24 @@ app.post("/forgot-password", async (req, res) => { | ||||
| 		  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: | ||||
| 		  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 saltRounds = 10; | ||||
| 	  const hashedPassword = await bcrypt.hash(sanitizedPassword, saltRounds); | ||||
|    | ||||
| 	  // Update user's password and clear reset token | ||||
| 	  const updateQuery = { | ||||
| 		password: hashedPassword, | ||||
| 		reset_token: null, | ||||
| 		reset_token_expiry: null, | ||||
| 	  }; | ||||
| 	  const whereCondition = { | ||||
| 		reset_token: sanitizedToken, | ||||
| 	  }; | ||||
|    | ||||
| 	  const whereCondition = {reset_token: sanitizedToken,}; | ||||
| 	  await User.update(updateQuery, { | ||||
| 		where: whereCondition, | ||||
| 	  }); | ||||
| @ -619,10 +515,8 @@ app.post("/reset-password", async (req, res) => { | ||||
|         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' }); | ||||
|             } | ||||
| @ -630,12 +524,10 @@ app.post("/reset-password", async (req, res) => { | ||||
|     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({ | ||||
| @ -643,31 +535,25 @@ app.post("/reset-password", async (req, res) => { | ||||
|                 "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.", | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     try { | ||||
|         // Find the user in the database | ||||
|         const user = await User.findOne({ where: { username: sanitizedUsername } }); | ||||
|  | ||||
|         if (!user) { | ||||
|             return res.status(404).json({ error: "User does not exist" }); | ||||
|         } | ||||
|  | ||||
|         // Generate a random salt and hash the new password | ||||
|         const saltRounds = 10; | ||||
|         const hashedPassword = await bcrypt.hash(sanitizedPassword, saltRounds); | ||||
|  | ||||
|         // Update user's password | ||||
|         await User.update( | ||||
| 			{ password: hashedPassword }, | ||||
| 			{ where: { username: sanitizedUsername } } | ||||
| 		); | ||||
|  | ||||
|         // Log password reset activity | ||||
|         await userLogs.create({ | ||||
|             username: creatorUsername, | ||||
|             activity: `Password has been reset for ${sanitizedUsername}`, | ||||
|         }); | ||||
|  | ||||
|         // Password update successful | ||||
|         return res.status(200).json({ success: "Password updated successfully" }); | ||||
|     } catch (error) { | ||||
| @ -679,10 +565,8 @@ app.post("/reset-password", async (req, res) => { | ||||
|  | ||||
| app.get('/searchUser', async (req, res) => { | ||||
|     const { username } = req.query; | ||||
|  | ||||
|     // Sanitize the input | ||||
|     const sanitizedUsername = validator.escape(username); | ||||
|  | ||||
|     try { | ||||
|         // Find the user in the database | ||||
|         const user = await User.findOne({ where: { username: sanitizedUsername } }); | ||||
| @ -690,11 +574,7 @@ app.get('/searchUser', async (req, res) => { | ||||
|         if (!user) { | ||||
|             // No user found with the given username | ||||
|             res.status(404).json({ success: false, error: 'User not found' }); | ||||
|         } else { | ||||
|             // User found, return user data | ||||
|             res.json(user); | ||||
|  | ||||
|         } | ||||
|         } else {res.json(user)} | ||||
|     } catch (error) { | ||||
|         console.error('Sequelize query error:', error); | ||||
|         res.status(500).json({ success: false, error: 'Internal Server Error' }); | ||||
| @ -705,7 +585,6 @@ app.get('/api/users', async (req, res) => { | ||||
|     try { | ||||
|         // Find all users in the database | ||||
|         const users = await User.findAll(); | ||||
|  | ||||
|         // Return the users in the response | ||||
|         res.json(users); | ||||
|     } catch (error) { | ||||
| @ -774,9 +653,6 @@ app.delete('/api/deleteUser/:username', async (req, res) => { | ||||
|     } | ||||
| }); | ||||
|  | ||||
|  | ||||
|    | ||||
|  | ||||
| app.get('/api/getLogs', async (req, res) => { | ||||
|     try { | ||||
|         // Query the database to fetch logs using Sequelize model | ||||
| @ -814,11 +690,6 @@ app.get("/locations", isAuthenticated, async (req, res) => { | ||||
| 	} | ||||
|   }); | ||||
|  | ||||
|   const locationValidation = [ | ||||
| 	body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(), | ||||
| 	body('added_by').trim().isLength({ min: 1 }).withMessage('Added by must not be empty').escape(), | ||||
| 	body('description').trim().escape(), | ||||
|   ]; | ||||
|   app.post('/location/new', locationValidation, async (req, res) => { | ||||
| 	try { | ||||
| 	  const errors = validationResult(req); | ||||
| @ -846,12 +717,6 @@ app.get("/locations", isAuthenticated, async (req, res) => { | ||||
| 	} | ||||
|   }); | ||||
|  | ||||
|   const locationValidationUpdate = [ | ||||
| 	body('id').trim().escape(), | ||||
| 	body('name').trim().isLength({ min: 1 }).withMessage('Name must not be empty').escape(), | ||||
| 	body('added_by').trim().isLength({ min: 1 }).withMessage('Added by must not be empty').escape(), | ||||
| 	body('description').trim().escape(), | ||||
|   ]; | ||||
|   app.post('/location/update', locationValidationUpdate, async (req, res) => { | ||||
| 	try { | ||||
| 	  const errors = validationResult(req); | ||||
| @ -880,6 +745,33 @@ app.get("/locations", isAuthenticated, async (req, res) => { | ||||
|   }); | ||||
|  | ||||
|   | ||||
|   app.post('location/delete',locationdeleteValidation, async (req, res) => { | ||||
| 	try { | ||||
| 	  const errors = validationResult(req); | ||||
| 	  if (!errors.isEmpty()) { | ||||
| 		return res.status(400).json({ errors: errors.array() }); | ||||
| 	  } | ||||
| 	  const sessionTokencookie = req.cookies['sessionToken']; | ||||
| 	  const user = await User.findOne({ where: { sessionid: sessionTokencookie } }); | ||||
| 	  if (!user) { | ||||
| 		  return res.status(403).json({ error: 'Invalid sessionToken' }); | ||||
| 	  } | ||||
| 	  const submittedCSRFToken = req.body.csrf_token; | ||||
| 	  if (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) { | ||||
| 		  return res.status(403).json({ error: 'CSRF token mismatch' }); | ||||
| 	  } | ||||
| 	  const {id} = req.body; | ||||
| 	  const preparedData = {id}; | ||||
| 	  // Make a POST request with the sanitized data using Axios | ||||
| 	  const axiosResponse = await axios.post(process.env.API_DELLOCATION, preparedData); | ||||
| 	  // Send the Axios response back to the client | ||||
| 	  res.status(axiosResponse.status).json(axiosResponse.data); | ||||
| 	} catch (error) { | ||||
| 	  console.error('Error handling new sensor submission:', error); | ||||
| 	  res.status(500).json({ message: 'Internal Server Error' }); | ||||
| 	} | ||||
|   }); | ||||
|  | ||||
| app.get("/sensors", isAuthenticated, async (req, res) => { | ||||
| 	try { | ||||
| 		// Render the inusers page with JSON data | ||||
| @ -894,21 +786,34 @@ app.get("/sensors", isAuthenticated, async (req, res) => { | ||||
| 	} | ||||
| }); | ||||
|  | ||||
| const sensorValidation = [ | ||||
| 	body('id').trim().escape(), | ||||
| 	body('sensorname').trim().isLength({ min: 1 }).withMessage('Sensor Name must not be empty').escape(), | ||||
| 	body('added_by').trim().isLength({ min: 1 }).withMessage('Added by must not be empty').escape(), | ||||
| 	body('macAddress').custom(value => { | ||||
| 	  const macAddressRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; | ||||
| 	  if (!macAddressRegex.test(value)) { | ||||
| 		throw new Error('Invalid MAC address format'); | ||||
| 	  } | ||||
| 	  return true; | ||||
| 	}).withMessage('Invalid MAC address format').escape(), | ||||
| 	body('description').trim().escape(), | ||||
| 	body('location').trim().escape() | ||||
|   ]; | ||||
|   app.post('sensor/new',sensorValidation, async (req, res) => { | ||||
| 	try { | ||||
| 	  const errors = validationResult(req); | ||||
| 	  if (!errors.isEmpty()) { | ||||
| 		return res.status(400).json({ errors: errors.array() }); | ||||
| 	  } | ||||
| 	  const sessionTokencookie = req.cookies['sessionToken']; | ||||
| 	  const user = await User.findOne({ where: { sessionid: sessionTokencookie } }); | ||||
| 	  if (!user) { | ||||
| 		  return res.status(403).json({ error: 'Invalid sessionToken' }); | ||||
| 	  } | ||||
| 	  const submittedCSRFToken = req.body.csrf_token; | ||||
| 	  if (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) { | ||||
| 		  return res.status(403).json({ error: 'CSRF token mismatch' }); | ||||
| 	  } | ||||
| 	  const { sensorname, added_by, macAddress, description, location} = req.body; | ||||
| 	  const preparedData = {sensorname, added_by, macAddress, description, location}; | ||||
| 	  // Make a POST request with the sanitized data using Axios | ||||
| 	  const axiosResponse = await axios.post(process.env.API_NEWSENSOR, preparedData); | ||||
| 	  // Send the Axios response back to the client | ||||
| 	  res.status(axiosResponse.status).json(axiosResponse.data); | ||||
| 	} catch (error) { | ||||
| 	  console.error('Error handling new sensor submission:', error); | ||||
| 	  res.status(500).json({ message: 'Internal Server Error' }); | ||||
| 	} | ||||
|   }); | ||||
|  | ||||
|   app.post('sensor/update',sensorupdateValidation, async (req, res) => { | ||||
| 	try { | ||||
| 	  const errors = validationResult(req); | ||||
| 	  if (!errors.isEmpty()) { | ||||
| @ -935,6 +840,33 @@ const sensorValidation = [ | ||||
| 	} | ||||
|   }); | ||||
|  | ||||
|   app.post('sensor/delete',sensordeleteValidation, async (req, res) => { | ||||
| 	try { | ||||
| 	  const errors = validationResult(req); | ||||
| 	  if (!errors.isEmpty()) { | ||||
| 		return res.status(400).json({ errors: errors.array() }); | ||||
| 	  } | ||||
| 	  const sessionTokencookie = req.cookies['sessionToken']; | ||||
| 	  const user = await User.findOne({ where: { sessionid: sessionTokencookie } }); | ||||
| 	  if (!user) { | ||||
| 		  return res.status(403).json({ error: 'Invalid sessionToken' }); | ||||
| 	  } | ||||
| 	  const submittedCSRFToken = req.body.csrf_token; | ||||
| 	  if (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) { | ||||
| 		  return res.status(403).json({ error: 'CSRF token mismatch' }); | ||||
| 	  } | ||||
| 	  const {id} = req.body; | ||||
| 	  const preparedData = {id}; | ||||
| 	  // Make a POST request with the sanitized data using Axios | ||||
| 	  const axiosResponse = await axios.post(process.env.API_DELSENSOR, preparedData); | ||||
| 	  // Send the Axios response back to the client | ||||
| 	  res.status(axiosResponse.status).json(axiosResponse.data); | ||||
| 	} catch (error) { | ||||
| 	  console.error('Error handling new sensor submission:', error); | ||||
| 	  res.status(500).json({ message: 'Internal Server Error' }); | ||||
| 	} | ||||
|   }); | ||||
|  | ||||
| app.use(express.static("views")); | ||||
|  | ||||
| app.listen(PORT, () => { | ||||
|  | ||||
| @ -3,19 +3,26 @@ $(document).ready(function () { | ||||
|       $('#locationContainer').show(); | ||||
|       $('#createLocationForm').hide(); | ||||
|       $('#updateLocationForm').hide(); | ||||
|       $('#deleteLocationForm').hide(); | ||||
|     }); | ||||
|     $('#addLocationLink').on('click', function () { | ||||
|         $('#locationContainer').hide(); | ||||
|         $('#createLocationForm').show(); | ||||
|         $('#updateLocationForm').hide(); | ||||
|         $('#deleteLocationForm').hide(); | ||||
|       }); | ||||
|       $('#updateLocationLink').on('click', function () { | ||||
|         $('#locationContainer').hide(); | ||||
|         $('#createLocationForm').hide(); | ||||
|         $('#updateLocationForm').show(); | ||||
|         populateLocationDropdown(); | ||||
|         $('#deleteLocationForm').hide(); | ||||
|       }); | ||||
|       $('#deleteLocationLink').on('click', function () { | ||||
|         $('#locationContainer').hide(); | ||||
|         $('#createLocationForm').hide(); | ||||
|         $('#updateLocationForm').show(); | ||||
|         $('#deleteLocationForm').show(); | ||||
|       });  | ||||
|        | ||||
|   }); | ||||
|  | ||||
|   let locationArray = []; | ||||
| @ -42,8 +49,7 @@ $(document).ready(function () { | ||||
|     }); | ||||
|   } | ||||
|   populateTableAndArray(locationsData); | ||||
|   console.log(locationArray); | ||||
|  | ||||
|   populateLocationDropdown(); | ||||
| $('#locationForm').on('submit', function (e) { | ||||
|     e.preventDefault(); | ||||
|  | ||||
| @ -136,3 +142,39 @@ $('#locationForm').on('submit', function (e) { | ||||
|     // Handle error as needed | ||||
| }); | ||||
|   }); | ||||
|  | ||||
|   $('#deleteForm').on('submit', function (e) { | ||||
|     e.preventDefault(); | ||||
|     const selectedLocationId = $('#locationDropdown').val(); | ||||
|     const csrf_token = $('#userForm input[name="csrf_token"]').val(); | ||||
|  | ||||
|     fetch('/location/delete', { | ||||
|       method: 'DELETE', | ||||
|       headers: { | ||||
|           'Content-Type': 'application/json' | ||||
|       }, | ||||
|       body: JSON.stringify({ | ||||
|           id:selectedLocationId, | ||||
|           csrf_token: csrf_token | ||||
|       }), | ||||
|   }) | ||||
|   .then(response => { | ||||
|     if (response.ok) { | ||||
|         // Status 201 indicates successful creation | ||||
|         return response.json(); | ||||
|     } else { | ||||
|         return response.json().then(data => { | ||||
|             throw new Error(data.message || `HTTP error! Status: ${response.status}`); | ||||
|         }); | ||||
|     } | ||||
| }) | ||||
| .then(data => { | ||||
|     console.log(`Location deleted successfully. Message: ${data.message}`); | ||||
|     alert('Location deleted successfully!'); | ||||
|     resetFormFields(); | ||||
| }) | ||||
| .catch(error => { | ||||
|     console.error('Location not deleted successfully', error); | ||||
|     // Handle error as needed | ||||
| }); | ||||
|   }); | ||||
| @ -62,9 +62,9 @@ | ||||
|   </div> | ||||
|  | ||||
|   <div id="updateLocationForm" class="location-update-container" style="display: none;"> | ||||
|     <h3>Add Location</h3> | ||||
|     <h3>Update Location</h3> | ||||
|     <div class="content"> | ||||
|       <form action="/api/v0/location/update" id="updateForm" method="put"> | ||||
|       <form action="/location/update" id="updateForm" method="put"> | ||||
|         <div class="Location-details"> | ||||
|           <div class="input-box"> | ||||
|             <span class="details">Location to Update</span> | ||||
| @ -87,6 +87,25 @@ | ||||
|       </form> | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
|   <div id="deleteLocationForm" class="location-delete-container" style="display: none;"> | ||||
|     <h3>Delete Location</h3> | ||||
|     <div class="content"> | ||||
|       <form action="/location/delete" id="deleteForm" method="delete"> | ||||
|         <div class="Location-details"> | ||||
|           <div class="input-box"> | ||||
|             <span class="details">Location to Delete</span> | ||||
|             <select name="location" id="locationDropdown" required> | ||||
|             </select> | ||||
|         </div> | ||||
|         </div> | ||||
|         <input type="hidden" name="csrf_token" value="<%= csrfToken %>"> | ||||
|         <div class="button"> | ||||
|           <input type="submit" value="submit"> | ||||
|         </div> | ||||
|       </form> | ||||
|     </div> | ||||
|   </div> | ||||
|   <footer> | ||||
|     Any Issue faced, Please contact the administrator at 11111111 or ecosaverAdmin@gmail.com | ||||
| </footer> | ||||
|  | ||||
| @ -3,12 +3,32 @@ $(document).ready(function () { | ||||
|       $('#sensorContainer').show(); | ||||
|       $('#createSensorForm').hide(); | ||||
|       $('#additional-text4').hide(); | ||||
|       $('#updateSensorForm').hide(); | ||||
|       $('#deleteSensorForm').hide(); | ||||
|     }); | ||||
|     $('#addSensorLink').on('click', function () { | ||||
|         $('#sensorContainer').hide(); | ||||
|         $('#createSensorForm').show(); | ||||
|         $('#additional-text4').show(); | ||||
|         $('#updateSensorForm').hide(); | ||||
|         $('#deleteSensorForm').hide(); | ||||
|       }); | ||||
|       $('#updateSensorLink').on('click', function () { | ||||
|         $('#sensorContainer').hide(); | ||||
|         $('#createSensorForm').show(); | ||||
|         $('#additional-text4').show(); | ||||
|         $('#updateSensorForm').show(); | ||||
|         $('#deleteSensorForm').hide(); | ||||
|       }); | ||||
|       $('#deleteSensorLink').on('click', function () { | ||||
|         $('#sensorContainer').hide(); | ||||
|         $('#createSensorForm').show(); | ||||
|         $('#additional-text4').show(); | ||||
|         $('#updateSensorForm').hide(); | ||||
|         $('#deleteSensorForm').show(); | ||||
|       });   | ||||
|  | ||||
|      | ||||
|   }); | ||||
|   function populateTableAndArray(data, locationsArray) { | ||||
|     const tableBody = document.getElementById("sensorTableBody"); | ||||
| @ -38,16 +58,13 @@ $(document).ready(function () { | ||||
|  | ||||
|   function populateLocationDropdown() { | ||||
|     const locationDropdown = document.getElementById('locationDropdown'); | ||||
|  | ||||
|     // Clear existing options | ||||
|     locationDropdown.innerHTML = ''; | ||||
|  | ||||
|     // Add a default option | ||||
|     const defaultOption = document.createElement('option'); | ||||
|     defaultOption.text = 'Select a Location'; | ||||
|     defaultOption.value = ''; | ||||
|     locationDropdown.add(defaultOption); | ||||
|  | ||||
|     // Add locations as options | ||||
|     locationsArray.forEach(location => { | ||||
|         const option = document.createElement('option'); | ||||
| @ -58,6 +75,29 @@ $(document).ready(function () { | ||||
| } | ||||
| populateLocationDropdown(); | ||||
|  | ||||
| function populateSensorDropdown() { | ||||
|   const sensorDropdown = document.getElementById('sensorDropdown'); | ||||
|  | ||||
|   // Clear existing options | ||||
|   sensorDropdown.innerHTML = ''; | ||||
|  | ||||
|   // Add a default option | ||||
|   const defaultOption = document.createElement('option'); | ||||
|   defaultOption.text = 'Select a Sensor'; | ||||
|   defaultOption.value = ''; | ||||
|   sensorDropdown.add(defaultOption); | ||||
|  | ||||
|   // Add locations as options | ||||
|   sensorArray.forEach(location => { | ||||
|       const option = document.createElement('option'); | ||||
|       option.text = sensor.sensorname; | ||||
|       option.value = sensor.id; | ||||
|       sensorDropdown.add(option); | ||||
|   }); | ||||
| } | ||||
| populateSensorDropdown(); | ||||
|  | ||||
|  | ||||
| $('#sensorForm').on('submit', function (e) { | ||||
|     e.preventDefault(); | ||||
|        const sensor = $('#sensor').val(); | ||||
| @ -73,7 +113,6 @@ $('#sensorForm').on('submit', function (e) { | ||||
|           'Content-Type': 'application/json' | ||||
|       }, | ||||
|       body: JSON.stringify({ | ||||
|           id: id, | ||||
|           sensorname: sensor, | ||||
|           added_by: user, | ||||
|           mac_address: macAddress, | ||||
| @ -102,3 +141,85 @@ $('#sensorForm').on('submit', function (e) { | ||||
|     // Handle error as needed | ||||
| }); | ||||
|   }); | ||||
|  | ||||
|   $('#updatesensorForm').on('submit', function (e) { | ||||
|     e.preventDefault(); | ||||
|        const id = $('#sensorDropdown').val(); | ||||
|        const sensorname = $('#sensorname').val(); | ||||
|        const user = req.session.jobTitle; | ||||
|        const macAddress = $('#macAddress').val(); | ||||
|        const description = $('#description').val(); | ||||
|        const location = $('#locationDropdown').val(); | ||||
|        const csrf_token = $('#userForm input[name="csrf_token"]').val(); | ||||
|     | ||||
|     fetch('sensor/update', { | ||||
|       method: 'PUT', | ||||
|       headers: { | ||||
|           'Content-Type': 'application/json' | ||||
|       }, | ||||
|       body: JSON.stringify({ | ||||
|           id: id, | ||||
|           sensorname: sensorname, | ||||
|           added_by: user, | ||||
|           mac_address: macAddress, | ||||
|           description: description, | ||||
|           location: location, | ||||
|           csrf_token: csrf_token | ||||
|       }), | ||||
|   }) | ||||
|   .then(response => { | ||||
|     if (response.ok) { | ||||
|         // Status 201 indicates successful creation | ||||
|         return response.json(); | ||||
|     } else { | ||||
|         return response.json().then(data => { | ||||
|             throw new Error(data.message || `HTTP error! Status: ${response.status}`); | ||||
|         }); | ||||
|     } | ||||
| }) | ||||
| .then(data => { | ||||
|     console.log(`Sensor updated successfully. Message: ${data.message}`); | ||||
|     alert('Sensor updated successfully!'); | ||||
|     resetFormFields(); | ||||
| }) | ||||
| .catch(error => { | ||||
|     console.error('Sensor not updated successfully', error); | ||||
|     // Handle error as needed | ||||
| }); | ||||
|   }); | ||||
|  | ||||
|   $('#deleteForm').on('submit', function (e) { | ||||
|     e.preventDefault(); | ||||
|     const selectedSensorId = $('#sensorDropdown').val(); | ||||
|     const csrf_token = $('#userForm input[name="csrf_token"]').val(); | ||||
|  | ||||
|     fetch('/sensor/delete', { | ||||
|       method: 'DELETE', | ||||
|       headers: { | ||||
|           'Content-Type': 'application/json' | ||||
|       }, | ||||
|       body: JSON.stringify({ | ||||
|           id:selectedSensorId, | ||||
|           csrf_token: csrf_token | ||||
|       }), | ||||
|   }) | ||||
|   .then(response => { | ||||
|     if (response.ok) { | ||||
|         // Status 201 indicates successful creation | ||||
|         return response.json(); | ||||
|     } else { | ||||
|         return response.json().then(data => { | ||||
|             throw new Error(data.message || `HTTP error! Status: ${response.status}`); | ||||
|         }); | ||||
|     } | ||||
| }) | ||||
| .then(data => { | ||||
|     console.log(`Sensor deleted successfully. Message: ${data.message}`); | ||||
|     alert('Sensor deleted successfully!'); | ||||
|     resetFormFields(); | ||||
| }) | ||||
| .catch(error => { | ||||
|     console.error('Sensor not deleted successfully', error); | ||||
|     // Handle error as needed | ||||
| }); | ||||
|   }); | ||||
|  | ||||
| @ -20,8 +20,8 @@ | ||||
|   <nav> | ||||
|     <a href="#" id="allSensorLink">All Sensor</a> | ||||
|     <a href="#" id="addSensorLink">Add Sensor</a> | ||||
|     <a href="#">Update Sensor</a> | ||||
|     <a href="#">Delete Sensor</a> | ||||
|     <a href="#" id="updateSensorLink">Update Sensor</a> | ||||
|     <a href="#" id="deleteSensorLink">Delete Sensor</a> | ||||
|     <a href="/home" id="homeLink">Home</a> | ||||
|   </nav> | ||||
|  | ||||
| @ -83,13 +83,68 @@ | ||||
|       </ul> | ||||
|     </div> | ||||
|   </div> | ||||
|   <div id="updateSensorForm" class="sensor-update-container" style="display: none;"> | ||||
|     <h3>Add Location</h3> | ||||
|     <div class="content"> | ||||
|       <form action="/sensor/update" id="updateForm" method="put"> | ||||
|         <div class="Location-details"> | ||||
|           <div class="input-box"> | ||||
|             <span class="details">Sensor to Update</span> | ||||
|             <select name="Sensor" id="sensorDropdown" required> | ||||
|             </select> | ||||
|         </div> | ||||
|           <div class="input-box"> | ||||
|             <span class="details">Sensor Name</span> | ||||
|             <input type="text" name="sensorname"  id="sensorname" placeholder="Enter Sensor name" required> | ||||
|           </div> | ||||
|           <div class="input-box"> | ||||
|             <span class="details">Mac Address</span> | ||||
|             <input type="text" name="macAddress" id="macAddress" placeholder="Enter the Mac Address" required> | ||||
|         </div> | ||||
|           <div class="input-box"> | ||||
|             <span class="details">Description</span> | ||||
|             <input type="text" name="description" id="description" placeholder="Enter the description here" required> | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="input-box"> | ||||
|           <span class="details">Location</span> | ||||
|           <select name="location" id="locationDropdown" required> | ||||
|           </select> | ||||
|       </div> | ||||
|   </div> | ||||
|   <input type="hidden" name="csrf_token" value="<%= csrfToken %>"> | ||||
|         <input type="hidden" name="csrf_token" value="<%= csrfToken %>"> | ||||
|         <div class="button"> | ||||
|           <input type="submit" value="submit"> | ||||
|         </div> | ||||
|       </form> | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
|   <div id="deleteSensorForm" class="sensor-delete-container" style="display: none;"> | ||||
|     <h3>Delete Location</h3> | ||||
|     <div class="content"> | ||||
|       <form action="/sensor/delete" id="deleteForm" method="delete"> | ||||
|         <div class="Location-details"> | ||||
|           <div class="input-box"> | ||||
|             <span class="details">Sensor to Delete</span> | ||||
|             <select name="sensor" id="sensorDropdown" required> | ||||
|             </select> | ||||
|         </div> | ||||
|         </div> | ||||
|         <input type="hidden" name="csrf_token" value="<%= csrfToken %>"> | ||||
|         <div class="button"> | ||||
|           <input type="submit" value="submit"> | ||||
|         </div> | ||||
|       </form> | ||||
|     </div> | ||||
|   </div> | ||||
|   <footer> | ||||
|     Any Issue faced, Please contact the administrator at 11111111 or ecosaverAdmin@gmail.com | ||||
| </footer> | ||||
| </body> | ||||
| <script> | ||||
| const locationsArray = <%=-JSON.stringify(locationsData) %>; | ||||
| const locationsArray = <%-JSON.stringify(locationsData) %>; | ||||
| const sensorArray = <%- JSON.stringify(sensorData) %>; | ||||
| </script> | ||||
| <script src="https://cdnjs.cloudflare.com/ajax/libs/dompurify/2.3.3/purify.min.js"></script> | ||||
|  | ||||
							
								
								
									
										5
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1626,6 +1626,11 @@ | ||||
|         "formdata-polyfill": "^4.0.10" | ||||
|       } | ||||
|     }, | ||||
|     "nodemailer": { | ||||
|       "version": "6.9.8", | ||||
|       "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.8.tgz", | ||||
|       "integrity": "sha512-cfrYUk16e67Ks051i4CntM9kshRYei1/o/Gi8K1d+R34OIs21xdFnW7Pt7EucmVKA0LKtqUGNcjMZ7ehjl49mQ==" | ||||
|     }, | ||||
|     "nopt": { | ||||
|       "version": "5.0.0", | ||||
|       "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", | ||||
|  | ||||
		Reference in New Issue
	
	Block a user