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: {
type: DataTypes.DATE,
},
sessionid: {
type: DataTypes.STRING,
},
}, {
hooks: {
beforeCreate: async (user) => {

View File

@ -1,7 +1,7 @@
const express = require("express");
const session = require("express-session");
const rateLimit = require('express-rate-limit');
const cookieParser = require('cookie-parser');
const bodyParser = require("body-parser");
const bcrypt = require("bcrypt");
const crypto = require("crypto");
@ -10,21 +10,37 @@ const otpGenerator = require('otp-generator');
const { body, validationResult } = require('express-validator');
const validator = require('validator');
const { format } = require('date-fns');
const helmet = require('helmet');
const { Sequelize } = require('sequelize');
const { transporter } = require("./modules/nodeMailer");
const { sequelize, User } = require("./modules/mysql");
const userLogs= require('./models/userLogs')(sequelize); // Adjust the path based on your project structure
const app = express();
const nonce = crypto.randomBytes(16).toString('base64');
console.log('Nonce:', nonce);
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cookieParser());
const PORT = process.env.PORT || 3000;
require("dotenv").config();
app.use(bodyParser.urlencoded({ extended: true }));
app.set("view engine", "ejs");
app.use(
helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'",`'nonce-${nonce}'`],
scriptSrc: ["'self'",`'nonce-${nonce}'`,"'strict-dynamic'", 'cdn.jsdelivr.net', 'fonts.googleapis.com', 'stackpath.bootstrapcdn.com', 'code.jquery.com', 'cdnjs.cloudflare.com'],
styleSrc: ["'self'",`'nonce-${nonce}'`, 'cdn.jsdelivr.net', 'fonts.googleapis.com'],
imgSrc: ["'self'"],
fontSrc: ["'self'", 'fonts.gstatic.com'],
},
})
);
app.use(session({
secret: process.env.key,
@ -189,6 +205,19 @@ app.post("/verify-otp", [
}
const sessionToken = crypto.randomBytes(32).toString('hex');
const username = req.body.username; // Replace with the actual username
User.update({ sessionid: sessionToken }, { where: { username } })
.then(([rowsUpdated]) => {
if (rowsUpdated > 0) {
console.log(`SessionId updated for user: ${username}`);
} else {
console.error('User not found.');
}
})
.catch(error => {
console.error('Error updating sessionId:', error);
});
req.session.authenticated = true;
req.session.username = req.body.username;
@ -200,7 +229,6 @@ app.post("/verify-otp", [
// Log anti-CSRF token
console.log(`Generated Anti-CSRF Token: ${csrfTokenSession}`);
// Set CSRF token as a cookie
res.cookie('sessionToken', sessionToken, { secure: true, httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000) }); // Expires in 1 day
console.log(`Generated Session Token: ${sessionToken}`);
@ -232,6 +260,7 @@ app.post("/verify-otp", [
} else {
console.log("Session destroyed.");
// Log the logout activity using Sequelize
await User.update({ sessionid: null }, { where: { username } })
await userLogs.create({ username, activity: "User logged out. Session destroyed." });
// Clear the session token cookie
res.clearCookie('sessionToken');
@ -267,7 +296,7 @@ app.post("/verify-otp", [
const currentUsername = req.session.username;
// Render the inusers page with JSON data
res.render("inusers", { allUsers, csrfToken: csrfTokenSession, currentUsername });
res.render("inusers", { nonce: nonce, allUsers, csrfToken: csrfTokenSession, currentUsername });
} catch (error) {
console.error("Error fetching all users:", error);
res.status(500).send("Internal Server Error");
@ -324,6 +353,14 @@ app.post(
return res.status(400).json({ errors: errors.array() });
}
const sessionTokencookie = req.cookies['sessionToken'];
// Verify sessionToken with the one stored in the database
const user = await User.findOne({ where: { sessionid: sessionTokencookie } });
if (!user) {
return res.status(403).json({ error: 'Invalid sessionToken' });
}
// Validate the anti-CSRF token
const submittedCSRFToken = req.body.csrf_token;
@ -595,7 +632,14 @@ app.post("/reset-password", async (req, res) => {
if (!csrfTokenSession || submittedCSRFToken !== csrfTokenSession) {
return res.status(403).json({ error: 'CSRF token mismatch' });
}
const sessionTokencookie = req.cookies['sessionToken'];
// Verify sessionToken with the one stored in the database
const user = await User.findOne({ where: { sessionid: sessionTokencookie } });
if (!user) {
return res.status(403).json({ error: 'Invalid sessionToken' });
}
// Sanitize the inputs
const sanitizedUsername = validator.escape(username);
const sanitizedPassword = validator.escape(password);
@ -686,7 +730,6 @@ app.get('/api/users', async (req, res) => {
app.get('/api/searchUser', async (req, res) => {
const { username } = req.query;
console.log(username);
try {
// Find the user in the database by username
const user = await User.findOne({ where: { username } });
@ -710,14 +753,23 @@ app.delete('/api/deleteUser/:username', async (req, res) => {
const creatorUsername = req.session.username;
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;
console.log(csrfToken);
// Compare CSRF token with the one stored in the session
if (csrfToken !== csrfTokenSession) {
return res.status(403).json({ success: false, error: 'CSRF token mismatch' });
}
// Verify sessionToken with the one stored in the database
const user = await User.findOne({ where: { sessionid: sessionTokencoookie } });
if (!user) {
return res.status(403).json({ success: false, error: 'Invalid sessionToken or user not found' });
}
// Log deletion activity to UserLogs model
const deletionActivity = `User ${username} has been successfully deleted`;
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 name="viewport" content="width=device-width, initial-scale=1.0">
<title>Forgot Password - Your Website</title>
<style>
body {
font-family: 'Arial', sans-serif;
background-color: #f4f4f4;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
h1 {
text-align: center;
color: #333;
}
form {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 300px;
}
label {
display: block;
margin-bottom: 8px;
color: #555;
}
input {
width: 100%;
padding: 8px;
margin-bottom: 16px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
background-color: #4caf50;
color: #fff;
padding: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 100%;
}
button:hover {
background-color: #45a049;
}
.error-message {
color: red;
margin-top: 10px;
}
.success-message {
color: green;
margin-top: 10px;
}
</style>
<link rel="stylesheet" href="forgot-password.css">
</head>
<body>
<div id="forgotPasswordForm">

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 name="viewport" content="width=device-width, initial-scale=1.0">
<title>In-House Users</title>
<link rel="stylesheet" href="/style.css">
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap">
</head>
@ -173,19 +173,21 @@
</div>
<script>
<script nonce="<%= nonce %>">
const allUsers = <%- JSON.stringify(allUsers) %>;
const currentUsername = '<%= currentUsername %>';
const nonce = "<%= nonce %>"
console.log('Nonce:', nonce);
</script>
<script src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.3.0/js/bootstrap.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.4/xlsx.full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.2.1/exceljs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.js"></script>
<script src="inusers.js"></script>
<script src="https://code.jquery.com/jquery-3.6.4.min.js" nonce="<%= nonce %>"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/5.3.0/js/bootstrap.bundle.min.js" nonce="<%= nonce %>" ></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.4/xlsx.full.min.js" nonce="<%= nonce %>"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js" nonce="<%= nonce %>"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/exceljs/4.2.1/exceljs.min.js" nonce="<%= nonce %>"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" nonce="<%= nonce %>"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.js" nonce="<%= nonce %>"></script>
<script src="inusers.js" nonce="<%= nonce %>"></script>
</body>

View File

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

67
Sean/views/login.css Normal file
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 name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Eco Saver</title>
<style>
body {
font-family: 'Arial', sans-serif;
background-color: #f4f4f4;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
.login-container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 300px;
margin: auto; /* Center the container horizontally */
}
h1 {
text-align: center;
color: #333;
}
form {
margin-top: 20px;
}
label {
display: block;
margin-bottom: 8px;
color: #555;
}
input {
width: calc(100% - 16px); /* Adjust the width and center the input */
padding: 8px;
margin-bottom: 16px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
background-color: #4caf50;
color: #fff;
padding: 10px;
border: none;
border-radius: 4px;
cursor: pointer;
width: 100%;
}
button:hover {
background-color: #45a049;
}
.reset-link-container {
text-align: center;
margin-top: 10px;
}
.reset-link {
color: #4caf50;
text-decoration: underline;
cursor: pointer;
}
</style>
<link rel="stylesheet" href="login.css">
</head>
<body>
<div class="login-container">

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 name="viewport" content="width=device-width, initial-scale=1.0">
<title>Enter OTP</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
text-align: center;
margin: 50px;
}
h2 {
color: #333;
}
form {
max-width: 300px;
margin: 20px auto;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
label {
display: block;
margin-bottom: 8px;
color: #333;
}
input {
width: 100%;
padding: 10px;
margin-bottom: 20px;
box-sizing: border-box;
}
button {
background-color: #4caf50;
color: #fff;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
.error {
color: red;
margin-top: 10px;
}
</style>
<link rel="stylesheet" href="otp.css">
</head>
<body>
<h2>Enter OTP</h2>

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
async function loginUser(user) {
//console.log(user);
//look up username or email in db
const userRes = await userModel.findOne({
where: {
[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);
}
//generate me an regex for alpha
//https://stackoverflow.com/questions/11522529/regexp-for-alphabets-with-spaces
module.exports = {
isAlphaNumericwithSpaces,
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;
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{
padding: 10px 15px;
color: #4e3914;
font-size: 14px;
font-size: 15px;
font-weight: 300;
text-transform: uppercase;
}

View File

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

View File

@ -283,9 +283,6 @@
};
$( document ).ready( function(){
console.log('jq-repeat', $.scope)
//$.jqrepeat = $.scope
$( '[jq-repeat]' ).each(function(key, 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
router.post("/register", async (req, res, next) => {
try {
console.log(req.body);
let Res = await addUser(req.body);
if (Res == false) {
let error = new Error("UserRegFailed");
@ -36,7 +35,6 @@ router.post("/login", async (req, res, next) => {
}
else{
//pass res back to form to be set in local storage
console.log("my res" , Res);
return res.json({
message: "User login successfully",
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;

View File

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

View File

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

View File

@ -12,25 +12,35 @@
<div class="left-menu">
<div class="content-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>
</div>
<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>
</div>
<div class="mobile-menu-closer"></div>
<div class="content-menu">
<div class="content-infos">
<div class="info"><b>Version:</b> 1.0.5</div>
<div class="info"><b>Last Updated:</b> 15th Sep, 2021</div>
<div class="info"><b>Version:</b> 0</div>
<div class="info"><b>Last Updated:</b> 22th January 2024</div>
</div>
<ul>
<li class="scroll-to-link active" data-target="content-get-started">
<a>GET STARTED</a>
</li>
<li class="scroll-to-link" data-target="content-get-characters">
<a>Get Characters</a>
<a>Get Data From API</a>
</li>
<li class="scroll-to-link" data-target="content-errors">
<a>Errors</a>
@ -45,17 +55,22 @@
<div class="overflow-hidden content-section" id="content-get-started">
<h1>Get started</h1>
<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>
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>
</div>
<div class="overflow-hidden content-section" id="content-get-characters">
<h2>get characters</h2>
<div class="overflow-hidden content-section" id="content-get-location">
<h2>Get all location</h2>
<p>
To get characters you need to make a POST call to the following url :<br>
<code class="higlighted break-word">http://api.westeros.com/character/get</code>
To get Location of sensors you need to make a GET call to the following url :<br>
<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>
<br>
<h4>QUERY PARAMETERS</h4>
@ -69,49 +84,237 @@
</thead>
<tbody>
<tr>
<td>secret_key</td>
<td>String</td>
<td>Authorization</td>
<td>JSON</td>
<td>Your API key.</td>
</tr>
<tr>
<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>(Required) Example: curl https://api.teeseng.uk/api/v0/location -H "Authorization: {provide your
API key here}"</td>
</td>
</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>
</table>
</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">
<h2>Errors</h2>
<p>
@ -128,25 +331,30 @@
<tr>
<td>X000</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>
</tr>
<tr>
<td>X001</td>
<td>403</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>
</tr>
<tr>
<td>X002</td>
<td>500</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>
</tr>
<tr>
<td>X003</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>
</tr>
</tbody>
@ -158,4 +366,3 @@
</body>
</html>

View File

@ -82,9 +82,6 @@
</div>
</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/search.js"></script>
<script src="js/api.js"></script>

View File

@ -8,7 +8,7 @@
<input type="text" id="email" placeholder="Email" required />
<input type="password" id="password" placeholder="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>
<br>
<a>Dont have an account?</a> <a href="/login">Sign Up</a>

View File

@ -91,7 +91,6 @@
<div class="row">
<div class="col-lg-6">
<title>EcoSaver - Your Air Quality Index Source</title>
<link rel="stylesheet" href="css/style.css">
</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.js"></script>
<!-- jquery app.js -->
<script src="js/app.js"></script>
@ -83,7 +82,7 @@
</li>
<!-- profile button -->
<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;">
<i class="fas fa-sign-out"></i>
Profile

View File

@ -1,6 +1,7 @@
<%- include('logintop') %>
<script type="text/javascript">
app.auth.redirectIfLoggedIn();
// Require login to see this page.
//app.auth.redirectIfLoggedIn();
</script>
<body>
@ -11,14 +12,18 @@
<!-- localhost/api/v0/user/register -->
<!-- evalAjax Fires when status 200 is returned -->
<form action="auth/register" onsubmit="formAJAX(this)" evalAJAX="app.auth.logInRedirect();">
<input type="text" name="firstname" placeholder="First Name" required />
<input type="text" name="lastname" placeholder="Last Name" required />
<input type="text" name="username" placeholder="Username" required />
<input type="text" name="email" placeholder="Email" required />
<input type="text" name="address" placeholder="Address" required />
<input type="text" name="phone" placeholder="Phone Number" required />
<input type="password" name="password" placeholder="Password" required />
<input type="password" name="confirmPassword" placeholder="Confirm Password" required />
<input type="text" name="firstname" placeholder="First Name" required pattern="^[a-zA-Z\s]+$" />
<input type="text" name="lastname" placeholder="Last Name" required pattern="^[a-zA-Z\s]+$" />
<input type="text" name="username" placeholder="Username" required pattern="^\w+$" />
<input type="email" name="email" placeholder="Email" required
pattern="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" />
<input type="text" name="address" placeholder="Address" required
pattern="^(\d{1,3}.)?.+\s(\d{6})$" />
<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" />
</form>
</div>
@ -27,28 +32,41 @@
<header>Login</header>
<div class="card-header shadow actionMessage" style="display:none"></div>
<!-- evalAjax Fires when status 200 is returned -->
<form action="auth/login" onsubmit="formAJAX(this)"
evalAJAX="app.auth.homeRedirect();
app.auth.setToken(data.token);
app.auth.setUserId(data.userid);
app.auth.setUsername(data.username);
">
<input type="text" name="userInfo" placeholder="Email address | Username" required />
<form action="auth/login" onsubmit="formAJAX(this)" evalAJAX="app.auth.homeRedirect();
app.auth.setToken(data.token);">
<input type="text" name="username" placeholder="Email address | Username" required
pattern="^\w+$" />
<input type="password" name="password" placeholder="Password" required />
<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>
<a href="/forgotpassword">Forgot password?</a>
<input type="submit" value="Login" />
</form>
</div>
<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"),
signupHeader = document.querySelector(".signup header"),
loginHeader = document.querySelector(".login header");

View File

@ -33,10 +33,8 @@
<!-- Mustache JS -->
<script src="https://sso.theta42.com/static/js/mustache.min.js"></script>
<!-- jQuery library -->
<script
src="https://code.jquery.com/jquery-3.7.1.min.js"
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<!-- Bootstrap 5 JavaScript -->
<script
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
app.auth.isLoggedIn(function (error, data) {
if (data) {
app.auth.showUser();
$("#cl-logout-button").show("fast");
$("#cl-profile-button").show("fast");
$("#cl-login-button").hide("fast");
@ -72,6 +71,7 @@
$("body").show("fast");
});
});
</script>
<body>
@ -94,7 +94,7 @@
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ms-auto">
<li jq-repeat="getUsername" class="nav-item">
{{ username }}
<a class="nav-link"> Welcome {{ user.username }} </a>
</li>
<li class="nav-item">
<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",
"mysql2": "^3.7.1",
"node-fetch": "^3.3.2",
"nodemailer": "^6.9.7",
"nodemailer": "^6.9.8",
"otp-generator": "^4.0.1",
"otplib": "^12.0.1",
"qrcode": "^1.5.3",
"sanitize-html": "^2.11.0",
"sequelize": "^6.35.2",
"sequelize-cli": "^6.6.2",
"sql": "^0.78.0",
"validator": "^13.11.0"
},
"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) {
//const authHeader = req.headers.authorization
try{
let apikey = req.headers.authorization
if(!apikey){
throw new Error('No API key was supplied. Invalid request')
}
else{
try {
let apikey = req.headers.authorization;
if (!apikey) {
res.status(401).json({
message: "No API key was supplied. Invalid request",
});
//throw new Error("No API key was supplied. Invalid request");
} else {
//split the string by the -
let splitAPIkey = apikey.split('-');
let splitAPIkey = apikey.split("-");
let rowid = splitAPIkey[0];
//rejoin withouth the rowid
let SuppliedKey = splitAPIkey.slice(1).join('-');
if (checkAPikey(SuppliedKey , rowid))
{
let SuppliedKey = splitAPIkey.slice(1).join("-");
if (checkAPikey(SuppliedKey, rowid)) {
//get permission
let permission = await checkAPikey(SuppliedKey , rowid);
let permission = await checkAPikey(SuppliedKey, rowid);
console.log(permission);
if (req.method === 'GET' && permission === 'canRead'){
return next()
if (req.method === "GET" && permission === "canRead") {
return next();
}
//['POST', 'PUT', 'PATCH', 'DELETE'].includes(req.method)
if (["GET" , "POST" , "PUT" , "DELETE"].includes(req.method) && permission === 'canWrite'){
console.log('write')
return next()
if (
["GET", "POST", "PUT", "DELETE"].includes(req.method) &&
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);
}
}
module.exports = { apikeyCheck };