diff --git a/backend/.env.example b/backend/.env.example
new file mode 100644
index 0000000..5006a5b
--- /dev/null
+++ b/backend/.env.example
@@ -0,0 +1,10 @@
+MONGODB_URI=mongodb://localhost:27017/classsync
+PORT=3000
+
+JWT_SECRET="yoursecret"
+GROQ_API_KEY=""
+
+GOOGLE_MAILID=
+GOOGLE_APP_PASSWORD=
+
+CORS_ORIGIN=http://localhost:5173
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 9e9bd40..8da3c1d 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -10,13 +10,14 @@
"license": "ISC",
"dependencies": {
"bcrypt": "^6.0.0",
+ "cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.15.1",
"node-cron": "^4.1.1",
- "nodemailer": "^7.0.3",
+ "nodemailer": "^7.0.6",
"openai": "^5.8.2"
},
"devDependencies": {
@@ -2182,6 +2183,25 @@
"node": ">= 0.6"
}
},
+ "node_modules/cookie-parser": {
+ "version": "1.4.7",
+ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
+ "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "0.7.2",
+ "cookie-signature": "1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/cookie-parser/node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+ "license": "MIT"
+ },
"node_modules/cookie-signature": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
@@ -4825,9 +4845,9 @@
"license": "MIT"
},
"node_modules/nodemailer": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.3.tgz",
- "integrity": "sha512-Ajq6Sz1x7cIK3pN6KesGTah+1gnwMnx5gKl3piQlQQE/PwyJ4Mbc8is2psWYxK3RJTVeqsDaCv8ZzXLCDHMTZw==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.6.tgz",
+ "integrity": "sha512-F44uVzgwo49xboqbFgBGkRaiMgtoBrBEWCVincJPK9+S9Adkzt/wXCLKbf7dxucmxfTI5gHGB+bEmdyzN6QKjw==",
"license": "MIT-0",
"engines": {
"node": ">=6.0.0"
diff --git a/backend/package.json b/backend/package.json
index 1b57385..eb50654 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -13,13 +13,14 @@
"type": "commonjs",
"dependencies": {
"bcrypt": "^6.0.0",
+ "cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.15.1",
"node-cron": "^4.1.1",
- "nodemailer": "^7.0.3",
+ "nodemailer": "^7.0.6",
"openai": "^5.8.2"
},
"devDependencies": {
diff --git a/backend/src/app.js b/backend/src/app.js
index 4f0c7d4..39c55e6 100644
--- a/backend/src/app.js
+++ b/backend/src/app.js
@@ -3,6 +3,7 @@ require('dotenv').config();
const express = require('express');
const cors = require('cors');
const connectDB = require('./config/db');
+const cookieParser = require('cookie-parser');
const app = express();
connectDB();
@@ -16,6 +17,7 @@ const corsOptions = {
app.use(cors(corsOptions));
app.use(express.json());
+app.use(cookieParser());
const chatbotRoutes = require('./routes/chatbotRoutes');
app.use('/api/chatbot', chatbotRoutes);
diff --git a/backend/src/controllers/authController.js b/backend/src/controllers/authController.js
index 1d47ed2..7caa200 100644
--- a/backend/src/controllers/authController.js
+++ b/backend/src/controllers/authController.js
@@ -1,6 +1,15 @@
-const User = require('../models/User');
-const bcrypt = require('bcrypt');
-const jwt = require('jsonwebtoken');
+const User = require("../models/User");
+const bcrypt = require("bcrypt");
+const jwt = require("jsonwebtoken");
+const nodemailer = require("nodemailer");
+
+const transporter = nodemailer.createTransport({
+ service: "gmail",
+ auth: {
+ user: process.env.GOOGLE_MAILID,
+ pass: process.env.GOOGLE_APP_PASSWORD,
+ },
+});
// Register new user
exports.register = async (req, res) => {
@@ -8,12 +17,12 @@ exports.register = async (req, res) => {
const { name, email, password, role, schoolId } = req.body;
if (!schoolId) {
- return res.status(400).json({ message: 'schoolId is required' });
+ return res.status(400).json({ message: "schoolId is required" });
}
// Check if user exists
let user = await User.findOne({ email });
- if (user) return res.status(400).json({ message: 'User already exists' });
+ if (user) return res.status(400).json({ message: "User already exists" });
// Hash password
const salt = await bcrypt.genSalt(10);
@@ -30,13 +39,20 @@ exports.register = async (req, res) => {
await user.save();
// Create JWT token
- const payload = { userId: user._id, role: user.role, email: user.email, name: user.name };
- const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1d' });
+ const payload = {
+ userId: user._id,
+ role: user.role,
+ email: user.email,
+ name: user.name,
+ };
+ const token = jwt.sign(payload, process.env.JWT_SECRET, {
+ expiresIn: "1d",
+ });
res.status(201).json({ token });
} catch (err) {
console.error(err.message);
- res.status(500).send('Server error');
+ res.status(500).send("Server error");
}
};
@@ -47,23 +63,129 @@ exports.login = async (req, res) => {
// Find user
const user = await User.findOne({ email });
- if (!user) return res.status(400).json({ message: 'Invalid credentials' });
+ if (!user) return res.status(400).json({ message: "Invalid credentials" });
// Check password
const isMatch = await bcrypt.compare(password, user.password);
- if (!isMatch) return res.status(400).json({ message: 'Invalid credentials' });
+ if (!isMatch)
+ return res.status(400).json({ message: "Invalid credentials" });
// Create JWT token
- const payload = { userId: user._id, role: user.role, email: user.email, name: user.name };
- const token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '1d' });
+ const payload = {
+ userId: user._id,
+ role: user.role,
+ email: user.email,
+ name: user.name,
+ };
+ const token = jwt.sign(payload, process.env.JWT_SECRET, {
+ expiresIn: "1d",
+ });
res.json({ token });
} catch (err) {
console.error(err.message);
- res.status(500).send('Server error');
+ res.status(500).send("Server error");
+ }
+};
+
+exports.sendotp = async (req, res) => {
+ try {
+ console.log("triggered");
+ const { email } = req.body;
+ const user = await User.findOne({ email });
+
+ if (!user) return res.status(400).json({ message: "No such user found." });
+
+ const otp = Math.floor(100000 + Math.random() * 900000);
+
+ const mailOptions = {
+ from: process.env.GOOGLE_MAILID,
+ to: email,
+ subject: "OTP for password change request",
+ html: `
Dear User,
+ Your OTP for the password change request is ${otp}.
+ Please do not share this OTP with anyone to keep your account secure.
+ Thank you,
The Support Team,ClassSync
`,
+ };
+
+ const otpToken = jwt.sign({ otp, email }, process.env.JWT_SECRET, {
+ expiresIn: "5m",
+ });
+
+ res.cookie('otpToken',otpToken,{
+ httpOnly: true,
+ maxAge: 5 * 60 * 1000
+ });
+
+ await transporter.sendMail(mailOptions);
+ console.log("sent");
+ return res.status(200).json({message:'Otp sent'});
+ } catch (err) {
+ console.error(err.message);
+ res.status(500).send("Server error");
+ }
+};
+
+exports.verifyotp = async (req, res) => {
+ try {
+ const { otp } = req.body;
+ const otpToken = req.cookies.otpToken;
+ console.log("otpToken:", otpToken);
+ console.log("here");
+ const decoded = jwt.verify(otpToken, process.env.JWT_SECRET);
+ console.log("after");
+ if (otp == decoded.otp) {
+ return res.status(200).json({ message: "OTP verified successfully" });
+ } else {
+ return res.status(400).json({ message: "Wrong Otp" });
+ }
+ } catch (error) {
+ if (error.name === "TokenExpiredError") {
+ return res
+ .status(400)
+ .json({ message: "OTP expired. Please request a new one." });
+ }
+ console.error("yahai pe");
+ res.status(500).send("Server error");
}
};
+exports.updatepassword = async (req,res) => {
+ try {
+ const {email, password, confirmPassword} = req.body;
+
+ let user = await User.findOne({ email });
+ if (!user) return res.status(400).json({ message: "User does not exists" });
+
+ if(password!=confirmPassword)
+ return res.status(400).json({message:'Passwords do not match.'});
+
+ const salt = await bcrypt.genSalt(10);
+ const hashedPassword = await bcrypt.hash(password, salt);
+
+ user.password=hashedPassword;
+ await user.save();
+
+ const mailOptions = {
+ from: process.env.GOOGLE_MAILID,
+ to: email,
+ subject: "OTP for password change request",
+ html: `Dear User,
+ Your password has been successfully updated..
+ Please contact if you face any login issues.
+ Thank you,
The Support Team,ClassSync
`,
+ };
+
+ await transporter.sendMail(mailOptions);
+ res.clearCookie('otptoken');
+
+ return res.status(200).json({message:'Password updated successfully.'});
+ } catch (error) {
+ console.error(error.message);
+ res.status(500).send("Server Error");
+ }
+}
+
//This code defines the authentication controller for user registration and login in an Express application.
// It includes functions to register a new user and log in an existing user, handling password hashing and JWT token generation.
// The `register` function checks if a user already exists, hashes the password, creates a new user, and returns a JWT token.
@@ -71,4 +193,4 @@ exports.login = async (req, res) => {
// The controller uses Mongoose for database operations, bcrypt for password hashing, and jsonwebtoken for token generation.
// The `register` function handles user registration, including password hashing and JWT token creation.
// The `login` function handles user login, verifying credentials and generating a JWT token.
-// The controller responds with appropriate status codes and messages for success and error cases.
\ No newline at end of file
+// The controller responds with appropriate status codes and messages for success and error cases.
diff --git a/backend/src/routes/authRoutes.js b/backend/src/routes/authRoutes.js
index bef78a4..ee20e4f 100644
--- a/backend/src/routes/authRoutes.js
+++ b/backend/src/routes/authRoutes.js
@@ -1,10 +1,14 @@
const express = require('express');
const router = express.Router();
-const { register, login } = require('../controllers/authController');
+const { register, login, sendotp, verifyotp, updatepassword } = require('../controllers/authController');
router.post('/register', register);
router.post('/login', login);
+router.post('/sendotp',sendotp);
+router.post('/verifyotp',verifyotp);
+router.put('/updatepassword',updatepassword);
+
module.exports = router;
// This code defines the authentication routes for user registration and login.
// It imports the necessary modules, sets up the routes, and exports the router for use in the main application file.
diff --git a/frontend/.env.example b/frontend/.env.example
new file mode 100644
index 0000000..5317fce
--- /dev/null
+++ b/frontend/.env.example
@@ -0,0 +1 @@
+VITE_API_URL=http://localhost:3000
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 02911cd..54b857b 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -34,7 +34,7 @@
"globals": "^16.0.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.1",
- "vite": "^6.3.5"
+ "vite": "^6.3.6"
}
},
"node_modules/@alloc/quick-lru": {
@@ -4948,9 +4948,9 @@
}
},
"node_modules/vite": {
- "version": "6.3.5",
- "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
- "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
+ "version": "6.3.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
+ "integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/frontend/package.json b/frontend/package.json
index 829705c..4bd17fe 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -36,6 +36,6 @@
"globals": "^16.0.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.1",
- "vite": "^6.3.5"
+ "vite": "^6.3.6"
}
}
diff --git a/frontend/src/pages/auth/Login.jsx b/frontend/src/pages/auth/Login.jsx
index 2f0f4e5..94db491 100644
--- a/frontend/src/pages/auth/Login.jsx
+++ b/frontend/src/pages/auth/Login.jsx
@@ -1,10 +1,8 @@
-import React, { useState } from 'react';
-import { Mail, Lock, GraduationCap, Shield } from 'lucide-react';
-import api from '../../utils/api';
-import { useNavigate } from 'react-router-dom';
-import { useAuth } from '../../context/AuthContext';
-import logo from '../../logo.svg';
-
+import React, { useState } from "react";
+import { Mail, Lock, GraduationCap, Shield, KeyRound} from "lucide-react";
+import api from "../../utils/api";
+import { useNavigate } from "react-router-dom";
+import { useAuth } from "../../context/AuthContext";
const demoUsers = [
{
@@ -24,41 +22,119 @@ const demoUsers = [
];
const Login = () => {
- const [email, setEmail] = useState('');
- const [password, setPassword] = useState('');
- const [error, setError] = useState('');
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [error, setError] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [selectedRole, setSelectedRole] = useState(null);
const { login } = useAuth();
const navigate = useNavigate();
+ const [page, setPage] = useState(0);
+ const [otp, setOtp] = useState();
+ const [confirmPassword,setConfirmPassword] = useState("");
+
const handleSubmit = async (e) => {
e.preventDefault();
- setError('');
+ setError("");
setIsLoading(true);
try {
- const response = await api.post('/api/auth/login', {
+ const response = await api.post("/api/auth/login", {
email,
password,
});
const userRole = login(response.data.token);
- if (userRole === 'admin') {
- navigate('/admin/dashboard');
+ if (userRole === "admin") {
+ navigate("/admin/dashboard");
} else {
- navigate('/teacher/dashboard');
+ navigate("/teacher/dashboard");
}
-
} catch (err) {
- console.error('Login failed:', err.response?.data?.message || err.message);
+ console.error(
+ "Login failed:",
+ err.response?.data?.message || err.message
+ );
setError(err.response?.data?.message || 'An unexpected error occurred. Please try again.');
} finally {
setIsLoading(false);
}
};
+ const handleforgotflow = async () => {
+ setPage(1);
+ console.log("Opened modal for email");
+ };
+
+ const handleforgotPassword = async () => {
+ console.log("Sending otp");
+ try {
+ const response = await api.post("/api/auth/sendotp", { email },{
+ withCredentials: true
+ });
+ if (response.status == 200) {
+ setPage(2);
+ } else setError(response.data.message);
+ } catch (err) {
+ console.error(
+ "Could not send Otp:",
+ err.response?.data?.message || err.message
+ );
+ setError(
+ err.response?.data?.message ||
+ "An unexpected error occurred. Please try again."
+ );
+ }
+ };
+
+ const handlesubmitotp = async () => {
+ console.log("verifying");
+ try {
+ const response = await api.post("/api/auth/verifyotp", { otp },{
+ withCredentials:true
+ });
+ setError(response.data.message);
+ if(response.status == 200){
+ setPage(3);
+ console.log("otp verified");
+ }
+ } catch (err) {
+ console.error(
+ "Could not verify:",
+ err.response?.data?.message || err.message
+ );
+ setError(
+ err.response?.data?.message ||
+ "An unexpected error occurred. Please try again."
+ );
+ }
+ };
+
+ const handleUpdatePass = async (req,res) => {
+ console.log("updating");
+ try {
+ const response = await api.put('/api/auth/updatepassword',{email,password,confirmPassword});
+ setError(response.data.message);
+ if(response.status == 200){
+ setTimeout(setPage(0),3000);
+ setEmail("");
+ setPassword("");
+ setError("");
+ }
+ } catch (err) {
+ console.error(
+ "Could not update password:",
+ err.response?.data?.message || err.message
+ );
+ setError(
+ err.response?.data?.message ||
+ "An unexpected error occurred. Please try again."
+ );
+ }
+ }
+
const handleRoleSelect = (role) => {
setSelectedRole(role);
const user = demoUsers.find((u) => u.role === role);
@@ -66,7 +142,7 @@ const Login = () => {
setEmail(user.email);
setPassword(user.password);
}
- setError(''); // Clear any existing errors when selecting demo user
+ setError(""); // Clear any existing errors when selecting demo user
};
return (
@@ -74,39 +150,49 @@ const Login = () => {
-

-
+

+
{/* Header */}
-
Welcome Back
-
Sign in to your account
+
+ Welcome Back
+
+ {page == 0 && (
+
Sign in to your account
+ )}
{/* Demo User Selection */}
-
-
Login as Demo User
-
- {demoUsers.map((user) => (
-
- ))}
+ {page == 0 && (
+
+
+ Login as Demo User
+
+
+ {demoUsers.map((user) => (
+
+ ))}
+
-
+ )}
{/* Error Message */}
{error && (
@@ -116,7 +202,18 @@ const Login = () => {
)}
{/* Login Form */}
-
{/* Additional Info for Demo */}
-
-
- Select a demo user above or use your credentials
-
-
+ {page == 0 && (
+
+
+ Select a demo user above or use your credentials
+
+
+ )}
@@ -203,4 +352,4 @@ const Login = () => {
);
};
-export default Login;
\ No newline at end of file
+export default Login;