From c79a9636a70ed8b71884b747ce07e72be9750168 Mon Sep 17 00:00:00 2001 From: mudabbir-ahmad Date: Sat, 15 Mar 2025 00:22:47 +0000 Subject: [PATCH] FIX OF ALL ISSUES WITH LOG IN PAGE. EVERYTHING TO THE POINT OF TESTING THE MAP WORKS SO FAR. --- app/(main)/create-team/page.js | 21 ++++-- app/(main)/team/[teamId]/members/route.js | 25 ++++--- app/(main)/team/[teamId]/page.js | 47 +++++++------ app/(main)/team/[teamId]/route.js | 33 ++++----- app/api/auth/[...nextauth]/route.js | 34 +++------- app/api/teams/[teamId]/route.js | 55 +++++++-------- app/api/teams/create/route.js | 81 +++++++++++++++-------- app/api/teams/route.js | 12 +++- app/api/user/route.js | 79 +++++++++------------- app/api/user/team/route.js | 74 +++++---------------- 10 files changed, 228 insertions(+), 233 deletions(-) diff --git a/app/(main)/create-team/page.js b/app/(main)/create-team/page.js index 0d8b408..11ff6de 100644 --- a/app/(main)/create-team/page.js +++ b/app/(main)/create-team/page.js @@ -15,8 +15,8 @@ export default function CreateTeam() { useEffect(() => { if (status === "authenticated") { - if (session.user.teamId) { - router.push(`/team/${session.user.teamId}`); + if (session?.user?.teamId) { + router.push(`/${session.user.teamId}`); } else { setIsLoading(false); } @@ -31,12 +31,17 @@ export default function CreateTeam() { setError(""); try { + // Add a check to ensure session is valid + if (status !== "authenticated") { + throw new Error("You must be logged in to create a team"); + } + const response = await fetch("/api/teams", { method: "POST", headers: { "Content-Type": "application/json", }, - credentials: "include", + credentials: "include", // Important: include credentials body: JSON.stringify({ name: teamName }), }); @@ -46,12 +51,18 @@ export default function CreateTeam() { throw new Error(data.error || "Failed to create team"); } - // Navigate with the correct URL pattern for the page component console.log("Team created successfully with ID:", data.teamId); - // Hard navigation to make sure we're going to the page component not the API route + // Force a session refresh to update the session with the new teamId + await fetch("/api/auth/session", { + method: "GET", + credentials: "include", + }); + + // Hard navigation to make sure we're going to the right page window.location.href = `/${data.teamId}`; } catch (err) { + console.error("Team creation error:", err); setError(err.message); setIsLoading(false); } diff --git a/app/(main)/team/[teamId]/members/route.js b/app/(main)/team/[teamId]/members/route.js index 74675d9..4851676 100644 --- a/app/(main)/team/[teamId]/members/route.js +++ b/app/(main)/team/[teamId]/members/route.js @@ -1,12 +1,21 @@ import { query } from "../../../../lib/db"; -import { getServerSession } from "next-auth/next"; -import { authOptions } from "../../auth/[...nextauth]/route"; +import { cookies } from "next/headers"; +import { verifyToken } from "@/app/api/auth/[...nextauth]/route"; export async function GET(request, context) { - const session = await getServerSession(authOptions); + // Use cookie-based verification instead of getServerSession + const cookieStore = await cookies(); + const sessionToken = + cookieStore.get("next-auth.session-token")?.value || + cookieStore.get("__Secure-next-auth.session-token")?.value; - if (!session) { - return Response.json({ error: "Unauthorised" }, { status: 401 }); + if (!sessionToken) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + const userData = verifyToken(sessionToken); + if (!userData) { + return Response.json({ error: "Invalid session" }, { status: 401 }); } try { @@ -14,15 +23,15 @@ export async function GET(request, context) { // Convert teamId to number for proper comparison const requestedTeamId = parseInt(teamId, 10); - const userTeamId = parseInt(session.user.teamId, 10); + const userTeamId = parseInt(userData.teamId, 10); // Log for debugging console.log("Requested team:", requestedTeamId); console.log("User team:", userTeamId); - console.log("User role:", session.user.role); + console.log("User role:", userData.role); // Ensure the user is requesting their own team or is an admin - if (userTeamId !== requestedTeamId && session.user.role !== "admin") { + if (userTeamId !== requestedTeamId && userData.role !== "admin") { return Response.json( { error: "Not authorized to view this team" }, { status: 403 } diff --git a/app/(main)/team/[teamId]/page.js b/app/(main)/team/[teamId]/page.js index bd77353..c8e6a17 100644 --- a/app/(main)/team/[teamId]/page.js +++ b/app/(main)/team/[teamId]/page.js @@ -14,6 +14,9 @@ export default function TeamPage({ params }) { const router = useRouter(); const { teamId } = params; + // Use the authenticated teamId if available + const effectiveTeamId = session?.user?.teamId || teamId; + const checkHuntStatus = useCallback(async () => { try { const response = await fetch("/api/hunt-status", { @@ -33,11 +36,11 @@ export default function TeamPage({ params }) { const fetchTeamData = useCallback(async () => { try { - if (!teamId || isNaN(parseInt(teamId, 10))) { + if (!effectiveTeamId || isNaN(parseInt(effectiveTeamId, 10))) { throw new Error("Invalid team ID"); } - // First check if the user is still part of this team by fetching current user data + // Validate current user data against effectiveTeamId const userResponse = await fetch("/api/user", { method: "GET", headers: { @@ -50,26 +53,32 @@ export default function TeamPage({ params }) { const userData = await userResponse.json(); // If the user is not in this team anymore, redirect to team selection - if (!userData.teamId || userData.teamId != teamId) { + if ( + !userData.teamId || + parseInt(userData.teamId, 10) !== parseInt(effectiveTeamId, 10) + ) { console.log("User no longer in team, redirecting to team selection"); window.location.href = "/team-selection"; return; } - console.log("Fetching team data for team ID:", teamId); + console.log("Fetching team data for team ID:", effectiveTeamId); // Add timestamp to URL to ensure fresh data on every request const timestamp = new Date().getTime(); - const response = await fetch(`/api/teams/${teamId}?_t=${timestamp}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - "Cache-Control": "no-cache, no-store, must-revalidate", - Pragma: "no-cache", - }, - credentials: "include", - cache: "no-store", - }); + const response = await fetch( + `/api/teams/${effectiveTeamId}?_t=${timestamp}`, + { + method: "GET", + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-cache, no-store, must-revalidate", + Pragma: "no-cache", + }, + credentials: "include", + cache: "no-store", + } + ); if (!response.ok) { const text = await response.text(); @@ -95,6 +104,8 @@ export default function TeamPage({ params }) { setTeam(data.team); setTeamMembers(data.members || []); setError(""); + + await checkHuntStatus(); } catch (err) { console.error("Team page error:", err); setError(err.message); @@ -105,13 +116,11 @@ export default function TeamPage({ params }) { } finally { setLoading(false); } - - await checkHuntStatus(); - }, [teamId, router, checkHuntStatus]); + }, [effectiveTeamId, router, checkHuntStatus]); useEffect(() => { // Only attempt to fetch data when session is ready and we have a teamId - if (status === "authenticated" && teamId) { + if (status === "authenticated" && effectiveTeamId) { console.log("Session authenticated, fetching team data"); fetchTeamData(); @@ -124,7 +133,7 @@ export default function TeamPage({ params }) { console.log("User not authenticated, redirecting to login"); router.push("/login"); } - }, [teamId, status, fetchTeamData, router]); + }, [effectiveTeamId, status, fetchTeamData, router]); if (status === "loading" || loading) { return ( diff --git a/app/(main)/team/[teamId]/route.js b/app/(main)/team/[teamId]/route.js index 5da57e0..7c7c6b4 100644 --- a/app/(main)/team/[teamId]/route.js +++ b/app/(main)/team/[teamId]/route.js @@ -1,24 +1,27 @@ import { redirect } from "next/navigation"; -import { cookies } from "next/headers"; -import { verifyToken } from "@/app/api/auth/[...nextauth]/route"; +import { getToken } from "next-auth/jwt"; -export async function GET(_, context) { +export async function GET(request, context) { try { - // Use our custom token verification instead of getServerSession - const cookieStore = await cookies(); - const sessionToken = cookieStore.get("next-auth.session-token")?.value; - - if (!sessionToken) { + const token = await getToken({ + req: request, + secret: + process.env.NEXTAUTH_SECRET || + "your-fallback-secret-should-be-at-least-32-chars", + }); + if (!token) { return redirect("/login"); } - - const userData = verifyToken(sessionToken); - if (!userData) { - return redirect("/login"); + // If the token's teamId matches the requested teamId or the user is admin, redirect accordingly. + if ( + (token.teamId && + parseInt(token.teamId, 10) === parseInt(context.params.teamId, 10)) || + token.role === "admin" + ) { + return redirect(`/${context.params.teamId}`); } - - // Instead of returning JSON, redirect to the page component - return redirect(`/${context.params.teamId}`); + // If not, the user is not associated with this team. + return redirect("/team-selection"); } catch (error) { console.error("Team route error:", error); return redirect("/team-selection"); diff --git a/app/api/auth/[...nextauth]/route.js b/app/api/auth/[...nextauth]/route.js index 1d8d874..f314989 100644 --- a/app/api/auth/[...nextauth]/route.js +++ b/app/api/auth/[...nextauth]/route.js @@ -3,6 +3,7 @@ import CredentialsProvider from "next-auth/providers/credentials"; import { validateCredentials } from "@/actions/auth"; import jwt from "jsonwebtoken"; import { cookies } from "next/headers"; +import { queryOne } from "@/lib/db"; const JWT_SECRET = process.env.NEXTAUTH_SECRET || @@ -53,6 +54,14 @@ export const authOptions = { token.username = user.username; token.teamId = user.team_id || user.teamId; } + // If teamId is not set on the token, query DB for the latest value. + if (!token.teamId) { + const freshUser = await queryOne( + "SELECT team_id FROM users WHERE id = ?", + [token.id] + ); + token.teamId = freshUser?.team_id; + } return token; }, async session({ session, token }) { @@ -92,30 +101,9 @@ export function createToken(payload) { } export function verifyToken(token) { + if (!token) return null; try { - if (!token) return null; - const parts = token.split("."); - if (parts.length === 2) { - // Custom token verification - const [encodedData, signature] = parts; - const expectedSignature = Buffer.from( - `${encodedData}.${JWT_SECRET}` - ).toString("base64"); - if (signature !== expectedSignature) { - console.error("Signature mismatch"); - return null; - } - const data = JSON.parse( - Buffer.from(encodedData, "base64").toString("utf-8") - ); - if (data.exp && data.exp < Math.floor(Date.now() / 1000)) return null; - return data; - } else if (parts.length === 3) { - // Standard JWT verification - return jwt.verify(token, JWT_SECRET); - } else { - throw new Error("Invalid token format"); - } + return jwt.verify(token, JWT_SECRET); } catch (error) { console.error("Token verification error:", error); return null; diff --git a/app/api/teams/[teamId]/route.js b/app/api/teams/[teamId]/route.js index 6477b63..d4fad35 100644 --- a/app/api/teams/[teamId]/route.js +++ b/app/api/teams/[teamId]/route.js @@ -1,44 +1,40 @@ -import { query, queryOne } from "@/lib/db"; -import { cookies } from "next/headers"; -import { verifyToken } from "@/app/api/auth/[...nextauth]/route"; +import { queryOne, query } from "@/lib/db"; +import { getToken } from "next-auth/jwt"; export const dynamic = "force-dynamic"; // Disable static rendering and caching export async function GET(request, context) { try { - // Use our custom token verification - const cookieStore = await cookies(); - const sessionToken = cookieStore.get("next-auth.session-token")?.value; - - if (!sessionToken) { + const token = await getToken({ + req: request, + secret: + process.env.NEXTAUTH_SECRET || + "your-fallback-secret-should-be-at-least-32-chars", + }); + if (!token) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } - - const userData = verifyToken(sessionToken); - if (!userData) { - return Response.json({ error: "Invalid session" }, { status: 401 }); + // Use token.teamId for validation + if ( + !token.teamId || + parseInt(token.teamId, 10) !== parseInt(context.params.teamId, 10) + ) { + return Response.json( + { error: "Not authorized to view this team" }, + { status: 403 } + ); } - // FIXED: Always await context.params before accessing properties - // This approach ensures we properly handle the promise before accessing teamId - const params = await Promise.resolve(context.params); - const teamId = params?.teamId; - - // Validate teamId - if (!teamId || isNaN(parseInt(teamId, 10))) { - return Response.json({ error: "Invalid team ID" }, { status: 400 }); - } - - const team = await queryOne("SELECT * FROM teams WHERE id = ?", [teamId]); - + const team = await queryOne("SELECT * FROM teams WHERE id = ?", [ + token.teamId, + ]); if (!team) { return Response.json({ error: "Team not found" }, { status: 404 }); } - // Use a fresh query to get the latest team members each time const members = await query( "SELECT id, username FROM users WHERE team_id = ? ORDER BY username ASC", - [teamId] + [token.teamId] ); const timestamp = new Date().toISOString(); // Add timestamp for debugging @@ -62,10 +58,7 @@ export async function GET(request, context) { } ); } catch (error) { - console.error("Team fetch error:", error); - return Response.json( - { error: "Failed to fetch team data" }, - { status: 500 } - ); + console.error("Team route error:", error); + return Response.json({ error: "Server error" }, { status: 500 }); } } diff --git a/app/api/teams/create/route.js b/app/api/teams/create/route.js index 023fcbd..3f8a8cb 100644 --- a/app/api/teams/create/route.js +++ b/app/api/teams/create/route.js @@ -1,57 +1,86 @@ import { queryOne, run } from "@/lib/db"; -import { getServerSession } from "next-auth/next"; -import { authOptions } from "../../auth/[...nextauth]/route"; +import { cookies } from "next/headers"; +import { verifyToken } from "@/app/api/auth/[...nextauth]/route"; import { nanoid } from "nanoid"; export async function POST(request) { try { - const session = await getServerSession(authOptions); + const cookieStore = await cookies(); + // Check for both possible cookie names + const sessionToken = + cookieStore.get("next-auth.session-token")?.value || + cookieStore.get("__Secure-next-auth.session-token")?.value; - if (!session) { - return new Response( - JSON.stringify({ success: false, error: "Unauthorised" }), - { status: 401 } - ); + if (!sessionToken) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); } - if (session.user.role === "admin") { - return new Response( - JSON.stringify({ success: false, error: "Admins cannot create teams" }), + const userData = verifyToken(sessionToken); + if (!userData) { + // Add debugging info to help diagnose the issue + console.log( + "Invalid session token:", + sessionToken.substring(0, 10) + "..." + ); + return Response.json({ error: "Invalid session" }, { status: 401 }); + } + + if (userData.role === "admin") { + return Response.json( + { error: "Admins cannot create teams" }, { status: 403 } ); } - const { name, userId } = await request.json(); - + const { name } = await request.json(); if (!name || name.trim() === "") { - return new Response( - JSON.stringify({ success: false, error: "Team name is required" }), - { status: 400 } - ); + return Response.json({ error: "Team name is required" }, { status: 400 }); } const teamCode = nanoid(6).toUpperCase(); - await run("INSERT INTO teams (name, code) VALUES (?, ?)", [name, teamCode]); - const newTeam = await queryOne("SELECT id FROM teams WHERE code = ?", [ teamCode, ]); + if (!newTeam || !newTeam.id) { + throw new Error("Failed to create team"); + } + // Update the user's team association in DB await run("UPDATE users SET team_id = ? WHERE id = ?", [ newTeam.id, - userId, + userData.id, ]); - session.user.teamId = newTeam.id; + // Include the Set-Cookie header to update the session token with the new teamId + const updatedUserData = { + ...userData, + teamId: newTeam.id, + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, // 30 days + }; - return new Response(JSON.stringify({ success: true, teamId: newTeam.id }), { - status: 201, - }); + const { createToken } = await import("@/app/api/auth/[...nextauth]/route"); + const newToken = createToken(updatedUserData); + + // Calculate expiration date for cookie + const expiryDate = new Date(); + expiryDate.setDate(expiryDate.getDate() + 30); + + return new Response( + JSON.stringify({ success: true, teamId: newTeam.id, teamName: name }), + { + status: 201, + headers: { + "Content-Type": "application/json", + "Set-Cookie": `next-auth.session-token=${newToken}; Path=/; HttpOnly; SameSite=Lax; Expires=${expiryDate.toUTCString()}`, + }, + } + ); } catch (error) { console.error("Team creation error:", error); - return new Response( - JSON.stringify({ success: false, error: "Failed to create team" }), + return Response.json( + { error: "Failed to create team: " + error.message }, { status: 500 } ); } diff --git a/app/api/teams/route.js b/app/api/teams/route.js index 7e1f82b..54a2709 100644 --- a/app/api/teams/route.js +++ b/app/api/teams/route.js @@ -6,7 +6,10 @@ export async function POST(request) { try { // Use our custom token verification instead of getServerSession const cookieStore = await cookies(); - const sessionToken = cookieStore.get("next-auth.session-token")?.value; + // Check for both possible cookie names + const sessionToken = + cookieStore.get("next-auth.session-token")?.value || + cookieStore.get("__Secure-next-auth.session-token")?.value; if (!sessionToken) { return Response.json({ error: "Unauthorized" }, { status: 401 }); @@ -42,6 +45,13 @@ export async function POST(request) { userData.id, ]); + // Update the user's token with the new team information + const updatedUserData = { + ...userData, + teamId: teamResult.lastID, + iat: Math.floor(Date.now() / 1000), + }; + // Return success with the team ID for redirection return Response.json( { diff --git a/app/api/user/route.js b/app/api/user/route.js index bfe930a..6d06fe9 100644 --- a/app/api/user/route.js +++ b/app/api/user/route.js @@ -1,58 +1,45 @@ -// app/api/user/route.js import { queryOne } from "@/lib/db"; +import { getToken } from "next-auth/jwt"; import { cookies } from "next/headers"; export const dynamic = "force-dynamic"; // Disable caching -export async function GET() { +export async function GET(request) { try { - const cookieStore = await cookies(); - const sessionToken = - cookieStore.get("next-auth.session-token")?.value || - cookieStore.get("__Secure-next-auth.session-token")?.value; - - if (!sessionToken) { + // Use next-auth's getToken to safely decode the session token + const token = await getToken({ + req: request, + secret: + process.env.NEXTAUTH_SECRET || + "your-fallback-secret-should-be-at-least-32-chars", + }); + if (!token) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } - - const { verifyToken } = await import("../auth/[...nextauth]/route"); - - try { - const userData = verifyToken(sessionToken); - - if (!userData) { - return Response.json({ error: "Invalid session" }, { status: 401 }); - } - - const freshUserData = await queryOne( - "SELECT id, username, role, team_id FROM users WHERE id = ?", - [userData.id] - ); - - if (!freshUserData) { - return Response.json({ error: "User not found" }, { status: 404 }); - } - - return Response.json( - { - id: freshUserData.id, - username: freshUserData.username, - role: freshUserData.role, - teamId: freshUserData.team_id, - timestamp: new Date().toISOString(), - }, - { - headers: { - "Cache-Control": "no-store, must-revalidate", - Pragma: "no-cache", - Expires: "0", - }, - } - ); - } catch (tokenError) { - console.error("Token verification error:", tokenError); - return Response.json({ error: "Invalid session token" }, { status: 401 }); + // Fetch fresh user data from DB using token id + const freshUserData = await queryOne( + "SELECT id, username, role, team_id FROM users WHERE id = ?", + [token.id] + ); + if (!freshUserData) { + return Response.json({ error: "User not found" }, { status: 404 }); } + return Response.json( + { + id: freshUserData.id, + username: freshUserData.username, + role: freshUserData.role, + teamId: freshUserData.team_id, + timestamp: new Date().toISOString(), + }, + { + headers: { + "Cache-Control": "no-store, must-revalidate", + Pragma: "no-cache", + Expires: "0", + }, + } + ); } catch (error) { console.error("User API error:", error); return Response.json({ error: "Server error" }, { status: 500 }); diff --git a/app/api/user/team/route.js b/app/api/user/team/route.js index 5dab45b..1de85cc 100644 --- a/app/api/user/team/route.js +++ b/app/api/user/team/route.js @@ -1,80 +1,36 @@ -import { getServerSession } from "next-auth/next"; -import { authOptions } from "../../auth/[...nextauth]/route"; -import { query, queryOne } from "../../../../lib/db"; +import { query, queryOne } from "@/lib/db"; +import { getToken } from "next-auth/jwt"; export async function GET(request) { try { - // Get the user's session with better error handling - let session; - try { - session = await getServerSession(authOptions); - } catch (sessionError) { - console.error("Session fetch error:", sessionError); - return Response.json( - { error: "Authentication error", details: sessionError.message }, - { status: 401 } - ); + const token = await getToken({ + req: request, + secret: + process.env.NEXTAUTH_SECRET || + "your-fallback-secret-should-be-at-least-32-chars", + }); + if (!token) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); } - - // Check if user is authenticated - if (!session || !session.user) { - return Response.json({ error: "You must be logged in" }, { status: 401 }); - } - - // Check if user is part of a team - if (!session.user.teamId) { + if (!token.teamId) { return Response.json( { error: "You are not part of a team" }, { status: 404 } ); } - - // Fetch team data const team = await queryOne("SELECT * FROM teams WHERE id = ?", [ - session.user.teamId, + token.teamId, ]); - if (!team) { return Response.json({ error: "Team not found" }, { status: 404 }); } - - // Fetch team members + // Optionally, fetch additional team details (e.g. team members) const members = await query( "SELECT id, username FROM users WHERE team_id = ? ORDER BY username ASC", - [session.user.teamId] + [token.teamId] ); - - // Fetch hunt status to provide start time - safely check multiple places - let huntStartTime = null; - - try { - // Try hunt_status table first - const huntStatus = await queryOne("SELECT * FROM hunt_status LIMIT 1"); - if (huntStatus) { - huntStartTime = huntStatus.start_time; - } else { - // Fallback to hunts table if it exists - const oldHuntRecord = await queryOne( - "SELECT start_time FROM hunts ORDER BY id DESC LIMIT 1" - ); - if (oldHuntRecord) { - huntStartTime = oldHuntRecord.start_time; - } - } - } catch (err) { - console.log("Error fetching hunt status (non-critical):", err.message); - // Continue even if hunt status check fails - } - - // Add cache-busting headers return Response.json( - { - success: true, - team, - members, - huntStartTime: huntStartTime, - refreshed: new Date().toISOString(), - }, + { success: true, team, members }, { status: 200, headers: {