diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..727217f --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +MONGODB_URI=your_mongodb_connection_string +JWT_SECRET=your_jwt_secret_key +HASH_SECRET=your_HmacSHA256_secret_key +SYSTEM_MAIL=your_email_address +SYSTEM_MAIL_PASS=your_email_address_password \ No newline at end of file diff --git a/README.md b/README.md index 504e797..c2f042b 100644 --- a/README.md +++ b/README.md @@ -82,17 +82,20 @@ To set up Travel Buddy locally, follow these steps: 3. Configure environment variables: - Create a `.env` file in the root directory. - - Add your MongoDB Atlas connection string: + - Copy the content from `.env.example` to `.env`: - ```env - MONGODB_URI=your_mongodb_connection_string + ```bash + cp .env.example .env ``` - - Add your JWT secret key: - ```env + MONGODB_URI=your_mongodb_connection_string JWT_SECRET=your_jwt_secret_key + HASH_SECRET=your_HmacSHA256_secret_key + SYSTEM_MAIL=your_email_address + SYSTEM_MAIL_PASS=your_email_address_password ``` + - Replace placeholder values in the .env file with actual values. 4. Run the application: ```sh diff --git a/app/api/authenticate/generate/route.js b/app/api/authenticate/generate/route.js new file mode 100644 index 0000000..e4a9324 --- /dev/null +++ b/app/api/authenticate/generate/route.js @@ -0,0 +1,82 @@ +import { NextResponse } from "next/server"; +import otpGenerator from "otp-generator"; +import { HmacSHA256 } from "crypto-js"; +import nodemailer from "nodemailer"; +import { cookies } from "next/headers"; +import { instituteDetails } from "@/app/utils/institute"; + +export async function POST(req) { + try { + req = await req.json(); + + // Form fields: + // i) email – (Institute Email) – Text + + const { email, instituteCode } = req; + + const institute = await instituteDetails({ instituteCode }); + + const emailDomain = email.split("@")[1]; + + if (emailDomain != institute.domain) { + throw new Error("Invalid email. Choose the correct institute."); + } + + const otp = otpGenerator.generate(6, { lowerCaseAlphabets: false, upperCaseAlphabets: false, specialChars: false }); + + const expiryTime = Date.now() + 5 * 60 * 1000; // 5 mins + + const data = `${instituteCode}.${email}.${otp}`; + + const hashData = HmacSHA256(data, process.env.HASH_SECRET).toString() + "." + expiryTime; + + const transporter = nodemailer.createTransport({ + service: 'gmail', + auth: { + user: process.env.SYSTEM_MAIL, + pass: process.env.SYSTEM_MAIL_PASS + } + }); + + const mailOptions = { + from: process.env.SYSTEM_MAIL, + to: email, + subject: 'OTP Code for Authentication', + text: `Your OTP code is: ${otp}. This will expire at ${new Date(expiryTime).toLocaleString()}` + }; + + await transporter.sendMail(mailOptions); + + const cookieStore = cookies(); + + cookieStore.set({ + name: "otpData", + value: JSON.stringify({ email, hashData, instituteCode }), + httpOnly: true, + path: "/", + maxAge: 5 * 60, // 5 mins + }); + + return NextResponse.json( + { + message: "OTP sent successfully! Please check your inbox.", + }, + { + status: 200, + } + ); + + } catch (error) { + console.log(error.message); + return NextResponse.json( + { + message: + error.message || + "Something went wrong - please try again later!", + }, + { + status: 500, + } + ); + } +} \ No newline at end of file diff --git a/app/api/authenticate/validate-jwt/route.js b/app/api/authenticate/validate-jwt/route.js new file mode 100644 index 0000000..4033d15 --- /dev/null +++ b/app/api/authenticate/validate-jwt/route.js @@ -0,0 +1,65 @@ +import { NextResponse } from "next/server"; +import { cookies } from "next/headers"; +import jwt from "jsonwebtoken"; +import { instituteDetails } from "@/app/utils/institute"; + +export async function GET(req) { + try { + + const { searchParams } = new URL(req.url); + + const instituteCode = searchParams.get("instituteCode"); + + const institute = await instituteDetails({ instituteCode }); + + const { authCookie } = institute; + + const cookieStore = cookies(); + + const cookie = cookieStore.get(authCookie); + + if (!cookie) { + throw new Error("No JWT cookie found in the request."); + } + + const token = cookie.value; + + if (!token) { + throw new Error("No JWT session token found."); + } + + const decoded = jwt.verify(token, process.env.JWT_SECRET); + + const expiryTime = decoded.exp; + const currentTime = Date.now(); + + if (currentTime >= expiryTime * 1000) { + cookieStore.delete(cookie); + throw new Error("JWT cookie token has expired."); + } + + return NextResponse.json( + { + message: "Authentication successful.", + email: decoded.email, + }, + { + status: 200, + } + ); + + } catch (error) { + console.log(error.message); + return NextResponse.json( + { + message: + error.message || + "Something went wrong - please try again later!", + }, + { + status: 500, + } + ); + } + +} \ No newline at end of file diff --git a/app/api/authenticate/verify/route.js b/app/api/authenticate/verify/route.js new file mode 100644 index 0000000..aad594f --- /dev/null +++ b/app/api/authenticate/verify/route.js @@ -0,0 +1,103 @@ + +import { NextResponse } from "next/server"; +import { HmacSHA256 } from "crypto-js"; +import validator from "validator"; +import { cookies } from "next/headers"; +import { instituteDetails } from "@/app/utils/institute"; +import jwt from "jsonwebtoken"; + +const createJwt = (payload) => { + const secret = process.env.JWT_SECRET; + const token = jwt.sign(payload, secret, { expiresIn: '1h' }); // Set expiration time + return token; +}; + +export async function POST(req) { + try { + req = await req.json(); + + // Form fields: + // i) email – (Institute Email) – Text + // ii) hashData – (Hashed Data for OTP verification) – Text + // iii) instituteCode - (Institute Code) – Text + // iv) otp - (Submitted OTP) - Text + + const cookieStore = cookies(); + + const { email, hashData, instituteCode, otp } = req; + + if ( + validator.isEmpty(email) || + validator.isEmpty(hashData) || + validator.isEmpty(otp) || + validator.isEmpty(instituteCode) + ) { + throw new Error("Please fill all the fields!"); + } + + if (otp.length > 0 && otp.length != 6) { + throw new Error("Please enter a valid 6-digit OTP!"); + } + + const hash = hashData.split('.'); + + if (hash.length !== 2) { + throw new Error("Invalid hash data format!"); + } + + const data = `${instituteCode}.${email}.${otp}`; + + const userHash = HmacSHA256(data, process.env.HASH_SECRET).toString(); + + if (userHash !== hash[0]) { + throw new Error("Verification failed try again."); + } + + const expiryTime = parseInt(hash[1]); + + const currentTime = Date.now(); + + if (currentTime > expiryTime) { + cookieStore.delete("otpData"); + throw new Error("OTP has expired. Please request a new one."); + } + + const institute = await instituteDetails({ instituteCode }); + + const payload = { email }; + + const token = createJwt(payload); + + cookieStore.set({ + name: institute.authCookie, + value: token, + httpOnly: true, + path: "/", + maxAge: 60 * 60, // Temporary maxAge + }); + + cookieStore.delete("otpData"); + + return NextResponse.json( + { + message: "OTP verified successfully!", + }, + { + status: 200, + } + ); + + } catch (error) { + console.log(error.message); + return NextResponse.json( + { + message: + error.message || + "Something went wrong - please try again later!", + }, + { + status: 500, + } + ); + } +} \ No newline at end of file diff --git a/app/api/register/route.js b/app/api/register/route.js index 014a247..fc21dcf 100644 --- a/app/api/register/route.js +++ b/app/api/register/route.js @@ -3,7 +3,7 @@ import sanitize from "mongo-sanitize"; import validator from "validator"; import { connectToDatabase } from "@/app/lib/mongodb"; import User from "@/app/models/User"; -import { cookies } from "next/headers"; +import { cookies, headers } from "next/headers"; import Axios from "axios"; import { checkUser } from "@/app/utils/auth"; import { instituteDetails } from "@/app/utils/institute"; @@ -11,7 +11,6 @@ import { instituteDetails } from "@/app/utils/institute"; export async function POST(req) { try { req = await req.json(); - // Form fields: // i) name – (Full Name) - Text // ii) roll – (Roll Number) – Text @@ -20,7 +19,6 @@ export async function POST(req) { // v) instituteCode - (Institute Code) – Text let { name, roll, number, instituteCode } = req; - const institute = await instituteDetails({ instituteCode }); let { authCookie, verifyAuthLink } = institute; @@ -51,7 +49,26 @@ export async function POST(req) { ); } - const response = await Axios.get(verifyAuthLink, { + const isExternal = verifyAuthLink.startsWith("http://") || verifyAuthLink.startsWith("https://"); + + let verifyrouteURL; + + if (isExternal) { + verifyrouteURL = verifyAuthLink; + } + + else { + const requestHeaders = headers(); + const host = requestHeaders.get('host'); + const protocol = requestHeaders.get('x-forwarded-proto') || 'http'; + verifyrouteURL = protocol + "://" + host + verifyAuthLink; + } + + if (!verifyrouteURL || verifyrouteURL === "") { + throw new Error("Invalid authentication verfiy URL."); + } + + const response = await Axios.get(verifyrouteURL, { headers: { Cookie: Object.entries({ [authCookie]: token, @@ -60,6 +77,11 @@ export async function POST(req) { .join("; "), }, }); + + if (!response || !response.data || !response.data.email) { + throw new Error("No JWT session token found."); + } + const email = response.data.email; if (!email) { diff --git a/app/authenticate/AuthForm.jsx b/app/authenticate/AuthForm.jsx index 3b04977..7b69c1d 100644 --- a/app/authenticate/AuthForm.jsx +++ b/app/authenticate/AuthForm.jsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; -export default function AuthForm({ institutes }) { +export default function AuthForm({ institutes, redirect_url }) { const router = useRouter(); const check = () => { @@ -28,7 +28,7 @@ export default function AuthForm({ institutes }) { alert("Please select an institute"); return; } - router.push("/register?instituteCode=" + institute); + router.push("/register?instituteCode=" + institute + "&redirect_url=" + redirect_url); return; }; diff --git a/app/authenticate/generate-otp/GenerateOtpForm.jsx b/app/authenticate/generate-otp/GenerateOtpForm.jsx new file mode 100644 index 0000000..150c124 --- /dev/null +++ b/app/authenticate/generate-otp/GenerateOtpForm.jsx @@ -0,0 +1,114 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { instituteDetails } from "@/app/utils/institute"; + +const GenerateOtpForm = ({ instituteCode, redirect_url }) => { + const [loading, setLoading] = useState(false); + const [email, setEmail] = useState(""); + const [institute, setInstitute] = useState(""); + const router = useRouter(); + + const checkInstituteCode = async () => { + if (!instituteCode) { + alert('Institute not found.'); + router.push("/"); + } + try { + const selectInstitute = await instituteDetails({ instituteCode }); + setInstitute(selectInstitute.name); + } catch (error) { + router.push("/"); + } + } + + useEffect(() => { + checkInstituteCode(); + }, []) + + const handleChange = (e) => { + setEmail(e.target.value); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + const data = { + email: email, + instituteCode: instituteCode + } + + setLoading(true); + + const res = await fetch("/api/authenticate/generate", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + setLoading(false); + + if (res.ok) { + const json = await res.json(); + alert(json.message); + router.push("/authenticate/verify-otp?redirect_url=" + redirect_url); + } else { + const json = await res.json(); + alert(json.message); + } + }; + + return ( +
+
+

+ College Email Authentication +

+
+ + +
+ {institute && ( +
+ + +
+ )} + +
+
+ ); +} + +export default GenerateOtpForm; diff --git a/app/authenticate/generate-otp/page.jsx b/app/authenticate/generate-otp/page.jsx new file mode 100644 index 0000000..54eee69 --- /dev/null +++ b/app/authenticate/generate-otp/page.jsx @@ -0,0 +1,18 @@ +import GenerateOtpForm from "./GenerateOtpForm"; + +export const metadata = { + title: "Request OTP for Email verification", +}; + +const Page = async ({ searchParams }) => { + + let { instituteCode, redirect_url } = searchParams; + + if (instituteCode) { + instituteCode = instituteCode.split("?")[0]; + } + + return ; +}; + +export default Page; diff --git a/app/authenticate/page.jsx b/app/authenticate/page.jsx index e0d6ded..99f926b 100644 --- a/app/authenticate/page.jsx +++ b/app/authenticate/page.jsx @@ -1,18 +1,29 @@ import { connectToDatabase } from "@/app/lib/mongodb"; import Institute from "@/app/models/Institute"; import AuthForm from "./AuthForm"; +import { headers } from "next/headers"; export const metadata = { title: "Select Institute", }; -const Page = async () => { +const Page = async ({ searchParams }) => { + + const { redirect_path } = searchParams; + + const requestHeaders = headers(); + const host = requestHeaders.get('host'); + const protocol = requestHeaders.get('x-forwarded-proto') || 'http'; + const redirect_url = protocol + "://" + host + redirect_path; + await connectToDatabase(); // Return only names of Institues const institutes = await Institute.find({}, { _id: 0, name: 1, code: 1 }); - return ; + const institutesData = JSON.parse(JSON.stringify(institutes)); + + return ; }; export default Page; diff --git a/app/authenticate/verify-otp/VerifyOtpForm.jsx b/app/authenticate/verify-otp/VerifyOtpForm.jsx new file mode 100644 index 0000000..849bd0b --- /dev/null +++ b/app/authenticate/verify-otp/VerifyOtpForm.jsx @@ -0,0 +1,97 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; + +const VerifyOtpForm = ({ email, hashData, instituteCode, redirect_url }) => { + + const [loading, setLoading] = useState(false); + const [otp, setOtp] = useState(""); + const router = useRouter(); + + const handleChange = (e) => { + setOtp(e.target.value); + }; + + const check = () => { + if (!hashData || !instituteCode || !email) { + alert("Invalid access. Redirecting to authentication page."); + router.push("/authenticate"); + } + } + + useEffect(() => { + check(); + }, []) + + const handleSubmit = async (e) => { + e.preventDefault(); + + setLoading(true); + + const data = { + email: email, + hashData: hashData, + instituteCode: instituteCode, + otp: otp + } + + const res = await fetch("/api/authenticate/verify", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + setLoading(false); + + if (res.ok) { + const json = await res.json(); + alert(json.message); + router.push("/register?instituteCode=" + instituteCode + "&redirect_url=" + redirect_url); + } else { + const json = await res.json(); + alert(json.message); + } + }; + + + return ( +
+
+

+ OTP Verification +

+
+ + +
+ +
+
+ ); +} + +export default VerifyOtpForm; \ No newline at end of file diff --git a/app/authenticate/verify-otp/page.jsx b/app/authenticate/verify-otp/page.jsx new file mode 100644 index 0000000..2591be6 --- /dev/null +++ b/app/authenticate/verify-otp/page.jsx @@ -0,0 +1,39 @@ +import { redirect } from "next/navigation"; +import VerifyOtpForm from "./VerifyOtpForm"; +import { cookies } from "next/headers"; + +export const metadata = { + title: "Enter OTP for Email verification", +}; + +const Page = async ({ searchParams }) => { + const { redirect_url } = searchParams; + console.log(redirect_url); + const cookieStore = cookies(); + const cookie = cookieStore.get("otpData"); + + if (!cookie) { + redirect("/authenticate/generate-otp"); + } + + const token = cookie.value; + + if (!token) { + redirect("/authenticate/generate-otp"); + } + + const { email, hashData, instituteCode } = JSON.parse(token); + + if (!email || !hashData || !instituteCode) { + redirect("/authenticate/generate-otp"); + } + + return ; +}; + +export default Page; diff --git a/app/data.json b/app/data.json index 8e0103f..0c9947f 100644 --- a/app/data.json +++ b/app/data.json @@ -1,13 +1,4 @@ { - "locations": { - "IIT": "IIT Kharagpur Campus", - "CCU": "Kolkata Airport", - "KGP": "Kharagpur Railway Station", - "HWH": "Howrah Railway Station", - "SDAH": "Sealdah Railway Station", - "HIJ": "Hijli Railway Station", - "CAT": "Kolkata CAT Centre(s)" - }, "slots": { "0": "12 AM - 1 AM", "1": "1 AM - 2 AM", diff --git a/app/models/Institute.js b/app/models/Institute.js index 9c659f9..bd4b911 100644 --- a/app/models/Institute.js +++ b/app/models/Institute.js @@ -23,14 +23,8 @@ const instituteSchema = new Schema({ required: [true, "Please provide a domain for the email"], }, locations: { - type: [String], + type: Object, required: [true, "Please provide at least one location"], - validate: { - validator: function (locations) { - return locations.length > 0; - }, - message: "Institute must have at least one location", - }, }, authLink: { type: String, diff --git a/app/register/RegForm.jsx b/app/register/RegForm.jsx index bb5f170..f4e1dcb 100644 --- a/app/register/RegForm.jsx +++ b/app/register/RegForm.jsx @@ -5,7 +5,7 @@ import Loading from "@/app/utils/Loading"; import { useRouter } from "next/navigation"; import { checkUser } from "@/app/utils/auth"; -export default function RegForm({ email, instituteCode }) { +export default function RegForm({ email, instituteCode, redirect_url }) { const router = useRouter(); const [loading, setLoading] = useState(true); const [formData, setFormData] = useState({ @@ -76,7 +76,7 @@ export default function RegForm({ email, instituteCode }) { const json = await res.json(); alert(json.message); localStorage.setItem("travelbuddy", json.user); - router.push("/"); + router.push(redirect_url); } else { const json = await res.json(); alert(json.message); diff --git a/app/register/page.jsx b/app/register/page.jsx index d399401..67c6bcb 100644 --- a/app/register/page.jsx +++ b/app/register/page.jsx @@ -8,7 +8,7 @@ export const metadata = { }; const Page = async ({ searchParams }) => { - const { instituteCode } = searchParams; + const { instituteCode, redirect_url } = searchParams; const institute = await instituteDetails({ instituteCode }); @@ -18,21 +18,21 @@ const Page = async ({ searchParams }) => { const cookie = cookieStore.get(authCookie); if (!cookie) { - redirect(authLink + "?redirect_url=https://travel.metakgp.org/"); + redirect(authLink + "&redirect_url=" + redirect_url); } const token = cookie.value; if (!token) { - redirect(authLink + "?redirect_url=https://travel.metakgp.org/"); + redirect(authLink + "&redirect_url=" + redirect_url); } const email = JSON.parse(atob(token.split(".")[1])).email; // get the user email from jwt if (!email) { - redirect(authLink + "?redirect_url=https://travel.metakgp.org/"); + redirect(authLink + "&redirect_url=" + redirect_url); } - return ; + return ; }; export default Page; diff --git a/app/trains/create/TrainForm.jsx b/app/trains/create/TrainForm.jsx index de5d4e4..e94e976 100644 --- a/app/trains/create/TrainForm.jsx +++ b/app/trains/create/TrainForm.jsx @@ -1,6 +1,6 @@ "use client"; -import { useRouter } from "next/navigation"; +import { useRouter, usePathname } from "next/navigation"; import { useEffect, useState } from "react"; import Loading from "@/app/utils/Loading"; import Link from "next/link"; @@ -9,20 +9,21 @@ import { verifyUser } from "@/app/utils/auth"; const TrainForm = () => { const router = useRouter(); + const pathname = usePathname(); const [loading, setLoading] = useState(true); const [email, setEmail] = useState(""); const check = async () => { if (!localStorage.getItem("travelbuddy")) { - router.push("/authenticate"); + router.push("/authenticate?redirect_path=" + pathname); return; } const token = localStorage.getItem("travelbuddy"); const email = await verifyUser({ token }); if (!email) { localStorage.removeItem("travelbuddy"); - router.push("/authenticate"); + router.push("/authenticate?redirect_path=" + pathname); return; } setEmail(email); diff --git a/app/trains/my-trains/MyTrains.jsx b/app/trains/my-trains/MyTrains.jsx index 1376cf1..72b5856 100644 --- a/app/trains/my-trains/MyTrains.jsx +++ b/app/trains/my-trains/MyTrains.jsx @@ -1,18 +1,18 @@ "use client"; import Link from "next/link"; -import { useRouter } from "next/navigation"; +import { useRouter, usePathname } from "next/navigation"; import { useEffect, useState } from "react"; import Loading from "@/app/utils/Loading"; const MyTrains = () => { const router = useRouter(); - + const pathname = usePathname(); const [myTrains, setMyTrains] = useState(null); const getDetails = async () => { if (!localStorage.getItem("travelbuddy")) { - router.push("/authenticate"); + router.push("/authenticate?redirect_path=" + pathname); return; } const res = await fetch("/api/trains/find", { diff --git a/app/trains/train/[trainID]/TrainDetails.jsx b/app/trains/train/[trainID]/TrainDetails.jsx index 300d8c2..2872088 100644 --- a/app/trains/train/[trainID]/TrainDetails.jsx +++ b/app/trains/train/[trainID]/TrainDetails.jsx @@ -1,18 +1,18 @@ "use client"; -import { useRouter } from "next/navigation"; +import { useRouter, usePathname } from "next/navigation"; import { useEffect, useState } from "react"; import Link from "next/link"; import Loading from "@/app/utils/Loading"; const TrainDetails = ({ trainID }) => { const router = useRouter(); - + const pathname = usePathname(); const [data, setData] = useState(null); const getDetails = async () => { if (!localStorage.getItem("travelbuddy")) { - router.push("/authenticate"); + router.push("/authenticate?redirect_path=" + pathname); return; } const res = await fetch("/api/trains/find", { diff --git a/app/trips/create/TripForm.jsx b/app/trips/create/TripForm.jsx index 561227b..b7e207b 100644 --- a/app/trips/create/TripForm.jsx +++ b/app/trips/create/TripForm.jsx @@ -1,33 +1,40 @@ "use client"; -import { useRouter } from "next/navigation"; +import { useRouter, usePathname } from "next/navigation"; import { useEffect, useState } from "react"; import Loading from "@/app/utils/Loading"; import Link from "next/link"; import { today } from "@/app/utils/date"; import { verifyUser } from "@/app/utils/auth"; import data from "@/app/data.json"; +import { userInstituteDetails } from "@/app/utils/institute"; const TripForm = () => { const router = useRouter(); + const pathname = usePathname(); const [loading, setLoading] = useState(true); - + const [locations, setLocations] = useState({}); const [email, setEmail] = useState(""); const check = async () => { if (!localStorage.getItem("travelbuddy")) { - router.push("/authenticate"); + router.push("/authenticate?redirect_path=" + pathname); return; } const token = localStorage.getItem("travelbuddy"); const email = await verifyUser({ token }); if (!email) { localStorage.removeItem("travelbuddy"); - router.push("/authenticate"); + router.push("/authenticate?redirect_path=" + pathname); return; } setEmail(email); - + try { + const userInstitue = await userInstituteDetails({email}); + setLocations(userInstitue.locations); + } catch (error) { + router.push("/"); + } setLoading(false); }; @@ -155,9 +162,9 @@ const TripForm = () => { className="border rounded-md p-2 w-full" > - {Object.keys(data.locations).map((location) => ( + {Object.keys(locations).map((location) => ( ))} @@ -173,9 +180,9 @@ const TripForm = () => { className="border rounded-md p-2 w-full" > - {Object.keys(data.locations).map((location) => ( + {Object.keys(locations).map((location) => ( ))} diff --git a/app/trips/my-trips/MyTrips.jsx b/app/trips/my-trips/MyTrips.jsx index 52fb724..af9ffb2 100644 --- a/app/trips/my-trips/MyTrips.jsx +++ b/app/trips/my-trips/MyTrips.jsx @@ -1,19 +1,19 @@ "use client"; import Link from "next/link"; -import { useRouter } from "next/navigation"; +import { useRouter, usePathname } from "next/navigation"; import { useEffect, useState } from "react"; import Loading from "@/app/utils/Loading"; import mapping from "@/app/data.json"; const MyTrips = () => { const router = useRouter(); - + const pathname = usePathname(); const [myTrips, setMyTrips] = useState(null); const getDetails = async () => { if (!localStorage.getItem("travelbuddy")) { - router.push("/authenticate"); + router.push("/authenticate?redirect_path=" + pathname); return; } const res = await fetch("/api/trips/find", { diff --git a/app/trips/trip/[tripID]/TripDetails.jsx b/app/trips/trip/[tripID]/TripDetails.jsx index 3d9df84..f433fc7 100644 --- a/app/trips/trip/[tripID]/TripDetails.jsx +++ b/app/trips/trip/[tripID]/TripDetails.jsx @@ -1,20 +1,28 @@ "use client"; -import { useRouter } from "next/navigation"; +import { useRouter, usePathname } from "next/navigation"; import { useEffect, useState } from "react"; import Loading from "@/app/utils/Loading"; import mapping from "@/app/data.json"; +import { userInstituteDetails } from "@/app/utils/institute"; -const TripDetails = ({ tripID }) => { +const TripDetails = ({ tripID, email }) => { const router = useRouter(); - + const pathname = usePathname(); const [data, setData] = useState(null); + const [locations, setLocations] = useState({}); const getDetails = async () => { if (!localStorage.getItem("travelbuddy")) { - router.push("/authenticate"); + router.push("/authenticate?redirect_path=" + pathname); return; } + try { + const userInstitue = await userInstituteDetails({ email }); + setLocations(userInstitue.locations); + } catch (error) { + router.push("/"); + } const res = await fetch("/api/trips/find", { method: "POST", headers: { @@ -107,11 +115,11 @@ const TripDetails = ({ tripID }) => { Email: {data.trip.email}

- Source: {mapping.locations[data.trip.source]} + Source: {locations[data.trip.source]}

Destination:{" "} - {mapping.locations[data.trip.destination]} + {locations[data.trip.destination]}

Date:{" "} @@ -158,11 +166,11 @@ const TripDetails = ({ tripID }) => {

Source:{" "} - {mapping.locations[trip.source]} + {locations[trip.source]}

Destination:{" "} - {mapping.locations[trip.destination]} + {locations[trip.destination]}

Date:{" "} diff --git a/app/trips/trip/[tripID]/page.jsx b/app/trips/trip/[tripID]/page.jsx index 026ce4f..dcd1df0 100644 --- a/app/trips/trip/[tripID]/page.jsx +++ b/app/trips/trip/[tripID]/page.jsx @@ -1,4 +1,7 @@ +import { connectToDatabase } from "@/app/lib/mongodb"; import TripDetails from "./TripDetails"; +import Trip from "@/app/models/Trip"; +import { redirect } from "next/navigation"; export const metadata = { title: "Trip Details", @@ -7,7 +10,19 @@ export const metadata = { const Page = async ({ params }) => { const tripID = params.tripID; - return ; + await connectToDatabase(); + + const trip = await Trip.findOne({ + tripID: tripID + }); + + if(!trip){ + redirect('/'); + } + + const tripData = JSON.parse(JSON.stringify(trip)); + + return ; }; export default Page; diff --git a/app/utils/auth.js b/app/utils/auth.js index 158d915..c12b1b2 100644 --- a/app/utils/auth.js +++ b/app/utils/auth.js @@ -48,4 +48,4 @@ export async function checkAuth() { } return email; -} +} \ No newline at end of file diff --git a/app/utils/institute.js b/app/utils/institute.js index fc2863a..b300461 100644 --- a/app/utils/institute.js +++ b/app/utils/institute.js @@ -2,6 +2,7 @@ import { connectToDatabase } from "@/app/lib/mongodb"; import Institute from "@/app/models/Institute"; +import User from "@/app/models/User"; export async function instituteDetails({ instituteCode }) { if (!instituteCode || instituteCode == "") { @@ -14,17 +15,31 @@ export async function instituteDetails({ instituteCode }) { if (!institute) { throw new Error("Institute not found."); } - if(!institute.authLink || institute.authLink.trim() == ""){ + if (!institute.domain) { + throw new Error("Institute authentication Email domain not found."); + } + if (!institute.authLink || institute.authLink.trim() == "") { throw new Error("Institute authentication URL not found."); } - if(!institute.verifyAuthLink || institute.verifyAuthLink.trim() == ""){ + if (!institute.verifyAuthLink || institute.verifyAuthLink.trim() == "") { throw new Error("Institute authentication verification URL not found."); } - if(!institute.authCookie){ + if (!institute.authCookie) { throw new Error("Institute authentication cookie not found."); } - return institute; + const data = JSON.parse(JSON.stringify(institute)); + return data; } - +export async function userInstituteDetails({ email }) { + await connectToDatabase(); + const user = await User.findOne({ + email: email + }); + if (!user) { + throw new Error("User not found."); + } + const institute = await instituteDetails({ instituteCode: user.instituteCode }); + return institute; +} diff --git a/package-lock.json b/package-lock.json index bc7334e..0df003d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,13 @@ "@next/third-parties": "^15.0.3", "axios": "^1.7.7", "bootstrap": "^5.3.3", + "crypto-js": "^4.2.0", "jsonwebtoken": "^9.0.2", "mongo-sanitize": "^1.1.0", "mongoose": "^8.7.0", "next": "14.2.13", + "nodemailer": "^6.9.16", + "otp-generator": "^4.0.1", "react": "^18", "react-bootstrap": "^2.10.5", "react-dom": "^18", @@ -705,6 +708,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1466,6 +1475,15 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/nodemailer": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", + "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1492,6 +1510,15 @@ "node": ">= 6" } }, + "node_modules/otp-generator": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/otp-generator/-/otp-generator-4.0.1.tgz", + "integrity": "sha512-2TJ52vUftA0+J3eque4wwVtpaL4/NdIXDL0gFWFJFVUAZwAN7+9tltMhL7GCNYaHJtuONoier8Hayyj4HLbSag==", + "license": "MIT", + "engines": { + "node": ">=14.10.0" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", diff --git a/package.json b/package.json index 90bbf92..8427ef1 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,13 @@ "@next/third-parties": "^15.0.3", "axios": "^1.7.7", "bootstrap": "^5.3.3", + "crypto-js": "^4.2.0", "jsonwebtoken": "^9.0.2", "mongo-sanitize": "^1.1.0", "mongoose": "^8.7.0", "next": "14.2.13", + "nodemailer": "^6.9.16", + "otp-generator": "^4.0.1", "react": "^18", "react-bootstrap": "^2.10.5", "react-dom": "^18",