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 +
+ +
+ 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/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