export const runtime = "nodejs"; import { validateCredentials } from "@/actions/auth"; import { cookies } from "next/headers"; import { redirect } from "next/navigation"; // Define hardcoded values for NextAuth const NEXTAUTH_SECRET = "banana"; const NEXTAUTH_URL = "http://localhost:3000"; // Use hardcoded values instead of environment variables console.log("Using NextAuth Secret: banana"); console.log(`Using NextAuth URL: ${NEXTAUTH_URL}`); // Function to create a signed token function createToken(payload) { const encodedData = Buffer.from(JSON.stringify(payload)).toString("base64"); const signature = Buffer.from(`${encodedData}.${NEXTAUTH_SECRET}`).toString( "base64" ); return `${encodedData}.${signature}`; } // Function to verify a token function verifyToken(token) { try { if (!token) return null; const [encodedData, signature] = token.split("."); const expectedSignature = Buffer.from( `${encodedData}.${NEXTAUTH_SECRET}` ).toString("base64"); if (signature !== expectedSignature) return null; const data = JSON.parse( Buffer.from(encodedData, "base64").toString("utf-8") ); // Check if token is expired if (data.exp && data.exp < Math.floor(Date.now() / 1000)) return null; return data; } catch (error) { console.error("Token verification error:", error); return null; } } // Create the handler functions directly without calling NextAuth function export async function GET(request) { try { // Safely parse URL with error handling let pathname = "/"; let searchParams = new URLSearchParams(); try { if (request && request.url) { const url = new URL(request.url); pathname = url.pathname; searchParams = url.searchParams; } else { console.warn("Request or request.url is undefined"); } } catch (urlError) { console.error("Failed to parse URL:", urlError); pathname = request.nextUrl?.pathname || "/api/auth/session"; } console.log("Auth GET request path:", pathname); // Handle session requests if (pathname.includes("/session")) { try { const cookieStore = await cookies(); const sessionToken = cookieStore.get("next-auth.session-token")?.value; if (!sessionToken) { return Response.json({ user: null }); } const userData = verifyToken(sessionToken); return Response.json({ user: userData ? { id: userData.id, name: userData.username, email: `${userData.username}@example.com`, // Next-auth expects an email image: null, role: userData.role, teamId: userData.teamId, } : null, }); } catch (cookieError) { console.error("Error accessing cookies:", cookieError); return Response.json({ user: null }); } } // For CSRF token requests - IMPORTANT: Return JSON, don't redirect if (pathname.includes("/csrf")) { const csrfToken = Buffer.from( `csrf-${Date.now()}-${NEXTAUTH_SECRET}` ).toString("base64"); return new Response(JSON.stringify({ csrfToken }), { status: 200, headers: { "Content-Type": "application/json" }, }); } // Check if this is a signin request if (pathname.includes("/signin")) { // Redirect to the login page with callbackUrl const callbackUrl = searchParams.get("callbackUrl") || "/"; return Response.redirect( `${NEXTAUTH_URL}/login?callbackUrl=${encodeURIComponent(callbackUrl)}` ); } // Handle providers request if (pathname.includes("/providers")) { return Response.json({ credentials: { id: "credentials", name: "Credentials", type: "credentials", signinUrl: `${NEXTAUTH_URL}/api/auth/signin/credentials`, callbackUrl: `${NEXTAUTH_URL}/api/auth/callback/credentials`, }, }); } // Default: redirect to login return Response.redirect(`${NEXTAUTH_URL}/login`); } catch (error) { console.error("Auth GET error:", error); return Response.json({ error: "Internal server error" }, { status: 500 }); } } export async function POST(request) { try { // Debug the actual URL value before trying to parse it console.log("Raw request URL:", request.url); // More robust URL parsing let pathname = ""; let requestUrl = null; try { // Add protocol and host if the URL doesn't have them const urlString = request.url; // Only try to parse if we have a string if (typeof urlString === "string" && urlString) { // Check if URL has proper protocol const hasProtocol = urlString.startsWith("http://") || urlString.startsWith("https://"); if (hasProtocol) { requestUrl = new URL(urlString); } else { // Add protocol and host to handle relative URLs requestUrl = new URL(urlString, "http://localhost:3000"); } pathname = requestUrl.pathname; console.log("Successfully parsed URL:", pathname); } else { console.log("URL is not valid, using fallback methods"); } } catch (urlError) { console.error("Invalid URL in request:", urlError); } // Use nextUrl as fallback if available if (!pathname && request.nextUrl?.pathname) { pathname = request.nextUrl.pathname; console.log("Using nextUrl pathname:", pathname); } const contentType = request.headers.get("content-type") || ""; console.log("Content-Type:", contentType); // Check for callback/credentials without relying on URL parsing const isCallbackRequest = pathname.includes("/callback/credentials") || (request.url && request.url.includes("/callback/credentials")); if (isCallbackRequest) { // Extract credentials from the appropriate source based on content type let username, password, callbackUrl; if (contentType.includes("application/json")) { // Handle JSON request const jsonData = await request.json(); username = jsonData.username; password = jsonData.password; callbackUrl = jsonData.callbackUrl || "/team-selection"; console.log("Parsed JSON credentials from callback request"); } else if ( contentType.includes("application/x-www-form-urlencoded") || contentType.includes("multipart/form-data") ) { // Handle form data const formData = await request.formData(); username = formData.get("username"); password = formData.get("password"); callbackUrl = formData.get("callbackUrl") || "/team-selection"; console.log("Parsed form credentials from callback request"); } else { return Response.json( { error: "Unsupported content type for authentication" }, { status: 400 } ); } console.log(`Credentials callback for user: ${username || "(unknown)"}`); // Validate credentials const user = await validateCredentials(username, password); if (!user) { return Response.json({ error: "Invalid credentials" }, { status: 401 }); } // Create token with user data const token = { id: user.id, username: user.username, role: user.role, teamId: user.teamId, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, // 30 days }; // Create JWT-like token const jwtToken = createToken(token); // Calculate expiration date for cookie const expiryDate = new Date(); expiryDate.setDate(expiryDate.getDate() + 30); // Return success with cookie and redirect URL return new Response(JSON.stringify({ url: callbackUrl }), { status: 200, headers: { "Content-Type": "application/json", "Set-Cookie": `next-auth.session-token=${jwtToken}; Path=/; HttpOnly; SameSite=Lax; Expires=${expiryDate.toUTCString()}`, }, }); } // Handle regular form submission let username, password, callbackUrl; // Handle form data (application/x-www-form-urlencoded) if (contentType.includes("application/x-www-form-urlencoded")) { // Clone request to read the body const clonedRequest = request.clone(); const formText = await clonedRequest.text(); console.log("Form data:", formText); // Parse form data manually const params = new URLSearchParams(formText); username = params.get("username"); password = params.get("password"); callbackUrl = params.get("callbackUrl") || "/team-selection"; console.log("Parsed credentials:", { username, password: password ? "[REDACTED]" : undefined, }); } // Handle JSON else if (contentType.includes("application/json")) { const jsonData = await request.json(); username = jsonData.username; password = jsonData.password; callbackUrl = jsonData.callbackUrl || "/team-selection"; } // Try form data as a last resort else { try { const formData = await request.formData(); username = formData.get("username"); password = formData.get("password"); callbackUrl = formData.get("callbackUrl") || "/team-selection"; } catch (formError) { console.error("Form data parsing error:", formError); // Instead of returning an error, log and continue - the request might be something else } } if (!username || !password) { // If no credentials were found, this might be a different kind of request // Log it and return an appropriate error console.error("Missing credentials in request"); return Response.json( { error: "Username and password required" }, { status: 400 } ); } // Validate credentials const user = await validateCredentials(username, password); if (!user) { return Response.json({ error: "Invalid credentials" }, { status: 401 }); } // Create token with user data const token = { id: user.id, username: user.username, role: user.role, teamId: user.teamId, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60, // 30 days }; // Create JWT-like token const jwtToken = createToken(token); // Calculate expiration date for cookie const expiryDate = new Date(); expiryDate.setDate(expiryDate.getDate() + 30); // Return success with cookie and redirect URL return new Response( JSON.stringify({ ok: true, url: callbackUrl, }), { status: 200, headers: { "Content-Type": "application/json", "Set-Cookie": `next-auth.session-token=${jwtToken}; Path=/; HttpOnly; SameSite=Lax; Expires=${expiryDate.toUTCString()}`, }, } ); } catch (error) { console.error("Auth POST error:", error); return Response.json({ error: "Internal server error" }, { status: 500 }); } } // Export authOptions for compatibility with other code using getServerSession export const authOptions = { providers: [ { id: "credentials", name: "Credentials", type: "credentials", authorize: async (credentials) => { if (!credentials?.username || !credentials?.password) return null; return await validateCredentials( credentials.username, credentials.password ); }, }, ], callbacks: { async jwt({ token, user }) { if (user) { token.id = user.id; token.role = user.role; token.username = user.username; token.teamId = user.teamId; } return token; }, async session({ session, token }) { if (token) { session.user = session.user || {}; session.user.id = token.id; session.user.role = token.role; session.user.username = token.username; session.user.teamId = token.teamId; } return session; }, }, session: { strategy: "jwt", maxAge: 30 * 24 * 60 * 60 }, secret: NEXTAUTH_SECRET, }; // Export the token functions directly for use in other files export { createToken, verifyToken };