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..25d822f 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,27 @@
-# NexaSoft
\ No newline at end of file
+# NexaSoft
+
+# install the following Packages
+- `npm `:
+
+* redis
+* 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
+-
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
+
+
+
+ 
+
+
+ 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/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/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..9668177
--- /dev/null
+++ b/backend/Redis_config/Redis_setup.js
@@ -0,0 +1,34 @@
+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: process.env.REDIS_URI,
+ 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('Connection[Status]: Sucess✅ !\n-----------------------------------------------------\n<< [DEBUGGING SECTION [ACTIVE] >>'));
+
+
+// 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..f29b907 100644
--- a/backend/app.js
+++ b/backend/app.js
@@ -1,20 +1,31 @@
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 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');
+ console.log(`Connected to the database to : ${process.env.DB_NAME} `);
}).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}`);
+
})
\ No newline at end of file
diff --git a/backend/config/db.js b/backend/config/db.js
index 0e9e4ec..b71a15e 100644
--- a/backend/config/db.js
+++ b/backend/config/db.js
@@ -1,12 +1,12 @@
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,
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..061ccf1
--- /dev/null
+++ b/backend/config/setup_script.js
@@ -0,0 +1,30 @@
+// 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/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
new file mode 100644
index 0000000..afd5cd3
--- /dev/null
+++ b/backend/modules/user/OTP/OTP.script.js
@@ -0,0 +1,24 @@
+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 += charset[bytes[i] % charset.length];
+ }
+ return result;
+}
+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 41b960f..3659c22 100644
--- a/backend/modules/user/user.controller.js
+++ b/backend/modules/user/user.controller.js
@@ -1,6 +1,7 @@
-const userService = require('./user.service');
+const userService = require('../user/user.services');
exports.getUsers = async (req, res) => {
const users = await userService.getUsers();
res.status(200).json(users);
};
+
diff --git a/backend/modules/user/user.routes.js b/backend/modules/user/user.routes.js
index 8f01706..89cab24 100644
--- a/backend/modules/user/user.routes.js
+++ b/backend/modules/user/user.routes.js
@@ -1,7 +1,12 @@
const express = require('express')
-const router = express.Rounter()
+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..f4eba84
--- /dev/null
+++ b/backend/modules/user/user.sign_in-up.js
@@ -0,0 +1,74 @@
+const bcrypt = require('bcrypt');
+const pool = require('../../config/db');
+const {generateOTP}= require('../../modules/user/OTP/OTP.script');
+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
+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] });
+
+
+ //--------------- OTP HASHING STATION ------------------------------//
+
+ // Create the 6-character OTP
+ const otp = generateOTP();
+
+ // 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) {
+ console.error('Error logging in:', error);
+ res.status(500).json({ message: 'Internal server error' });
+ }
+};
\ 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..74b080b
--- /dev/null
+++ b/backend/modules/user/user.verifyOTP.js
@@ -0,0 +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 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' });
+
+ }
+ // Verify the provided OTP against the stored hash
+ const isOTPValid = await OTPverify(storedHash, otp);
+ if (!isOTPValid) {
+ return res.status(400).json({ message: 'Invalid OTP' });
+ }
+
+ // 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 2098b5a..63c33bd 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -9,12 +9,92 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
+ "argon2": "^0.44.0",
"bcrypt": "^6.0.0",
- "dotenv": "^17.2.4",
+ "crypto": "^1.0.1",
+ "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",
- "pg": "^8.18.0"
+ "pg": "^8.18.0",
+ "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",
+ "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": {
@@ -43,11 +123,30 @@
"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": "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",
@@ -100,13 +199,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": {
@@ -198,11 +299,14 @@
"fsevents": "~2.3.2"
}
},
- "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/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/content-disposition": {
"version": "1.0.1",
@@ -244,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",
@@ -311,9 +453,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"
@@ -490,6 +632,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",
@@ -772,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",
@@ -828,15 +982,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": {
@@ -874,16 +1031,25 @@
"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",
- "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",
@@ -973,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",
@@ -1155,9 +1330,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"
@@ -1205,6 +1380,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",
@@ -1302,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",
@@ -1484,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 53a617b..d7c04e0 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -9,13 +9,18 @@
"keywords": [],
"author": "",
"license": "ISC",
- "type": "commonjs",
+ "type": "CommonJS",
"dependencies": {
+ "argon2": "^0.44.0",
"bcrypt": "^6.0.0",
- "dotenv": "^17.2.4",
+ "crypto": "^1.0.1",
+ "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",
- "pg": "^8.18.0"
+ "pg": "^8.18.0",
+ "redis": "^5.10.0"
}
}
diff --git a/package.json b/package.json
index 4eb6d0f..b50e28b 100644
--- a/package.json
+++ b/package.json
@@ -18,4 +18,4 @@
"url": "https://github.com/Lonewolf-master/NexaSoft/issues"
},
"homepage": "https://github.com/Lonewolf-master/NexaSoft#readme"
-}
+}
\ No newline at end of file