Skip to content

Commit

Permalink
completed authentication advanced
Browse files Browse the repository at this point in the history
  • Loading branch information
baohuy2209 committed Dec 12, 2024
1 parent 7254844 commit 155b9d5
Show file tree
Hide file tree
Showing 2,126 changed files with 330,925 additions and 0 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
4 changes: 4 additions & 0 deletions setup/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
PORT_SERVER=5000
MONGO_URI=mongodb://localhost:27017/config
JWT_SECRET_KEY=haweihfrbt4t45_ere@etw43
CLIENT_URL=http://localhost:3000
199 changes: 199 additions & 0 deletions setup/backend/controllers/auth.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import bcryptjs from "bcryptjs";
import crypto from "crypto";

import { generateTokenAndSetCookie } from "../utils/generateTokenAndSetCookie.js";
import {
sendPasswordResetEmail,
sendResetSuccessEmail,
sendVerificationEmail,
sendWelcomeEmail,
} from "../mailtrap/emails.js";
import { User } from "../models/user.model.js";

export const signup = async (req, res) => {
const { email, password, name } = req.body;

try {
if (!email || !password || !name) {
throw new Error("All fields are required");
}

const userAlreadyExists = await User.findOne({ email });
console.log("userAlreadyExists", userAlreadyExists);

if (userAlreadyExists) {
return res.status(400).json({ success: false, message: "User already exists" });
}

const hashedPassword = await bcryptjs.hash(password, 10);
const verificationToken = Math.floor(100000 + Math.random() * 900000).toString();

const user = new User({
email,
password: hashedPassword,
name,
verificationToken,
verificationTokenExpiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24 hours
});

await user.save();

// jwt
generateTokenAndSetCookie(res, user._id);

await sendVerificationEmail(user.email, verificationToken);

res.status(201).json({
success: true,
message: "User created successfully",
user: {
...user._doc,
password: undefined,
},
});
} catch (error) {
res.status(400).json({ success: false, message: error.message });
}
};

export const verifyEmail = async (req, res) => {
const { code } = req.body;
try {
const user = await User.findOne({
verificationToken: code,
verificationTokenExpiresAt: { $gt: Date.now() },
});

if (!user) {
return res.status(400).json({ success: false, message: "Invalid or expired verification code" });
}

user.isVerified = true;
user.verificationToken = undefined;
user.verificationTokenExpiresAt = undefined;
await user.save();

await sendWelcomeEmail(user.email, user.name);

res.status(200).json({
success: true,
message: "Email verified successfully",
user: {
...user._doc,
password: undefined,
},
});
} catch (error) {
console.log("error in verifyEmail ", error);
res.status(500).json({ success: false, message: "Server error" });
}
};

export const login = async (req, res) => {
const { email, password } = req.body;
try {
const user = await User.findOne({ email });
if (!user) {
return res.status(400).json({ success: false, message: "Invalid credentials" });
}
const isPasswordValid = await bcryptjs.compare(password, user.password);
if (!isPasswordValid) {
return res.status(400).json({ success: false, message: "Invalid credentials" });
}

generateTokenAndSetCookie(res, user._id);

user.lastLogin = new Date();
await user.save();

res.status(200).json({
success: true,
message: "Logged in successfully",
user: {
...user._doc,
password: undefined,
},
});
} catch (error) {
console.log("Error in login ", error);
res.status(400).json({ success: false, message: error.message });
}
};

export const logout = async (req, res) => {
res.clearCookie("token");
res.status(200).json({ success: true, message: "Logged out successfully" });
};

export const forgotPassword = async (req, res) => {
const { email } = req.body;
try {
const user = await User.findOne({ email });

if (!user) {
return res.status(400).json({ success: false, message: "User not found" });
}

// Generate reset token
const resetToken = crypto.randomBytes(20).toString("hex");
const resetTokenExpiresAt = Date.now() + 1 * 60 * 60 * 1000; // 1 hour

user.resetPasswordToken = resetToken;
user.resetPasswordExpiresAt = resetTokenExpiresAt;

await user.save();

// send email
await sendPasswordResetEmail(user.email, `${process.env.CLIENT_URL}/reset-password/${resetToken}`);

res.status(200).json({ success: true, message: "Password reset link sent to your email" });
} catch (error) {
console.log("Error in forgotPassword ", error);
res.status(400).json({ success: false, message: error.message });
}
};

export const resetPassword = async (req, res) => {
try {
const { token } = req.params;
const { password } = req.body;

const user = await User.findOne({
resetPasswordToken: token,
resetPasswordExpiresAt: { $gt: Date.now() },
});

if (!user) {
return res.status(400).json({ success: false, message: "Invalid or expired reset token" });
}

// update password
const hashedPassword = await bcryptjs.hash(password, 10);

user.password = hashedPassword;
user.resetPasswordToken = undefined;
user.resetPasswordExpiresAt = undefined;
await user.save();

await sendResetSuccessEmail(user.email);

res.status(200).json({ success: true, message: "Password reset successful" });
} catch (error) {
console.log("Error in resetPassword ", error);
res.status(400).json({ success: false, message: error.message });
}
};

export const checkAuth = async (req, res) => {
try {
const user = await User.findById(req.userId).select("-password");
if (!user) {
return res.status(400).json({ success: false, message: "User not found" });
}

res.status(200).json({ success: true, user });
} catch (error) {
console.log("Error in checkAuth ", error);
res.status(400).json({ success: false, message: error.message });
}
};
12 changes: 12 additions & 0 deletions setup/backend/db/connectDB.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import mongoose from "mongoose";

export const connectDB = async () => {
try {
console.log("mongo_uri: ", process.env.MONGO_URI);
const conn = await mongoose.connect(process.env.MONGO_URI);
console.log(`MongoDB Connected: ${conn.connection.host}`);
} catch (error) {
console.log("Error connection to MongoDB: ", error.message);
process.exit(1); // 1 is failure, 0 status code is success
}
};
35 changes: 35 additions & 0 deletions setup/backend/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import express from "express";
import dotenv from "dotenv";
import cors from "cors";
import cookieParser from "cookie-parser";
import path from "path";

import { connectDB } from "./db/connectDB.js";

import authRoutes from "./routes/auth.route.js";

dotenv.config();

const app = express();
const PORT = process.env.PORT || 5000;
const __dirname = path.resolve();

app.use(cors({ origin: "http://localhost:5173", credentials: true }));

app.use(express.json()); // allows us to parse incoming requests:req.body
app.use(cookieParser()); // allows us to parse incoming cookies

app.use("/api/auth", authRoutes);

if (process.env.NODE_ENV === "production") {
app.use(express.static(path.join(__dirname, "/frontend/dist")));

app.get("*", (req, res) => {
res.sendFile(path.resolve(__dirname, "frontend", "dist", "index.html"));
});
}

app.listen(PORT, () => {
connectDB();
console.log("Server is running on port: ", PORT);
});
95 changes: 95 additions & 0 deletions setup/backend/mailtrap/emailTemplates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
export const VERIFICATION_EMAIL_TEMPLATE = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verify Your Email</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(to right, #4CAF50, #45a049); padding: 20px; text-align: center;">
<h1 style="color: white; margin: 0;">Verify Your Email</h1>
</div>
<div style="background-color: #f9f9f9; padding: 20px; border-radius: 0 0 5px 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
<p>Hello,</p>
<p>Thank you for signing up! Your verification code is:</p>
<div style="text-align: center; margin: 30px 0;">
<span style="font-size: 32px; font-weight: bold; letter-spacing: 5px; color: #4CAF50;">{verificationCode}</span>
</div>
<p>Enter this code on the verification page to complete your registration.</p>
<p>This code will expire in 15 minutes for security reasons.</p>
<p>If you didn't create an account with us, please ignore this email.</p>
<p>Best regards,<br>Your App Team</p>
</div>
<div style="text-align: center; margin-top: 20px; color: #888; font-size: 0.8em;">
<p>This is an automated message, please do not reply to this email.</p>
</div>
</body>
</html>
`;

export const PASSWORD_RESET_SUCCESS_TEMPLATE = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Password Reset Successful</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(to right, #4CAF50, #45a049); padding: 20px; text-align: center;">
<h1 style="color: white; margin: 0;">Password Reset Successful</h1>
</div>
<div style="background-color: #f9f9f9; padding: 20px; border-radius: 0 0 5px 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
<p>Hello,</p>
<p>We're writing to confirm that your password has been successfully reset.</p>
<div style="text-align: center; margin: 30px 0;">
<div style="background-color: #4CAF50; color: white; width: 50px; height: 50px; line-height: 50px; border-radius: 50%; display: inline-block; font-size: 30px;">
</div>
</div>
<p>If you did not initiate this password reset, please contact our support team immediately.</p>
<p>For security reasons, we recommend that you:</p>
<ul>
<li>Use a strong, unique password</li>
<li>Enable two-factor authentication if available</li>
<li>Avoid using the same password across multiple sites</li>
</ul>
<p>Thank you for helping us keep your account secure.</p>
<p>Best regards,<br>Your App Team</p>
</div>
<div style="text-align: center; margin-top: 20px; color: #888; font-size: 0.8em;">
<p>This is an automated message, please do not reply to this email.</p>
</div>
</body>
</html>
`;

export const PASSWORD_RESET_REQUEST_TEMPLATE = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset Your Password</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
<div style="background: linear-gradient(to right, #4CAF50, #45a049); padding: 20px; text-align: center;">
<h1 style="color: white; margin: 0;">Password Reset</h1>
</div>
<div style="background-color: #f9f9f9; padding: 20px; border-radius: 0 0 5px 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
<p>Hello,</p>
<p>We received a request to reset your password. If you didn't make this request, please ignore this email.</p>
<p>To reset your password, click the button below:</p>
<div style="text-align: center; margin: 30px 0;">
<a href="{resetURL}" style="background-color: #4CAF50; color: white; padding: 12px 20px; text-decoration: none; border-radius: 5px; font-weight: bold;">Reset Password</a>
</div>
<p>This link will expire in 1 hour for security reasons.</p>
<p>Best regards,<br>Your App Team</p>
</div>
<div style="text-align: center; margin-top: 20px; color: #888; font-size: 0.8em;">
<p>This is an automated message, please do not reply to this email.</p>
</div>
</body>
</html>
`;
Loading

0 comments on commit 155b9d5

Please sign in to comment.