From 0531eedbce1704c0a7c08676dee50632c77b798a Mon Sep 17 00:00:00 2001 From: luther10293-a11y Date: Thu, 12 Feb 2026 10:23:43 +0100 Subject: [PATCH 1/8] REDIS/MAIL setup --- .gitignore | 2 +- README.md | 8 +- backend/NodeMailer/Nodemail_connection.js | 25 ++++++ backend/ReadMe.md | 2 +- backend/Redis_config/Redis_setup.js | 33 ++++++++ backend/app.js | 20 +++-- backend/config/db.js | 4 +- backend/modules/user/OTP/OTP.script.js | 10 +++ backend/modules/user/user.controller.js | 2 +- backend/modules/user/user.routes.js | 4 +- backend/modules/user/user.verifyOTP.js | 25 ++++++ backend/package-lock.json | 98 ++++++++++++++++++++++- backend/package.json | 4 +- 13 files changed, 223 insertions(+), 14 deletions(-) create mode 100644 backend/NodeMailer/Nodemail_connection.js create mode 100644 backend/Redis_config/Redis_setup.js create mode 100644 backend/modules/user/OTP/OTP.script.js create mode 100644 backend/modules/user/user.verifyOTP.js diff --git a/.gitignore b/.gitignore index a2e04e4..d9ee72c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ node_modules - +.next .env \ No newline at end of file diff --git a/README.md b/README.md index 7ebfba9..fd56df9 100644 --- a/README.md +++ b/README.md @@ -1 +1,7 @@ -# NexaSoft \ No newline at end of file +# NexaSoft + +# install the following Packages +- `npm `: + +* redis +* nodemailer \ No newline at end of file diff --git a/backend/NodeMailer/Nodemail_connection.js b/backend/NodeMailer/Nodemail_connection.js new file mode 100644 index 0000000..7f78553 --- /dev/null +++ b/backend/NodeMailer/Nodemail_connection.js @@ -0,0 +1,25 @@ +const nodemailer = require('nodemailer'); + +const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: "obaseviv@gmail.com", + pass: " tfaj agox mwrh ejuv" // Use an app password for Gmail if 2FA is enabled + } +}); + +const SendOTPEmail = async (email, otp) => { + const mailOptions = { + from: `"NexaSoft System" <${transporter.options.auth.user}>`, + to: email, + subject: "Your Login OTP", + text: `Your OTP is ${otp}: . It will expire in 1 min seconds.`, + html: `Your OTP is ${otp}:

It will expire in 1 min seconds.

` + }; + + return transporter.sendMail(mailOptions); +}; + +module.exports = { SendOTPEmail }; + + diff --git a/backend/ReadMe.md b/backend/ReadMe.md index 3602f92..2d5b65e 100644 --- a/backend/ReadMe.md +++ b/backend/ReadMe.md @@ -21,7 +21,7 @@ src/ │ │ ├── user.service.js │ │ ├── user.repository.js │ │ └── user.schema.js # Validation (zod/joi) -│ │ +│ │ |__ user.verifyOTP.js │ ├── auth/ │ │ ├── auth.routes.js │ │ ├── auth.controller.js diff --git a/backend/Redis_config/Redis_setup.js b/backend/Redis_config/Redis_setup.js new file mode 100644 index 0000000..aa51268 --- /dev/null +++ b/backend/Redis_config/Redis_setup.js @@ -0,0 +1,33 @@ +const { createClient } = require('redis'); + +// Create the client +const redisClient = createClient({ +// use the url from the .env file + url: "rediss://default:ATavAAIncDI4YzU1ZmVjZTQyN2U0MDBhOGM4MDdkMmMzYWM5MDFiNnAyMTM5OTk@flexible-escargot-13999.upstash.io:6379", + socket: { + tls: true, // Crucial for Upstash + reconnectStrategy: (retries) => Math.min(retries * 50, 500) + } +}); + +// Event listeners for monitoring +redisClient.on('error', (err) => console.error('❌ Redis Client Error:', err)); +redisClient.on('connect', () => console.log('⏳ Connecting to Redis...')); +redisClient.on('ready', () => console.log('✅ Redis is ready!')); + +// Function to initiate connection +const connectRedis = async () => { + try { + if (!redisClient.isOpen) { + await redisClient.connect(); + } + } catch (err) { + console.error('❌ Could not connect to Redis:', err); + } +}; + +// Start the connection immediately when this file is required +connectRedis(); + +// Export the client so other files can use it +module.exports = redisClient; \ No newline at end of file diff --git a/backend/app.js b/backend/app.js index 86e48fe..39578b2 100644 --- a/backend/app.js +++ b/backend/app.js @@ -1,6 +1,7 @@ const express = require('express'); const userRoutes = require('./modules/user/user.routes'); const db = require('./config/db'); +const redisClient= require('./Redis_config/Redis_setup'); require('dotenv').config(); const Port = process.env.PORT || 2000; const app = express(); @@ -8,12 +9,21 @@ const app = express(); app.use(express.json()); app.use('/nexasoft/users', userRoutes); -db.connect().then(() => { - console.log('Connected to the database'); -}).catch((err) => { - console.error('Database connection error:', err); -}); +// // Connect to the DB +// db.connect().then(() => { +// console.log('Connected to the database'); +// }).catch((err) => { +// console.error('Database connection error:', err); +// }); + +// Connect to the Redis Client +app.post('/test-redis', async (req, res) => { + // You can now use the client directly here + await redisClient.set('status', 'running'); + const value = await redisClient.get('status'); + res.send(`Redis status: ${value}`); +}); app.listen(Port, ()=>{ console.log(`Server is running on port ${Port}`); diff --git a/backend/config/db.js b/backend/config/db.js index 0e9e4ec..31ba474 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -1,7 +1,7 @@ require('dotenv').config() -const { pool } = require('pg') +const { Pool } = require('pg') -const db = new pool({ +const db = new Pool({ host: process.env.DB_HOST, port: process.env.DB_PORT, user: process.env.DB_USER, diff --git a/backend/modules/user/OTP/OTP.script.js b/backend/modules/user/OTP/OTP.script.js new file mode 100644 index 0000000..38fb0e0 --- /dev/null +++ b/backend/modules/user/OTP/OTP.script.js @@ -0,0 +1,10 @@ +const generateOTP = () => { + const length= 6; + const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Excluded similar looking chars like O, 0, I, 1 + let result = ''; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} +module.exports = { generateOTP }; \ No newline at end of file diff --git a/backend/modules/user/user.controller.js b/backend/modules/user/user.controller.js index 41b960f..0cc961a 100644 --- a/backend/modules/user/user.controller.js +++ b/backend/modules/user/user.controller.js @@ -1,4 +1,4 @@ -const userService = require('./user.service'); +const userService = require('../user/user.services'); exports.getUsers = async (req, res) => { const users = await userService.getUsers(); diff --git a/backend/modules/user/user.routes.js b/backend/modules/user/user.routes.js index 8f01706..161be0e 100644 --- a/backend/modules/user/user.routes.js +++ b/backend/modules/user/user.routes.js @@ -1,7 +1,9 @@ const express = require('express') -const router = express.Rounter() +const router = express.Router() const userController = require('./user.controller') +const userOTP= require('./user.verifyOTP') router.get('/', userController.getUsers) +router.post('/VerifyOTP', userOTP.verifyOTP)// route to verify the OTP sent to the client module.exports = router \ No newline at end of file diff --git a/backend/modules/user/user.verifyOTP.js b/backend/modules/user/user.verifyOTP.js new file mode 100644 index 0000000..bdb9225 --- /dev/null +++ b/backend/modules/user/user.verifyOTP.js @@ -0,0 +1,25 @@ +const redisClient = require('../../Redis_config/Redis_setup'); + +exports.verifyOTP = async (req, res) => { + try { + const { email, otp } = req.body; + // Retrieve the OTP from Redis + const storedOTP = await redisClient.get(email); + + if (!storedOTP) { + return res.status(410).json({ message: 'OTP has expired or does not exist' }); + } + // Compare the provided OTP with the stored OTP + if (storedOTP !== otp) { + return res.status(400).json({ message: 'Invalid OTP' }); + } + // Proceed for valid OTP + res.status(200).json({ message: 'OTP verified successfully' }); + + // Optionally, you can delete the OTP from Redis after successful verification + await redisClient.del(email); + } catch (error) { + console.error('Error verifying OTP:', error); + res.status(500).json({ message: 'Internal server error' }); + } +}; \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 2098b5a..378ca8c 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -12,9 +12,71 @@ "bcrypt": "^6.0.0", "dotenv": "^17.2.4", "express": "^5.2.1", + "nodemailer": "^8.0.1", "nodemon": "^3.1.11", "open": "^11.0.0", - "pg": "^8.18.0" + "pg": "^8.18.0", + "redis": "^5.10.0" + } + }, + "node_modules/@redis/bloom": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.10.0.tgz", + "integrity": "sha512-doIF37ob+l47n0rkpRNgU8n4iacBlKM9xLiP1LtTZTvz8TloJB8qx/MgvhMhKdYG+CvCY2aPBnN2706izFn/4A==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.10.0" + } + }, + "node_modules/@redis/client": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-5.10.0.tgz", + "integrity": "sha512-JXmM4XCoso6C75Mr3lhKA3eNxSzkYi3nCzxDIKY+YOszYsJjuKbFgVtguVPbLMOttN4iu2fXoc2BGhdnYhIOxA==", + "license": "MIT", + "dependencies": { + "cluster-key-slot": "1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@redis/json": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-5.10.0.tgz", + "integrity": "sha512-B2G8XlOmTPUuZtD44EMGbtoepQG34RCDXLZbjrtON1Djet0t5Ri7/YPXvL9aomXqP8lLTreaprtyLKF4tmXEEA==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.10.0" + } + }, + "node_modules/@redis/search": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-5.10.0.tgz", + "integrity": "sha512-3SVcPswoSfp2HnmWbAGUzlbUPn7fOohVu2weUQ0S+EMiQi8jwjL+aN2p6V3TI65eNfVsJ8vyPvqWklm6H6esmg==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.10.0" + } + }, + "node_modules/@redis/time-series": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-5.10.0.tgz", + "integrity": "sha512-cPkpddXH5kc/SdRhF0YG0qtjL+noqFT0AcHbQ6axhsPsO7iqPi1cjxgdkE9TNeKiBUUdCaU1DbqkR/LzbzPBhg==", + "license": "MIT", + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@redis/client": "^5.10.0" } }, "node_modules/accepts": { @@ -198,6 +260,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -874,6 +945,15 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/nodemailer": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.1.tgz", + "integrity": "sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.11", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", @@ -1205,6 +1285,22 @@ "node": ">=8.10.0" } }, + "node_modules/redis": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-5.10.0.tgz", + "integrity": "sha512-0/Y+7IEiTgVGPrLFKy8oAEArSyEJkU0zvgV5xyi9NzNQ+SLZmyFbUsWIbgPcd4UdUh00opXGKlXJwMmsis5Byw==", + "license": "MIT", + "dependencies": { + "@redis/bloom": "5.10.0", + "@redis/client": "5.10.0", + "@redis/json": "5.10.0", + "@redis/search": "5.10.0", + "@redis/time-series": "5.10.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", diff --git a/backend/package.json b/backend/package.json index 53a617b..e1e1c01 100644 --- a/backend/package.json +++ b/backend/package.json @@ -14,8 +14,10 @@ "bcrypt": "^6.0.0", "dotenv": "^17.2.4", "express": "^5.2.1", + "nodemailer": "^8.0.1", "nodemon": "^3.1.11", "open": "^11.0.0", - "pg": "^8.18.0" + "pg": "^8.18.0", + "redis": "^5.10.0" } } From 8790a33e319a45e52d8146143ee31a200e3c252f Mon Sep 17 00:00:00 2001 From: luther10293-a11y Date: Thu, 12 Feb 2026 11:26:40 +0100 Subject: [PATCH 2/8] ENV_update --- backend/NodeMailer/Nodemail_connection.js | 5 +++-- backend/Redis_config/Redis_setup.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/NodeMailer/Nodemail_connection.js b/backend/NodeMailer/Nodemail_connection.js index 7f78553..97401f2 100644 --- a/backend/NodeMailer/Nodemail_connection.js +++ b/backend/NodeMailer/Nodemail_connection.js @@ -1,10 +1,11 @@ const nodemailer = require('nodemailer'); +require('dotenv').config(); // Load environment variables from .env file const transporter = nodemailer.createTransport({ service: 'gmail', auth: { - user: "obaseviv@gmail.com", - pass: " tfaj agox mwrh ejuv" // Use an app password for Gmail if 2FA is enabled + user: process.env.APP_EMAIL, + pass: process.env.APP_PASSWORD // Use an app password for Gmail if 2FA is enabled } }); diff --git a/backend/Redis_config/Redis_setup.js b/backend/Redis_config/Redis_setup.js index aa51268..5723264 100644 --- a/backend/Redis_config/Redis_setup.js +++ b/backend/Redis_config/Redis_setup.js @@ -1,9 +1,9 @@ const { createClient } = require('redis'); - +require('dotenv').config(); // Load environment variables from .env file // Create the client const redisClient = createClient({ // use the url from the .env file - url: "rediss://default:ATavAAIncDI4YzU1ZmVjZTQyN2U0MDBhOGM4MDdkMmMzYWM5MDFiNnAyMTM5OTk@flexible-escargot-13999.upstash.io:6379", + url: process.env.REDIS_URI, socket: { tls: true, // Crucial for Upstash reconnectStrategy: (retries) => Math.min(retries * 50, 500) From c8bc5b6aae73adefcbd1952995d222d210949df8 Mon Sep 17 00:00:00 2001 From: luther10293-a11y Date: Thu, 12 Feb 2026 11:28:03 +0100 Subject: [PATCH 3/8] settled --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 4eb6d0f..16f7952 100644 --- a/package.json +++ b/package.json @@ -17,5 +17,8 @@ "bugs": { "url": "https://github.com/Lonewolf-master/NexaSoft/issues" }, - "homepage": "https://github.com/Lonewolf-master/NexaSoft#readme" + "homepage": "https://github.com/Lonewolf-master/NexaSoft#readme", + "dependencies": { + "dotenv": "^17.2.4" + } } From b68c04701a08b6ab36fcd8562e4dc7303e495ace Mon Sep 17 00:00:00 2001 From: luther10293-a11y Date: Thu, 12 Feb 2026 11:30:01 +0100 Subject: [PATCH 4/8] done --- package.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 16f7952..b50e28b 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,5 @@ "bugs": { "url": "https://github.com/Lonewolf-master/NexaSoft/issues" }, - "homepage": "https://github.com/Lonewolf-master/NexaSoft#readme", - "dependencies": { - "dotenv": "^17.2.4" - } -} + "homepage": "https://github.com/Lonewolf-master/NexaSoft#readme" +} \ No newline at end of file From ca1f4903737793a2e2b5837c45536227e41f7a01 Mon Sep 17 00:00:00 2001 From: luther10293-a11y Date: Sun, 22 Feb 2026 22:09:57 +0100 Subject: [PATCH 5/8] Mail completion --- backend/NodeMailer/Nodemail_connection.js | 6 +- backend/app.js | 14 ++--- backend/config/db.js | 2 +- backend/config/setup_script.js | 26 ++++++++ backend/migrations/001_create_users.sql | 2 +- backend/modules/user/user.routes.js | 3 + backend/modules/user/user.sign_in-up.js | 59 ++++++++++++++++++ backend/package-lock.json | 73 +++++++++++++---------- backend/package.json | 3 +- 9 files changed, 143 insertions(+), 45 deletions(-) create mode 100644 backend/config/setup_script.js create mode 100644 backend/modules/user/user.sign_in-up.js diff --git a/backend/NodeMailer/Nodemail_connection.js b/backend/NodeMailer/Nodemail_connection.js index 97401f2..3836451 100644 --- a/backend/NodeMailer/Nodemail_connection.js +++ b/backend/NodeMailer/Nodemail_connection.js @@ -1,5 +1,5 @@ const nodemailer = require('nodemailer'); -require('dotenv').config(); // Load environment variables from .env file +require('dotenv').config();// Load environment variables from .env file const transporter = nodemailer.createTransport({ service: 'gmail', @@ -14,8 +14,8 @@ const SendOTPEmail = async (email, otp) => { from: `"NexaSoft System" <${transporter.options.auth.user}>`, to: email, subject: "Your Login OTP", - text: `Your OTP is ${otp}: . It will expire in 1 min seconds.`, - html: `Your OTP is ${otp}:

It will expire in 1 min seconds.

` + text: `Your OTP is: ${otp} . It will expire in 1 min .`, + html: `Your OTP is: ${otp}

It will expire in 1 min .

` }; return transporter.sendMail(mailOptions); diff --git a/backend/app.js b/backend/app.js index 39578b2..e8a5a7f 100644 --- a/backend/app.js +++ b/backend/app.js @@ -3,18 +3,18 @@ const userRoutes = require('./modules/user/user.routes'); const db = require('./config/db'); const redisClient= require('./Redis_config/Redis_setup'); require('dotenv').config(); -const Port = process.env.PORT || 2000; +const Port = process.env.PORT; const app = express(); app.use(express.json()); app.use('/nexasoft/users', userRoutes); -// // Connect to the DB -// db.connect().then(() => { -// console.log('Connected to the database'); -// }).catch((err) => { -// console.error('Database connection error:', err); -// }); +// Connect to the DB +db.connect().then(() => { + console.log(`Connected to the database to : ${process.env.DB_NAME} `); +}).catch((err) => { + console.error('Database connection error:', err); +}); // Connect to the Redis Client diff --git a/backend/config/db.js b/backend/config/db.js index 31ba474..b71a15e 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -6,7 +6,7 @@ const db = new Pool({ port: process.env.DB_PORT, user: process.env.DB_USER, password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, + database: process.env.DB_NAME }); module.exports = db diff --git a/backend/config/setup_script.js b/backend/config/setup_script.js new file mode 100644 index 0000000..fa9a05f --- /dev/null +++ b/backend/config/setup_script.js @@ -0,0 +1,26 @@ +// Create the setup script to run the sql file to build the table in the DB +const fs= require('fs'); +const path = require('path'); +// 1. Force dotenv to look one folder up (in the backend root) +require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); +// 2. Add a quick sanity check to prove it worked +console.log("🛠️ Attempting to connect on port:", process.env.DB_PORT); +const db = require('./db'); // import the db connection pool + + +// Read the SQL file and execute the query to create the users table +const initializeDB= async () => { +try{ +const sqlFilePath = path.join(__dirname, '../migrations/001_create_users.sql'); +const sql = fs.readFileSync(sqlFilePath, 'utf-8'); + + +// Execute the SQL query +await db.query(sql); +console.log("Database initialized successfully"); + } catch (error) { + console.error("Error initializing database:", error); + } + +} +initializeDB(); \ No newline at end of file diff --git a/backend/migrations/001_create_users.sql b/backend/migrations/001_create_users.sql index b420cd5..1a79763 100644 --- a/backend/migrations/001_create_users.sql +++ b/backend/migrations/001_create_users.sql @@ -1,6 +1,6 @@ + CREATE TABLE users ( id SERIAL PRIMARY KEY, - -- profile first_name TEXT NOT NULL, last_name TEXT NOT NULL, diff --git a/backend/modules/user/user.routes.js b/backend/modules/user/user.routes.js index 161be0e..89cab24 100644 --- a/backend/modules/user/user.routes.js +++ b/backend/modules/user/user.routes.js @@ -1,9 +1,12 @@ const express = require('express') const router = express.Router() const userController = require('./user.controller') +const userSignInUp = require('./user.sign_in-up') const userOTP= require('./user.verifyOTP') router.get('/', userController.getUsers) +router.post('/signup', userSignInUp.signUp) // route to handle user sign up client +router.post('/login', userSignInUp.login) // route to handle user login client router.post('/VerifyOTP', userOTP.verifyOTP)// route to verify the OTP sent to the client module.exports = router \ No newline at end of file diff --git a/backend/modules/user/user.sign_in-up.js b/backend/modules/user/user.sign_in-up.js new file mode 100644 index 0000000..639cb0d --- /dev/null +++ b/backend/modules/user/user.sign_in-up.js @@ -0,0 +1,59 @@ +const bcrypt = require('bcrypt'); +const pool = require('../../config/db'); +const {generateOTP}= require('../../modules/user/OTP/OTP.script'); +const {SendOTPEmail} = require('../../NodeMailer/Nodemail_connection'); +const redisClient = require('../../Redis_config/Redis_setup'); + +// Sign-up endpoint to handle user registration +exports.signUp = async (req, res) => { + try { + const { first_name, last_name, role, email, password } = req.body; + // Check if the user already exists + const existingUser = await pool.query('SELECT * FROM users WHERE email = $1', [email]); + if (existingUser.rows.length > 0) { + return res.status(400).json({ message: 'User already exists' }); + } + // Hash the password + const hashedPassword = await bcrypt.hash(password, 10); + // Insert the new user into the database + const newUser = await pool.query( + 'INSERT INTO users (first_name, last_name, role, email, password) VALUES ($1, $2, $3, $4, $5) RETURNING *', + [first_name, last_name, role, email, hashedPassword] + ); + res.status(201).json({ message: 'User registered successfully', user: newUser.rows[0] }); + } catch (error) { + console.error('Error signing up:', error); + res.status(500).json({ message: 'Internal server error' }); + } +}; + + +// login endpoint to handle user login +exports.login = async (req, res) => { + try { + const { email, password } = req.body; + // Check if the user exists + const user = await pool.query('SELECT * FROM users WHERE email = $1', [email]); + if (user.rows.length === 0) { + return res.status(400).json({ message: 'Invalid email or password' }); + } + // Compare the provided password with the hashed password in the database + const isPasswordValid = await bcrypt.compare(password, user.rows[0].password); + if (!isPasswordValid) { + return res.status(400).json({ message: 'Invalid email or password' }); + } + + res.status(200).json({ message: 'Login successful', user: user.rows[0] }); + + // Create the OTP and store it in the RedisClient with an expiration time of 1 mins + const otp = generateOTP(); + console.log("<< OTP >>:", otp); // Log the generated OTP for debugging + await redisClient.set(email, otp, 'EX', 60); // Store OTP with a 60-second expiration + // Send the OTP to the user's email + await SendOTPEmail(email, otp); + + } catch (error) { + console.error('Error logging in:', error); + res.status(500).json({ message: 'Internal server error' }); + } +}; \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 378ca8c..fb75989 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -10,8 +10,9 @@ "license": "ISC", "dependencies": { "bcrypt": "^6.0.0", - "dotenv": "^17.2.4", + "dotenv": "^17.3.1", "express": "^5.2.1", + "fs": "^0.0.1-security", "nodemailer": "^8.0.1", "nodemon": "^3.1.11", "open": "^11.0.0", @@ -106,10 +107,13 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } }, "node_modules/bcrypt": { "version": "6.0.0", @@ -162,13 +166,15 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/braces": { @@ -269,12 +275,6 @@ "node": ">=0.10.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, "node_modules/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -382,9 +382,9 @@ } }, "node_modules/dotenv": { - "version": "17.2.4", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz", - "integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==", + "version": "17.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", + "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -561,6 +561,12 @@ "node": ">= 0.8" } }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==", + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -899,15 +905,18 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ms": { @@ -955,15 +964,15 @@ } }, "node_modules/nodemon": { - "version": "3.1.11", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", - "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", + "minimatch": "^10.2.1", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", @@ -1235,9 +1244,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" diff --git a/backend/package.json b/backend/package.json index e1e1c01..df8715f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,8 +12,9 @@ "type": "commonjs", "dependencies": { "bcrypt": "^6.0.0", - "dotenv": "^17.2.4", + "dotenv": "^17.3.1", "express": "^5.2.1", + "fs": "^0.0.1-security", "nodemailer": "^8.0.1", "nodemon": "^3.1.11", "open": "^11.0.0", From 4d57b17de17cd0ad383f07f3e323463fbb588c3e Mon Sep 17 00:00:00 2001 From: OmniZlatoon Date: Sun, 22 Feb 2026 22:23:27 +0100 Subject: [PATCH 6/8] Update README.md --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fd56df9..25d822f 100644 --- a/README.md +++ b/README.md @@ -4,4 +4,24 @@ - `npm `: * redis -* nodemailer \ No newline at end of file +* nodemailer + +* For the .env variables +* 1) Nodemailer Connection +* create ` app password ` in the --- google center --- to use to connect to the Nodemailer module, to be able to send the email +* [ browse on how to create google app password, and follow all steps that would be provided ] + e.g., + APP_PASSWORD= trye d8wj uwie 9ow0 + APP_EMAIL= ruletrek@gmail.com + + + 2) Redis Connection + * create a redis account in the `https://console.upstash.com` platform + * create a DB , and copy your redis API endpoint to use in the redis connection script + * e.g., + * REDIS_URI=rediss://default:ATavAAIncDI4YzU1ZmVyrhdBN2U0MDBhOGM4MDdkMmMzYWM5MDFiNnAyMTM5OTk@flexible-escargot-13999.upstash.io:6379 + + * Note: you can read the redis documentation inside the platform how to connect, using different methods + +- With that, your connection would be easy to go +- From 75073ad5351cff87a7a62dce080f452508c54fce Mon Sep 17 00:00:00 2001 From: luther10293-a11y Date: Mon, 23 Feb 2026 14:26:52 +0100 Subject: [PATCH 7/8] Mail 2nd commit --- backend/NodeMailer/Nodemail_connection.js | 3 ++- backend/Redis_config/Redis_setup.js | 3 ++- backend/modules/user/user.sign_in-up.js | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/NodeMailer/Nodemail_connection.js b/backend/NodeMailer/Nodemail_connection.js index 3836451..c5dca47 100644 --- a/backend/NodeMailer/Nodemail_connection.js +++ b/backend/NodeMailer/Nodemail_connection.js @@ -14,9 +14,10 @@ const SendOTPEmail = async (email, otp) => { from: `"NexaSoft System" <${transporter.options.auth.user}>`, to: email, subject: "Your Login OTP", - text: `Your OTP is: ${otp} . It will expire in 1 min .`, + text: `Your OTP is: ${otp} . It will expire in 1 min .`, html: `Your OTP is: ${otp}

It will expire in 1 min .

` }; + console.log(`<< OTP: ${otp} - ${email} >>:`); // Log the email and OTP for debugging return transporter.sendMail(mailOptions); }; diff --git a/backend/Redis_config/Redis_setup.js b/backend/Redis_config/Redis_setup.js index 5723264..9668177 100644 --- a/backend/Redis_config/Redis_setup.js +++ b/backend/Redis_config/Redis_setup.js @@ -13,7 +13,8 @@ const redisClient = createClient({ // Event listeners for monitoring redisClient.on('error', (err) => console.error('❌ Redis Client Error:', err)); redisClient.on('connect', () => console.log('⏳ Connecting to Redis...')); -redisClient.on('ready', () => console.log('✅ Redis is ready!')); +redisClient.on('ready', () => console.log('Connection[Status]: Sucess✅ !\n-----------------------------------------------------\n<< [DEBUGGING SECTION [ACTIVE] >>')); + // Function to initiate connection const connectRedis = async () => { diff --git a/backend/modules/user/user.sign_in-up.js b/backend/modules/user/user.sign_in-up.js index 639cb0d..df7754c 100644 --- a/backend/modules/user/user.sign_in-up.js +++ b/backend/modules/user/user.sign_in-up.js @@ -47,7 +47,6 @@ exports.login = async (req, res) => { // Create the OTP and store it in the RedisClient with an expiration time of 1 mins const otp = generateOTP(); - console.log("<< OTP >>:", otp); // Log the generated OTP for debugging await redisClient.set(email, otp, 'EX', 60); // Store OTP with a 60-second expiration // Send the OTP to the user's email await SendOTPEmail(email, otp); From 077ad579cb42a30ff024cf03b29c2910b23e655f Mon Sep 17 00:00:00 2001 From: luther10293-a11y Date: Tue, 24 Feb 2026 14:41:21 +0100 Subject: [PATCH 8/8] Crypto-argon2 OTP enhancement --- .../Interface_Mailing/interface_otp.html | 110 ++++++++++++++++ backend/NodeMailer/Nodemail_connection.js | 27 ---- backend/NodeMailer/SendOTPEmail.js | 54 ++++++++ backend/NodeMailer/Transporter_Mail.js | 14 ++ backend/app.js | 1 + backend/config/setup_script.js | 4 + .../user/HASh-FOLD/hashingOTPService.js | 22 ++++ .../modules/user/HASh-FOLD/verifyHashOTP.js | 21 +++ backend/modules/user/OTP/OTP.script.js | 26 +++- backend/modules/user/user.controller.js | 1 + backend/modules/user/user.sign_in-up.js | 24 +++- backend/modules/user/user.verifyOTP.js | 27 ++-- backend/package-lock.json | 122 ++++++++++++++++++ backend/package.json | 4 +- 14 files changed, 408 insertions(+), 49 deletions(-) create mode 100644 backend/NodeMailer/Interface_Mailing/interface_otp.html delete mode 100644 backend/NodeMailer/Nodemail_connection.js create mode 100644 backend/NodeMailer/SendOTPEmail.js create mode 100644 backend/NodeMailer/Transporter_Mail.js create mode 100644 backend/modules/user/HASh-FOLD/hashingOTPService.js create mode 100644 backend/modules/user/HASh-FOLD/verifyHashOTP.js diff --git a/backend/NodeMailer/Interface_Mailing/interface_otp.html b/backend/NodeMailer/Interface_Mailing/interface_otp.html new file mode 100644 index 0000000..83e880e --- /dev/null +++ b/backend/NodeMailer/Interface_Mailing/interface_otp.html @@ -0,0 +1,110 @@ + + + + + + Nexasoft OTP Verification + + + + + + + +
+
+ +
+ +
+
+ Nexasoft +
+ +
+ Shield +
+ +

Verification Code

+

Enter this code to verify your identity

+
+ +
+
+ + + + + + + + + +
+
{{D1}}
+
+
{{D2}}
+
+
{{D3}}
+
+
{{D4}}
+
+
{{D5}}
+
+
{{D6}}
+
+
+ +
+ + + + + +

OTP Expires in 1 minute

+
+ +
+ + + + + +
+
+ ! +
+
+

Security Notice

+

Do not share this code with anyone. Nexasoft will never ask for your OTP via phone or email.

+
+
+
+ +
+

If you didn't request this code, please ignore this email or contact support.

+

+ © 2026 Nexasoft   •   Privacy Policy   •   Terms of Service +

+
+
+ +
+

Secured by Nexasoft Authentication

+
+
+ + \ No newline at end of file diff --git a/backend/NodeMailer/Nodemail_connection.js b/backend/NodeMailer/Nodemail_connection.js deleted file mode 100644 index c5dca47..0000000 --- a/backend/NodeMailer/Nodemail_connection.js +++ /dev/null @@ -1,27 +0,0 @@ -const nodemailer = require('nodemailer'); -require('dotenv').config();// Load environment variables from .env file - -const transporter = nodemailer.createTransport({ - service: 'gmail', - auth: { - user: process.env.APP_EMAIL, - pass: process.env.APP_PASSWORD // Use an app password for Gmail if 2FA is enabled - } -}); - -const SendOTPEmail = async (email, otp) => { - const mailOptions = { - from: `"NexaSoft System" <${transporter.options.auth.user}>`, - to: email, - subject: "Your Login OTP", - text: `Your OTP is: ${otp} . It will expire in 1 min .`, - html: `Your OTP is: ${otp}

It will expire in 1 min .

` - }; - console.log(`<< OTP: ${otp} - ${email} >>:`); // Log the email and OTP for debugging - - return transporter.sendMail(mailOptions); -}; - -module.exports = { SendOTPEmail }; - - diff --git a/backend/NodeMailer/SendOTPEmail.js b/backend/NodeMailer/SendOTPEmail.js new file mode 100644 index 0000000..7360b2b --- /dev/null +++ b/backend/NodeMailer/SendOTPEmail.js @@ -0,0 +1,54 @@ +const transporter = require('./Transporter_Mail'); +require('dotenv').config(); +const fs = require('fs'); +const path = require('path'); + +/** + * Sends the OTP using the interface_otp.html as a static template. + * Injects OTP digits while preserving original HTML/CSS structure. + */ +const SendOTPEmail = async (email, otp) => { + try { + // 1. Locate and Read the HTML template + // Using path.resolve ensures the path is absolute regardless of where the script is run + const templatePath = path.resolve(__dirname, './Interface_Mailing/interface_otp.html'); + + if (!fs.existsSync(templatePath)) { + throw new Error(`Template not found at: ${templatePath}`); + } + + let htmlContent = fs.readFileSync(templatePath, 'utf8'); + + // 2. Prepare the OTP string + const otpStr = otp.toString().padStart(6, '0'); // Ensures it's always 6 digits + + // 3. Inject digits into placeholders {{D1}} through {{D6}} + // This loop replaces each placeholder with the specific digit + for (let i = 0; i < 6; i++) { + const placeholder = `{{D${i + 1}}}`; + htmlContent = htmlContent.replace(placeholder, otpStr[i]); + } + + // 4. Configure Email Options + const mailOptions = { + from: `"NexaSoft System" <${process.env.APP_EMAIL}>`, + to: email, + subject: `NexaSoft verification code`, + // The 'html' field treats the string as a full static document + html: htmlContent, + // Added headers for better deliverability + priority: 'high', + }; + + console.log(`[Mail Service] OTP [${otpStr}] for: ${email}`); + + const info = await transporter.sendMail(mailOptions); + return info; + + } catch (error) { + console.error("Critical Error: Failed to send OTP email:", error.message); + throw error; + } +}; + +module.exports = { SendOTPEmail }; \ No newline at end of file diff --git a/backend/NodeMailer/Transporter_Mail.js b/backend/NodeMailer/Transporter_Mail.js new file mode 100644 index 0000000..296d22e --- /dev/null +++ b/backend/NodeMailer/Transporter_Mail.js @@ -0,0 +1,14 @@ +const nodemailer = require('nodemailer'); +require('dotenv').config();// Load environment variables from .env file + +const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: process.env.APP_EMAIL, + pass: process.env.APP_PASSWORD // Use an app password for Gmail if 2FA is enabled + } +}); + +module.exports = transporter; + + diff --git a/backend/app.js b/backend/app.js index e8a5a7f..f29b907 100644 --- a/backend/app.js +++ b/backend/app.js @@ -27,4 +27,5 @@ app.post('/test-redis', async (req, res) => { app.listen(Port, ()=>{ console.log(`Server is running on port ${Port}`); + }) \ No newline at end of file diff --git a/backend/config/setup_script.js b/backend/config/setup_script.js index fa9a05f..061ccf1 100644 --- a/backend/config/setup_script.js +++ b/backend/config/setup_script.js @@ -2,7 +2,11 @@ const fs= require('fs'); const path = require('path'); // 1. Force dotenv to look one folder up (in the backend root) + + require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); + + // 2. Add a quick sanity check to prove it worked console.log("🛠️ Attempting to connect on port:", process.env.DB_PORT); const db = require('./db'); // import the db connection pool diff --git a/backend/modules/user/HASh-FOLD/hashingOTPService.js b/backend/modules/user/HASh-FOLD/hashingOTPService.js new file mode 100644 index 0000000..27aff65 --- /dev/null +++ b/backend/modules/user/HASh-FOLD/hashingOTPService.js @@ -0,0 +1,22 @@ +const argon2 = require('argon2'); + +/** + * Hashes the OTP using Argon2id. + * Settings are tuned for high security (OWASP recommended). + */ +async function hashOTP(otp) { + try { + return await argon2.hash(otp, { + type: argon2.argon2id, // The most secure variant + hashLength: 16, // 32-byte hash output + memoryCost: 2 ** 16, // 64MB memory usage ---[ High memory consumption to slow down brute-force attacks ] + timeCost: 3, // 3 iterations + parallelism: 1 // Number of threads + }); + } catch (err) { + console.error('Error hashing OTP:', err); + throw new Error('Internal Security Error'); + } +} + +module.exports = { hashOTP}; \ No newline at end of file diff --git a/backend/modules/user/HASh-FOLD/verifyHashOTP.js b/backend/modules/user/HASh-FOLD/verifyHashOTP.js new file mode 100644 index 0000000..2fd8a30 --- /dev/null +++ b/backend/modules/user/HASh-FOLD/verifyHashOTP.js @@ -0,0 +1,21 @@ +const argon2 = require('argon2'); + + //Verifies the user's input against the hash stored in Redis. +async function OTPverify(storedHash, otp) { + try { + // argon2.verify handles extracting the salt from the hash automatically + return await argon2.verify(storedHash, otp); + } catch (err) { + console.error('Error verifying OTP:', err); + return false; + } +} + +module.exports = { OTPverify }; + + +// Explanation---------] + +// this Function is used to verify the OTP provided by the user against the hashed OTP stored in Redis. +// It uses argon2's verify function, which automatically extracts the salt from the stored hash and compares it with the provided OTP. +// If the verification is successful, it returns true; otherwise, it returns false. \ No newline at end of file diff --git a/backend/modules/user/OTP/OTP.script.js b/backend/modules/user/OTP/OTP.script.js index 38fb0e0..afd5cd3 100644 --- a/backend/modules/user/OTP/OTP.script.js +++ b/backend/modules/user/OTP/OTP.script.js @@ -1,10 +1,24 @@ -const generateOTP = () => { - const length= 6; - const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Excluded similar looking chars like O, 0, I, 1 - let result = ''; +const {randomBytes}= require ('node:crypto');// using crypto module to generate random bytes for OTP generation + const generateOTP = ( ) => { + const length = 6; + const charset= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let result= ''; + const bytes= randomBytes(length); + for (let i = 0; i < length; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); + result += charset[bytes[i] % charset.length]; } return result; } -module.exports = { generateOTP }; \ No newline at end of file +module.exports = { generateOTP }; + + + +// * Why use Crypto - randomBytes over Math.random() for OTP generation? + +// 1. Security: randomBytes provides cryptographically secure random values, making it suitable for generating OTPs that need to be resistant to prediction and attacks. +// Math.random() is not designed for security and can be predictable, which could lead to vulnerabilities in OTP generation. +// 2. Uniqueness: randomBytes generates unique values each time, +// while Math.random() may produce the same sequence of numbers if the same seed is used, increasing the risk of OTP collisions. +// 3. Compliance: For applications that require compliance with security standards (e.g., PCI DSS), using a secure method like randomBytes +// is essential to meet the requirements for generating secure OTPs. \ No newline at end of file diff --git a/backend/modules/user/user.controller.js b/backend/modules/user/user.controller.js index 0cc961a..3659c22 100644 --- a/backend/modules/user/user.controller.js +++ b/backend/modules/user/user.controller.js @@ -4,3 +4,4 @@ exports.getUsers = async (req, res) => { const users = await userService.getUsers(); res.status(200).json(users); }; + diff --git a/backend/modules/user/user.sign_in-up.js b/backend/modules/user/user.sign_in-up.js index df7754c..f4eba84 100644 --- a/backend/modules/user/user.sign_in-up.js +++ b/backend/modules/user/user.sign_in-up.js @@ -1,7 +1,8 @@ const bcrypt = require('bcrypt'); const pool = require('../../config/db'); const {generateOTP}= require('../../modules/user/OTP/OTP.script'); -const {SendOTPEmail} = require('../../NodeMailer/Nodemail_connection'); +const {hashOTP} = require('../user/HASh-FOLD/hashingOTPService'); +const {SendOTPEmail} = require('../../NodeMailer/SendOTPEmail'); const redisClient = require('../../Redis_config/Redis_setup'); // Sign-up endpoint to handle user registration @@ -45,10 +46,25 @@ exports.login = async (req, res) => { res.status(200).json({ message: 'Login successful', user: user.rows[0] }); - // Create the OTP and store it in the RedisClient with an expiration time of 1 mins + + //--------------- OTP HASHING STATION ------------------------------// + + // Create the 6-character OTP const otp = generateOTP(); - await redisClient.set(email, otp, 'EX', 60); // Store OTP with a 60-second expiration - // Send the OTP to the user's email + + // hash OTP before sending to Redis + const hashedOTP = await hashOTP(otp); + + // Store the hash OTP in Redis + await redisClient.set(email, hashedOTP, 'EX', 60); // Store OTP with a 60-second expiration + + //delete the OTP after expiration ( to not use of space in the Redis DB) + setTimeout(async () => { + await redisClient.del(email); + }, 60000); // Delete OTP after 60 seconds (1 minute) + + + // Send the original OTP to the user's email await SendOTPEmail(email, otp); } catch (error) { diff --git a/backend/modules/user/user.verifyOTP.js b/backend/modules/user/user.verifyOTP.js index bdb9225..74b080b 100644 --- a/backend/modules/user/user.verifyOTP.js +++ b/backend/modules/user/user.verifyOTP.js @@ -1,25 +1,30 @@ const redisClient = require('../../Redis_config/Redis_setup'); +const {OTPverify} = require('../user/HASh-FOLD/verifyHashOTP'); exports.verifyOTP = async (req, res) => { try { const { email, otp } = req.body; - // Retrieve the OTP from Redis - const storedOTP = await redisClient.get(email); - if (!storedOTP) { - return res.status(410).json({ message: 'OTP has expired or does not exist' }); + // Retrieve the hashed OTP from Redis + const storedHash = await redisClient.get(email); + if (!storedHash) { + + // delete the OTP from Redis after expiration or invalidation + await redisClient.del(email); + return res.status(410).json({ message: 'OTP has expired or is invalid' }); + } - // Compare the provided OTP with the stored OTP - if (storedOTP !== otp) { + // Verify the provided OTP against the stored hash + const isOTPValid = await OTPverify(storedHash, otp); + if (!isOTPValid) { return res.status(400).json({ message: 'Invalid OTP' }); } - // Proceed for valid OTP - res.status(200).json({ message: 'OTP verified successfully' }); - - // Optionally, you can delete the OTP from Redis after successful verification + + // Delete the OTP from Redis after Verification await redisClient.del(email); + res.status(200).json({ message: 'OTP verified successfully ✅ ' }); } catch (error) { console.error('Error verifying OTP:', error); res.status(500).json({ message: 'Internal server error' }); - } + } }; \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index fb75989..63c33bd 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,7 +9,9 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "argon2": "^0.44.0", "bcrypt": "^6.0.0", + "crypto": "^1.0.1", "dotenv": "^17.3.1", "express": "^5.2.1", "fs": "^0.0.1-security", @@ -20,6 +22,21 @@ "redis": "^5.10.0" } }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "license": "MIT" + }, + "node_modules/@phc/format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", + "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/@redis/bloom": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-5.10.0.tgz", @@ -106,6 +123,22 @@ "node": ">= 8" } }, + "node_modules/argon2": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.44.0.tgz", + "integrity": "sha512-zHPGN3S55sihSQo0dBbK0A5qpi2R31z7HZDZnry3ifOyj8bZZnpZND2gpmhnRGO1V/d555RwBqIK5W4Mrmv3ig==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@phc/format": "^1.0.0", + "cross-env": "^10.0.0", + "node-addon-api": "^8.5.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/balanced-match": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", @@ -315,6 +348,44 @@ "node": ">=6.6.0" } }, + "node_modules/cross-env": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz", + "integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==", + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", + "license": "ISC" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -849,6 +920,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1062,6 +1139,15 @@ "node": ">= 0.8" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-to-regexp": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", @@ -1407,6 +1493,27 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -1589,6 +1696,21 @@ "node": ">= 0.8" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/backend/package.json b/backend/package.json index df8715f..d7c04e0 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,9 +9,11 @@ "keywords": [], "author": "", "license": "ISC", - "type": "commonjs", + "type": "CommonJS", "dependencies": { + "argon2": "^0.44.0", "bcrypt": "^6.0.0", + "crypto": "^1.0.1", "dotenv": "^17.3.1", "express": "^5.2.1", "fs": "^0.0.1-security",