This commit is contained in:
Leo 2024-01-22 14:37:27 +08:00
commit 7dceee0b79
42 changed files with 3294 additions and 5920 deletions

View File

@ -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.');
}
}

View File

@ -37,6 +37,9 @@ module.exports = (sequelize) => {
reset_token_expiry: { reset_token_expiry: {
type: DataTypes.DATE, type: DataTypes.DATE,
}, },
sessionid: {
type: DataTypes.STRING,
},
}, { }, {
hooks: { hooks: {
beforeCreate: async (user) => { beforeCreate: async (user) => {

View File

@ -1,7 +1,7 @@
const express = require("express"); const express = require("express");
const session = require("express-session"); const session = require("express-session");
const rateLimit = require('express-rate-limit'); const rateLimit = require('express-rate-limit');
const cookieParser = require('cookie-parser');
const bodyParser = require("body-parser"); const bodyParser = require("body-parser");
const bcrypt = require("bcrypt"); const bcrypt = require("bcrypt");
const crypto = require("crypto"); const crypto = require("crypto");
@ -10,21 +10,37 @@ const otpGenerator = require('otp-generator');
const { body, validationResult } = require('express-validator'); const { body, validationResult } = require('express-validator');
const validator = require('validator'); const validator = require('validator');
const { format } = require('date-fns'); const { format } = require('date-fns');
const helmet = require('helmet');
const { Sequelize } = require('sequelize'); const { Sequelize } = require('sequelize');
const { transporter } = require("./modules/nodeMailer"); const { transporter } = require("./modules/nodeMailer");
const { sequelize, User } = require("./modules/mysql"); 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); // Adjust the path based on your project structure
const app = express(); const app = express();
const nonce = crypto.randomBytes(16).toString('base64');
console.log('Nonce:', nonce);
app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json()); app.use(bodyParser.json());
app.use(cookieParser());
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
require("dotenv").config(); require("dotenv").config();
app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.urlencoded({ extended: true }));
app.set("view engine", "ejs"); 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({ app.use(session({
secret: process.env.key, secret: process.env.key,
@ -189,6 +205,19 @@ app.post("/verify-otp", [
} }
const sessionToken = crypto.randomBytes(32).toString('hex'); 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.authenticated = true;
req.session.username = req.body.username; req.session.username = req.body.username;
@ -200,7 +229,6 @@ app.post("/verify-otp", [
// Log anti-CSRF token // Log anti-CSRF token
console.log(`Generated Anti-CSRF Token: ${csrfTokenSession}`); 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 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}`); console.log(`Generated Session Token: ${sessionToken}`);
@ -232,6 +260,7 @@ app.post("/verify-otp", [
} else { } else {
console.log("Session destroyed."); console.log("Session destroyed.");
// Log the logout activity using Sequelize // Log the logout activity using Sequelize
await User.update({ sessionid: null }, { where: { username } })
await userLogs.create({ username, activity: "User logged out. Session destroyed." }); await userLogs.create({ username, activity: "User logged out. Session destroyed." });
// Clear the session token cookie // Clear the session token cookie
res.clearCookie('sessionToken'); res.clearCookie('sessionToken');
@ -267,7 +296,7 @@ app.post("/verify-otp", [
const currentUsername = req.session.username; const currentUsername = req.session.username;
// Render the inusers page with JSON data // 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) { } catch (error) {
console.error("Error fetching all users:", error); console.error("Error fetching all users:", error);
res.status(500).send("Internal Server Error"); res.status(500).send("Internal Server Error");
@ -324,6 +353,14 @@ app.post(
return res.status(400).json({ errors: errors.array() }); 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 // Validate the anti-CSRF token
const submittedCSRFToken = req.body.csrf_token; const submittedCSRFToken = req.body.csrf_token;
@ -595,7 +632,14 @@ app.post("/reset-password", async (req, res) => {
if (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) { if (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) {
return res.status(403).json({ error: 'CSRF token mismatch' }); 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 // Sanitize the inputs
const sanitizedUsername = validator.escape(username); const sanitizedUsername = validator.escape(username);
const sanitizedPassword = validator.escape(password); const sanitizedPassword = validator.escape(password);
@ -686,7 +730,6 @@ app.get('/api/users', async (req, res) => {
app.get('/api/searchUser', async (req, res) => { app.get('/api/searchUser', async (req, res) => {
const { username } = req.query; const { username } = req.query;
console.log(username);
try { try {
// Find the user in the database by username // Find the user in the database by username
const user = await User.findOne({ where: { username } }); const user = await User.findOne({ where: { username } });
@ -710,14 +753,23 @@ app.delete('/api/deleteUser/:username', async (req, res) => {
const creatorUsername = req.session.username; const creatorUsername = req.session.username;
try { try {
// Extract CSRF token from the request body // Retrieve sessionToken from cookies
const sessionTokencoookie = req.cookies['sessionToken'];
// Retrieve CSRF token from the request body
const { csrfToken } = req.body; const { csrfToken } = req.body;
console.log(csrfToken);
// Compare CSRF token with the one stored in the session // Compare CSRF token with the one stored in the session
if (csrfToken !== csrfTokenSession) { if (csrfToken !== csrfTokenSession) {
return res.status(403).json({ success: false, error: 'CSRF token mismatch' }); 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 // Log deletion activity to UserLogs model
const deletionActivity = `User ${username} has been successfully deleted`; const deletionActivity = `User ${username} has been successfully deleted`;
await userLogs.create({ username: creatorUsername, activity: deletionActivity }); await userLogs.create({ username: creatorUsername, activity: deletionActivity });

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View 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;
}

View File

@ -5,66 +5,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Forgot Password - Your Website</title> <title>Forgot Password - Your Website</title>
<style> <link rel="stylesheet" href="forgot-password.css">
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>
</head> </head>
<body> <body>
<div id="forgotPasswordForm"> <div id="forgotPasswordForm">

View File

@ -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;
}, {});
}
});

View File

@ -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;

View File

@ -7,7 +7,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>In-House Users</title> <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://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"> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap">
</head> </head>
@ -173,19 +173,21 @@
</div> </div>
<script> <script nonce="<%= nonce %>">
const allUsers = <%- JSON.stringify(allUsers) %>; const allUsers = <%- JSON.stringify(allUsers) %>;
const currentUsername = '<%= currentUsername %>'; const currentUsername = '<%= currentUsername %>';
const nonce = "<%= nonce %>"
console.log('Nonce:', nonce);
</script> </script>
<script src="https://code.jquery.com/jquery-3.6.4.min.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"></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"></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"></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"></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"></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"></script> <script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.js" nonce="<%= nonce %>"></script>
<script src="inusers.js"></script> <script src="inusers.js" nonce="<%= nonce %>"></script>
</body> </body>

View File

@ -33,7 +33,7 @@ $(document).ready(function () {
$('#logsContainer').hide(); $('#logsContainer').hide();
$('#additional-text').hide(); $('#additional-text').hide();
$('#additional-text2').hide(); $('#additional-text2').hide();
$('#additional-text2').hide(); $('#additional-text3').hide();
}); });
$('#searchUserButton').on('click', function () { $('#searchUserButton').on('click', function () {
@ -124,6 +124,7 @@ $('#searchResultsList').on('click', '.deleteUserButton', function () {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
credentials: 'include', // Include cookies in the request
body: JSON.stringify({ csrfToken }), // Include CSRF token in the request body body: JSON.stringify({ csrfToken }), // Include CSRF token in the request body
}) })
.then(response => { .then(response => {
@ -243,8 +244,8 @@ function resetFormFields() {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
credentials: 'include', // Include cookies in the request
body: JSON.stringify({ body: JSON.stringify({
name: name, name: name,
username: username, username: username,
@ -330,6 +331,7 @@ $('#resetPasswordForm').on('submit', function (e) {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
credentials: 'include', // Include cookies in the request
body: JSON.stringify({ body: JSON.stringify({
username: username, username: username,
password: password, password: password,
@ -356,8 +358,6 @@ $('#resetPasswordForm').on('submit', function (e) {
$('#resetPassword').val(''); $('#resetPassword').val('');
$('#resetConfirmPassword').val(''); $('#resetConfirmPassword').val('');
// You might want to hide the reset password form after submission
$('#resetPasswordFormContainer').hide();
}) })
.catch(error => { .catch(error => {
// Handle 404 error separately to show an alert // Handle 404 error separately to show an alert

67
Sean/views/login.css Normal file
View 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;
}

View File

@ -4,75 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Eco Saver</title> <title>Login - Eco Saver</title>
<style> <link rel="stylesheet" href="login.css">
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>
</head> </head>
<body> <body>
<div class="login-container"> <div class="login-container">

View File

@ -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
View 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;
}

View File

@ -4,58 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enter OTP</title> <title>Enter OTP</title>
<style> <link rel="stylesheet" href="otp.css">
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>
</head> </head>
<body> <body>
<h2>Enter OTP</h2> <h2>Enter OTP</h2>

View File

@ -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;
}
}

View File

@ -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>

View File

@ -56,15 +56,16 @@ async function addUser(user) {
//api/v0/auth/login //api/v0/auth/login
async function loginUser(user) { async function loginUser(user) {
//console.log(user);
//look up username or email in db //look up username or email in db
const userRes = await userModel.findOne({ const userRes = await userModel.findOne({
where: { where: {
[Op.or]: [ [Op.or]: [
{ {
username: user.userInfo, username: user.username,
}, },
{ {
email: user.userInfo, email: user.username,
}, },
], ],
}, },

View File

@ -37,6 +37,9 @@ function isAddress(value){
return addressRegex.test(value); return addressRegex.test(value);
} }
//generate me an regex for alpha
//https://stackoverflow.com/questions/11522529/regexp-for-alphabets-with-spaces
module.exports = { module.exports = {
isAlphaNumericwithSpaces, isAlphaNumericwithSpaces,
isAlphaNumericWithSpacesAndDash, isAlphaNumericWithSpacesAndDash,

View File

@ -0,0 +1,16 @@
const nodemailer = require("nodemailer");
const dotenv = require("dotenv");
const path = require('path')
require('dotenv').config({ path: path.resolve(__dirname, '../.env') })
let transporter = nodemailer.createTransport({
service: 'gmail',
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user:
pass:
},
});
module.exports = { transporter };

View File

@ -254,3 +254,7 @@ form
color: #4eae3a; color: #4eae3a;
border-color: #4eae3a; border-color: #4eae3a;
} }
a {
color: black;
}

View File

@ -59,7 +59,7 @@ button.btn-secondary:hover{
.navbar-expand-lg.top-nav .navbar-nav .nav-link{ .navbar-expand-lg.top-nav .navbar-nav .nav-link{
padding: 10px 15px; padding: 10px 15px;
color: #4e3914; color: #4e3914;
font-size: 14px; font-size: 15px;
font-weight: 300; font-weight: 300;
text-transform: uppercase; text-transform: uppercase;
} }

View File

@ -149,42 +149,33 @@ app.auth = (function (app) {
function setToken(token) { function setToken(token) {
localStorage.setItem("APIToken", token); localStorage.setItem("APIToken", token);
} }
/*
function setUserId(userid) {
console.log("userid", userid);
localStorage.setItem("userid", userid);
}
function setUsername(username) {
localStorage.setItem("username", username);
}
*/
function getToken() { function getToken() {
return localStorage.getItem("APIToken"); return localStorage.getItem("APIToken");
} }
function isLoggedIn(callback) { function isLoggedIn(callback) {
if (getToken()) { if (getToken()) {
return app.api.get("user/me", function (error, data) { return app.api.get("user/me", function (error, data) {
if (!error) app.auth.user = data; if (!error) app.auth.user = data;
$.scope.getUsername.push(data.username); //$.scope.getUsername.push(data);
return callback(error, data); return callback(error, data);
}); });
} else { } else {
callback(null, false); callback(null, false);
} }
} }
/*
function logIn(args, callback) { function showUser(){
app.api.post("auth/login", args, function (error, data) { app.api.get("user/me", function (error, data) {
if (data.login) { if (!error) app.auth.user = data;
setToken(data.token); $.scope.getUsername.push(data);
}
callback(error, !!data.token);
}); });
} }
*/
function logOut(callback) { function logOut(callback) {
//call logout route //call logout route
@ -228,29 +219,28 @@ app.auth = (function (app) {
function homeRedirect() { function homeRedirect() {
window.location.href = location.href.replace(location.replace(`/`)) || "/"; window.location.href = location.href.replace(location.replace(`/`)) || "/";
} }
/*
//if isLoggedin is true, redirect user away from login / register page
function redirectIfLoggedIn() { function redirectIfLoggedIn() {
$.holdReady(true); if (getToken()){
app.auth.isLoggedIn(function (error, isLoggedIn) { homeRedirect();
if (error || isLoggedIn) {
location.replace(`/`);
} else {
$.holdReady(false);
} }
}); logInRedirect();
} }
*/
return { return {
getToken: getToken, getToken: getToken,
setToken: setToken, setToken: setToken,
isLoggedIn: isLoggedIn, isLoggedIn: isLoggedIn,
//logIn: logIn,
logOut: logOut, logOut: logOut,
forceLogin, forceLogin,
logInRedirect, logInRedirect,
homeRedirect, homeRedirect,
redirectIfLoggedIn, showUser,
//redirectIfLoggedIn,
}; };
})(app); })(app);

View File

@ -283,9 +283,6 @@
}; };
$( document ).ready( function(){ $( document ).ready( function(){
console.log('jq-repeat', $.scope)
//$.jqrepeat = $.scope
$( '[jq-repeat]' ).each(function(key, value){ $( '[jq-repeat]' ).each(function(key, value){
make(value); make(value);
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -6,7 +6,6 @@ const router = express.Router();
// /user/register // /user/register
router.post("/register", async (req, res, next) => { router.post("/register", async (req, res, next) => {
try { try {
console.log(req.body);
let Res = await addUser(req.body); let Res = await addUser(req.body);
if (Res == false) { if (Res == false) {
let error = new Error("UserRegFailed"); let error = new Error("UserRegFailed");
@ -36,7 +35,6 @@ router.post("/login", async (req, res, next) => {
} }
else{ else{
//pass res back to form to be set in local storage //pass res back to form to be set in local storage
console.log("my res" , Res);
return res.json({ return res.json({
message: "User login successfully", message: "User login successfully",
token: Res.token, token: Res.token,
@ -51,6 +49,12 @@ router.post("/login", async (req, res, next) => {
} }
}); });
//contact
//auth/contact
router.post("/contact", async (req, res, next) => {
});
module.exports = router; module.exports = router;

View File

@ -68,8 +68,8 @@ router.get("/profile", function (req, res, next) {
//forgot password page //forgot password page
router.get("/forgotPassword", function (req, res, next) { router.get("/forgotpassword", function (req, res, next) {
res.render("forgotPassword"); res.render("forgotpassword");
}); });
//resetted password page //resetted password page

View File

@ -7,8 +7,6 @@ const router = express.Router();
//getbyid //getbyid
router.get("/me", async function (req, res, next) { router.get("/me", async function (req, res, next) {
try { try {
//console.log(req.user);
let user = await getUserID(req.user); let user = await getUserID(req.user);
if (!user) { if (!user) {
let error = new Error("User not found"); let error = new Error("User not found");
@ -24,6 +22,7 @@ router.get("/me", async function (req, res, next) {
} }
}); });
//logout //logout
router.delete('/logout', async function(req, res, next){ router.delete('/logout', async function(req, res, next){
try{ try{

View File

@ -12,25 +12,35 @@
<div class="left-menu"> <div class="left-menu">
<div class="content-logo"> <div class="content-logo">
<div class="logo"> <div class="logo">
<img alt="platform by Emily van den Heever from the Noun Project" title="platform by Emily van den Heever from the Noun Project" src="images/apilogo.png" height="32" /> <img alt="platform by Emily van den Heever from the Noun Project"
title="platform by Emily van den Heever from the Noun Project" src="images/apilogo.png"
height="32" />
<span>API Documentation</span> <span>API Documentation</span>
</div> </div>
<button class="burger-menu-icon" id="button-menu-mobile"> <button class="burger-menu-icon" id="button-menu-mobile">
<svg width="34" height="34" viewBox="0 0 100 100"><path class="line line1" d="M 20,29.000046 H 80.000231 C 80.000231,29.000046 94.498839,28.817352 94.532987,66.711331 94.543142,77.980673 90.966081,81.670246 85.259173,81.668997 79.552261,81.667751 75.000211,74.999942 75.000211,74.999942 L 25.000021,25.000058"></path><path class="line line2" d="M 20,50 H 80"></path><path class="line line3" d="M 20,70.999954 H 80.000231 C 80.000231,70.999954 94.498839,71.182648 94.532987,33.288669 94.543142,22.019327 90.966081,18.329754 85.259173,18.331003 79.552261,18.332249 75.000211,25.000058 75.000211,25.000058 L 25.000021,74.999942"></path></svg> <svg width="34" height="34" viewBox="0 0 100 100">
<path class="line line1"
d="M 20,29.000046 H 80.000231 C 80.000231,29.000046 94.498839,28.817352 94.532987,66.711331 94.543142,77.980673 90.966081,81.670246 85.259173,81.668997 79.552261,81.667751 75.000211,74.999942 75.000211,74.999942 L 25.000021,25.000058">
</path>
<path class="line line2" d="M 20,50 H 80"></path>
<path class="line line3"
d="M 20,70.999954 H 80.000231 C 80.000231,70.999954 94.498839,71.182648 94.532987,33.288669 94.543142,22.019327 90.966081,18.329754 85.259173,18.331003 79.552261,18.332249 75.000211,25.000058 75.000211,25.000058 L 25.000021,74.999942">
</path>
</svg>
</button> </button>
</div> </div>
<div class="mobile-menu-closer"></div> <div class="mobile-menu-closer"></div>
<div class="content-menu"> <div class="content-menu">
<div class="content-infos"> <div class="content-infos">
<div class="info"><b>Version:</b> 1.0.5</div> <div class="info"><b>Version:</b> 0</div>
<div class="info"><b>Last Updated:</b> 15th Sep, 2021</div> <div class="info"><b>Last Updated:</b> 22th January 2024</div>
</div> </div>
<ul> <ul>
<li class="scroll-to-link active" data-target="content-get-started"> <li class="scroll-to-link active" data-target="content-get-started">
<a>GET STARTED</a> <a>GET STARTED</a>
</li> </li>
<li class="scroll-to-link" data-target="content-get-characters"> <li class="scroll-to-link" data-target="content-get-characters">
<a>Get Characters</a> <a>Get Data From API</a>
</li> </li>
<li class="scroll-to-link" data-target="content-errors"> <li class="scroll-to-link" data-target="content-errors">
<a>Errors</a> <a>Errors</a>
@ -45,17 +55,22 @@
<div class="overflow-hidden content-section" id="content-get-started"> <div class="overflow-hidden content-section" id="content-get-started">
<h1>Get started</h1> <h1>Get started</h1>
<p> <p>
The Westeros API provides programmatic access to read Game of Thrones data. Retrieve a character, provide an oauth connexion, retrieve a familly, filter them, etc. The following API is provided by the Eco saver developer team. It allows you to get Location and
Sensor and Sensor Data from the Eco saver database.
</p> </p>
<p> <p>
To use this API, you need an <strong>API key</strong>. Please contact us at <a href="mailto:jon.snow@nightswatch.wes">jon.snow@nightswatch.wes</a> to get your own API key. To use this API, you need an <strong>API key</strong>.
</p> </p>
</div> </div>
<div class="overflow-hidden content-section" id="content-get-characters"> <div class="overflow-hidden content-section" id="content-get-location">
<h2>get characters</h2> <h2>Get all location</h2>
<p> <p>
To get characters you need to make a POST call to the following url :<br> To get Location of sensors you need to make a GET call to the following url :<br>
<code class="higlighted break-word">http://api.westeros.com/character/get</code> <code class="higlighted break-word">https://api.teeseng.uk/api/v0/location</code>
<br>
<br>
Return Response :<br>
<code class="higlighted break-word">{"status":"200"}</code>
</p> </p>
<br> <br>
<h4>QUERY PARAMETERS</h4> <h4>QUERY PARAMETERS</h4>
@ -69,49 +84,237 @@
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>secret_key</td> <td>Authorization</td>
<td>String</td> <td>JSON</td>
<td>Your API key.</td> <td>Your API key.</td>
</tr> <td>(Required) Example: curl https://api.teeseng.uk/api/v0/location -H "Authorization: {provide your
<tr> API key here}"</td>
<td>search</td>
<td>String</td>
<td>(optional) A search word to find character by name.</td>
</tr>
<tr>
<td>house</td>
<td>String</td>
<td>
(optional) a string array of houses:
</td> </td>
</tr> </tr>
<tr>
<td>alive</td>
<td>Boolean</td>
<td>
(optional) a boolean to filter alived characters
</td>
</tr>
<tr>
<td>gender</td>
<td>String</td>
<td>
(optional) a string to filter character by gender:<br> m: male<br> f: female
</td>
</tr>
<tr>
<td>offset</td>
<td>Integer</td>
<td>(optional - default: 0) A cursor for use in pagination. Pagination starts offset the specified offset.</td>
</tr>
<tr>
<td>limit</td>
<td>Integer</td>
<td>(optional - default: 10) A limit on the number of objects to be returned, between 1 and 100.</td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="overflow-hidden content-section" id="content-get-location-by-id">
<h2>Get location by ID</h2>
<p>
To get Location you need to make a GET call to the following url :<br>
<code class="higlighted break-word">https://api.teeseng.uk/api/v0/location/{id}</code>
<br>
<br>
Return Response :<br>
<code class="higlighted break-word">{"status":"200"}</code>
</p>
<br>
<h4>QUERY PARAMETERS</h4>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Authorization</td>
<td>JSON</td>
<td>(Required) Your API key.</td>
<td>Example: curl https://api.teeseng.uk/api/v0/location -H "Authorization: {provide your
API key here}"</td>
</td>
</tr>
</tbody>
</table>
</div>
<div class="overflow-hidden content-section" id="content-add-location">
<h2>Add Location (Only for system or admin API key)</h2>
<p>
To add an Location you need to make a POST call to the following url :<br>
<code class="higlighted break-word">https://api.teeseng.uk/api/v0/location/new</code>
<br>
<br>
Example :<br>
<code class="higlighted break-word">curl https://api.teeseng.uk/api/v0/location/new -H "Content-Type: application/json" -X POST -d '{"name": "SAMPLE", "added_by": "system" , "description": "test"}'</code>
<br>
<br>
Return Response :<br>
<code class="higlighted break-word">{"status":"200"}</code>
</p>
<br>
<h4>QUERY PARAMETERS</h4>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Authorization</td>
<td>JSON</td>
<td>Your API key.</td>
<td>(Required) Example: curl https://api.teeseng.uk/api/v0/location/new -H "Authorization: {provide your
API key here}"</td>
</td>
</tr>
<tr>
<td>Location name</td>
<td>JSON</td>
<td>Location name.</td>
<td>(Required) Location name. Example: curl https://api.teeseng.uk/api/v0/location/new -H "Authorization: provide
your API key here" -d '{"name":"Location name"}'</td>
</td>
</tr>
<tr>
<td>Added by </td>
<td>JSON</td>
<td>System or Admin</td>
<td>(Required) System or Admin Example: curl https://api.teeseng.uk/api/v0/location/new -H "Authorization: provide
your API key here" -d '{"added_by":"system"}'</td>
</td>
</tr>
<tr>
<td>Description</td>
<td>JSON</td>
<td>Description of Location</td>
<td>(Required) System or Admin Example: curl https://api.teeseng.uk/api/v0/location/new -H "Authorization: provide
your API key here" -d '{"description":"test"}'</td>
</td>
</tr>
</tbody>
</table>
</div>
<!-- update -->
<div class="overflow-hidden content-section" id="content-update-location-by-id">
<h2>Update Location (Only for system or admin API key)</h2>
<p>
To update an Location you need to make a PUT call to the following url :<br>
<code class="higlighted break-word">https://api.teeseng.uk/api/v0/location/update</code>
<br>
<br>
Example :<br>
<code class="higlighted break-word">curl https://api.teeseng.uk/api/v0/location/update -H "Content-Type: application/json" -X POST -d '{"id": "7" , "name": "SAMPLE", "added_by": "system" , "description": "test"}'</code>
<br>
<br>
Return Response :<br>
<code class="higlighted break-word">{"status":"200","message":"Location 7 updated"}</code>
</p>
<br>
<h4>QUERY PARAMETERS</h4>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Authorization</td>
<td>JSON</td>
<td>Your API key.</td>
<td>(Required) example: curl https://api.teeseng.uk/api/v0/location/update -H "Authorization: {provide your
API key here}"</td>
</td>
</tr>
<tr>
<td>ID</td>
<td>JSON</td>
<td>Location ID</td>
<td>(Required) Location ID Example: curl https://api.teeseng.uk/api/v0/location/update -H "Authorization: provide
your API key here" -d '{"id": "7"}'</td>
</td>
</tr>
<tr>
<td>Location name</td>
<td>JSON</td>
<td>Location name.</td>
<td>(Optional) Location name. Example: curl https://api.teeseng.uk/api/v0/location/new -H "Authorization: provide
your API key here" -d '{"name":"Location name"}'</td>
</td>
</tr>
<tr>
<td>Added by </td>
<td>JSON</td>
<td>System or Admin</td>
<td>(Optional) System or Admin Example: curl https://api.teeseng.uk/api/v0/location/new -H "Authorization: provide
your API key here" -d '{"added_by":"system"}'</td>
</td>
</tr>
<tr>
<td>Description</td>
<td>JSON</td>
<td>Description of Location</td>
<td>(Optional) System or Admin Example: curl https://api.teeseng.uk/api/v0/location/new -H "Authorization: provide
your API key here" -d '{"description":"test"}'</td>
</td>
</tr>
</tbody>
</table>
</div>
<!-- delete location -->
<div class="overflow-hidden content-section" id="content-update-location-by-id">
<h2>Delete Location (Only for system or admin API key)</h2>
<p>
To delete an Location you need to make a DELETE call to the following url :<br>
<code class="higlighted break-word">https://api.teeseng.uk/api/v0/location/delete</code>
<br>
<br>
Example :<br>
<code class="higlighted break-word">curl https://api.teeseng.uk/api/v0/location/delete -H "Content-Type: application/json" -X POST -d '{"id": "7"}'</code>
</p>
<br>
<h4>QUERY PARAMETERS</h4>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Authorization</td>
<td>JSON</td>
<td>Your API key.</td>
<td>(Required) example: curl https://api.teeseng.uk/api/v0/location/delete -H "Authorization: {provide your
API key here}"</td>
</td>
</tr>
<tr>
<td>ID</td>
<td>JSON</td>
<td>Location ID</td>
<td>(Required) Location ID Example: curl https://api.teeseng.uk/api/v0/location/delete -H "Authorization: provide
your API key here" -d '{"id": "7"}'</td>
</td>
</tr>
</tbody>
</table>
</div>
<div class="overflow-hidden content-section" id="content-errors"> <div class="overflow-hidden content-section" id="content-errors">
<h2>Errors</h2> <h2>Errors</h2>
<p> <p>
@ -128,25 +331,30 @@
<tr> <tr>
<td>X000</td> <td>X000</td>
<td> <td>
Some parameters are missing. This error appears when you don't pass every mandatory parameters. Some parameters are missing. This error appears when you don't pass every mandatory
parameters.
</td> </td>
</tr> </tr>
<tr> <tr>
<td>X001</td> <td>403</td>
<td> <td>
Unknown or unvalid <code class="higlighted">secret_key</code>. This error appears if you use an unknow API key or if your API key expired. Unknown or unvalid <code class="higlighted">secret_key</code>. This error appears if
you use an unknow API key or if your API key expired.
</td> </td>
</tr> </tr>
<tr> <tr>
<td>X002</td> <td>500</td>
<td> <td>
Unvalid <code class="higlighted">secret_key</code> for this domain. This error appears if you use an API key non specified for your domain. Developper or Universal API keys doesn't have domain checker. Unvalid <code class="higlighted">secret_key</code> No API key was supplied. Invalid
request.
</td> </td>
</tr> </tr>
<tr> <tr>
<td>X003</td> <td>X003</td>
<td> <td>
Unknown or unvalid user <code class="higlighted">token</code>. This error appears if you use an unknow user <code class="higlighted">token</code> or if the user <code class="higlighted">token</code> expired. Unknown or unvalid user <code class="higlighted">token</code>. This error appears if
you use an unknow user <code class="higlighted">token</code> or if the user <code
class="higlighted">token</code> expired.
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -158,4 +366,3 @@
</body> </body>
</html> </html>

View File

@ -82,9 +82,6 @@
</div> </div>
</footer> </footer>
<!-- Bootstrap core JavaScript -->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="js/learnmore.js"></script> <script src="js/learnmore.js"></script>
<script src="js/search.js"></script> <script src="js/search.js"></script>
<script src="js/api.js"></script> <script src="js/api.js"></script>

View File

@ -8,7 +8,7 @@
<input type="text" id="email" placeholder="Email" required /> <input type="text" id="email" placeholder="Email" required />
<input type="password" id="password" placeholder="Password" required /> <input type="password" id="password" placeholder="Password" required />
<input type="password" id="confirmPassword" placeholder="Confirm Password" required /> <input type="password" id="confirmPassword" placeholder="Confirm Password" required />
<input type="submit" onclick="validateReset()" value="Reset Password" /> <input type="submit" value="Reset Password" />
</form> </form>
<br> <br>
<a>Dont have an account?</a> <a href="/login">Sign Up</a> <a>Dont have an account?</a> <a href="/login">Sign Up</a>

View File

@ -91,7 +91,6 @@
<div class="row"> <div class="row">
<div class="col-lg-6"> <div class="col-lg-6">
<title>EcoSaver - Your Air Quality Index Source</title> <title>EcoSaver - Your Air Quality Index Source</title>
<link rel="stylesheet" href="css/style.css">
</head> </head>

View File

@ -27,7 +27,6 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.1/mustache.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.1/mustache.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.1/mustache.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/0.1/mustache.js"></script>
<!-- jquery app.js --> <!-- jquery app.js -->
<script src="js/app.js"></script> <script src="js/app.js"></script>
@ -83,7 +82,7 @@
</li> </li>
<!-- profile button --> <!-- profile button -->
<div class="form-inline mt-2 mt-md-0"> <div class="form-inline mt-2 mt-md-0">
<a id="cl-profile-button" class="btn btn-outline-danger my-2 my-sm-0" href="/profile" <a id="cl-profile-button" class="btn btn-outline-info my-2 my-sm-0" href="/profile"
style="display: none;"> style="display: none;">
<i class="fas fa-sign-out"></i> <i class="fas fa-sign-out"></i>
Profile Profile

View File

@ -1,6 +1,7 @@
<%- include('logintop') %> <%- include('logintop') %>
<script type="text/javascript"> <script type="text/javascript">
app.auth.redirectIfLoggedIn(); // Require login to see this page.
//app.auth.redirectIfLoggedIn();
</script> </script>
<body> <body>
@ -11,14 +12,18 @@
<!-- localhost/api/v0/user/register --> <!-- localhost/api/v0/user/register -->
<!-- evalAjax Fires when status 200 is returned --> <!-- evalAjax Fires when status 200 is returned -->
<form action="auth/register" onsubmit="formAJAX(this)" evalAJAX="app.auth.logInRedirect();"> <form action="auth/register" onsubmit="formAJAX(this)" evalAJAX="app.auth.logInRedirect();">
<input type="text" name="firstname" placeholder="First Name" required /> <input type="text" name="firstname" placeholder="First Name" required pattern="^[a-zA-Z\s]+$" />
<input type="text" name="lastname" placeholder="Last Name" required /> <input type="text" name="lastname" placeholder="Last Name" required pattern="^[a-zA-Z\s]+$" />
<input type="text" name="username" placeholder="Username" required /> <input type="text" name="username" placeholder="Username" required pattern="^\w+$" />
<input type="text" name="email" placeholder="Email" required /> <input type="email" name="email" placeholder="Email" required
<input type="text" name="address" placeholder="Address" required /> pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" />
<input type="text" name="phone" placeholder="Phone Number" required /> <input type="text" name="address" placeholder="Address" required
<input type="password" name="password" placeholder="Password" required /> pattern="^(\d{1,3}.)?.+\s(\d{6})$" />
<input type="password" name="confirmPassword" placeholder="Confirm Password" required /> <input type="text" name="phone" placeholder="Phone Number" required
pattern="^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,8}$" />
<input type="password" id="password" name="password" placeholder="Password" required />
<input type="password" id="confirmPassword" name="confirmPassword" placeholder="Confirm Password"
required />
<input type="submit" value="Signup" /> <input type="submit" value="Signup" />
</form> </form>
</div> </div>
@ -27,28 +32,41 @@
<header>Login</header> <header>Login</header>
<div class="card-header shadow actionMessage" style="display:none"></div> <div class="card-header shadow actionMessage" style="display:none"></div>
<!-- evalAjax Fires when status 200 is returned --> <!-- evalAjax Fires when status 200 is returned -->
<form action="auth/login" onsubmit="formAJAX(this)" <form action="auth/login" onsubmit="formAJAX(this)" evalAJAX="app.auth.homeRedirect();
evalAJAX="app.auth.homeRedirect(); app.auth.setToken(data.token);">
app.auth.setToken(data.token); <input type="text" name="username" placeholder="Email address | Username" required
app.auth.setUserId(data.userid); pattern="^\w+$" />
app.auth.setUsername(data.username);
">
<input type="text" name="userInfo" placeholder="Email address | Username" required />
<input type="password" name="password" placeholder="Password" required /> <input type="password" name="password" placeholder="Password" required />
<a href="/forgotPassword">Forgot password?</a> <a href="/forgotpassword">Forgot password?</a>
<input type="submit" value="Login" />
</form>
</div>
<input type="text" name="userInfo" placeholder="Email address | Username" required />
<input type="password" name="password" placeholder="Password" required />
<a href="/resetPassword">Forgot password?</a>
<input type="submit" value="Login" /> <input type="submit" value="Login" />
</form> </form>
</div> </div>
<script> <script>
//both password fields must match
var password = document.getElementById("password");
var confirm_password = document.getElementById("confirmPassword");
function validatePassword() {
var passwordValue = password.value;
// Strong password regex pattern
var strongPasswordPattern = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/;
if (passwordValue != confirm_password.value) {
confirm_password.setCustomValidity("Passwords Don't Match");
} else if (!strongPasswordPattern.test(passwordValue)) {
confirm_password.setCustomValidity("Password must be at least 8 characters long and include at least one letter, one number, and one special character.");
} else {
confirm_password.setCustomValidity('');
}
}
password.onchange = validatePassword;
confirm_password.onkeyup = validatePassword;
const wrapper = document.querySelector(".wrapper"), const wrapper = document.querySelector(".wrapper"),
signupHeader = document.querySelector(".signup header"), signupHeader = document.querySelector(".signup header"),
loginHeader = document.querySelector(".login header"); loginHeader = document.querySelector(".login header");

View File

@ -33,10 +33,8 @@
<!-- Mustache JS --> <!-- Mustache JS -->
<script src="https://sso.theta42.com/static/js/mustache.min.js"></script> <script src="https://sso.theta42.com/static/js/mustache.min.js"></script>
<!-- jQuery library --> <!-- jQuery library -->
<script <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
crossorigin="anonymous"></script>
<!-- Bootstrap 5 JavaScript --> <!-- Bootstrap 5 JavaScript -->
<script <script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
@ -63,6 +61,7 @@
//check if user is logged in //check if user is logged in
app.auth.isLoggedIn(function (error, data) { app.auth.isLoggedIn(function (error, data) {
if (data) { if (data) {
app.auth.showUser();
$("#cl-logout-button").show("fast"); $("#cl-logout-button").show("fast");
$("#cl-profile-button").show("fast"); $("#cl-profile-button").show("fast");
$("#cl-login-button").hide("fast"); $("#cl-login-button").hide("fast");
@ -72,6 +71,7 @@
$("body").show("fast"); $("body").show("fast");
}); });
}); });
</script> </script>
<body> <body>
@ -94,7 +94,7 @@
<div class="collapse navbar-collapse" id="navbarResponsive"> <div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li jq-repeat="getUsername" class="nav-item"> <li jq-repeat="getUsername" class="nav-item">
{{ username }} <a class="nav-link"> Welcome {{ user.username }} </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/">Home</a> <a class="nav-link" href="/">Home</a>

2998
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -35,14 +35,13 @@
"mqtt": "^5.3.3", "mqtt": "^5.3.3",
"mysql2": "^3.7.1", "mysql2": "^3.7.1",
"node-fetch": "^3.3.2", "node-fetch": "^3.3.2",
"nodemailer": "^6.9.7", "nodemailer": "^6.9.8",
"otp-generator": "^4.0.1", "otp-generator": "^4.0.1",
"otplib": "^12.0.1", "otplib": "^12.0.1",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"sanitize-html": "^2.11.0", "sanitize-html": "^2.11.0",
"sequelize": "^6.35.2", "sequelize": "^6.35.2",
"sequelize-cli": "^6.6.2", "sequelize-cli": "^6.6.2",
"sql": "^0.78.0",
"validator": "^13.11.0" "validator": "^13.11.0"
}, },
"devDependencies": { "devDependencies": {

View File

@ -0,0 +1,17 @@
//model for getting API key from database
async function getAPIKey() {
}
module.exports = { getAPIKey }

View File

@ -1,39 +1,45 @@
const { checkAPikey } = require('../functions/database.js'); const { checkAPikey } = require("../functions/database.js");
async function apikeyCheck(req, res, next) { async function apikeyCheck(req, res, next) {
//const authHeader = req.headers.authorization //const authHeader = req.headers.authorization
try{ try {
let apikey = req.headers.authorization let apikey = req.headers.authorization;
if(!apikey){ if (!apikey) {
throw new Error('No API key was supplied. Invalid request') res.status(401).json({
} message: "No API key was supplied. Invalid request",
else{ });
//throw new Error("No API key was supplied. Invalid request");
} else {
//split the string by the - //split the string by the -
let splitAPIkey = apikey.split('-'); let splitAPIkey = apikey.split("-");
let rowid = splitAPIkey[0]; let rowid = splitAPIkey[0];
//rejoin withouth the rowid //rejoin withouth the rowid
let SuppliedKey = splitAPIkey.slice(1).join('-'); let SuppliedKey = splitAPIkey.slice(1).join("-");
if (checkAPikey(SuppliedKey , rowid)) if (checkAPikey(SuppliedKey, rowid)) {
{
//get permission //get permission
let permission = await checkAPikey(SuppliedKey , rowid); let permission = await checkAPikey(SuppliedKey, rowid);
console.log(permission); console.log(permission);
if (req.method === 'GET' && permission === 'canRead'){ if (req.method === "GET" && permission === "canRead") {
return next() return next();
} }
//['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method) //['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)
if (["GET" , "POST" , "PUT" , "DELETE"].includes(req.method) && permission === 'canWrite'){ if (
console.log('write') ["GET", "POST", "PUT", "DELETE"].includes(req.method) &&
return next() permission === "canWrite"
) {
console.log("write");
return next();
} }
throw new Error('Your API key does not have the correct permissions to access this resource') //throw status 403
res.status(403).json({
message:
"Your API key does not have the correct permissions to access this resource",
});
} }
} }
}catch(error){ } catch (error) {
next(error); next(error);
} }
} }
module.exports = { apikeyCheck }; module.exports = { apikeyCheck };