Browse Source

Added email verification system

features/playlist
Astri4-4 4 months ago
parent
commit
b9c13112bd
  1. 115
      backend/app/controllers/user.controller.js
  2. 6
      backend/app/routes/user.route.js
  3. 12
      backend/app/utils/database.js
  4. 30
      backend/app/utils/mail.js
  5. 93
      backend/logs/access.log
  6. 10
      backend/package-lock.json
  7. 1
      backend/package.json

115
backend/app/controllers/user.controller.js

@ -4,6 +4,8 @@ import jwt from "jsonwebtoken";
import path, {dirname} from "path";
import fs from "fs";
import {fileURLToPath} from "url";
import crypto from "crypto";
import {sendEmail} from "../utils/mail.js";
export async function register(req, res) {
try {
@ -43,6 +45,84 @@ export async function register(req, res) {
const playlistQuery = `INSERT INTO playlists (name, owner) VALUES ('A regarder plus tard', $1)`;
await client.query(playlistQuery, [id]);
// GENERATE EMAIL HEXA VERIFICATION TOKEN
const token = crypto.randomBytes(32).toString("hex").slice(0, 5);
const textMessage = "Merci de vous être inscrit. Veuillez vérifier votre e-mail. Code: " + token;
const htmlMessage = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bienvenue sur Freetube</title>
</head>
<body style="margin: 0; padding: 0; font-family: Arial, sans-serif; background-color: #f4f4f4;">
<div style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
<!-- Header -->
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 40px 20px; text-align: center;">
<h1 style="color: #ffffff; margin: 0; font-size: 28px; font-weight: bold;">
🎬 Bienvenue sur Freetube!
</h1>
</div>
<!-- Content -->
<div style="padding: 40px 30px;">
<h2 style="color: #333333; margin-top: 0; font-size: 24px;">
Bonjour ${user.username}! 👋
</h2>
<p style="color: #666666; font-size: 16px; line-height: 1.6; margin: 20px 0;">
Merci de vous être inscrit sur <strong>Freetube</strong>! Nous sommes ravis de vous accueillir dans notre communauté.
</p>
<p style="color: #666666; font-size: 16px; line-height: 1.6; margin: 20px 0;">
Pour finaliser votre inscription, veuillez utiliser le code de vérification ci-dessous :
</p>
<!-- Verification Code Box -->
<div style="background-color: #f8f9fa; border: 2px dashed #667eea; border-radius: 8px; padding: 25px; text-align: center; margin: 30px 0;">
<p style="color: #333333; font-size: 14px; margin: 0 0 10px 0; text-transform: uppercase; letter-spacing: 1px;">
Code de vérification
</p>
<div style="background-color: #667eea; color: #ffffff; font-size: 32px; font-weight: bold; padding: 15px 25px; border-radius: 6px; letter-spacing: 3px; display: inline-block;">
${token}
</div>
</div>
<p style="color: #999999; font-size: 14px; line-height: 1.5; margin: 30px 0 10px 0;">
Ce code expirera dans <strong>1 heure</strong>.
</p>
<p style="color: #999999; font-size: 14px; line-height: 1.5; margin: 10px 0;">
Si vous n'avez pas créé de compte, vous pouvez ignorer cet e-mail.
</p>
</div>
<!-- Footer -->
<div style="background-color: #f8f9fa; padding: 20px 30px; border-top: 1px solid #eee;">
<p style="color: #999999; font-size: 12px; margin: 0; text-align: center;">
© 2025 Freetube. Tous droits réservés.
</p>
</div>
</div>
</body>
</html>
`;
console.log("Sending email to:", user.email);
await sendEmail(user.email, "🎬 Bienvenue sur Freetube - Vérifiez votre email", textMessage, htmlMessage);
// Store the token in the database
const expirationDate = new Date();
expirationDate.setHours(expirationDate.getHours() + 1); // Token expires in 1 hour
const insertQuery = `INSERT INTO email_verification (email, token, expires_at) VALUES ($1, $2, $3)`;
await client.query(insertQuery, [user.email, token, expirationDate]);
client.end();
console.log("Successfully registered");
client.end();
logger.write("successfully registered", 200);
@ -51,7 +131,42 @@ export async function register(req, res) {
console.log(err);
}
}
export async function verifyEmail(req, res) {
const { email, token } = req.body;
const logger = req.body.logger;
logger.action("try to verify email for " + email + " with token " + token);
const client = await getClient();
try {
const query = `SELECT * FROM email_verification WHERE email = $1 AND token = $2`;
const result = await client.query(query, [email, token]);
if (result.rows.length === 0) {
logger.write("failed to verify email for " + email, 404);
return res.status(404).json({ error: "Invalid token or email" });
}
// If we reach this point, the email is verified
const queryDelete = `DELETE FROM email_verification WHERE email = $1`;
await client.query(queryDelete, [email]);
const updateQuery = `UPDATE users SET is_verified = TRUE WHERE email = $1`;
await client.query(updateQuery, [email]);
logger.write("successfully verified email for " + email, 200);
res.status(200).json({ message: "Email verified successfully" });
} catch (error) {
console.error("Error verifying email:", error);
logger.write("failed to verify email for " + email, 500);
res.status(500).json({ error: "Internal server error" });
} finally {
client.end();
}
}
export async function login(req, res) {

6
backend/app/routes/user.route.js

@ -7,7 +7,8 @@ import {
update,
deleteUser,
getChannel, getHistory,
isSubscribed
isSubscribed,
verifyEmail
} from "../controllers/user.controller.js";
import {
UserRegister,
@ -54,4 +55,7 @@ router.get("/:id/history", [addLogger, isTokenValid, User.id, validator], getHis
// CHECK IF SUBSCRIBED TO CHANNEL
router.get("/:id/channel/subscribed", [addLogger, isTokenValid, User.id, Channel.id, validator], isSubscribed)
// VERIFY EMAIL
router.post("/verify-email", [addLogger, validator], verifyEmail);
export default router;

12
backend/app/utils/database.js

@ -23,7 +23,8 @@ export async function initDb() {
email VARCHAR(255) NOT NULL,
username VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
picture VARCHAR(255)
picture VARCHAR(255),
is_verified BOOLEAN NOT NULL DEFAULT FALSE
);`;
await client.query(query);
@ -109,6 +110,15 @@ export async function initDb() {
)`;
await client.query(query);
query = `CREATE TABLE IF NOT EXISTS email_verification (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
token VARCHAR(255) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL
)`;
await client.query(query);
} catch (e) {
console.error("Error initializing database:", e);
}

30
backend/app/utils/mail.js

@ -0,0 +1,30 @@
import nodemailer from "nodemailer";
function getTransporter() {
return nodemailer.createTransport({
host: "smtp.gmail.com",
port: 587,
secure: false,
auth: {
user: process.env.GMAIL_USER,
pass: "yuuu kvoi ytrf blla",
},
});
};
export function sendEmail(to, subject, text, html = null) {
const transporter = getTransporter();
const mailOptions = {
from: process.env.GMAIL_USER,
to,
subject,
text,
};
// Add HTML if provided
if (html) {
mailOptions.html = html;
}
return transporter.sendMail(mailOptions);
}

93
backend/logs/access.log

@ -7334,3 +7334,96 @@
[2025-08-16 10:33:22.727] [undefined] GET(/:id/views): successfully added views for video 2 with status 200
[2025-08-16 16:10:03.689] [undefined] POST(/login): try to login with username 'astria'
[2025-08-16 16:10:03.742] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-17 08:50:14.128] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 08:50:14.181] [undefined] POST(/): successfully registered with status 200
[2025-08-17 08:50:14.195] [undefined] POST(/login): try to login with username 'astria'
[2025-08-17 08:50:14.248] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-17 08:51:42.597] [undefined] POST(/): try to register a user with username: test1 and email: thelolshow974@gmail.com
[2025-08-17 09:11:03.106] [undefined] POST(/): try to register a user with username: test1 and email: sachaguerin.sg@gmail.com
[2025-08-17 09:11:39.729] [undefined] POST(/): failed because email already exists with status 400
[2025-08-17 09:11:59.416] [undefined] POST(/): try to register a user with username: test2 and email: thelolshow974@gmail.com
[2025-08-17 09:14:31.308] [undefined] POST(/): try to register a user with username: test2 and email: thelolshow974@gmail.com
[2025-08-17 09:16:05.463] [undefined] POST(/): try to register a user with username: test1 and email: sachaguerin.sg@gmail.com
[2025-08-17 09:16:10.314] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:16:10.328] [undefined] POST(/login): try to login with username 'test1'
[2025-08-17 09:16:10.380] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-17 09:17:17.542] [undefined] POST(/): try to register a user with username: test1 and email: sachaguerin.sg@gmail.com
[2025-08-17 09:17:22.394] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:17:22.406] [undefined] POST(/login): try to login with username 'test1'
[2025-08-17 09:17:22.457] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-17 09:19:43.340] [undefined] GET(/:id/channel): try to retrieve channel of user 1
[2025-08-17 09:19:43.345] [undefined] GET(/:id/channel): failed to retrieve channel of user 1 because it doesn't exist with status 404
[2025-08-17 09:19:43.350] [undefined] GET(/:id/history): try to retrieve history of user 1
[2025-08-17 09:19:43.354] [undefined] GET(/:id/history): failed to retrieve history of user 1 because it doesn't exist with status 404
[2025-08-17 09:19:43.363] [undefined] GET(/user/:id): Playlists retrieved for user with id 1 with status 200
[2025-08-17 09:20:05.669] [undefined] POST(/): try to register a user with username: test2 and email: thelolshow974@gmail.com
[2025-08-17 09:20:10.512] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:20:10.525] [undefined] POST(/login): try to login with username 'test2'
[2025-08-17 09:20:10.576] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-17 09:21:15.604] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-17 09:21:15.608] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-17 09:21:15.611] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-08-17 09:21:15.615] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-08-17 09:21:15.627] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-17 09:25:52.056] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200
[2025-08-17 09:30:07.808] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:30:13.318] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:30:13.333] [undefined] POST(/login): try to login with username 'astria'
[2025-08-17 09:30:13.342] [undefined] POST(/login): failed to login with status 401
[2025-08-17 09:31:15.336] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:31:16.156] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:31:16.165] [undefined] POST(/login): try to login with username 'astria'
[2025-08-17 09:31:16.175] [undefined] POST(/login): failed to login with status 401
[2025-08-17 09:32:08.935] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:32:09.739] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:32:09.746] [undefined] POST(/login): try to login with username 'astria'
[2025-08-17 09:32:09.755] [undefined] POST(/login): failed to login with status 401
[2025-08-17 09:33:37.507] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:33:38.302] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:35:22.955] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:35:27.758] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:36:56.856] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:36:57.653] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:40:35.290] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:40:40.072] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:43:01.395] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:43:02.183] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:43:14.728] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com
[2025-08-17 09:43:14.737] [undefined] POST(/verify-email): failed to verify email for sachaguerin.sg@gmail.com with status 404
[2025-08-17 09:43:23.307] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com
[2025-08-17 09:43:23.316] [undefined] POST(/verify-email): failed to verify email for sachaguerin.sg@gmail.com with status 404
[2025-08-17 09:43:51.630] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com
[2025-08-17 09:43:51.639] [undefined] POST(/verify-email): failed to verify email for sachaguerin.sg@gmail.com with status 404
[2025-08-17 09:46:34.186] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com with token undefined
[2025-08-17 09:46:34.196] [undefined] POST(/verify-email): failed to verify email for sachaguerin.sg@gmail.com with status 404
[2025-08-17 09:47:04.196] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:47:09.664] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:48:48.612] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:48:50.099] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:49:00.678] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com with token undefined
[2025-08-17 09:49:00.687] [undefined] POST(/verify-email): failed to verify email for sachaguerin.sg@gmail.com with status 404
[2025-08-17 09:50:09.224] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:50:10.001] [undefined] POST(/): successfully registered with status 200
[2025-08-17 09:50:22.079] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com with token undefined
[2025-08-17 09:50:22.089] [undefined] POST(/verify-email): failed to verify email for sachaguerin.sg@gmail.com with status 404
[2025-08-17 09:59:51.783] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 09:59:56.691] [undefined] POST(/): successfully registered with status 200
[2025-08-17 10:00:23.412] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com with token 08185
[2025-08-17 10:00:23.422] [undefined] POST(/verify-email): successfully verified email for sachaguerin.sg@gmail.com with status 200
[2025-08-17 10:03:07.906] [undefined] POST(/): try to register a user with username: astria and email: sachaguerin.sg@gmail.com
[2025-08-17 10:03:12.799] [undefined] POST(/): successfully registered with status 200
[2025-08-17 10:03:28.573] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com with token da3cb
[2025-08-17 10:03:28.585] [undefined] POST(/verify-email): failed to verify email for sachaguerin.sg@gmail.com with status 500
[2025-08-17 10:04:05.961] [undefined] POST(/verify-email): try to verify email for sachaguerin.sg@gmail.com with token da3cb
[2025-08-17 10:04:05.972] [undefined] POST(/verify-email): failed to verify email for sachaguerin.sg@gmail.com with status 404
[2025-08-17 10:05:00.597] [undefined] POST(/): try to register a user with username: test1 and email: thelolshow974@gmail.com
[2025-08-17 10:05:05.427] [undefined] POST(/): successfully registered with status 200
[2025-08-17 10:05:18.158] [undefined] POST(/verify-email): try to verify email for thelolshow974@gmail.com with token 6e57b
[2025-08-17 10:05:18.170] [undefined] POST(/verify-email): successfully verified email for thelolshow974@gmail.com with status 200
[2025-08-17 10:06:16.785] [undefined] POST(/login): try to login with username 'test1'
[2025-08-17 10:06:16.839] [undefined] POST(/login): Successfully logged in with status 200
[2025-08-17 10:06:21.123] [undefined] GET(/:id/channel): try to retrieve channel of user 2
[2025-08-17 10:06:21.127] [undefined] GET(/:id/channel): failed to retrieve channel of user 2 because it doesn't exist with status 404
[2025-08-17 10:06:21.133] [undefined] GET(/:id/history): try to retrieve history of user 2
[2025-08-17 10:06:21.138] [undefined] GET(/:id/history): failed to retrieve history of user 2 because it doesn't exist with status 404
[2025-08-17 10:06:21.148] [undefined] GET(/user/:id): Playlists retrieved for user with id 2 with status 200

10
backend/package-lock.json

@ -18,6 +18,7 @@
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1",
"multer": "^2.0.1",
"nodemailer": "^7.0.5",
"pg": "^8.16.3"
},
"devDependencies": {
@ -3104,6 +3105,15 @@
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/nodemailer": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.5.tgz",
"integrity": "sha512-nsrh2lO3j4GkLLXoeEksAMgAOqxOv6QumNRVQTJwKH4nuiww6iC2y7GyANs9kRAxCexg3+lTWM3PZ91iLlVjfg==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nodemon": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",

1
backend/package.json

@ -22,6 +22,7 @@
"jsonwebtoken": "^9.0.2",
"moment": "^2.30.1",
"multer": "^2.0.1",
"nodemailer": "^7.0.5",
"pg": "^8.16.3"
},
"devDependencies": {

Loading…
Cancel
Save