Skip to content

Commit

Permalink
Merge pull request #56 from StefanPenchev05/controllers
Browse files Browse the repository at this point in the history
Controllers
  • Loading branch information
StefanPenchev05 authored Mar 13, 2024
2 parents a8a7588 + ea6a071 commit a38c6c3
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 32 deletions.
5 changes: 3 additions & 2 deletions server/.env
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ MONGO_URL = "mongodb://localhost:27017/MyClothes"
NODEMAILER_EMAIL = "myclothes238@gmail.com"
NODEMAILER_PASSWORD = "ysqxipdihtmarjih"

JWT_SECRET = "secrateCodeForVerify"
SESSION_SECTER = "supersecrateCodeSession"
JWT_SECRET = "821b95cd8de25ea75f784d84736beb55b9d711b4f81bebdef0d968f3b90e7e08"
SESSION_SECTER = "a4d8cbe93e5b355d72f5ff785c3518d6e3a37d1991a3bffb0fe9518053e80d33"
KEY_PASSPHRASE="Preface44flemish65down"
ENCRYPTION_SECRET="5155211761eae6361b4196226e62955b749c12a3599b2354ce05f945dca62032"

32 changes: 30 additions & 2 deletions server/controllers/auth/loginController.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import bcrypt from "bcrypt";

import { User } from "../../models/User.js";
import { Settings } from "../../models/Settings.js";
import bcrypt from "bcrypt";
import { encrypt } from "../../utils/securityUtils.js";

/**
*
* @param {Request} req
* @param {Response} res
*/

export default async function LoginController(req,res){
const { usernameOrEmail, password, rememberMe } = req.body;

Expand Down Expand Up @@ -48,6 +49,33 @@ export default async function LoginController(req,res){
// Fetch the user's settings
const userSettings = await Settings.findOne({userId : user._id});

// Check if user settings exist
if(!userSettings){
// If not, throw an error
throw new Error();
}

// Get the two-factor authentication settings
const twoFactorAuth = userSettings.security.twoFactorAuth;

// Check if two-factor authentication is enabled
if(twoFactorAuth.enabled){

// Encrypt the user's ID
const encryptedUserId = encrypt(user._id);

// If two-factor authentication is required, return a response indicating that
// an email with a code has been sent and the user can enter it
return res.status(200).json({
// Indicate that two-factor authentication is required and make the client to await and join into socket room
twoFactorAuth: true,
// Provide a message to the user
message : "We have sent an email with a code, you can enter it here",
// Include the encrypted user ID in the response in order to connect to the socket room
encryptedUserId
});
}

// Set the user's ID in the session. This will persist across requests
// as long as the session is active. You can use this to check if the user is logged in.
req.session.user = user._id;
Expand Down
7 changes: 6 additions & 1 deletion server/controllers/auth/registerController.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,12 @@ export default async function registerController(req, res) {
});

// If everything is successful, return a 200 status code and a success message
return res.status(200).json({message: "Waiting for you to verify with link sended to your email"})
// and tells the client to await for veriification sent via email
// and the uuid is what room to join the socket
return res.status(200).json({
awaitForVerify: true,
room: uuid,
message: "Waiting for you to verify with link sended to your email"});

} catch (err) {
// If the username is already taken, suggest a new one by calling the generateUniqueUsername function
Expand Down
2 changes: 0 additions & 2 deletions server/controllers/auth/verifyUser.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ export default async function verifyUser(req, res) {
return res.status(401).json({ message: "Invalid token" });
}

console.log(decoded);

let tempUserData;
try{
// Find the temporary user data associated with the token
Expand Down
9 changes: 9 additions & 0 deletions server/emailTemplates/styles/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ['./src/emailTemplates/**/*.html'],
theme: {
extend: {},
},
plugins: [],
}

6 changes: 6 additions & 0 deletions server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 8 additions & 4 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,19 @@
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"crypto-js": "^4.2.0",
"ejs": "^3.1.9",
"express": "^4.18.3",
"express-rate-limit": "^7.2.0",
"express-session": "^1.18.0",
"helmet": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"juice": "^10.0.0",
"lusca": "^1.7.0",
"mongoose": "^8.2.0",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.11",
"socket.io": "^4.7.4",
"tailwindcss": "^3.4.1",
"useragent": "^2.3.0",
"uuid": "^9.0.1"
},
Expand All @@ -37,6 +38,9 @@
"babel-jest": "^29.7.0",
"jest": "^29.7.0",
"nodemon": "^3.1.0",
"supertest": "^6.3.4"
"supertest": "^6.3.4",
"nodemailer": "^6.9.11",
"morgan": "^1.10.0",
"dotenv": "^16.4.5"
}
}
8 changes: 7 additions & 1 deletion server/sockets/socketsNamespaces/authNamespace.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Namespace } from "socket.io";
import { decrypt } from "../../utils/securityUtils.js";

/**
* Initializes the /auth namespace.
Expand All @@ -8,8 +9,13 @@ export default function initializeAuthNamespace(namespace) {
// Listen for new connections to the namespace
namespace.on("connection", (socket) => {
// When a 'registerUserId' event is received, join the socket to a room with the same name as the user ID
socket.on("registerUserId", (userId) => {
socket.on("/register/verify", (userId) => {
socket.join(userId);
});

socket.on("login/2FA/", (encryptedUserId) => {
const decryptedUserId = decrypt(encryptedUserId);
socket.join(decryptedUserId);
})
});
}
43 changes: 23 additions & 20 deletions server/utils/emailService.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
import nodemailer from "nodemailer"

/**
* This fucntion sends an email for verifying when registration
* This function sends an email using the provided parameters.
* It's designed to be reusable, so you can use it to send different types of emails.
*
* @param {string} email
* @param {string} token
* @param {string} email - The recipient's email address.
* @param {string} subject - The subject line of the email.
* @param {string} html - The HTML body of the email.
*
* @returns {void}
*/

export default function sendVerifyMail(email, token, username){
export default function sendMail(email, subject, html){
try {
// Define email options, including sender, recipient, subject, and message body
// Define the options for the email, including the sender's email address,
// the recipient's email address, the subject line, and the HTML body.
const mailOptions = {
from: process.env.NODEMAILER_EMAIL,
to: email,
subject: `Verify Email for username: ${username}`,
html: `
<div style="max-width: 600px; margin: auto; padding: 20px; border: 1px solid #ccc; font-family: Arial, sans-serif;">
<h2 style="text-align: center; color: #333; font-size: 24px;">Welcome, ${username}!</h2>
<p style="color: #555; font-size: 16px;">Please verify your email address by clicking the link below:</p>
<a href="https://localhost:3000/auth/verify/${token}" style="display: inline-block; margin-top: 20px; padding: 10px 20px; color: #fff; background-color: #007bff; text-decoration: none; border-radius: 5px;">Verify Email</a>
</div>
`,
from: process.env.NODEMAILER_EMAIL, // Sender's email address
to: email, // Recipient's email address
subject, // Subject line
html, // HTML body
};

// Create a transporter for sending emails using nodemailer
// Create a transporter for sending emails. This uses the 'gmail' service,
// but you can replace this with any email service that is supported by nodemailer.
// The authentication for the email service is provided by environment variables.
const transporter = nodemailer.createTransport({
service: 'gmail', // Email service provider
service: 'gmail', // Email service provider
auth: {
user: process.env.NODEMAILER_EMAIL, // Sender's email address
user: process.env.NODEMAILER_EMAIL, // Sender's email address
pass: process.env.NODEMAILER_PASSWORD // Sender's email password
}
});

// Send the email using the configured transporter
// Use the transporter to send the email with the defined options.
// If there's an error during sending, it will throw an error.
// Otherwise, it will return the response from the email service.
transporter.sendMail(mailOptions, (err, info) => {
if (err) {
throw new Error("Error during sending email");
Expand All @@ -41,6 +43,7 @@ export default function sendVerifyMail(email, token, username){
return info.response;
});
} catch (err) {
// If there's an error at any point in the function, it will be thrown so it can be handled by the caller.
throw err;
}
}
53 changes: 53 additions & 0 deletions server/utils/emailTemplateManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import fs from "fs"
import ejs from "ejs"
import path from "path"
import juice from "juice"
import tailwindcss from "tailwindcss"

/**
* Load and convert an HTML template.
* @param {string} templateName - The name of the template to load. This should be the name of an HTML file in the emailTemplates directory, without the .html extension.
* @param {Object} data - The data to insert into the template. This should be an object where the keys are the names of placeholders in the template and the values are the values to replace the placeholders with.
* @returns {Promise<string>} The converted HTML template.
*/
export function convertTemplate(templateName, data){
return new Promise((resolve, reject) => {
// Define the paths for the template and its settings
const templatePath = path.resolve(__dirname, `../emailTemplates/${templateName}/${templateName}.html`);
const settingsPath = path.resolve(__dirname, `../emailTemplates/${templateName}/settings.json`);

// Read settings.json
fs.readFile(settingsPath, 'utf-8', (err, settings) => {
if(err){
// If an error occurred while reading the file, reject the promise
reject(err);
return;
}

// Parse the settings from JSON to a JavaScript object
const settings = JSON.parse(settings);
// Get the style compilator from the settings
const styleCompilator = settings.styleCompilator;

// Render the template with the provided data
ejs.renderFile(templatePath, data, {}, (err, str) => {
if(err){
// If an error occurred while rendering the template, reject the promise
reject(err);
}else{
// If the style compilator is Tailwind CSS, apply Tailwind CSS styles
if(styleCompilator === 'tailwind'){
const css = tailwindcss(path.resolve(__dirname, "../emailTemplates/style/tailwind.config.js"));
const inlineHtml = juice.inlineContent(str,css);
resolve(inlineHtml);
}else{
// Otherwise, apply the styles from the style.css file in the template's directory
const css = path.resolve(__dirname, `../emailTemplates/${templateName}/style.css`);
const inlineHtml = juice.inlineContent(str,css);
resolve(inlineHtml);
}
}
});
})
})
}
25 changes: 25 additions & 0 deletions server/utils/securityUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import CryptoJS from "crypto-js";

/**
* Encrypt a string using AES encryption.
* @param {string} text - The text to encrypt. This could be any string that you want to encrypt, such as a user ID.
* @param {string} secretKey - The secret key for encryption. This should be a string that you use as the key for AES encryption. If no key is provided, the function will use the ENCRYPTION_SECRET environment variable as the key.
* @returns {string} The encrypted text. This is the original text after it has been encrypted using AES encryption.
*/
export function encrypt(text, secretKey = process.env.ENCRYPTION_SECRET) {
// Use the AES encryption method from the CryptoJS library to encrypt the text
return CryptoJS.AES.encrypt(text, secretKey).toString();
}

/**
* Decrypt a string using AES decryption.
* @param {string} text - A string to decrypt. This is expected to be a string that was previously encrypted using AES encryption.
* @param {string} secretKey - The secret key for decryption. This is expected to be a string that was used as the key during AES encryption. If no key is provided, the function will use the ENCRYPTION_SECRET environment variable as the key.
* @returns {string} - The decrypted userId. This is the original userId before it was encrypted.
*/
export function decrypt(text, secretKey = process.env.ENCRYPTION_SECRET){
// Use the AES decryption method from the CryptoJS library to decrypt the userId
const bytes = CryptoJS.AES.decrypt(text, secretKey);
// Convert the decrypted bytes back into a string and return it
return bytes.toString(CryptoJS.enc.Utf8);
}

0 comments on commit a38c6c3

Please sign in to comment.