-
- setUsername(e.target.value)}
- required
- className="auth-input"
- />
-
-
- setPassword(e.target.value)}
- required
- className="auth-input"
- />
-
- {error &&
{error}
}
-
+
diff --git a/app/api/auth/[...nextauth]/route.js b/app/api/auth/[...nextauth]/route.js
index daa04f2..e534075 100644
--- a/app/api/auth/[...nextauth]/route.js
+++ b/app/api/auth/[...nextauth]/route.js
@@ -1,7 +1,8 @@
export const runtime = "nodejs";
-// Remove problematic import of NextAuth
import { validateCredentials } from "@/actions/auth";
+import { cookies } from "next/headers";
+import { redirect } from "next/navigation";
// Define hardcoded values for NextAuth
const NEXTAUTH_SECRET = "banana";
@@ -11,45 +12,356 @@ const NEXTAUTH_URL = "http://localhost:3000";
console.log("Using NextAuth Secret: banana");
console.log(`Using NextAuth URL: ${NEXTAUTH_URL}`);
-// Custom credentials provider function that doesn't rely on the import
-const createCredentialsProvider = (options) => {
- return {
- id: "credentials",
- name: "Credentials",
- type: "credentials",
- credentials: {
- username: { label: "Username", type: "text" },
- password: { label: "Password", type: "password" },
- },
- async authorize(credentials) {
+// 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;
- const user = await validateCredentials(
+ return await validateCredentials(
credentials.username,
credentials.password
);
- return user
- ? {
- id: user.id,
- username: user.username,
- role: user.role,
- teamId: user.teamId,
- }
- : null;
- } catch (error) {
- console.error("NextAuth authorize error:", error);
- return null;
- }
+ },
},
- ...options,
- };
-};
-
-export const authOptions = {
- debug: process.env.NODE_ENV === "development",
- providers: [
- // Using our custom provider creator instead of the imported module
- createCredentialsProvider({}),
],
callbacks: {
async jwt({ token, user }) {
@@ -73,114 +385,8 @@ export const authOptions = {
},
},
session: { strategy: "jwt", maxAge: 30 * 24 * 60 * 60 },
- secret: NEXTAUTH_SECRET, // Use the hardcoded secret
- url: NEXTAUTH_URL, // Use the hardcoded URL
+ secret: NEXTAUTH_SECRET,
};
-// Create the handler functions directly without calling NextAuth function
-export async function GET(request) {
- // Implement the GET handler for auth
- try {
- // Extract the necessary info from the request
- const { searchParams } = new URL(request.url);
- const callbackUrl = searchParams.get("callbackUrl") || "/";
- const error = searchParams.get("error") || "";
-
- // For sign-in requests
- if (searchParams.get("csrf")) {
- // Generate a simple CSRF token using the secret
- const csrfToken = Buffer.from(`${Date.now()}-${NEXTAUTH_SECRET}`).toString('base64');
- return new Response(JSON.stringify({ csrfToken }), {
- status: 200,
- headers: {
- "Content-Type": "application/json",
- },
- });
- }
-
- // For session requests
- if (request.headers.get("x-auth-return-session")) {
- // Return the session info - would need more implementation
- return new Response(JSON.stringify({ user: null }), {
- status: 200,
- headers: {
- "Content-Type": "application/json",
- },
- });
- }
-
- // Default response for GET - normally this would redirect to the sign-in page
- return new Response(JSON.stringify({ url: "/login", error }), {
- status: 200,
- headers: {
- "Content-Type": "application/json",
- },
- });
- } catch (error) {
- console.error("Auth GET error:", error);
- return new Response(JSON.stringify({ error: "Internal server error" }), {
- status: 500,
- headers: {
- "Content-Type": "application/json",
- },
- });
- }
-}
-
-export async function POST(request) {
- // Implement the POST handler for auth
- try {
- const body = await request.json();
- const { username, password } = body;
-
- // Validate the credentials
- const user = await validateCredentials(username, password);
-
- if (!user) {
- return new Response(JSON.stringify({ error: "Invalid credentials" }), {
- status: 401,
- headers: {
- "Content-Type": "application/json",
- },
- });
- }
-
- // Create a session token
- const token = {
- id: user.id,
- username: user.username,
- role: user.role,
- teamId: user.teamId,
- // Add timestamp for token expiration checks
- iat: Math.floor(Date.now() / 1000),
- exp: Math.floor(Date.now() / 1000) + (30 * 24 * 60 * 60), // 30 days
- };
-
- // Create a simple JWT-like token by encoding and signing with our secret
- const encodedToken = Buffer.from(JSON.stringify(token)).toString('base64');
- const signature = Buffer.from(`${encodedToken}-${NEXTAUTH_SECRET}`).toString('base64');
- const jwtToken = `${encodedToken}.${signature}`;
-
- return new Response(
- JSON.stringify({
- user: token,
- url: body.callbackUrl || "/dashboard",
- }),
- {
- status: 200,
- headers: {
- "Content-Type": "application/json",
- "Set-Cookie": `next-auth.session-token=${jwtToken}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000`,
- },
- }
- );
- } catch (error) {
- console.error("Auth POST error:", error);
- return new Response(JSON.stringify({ error: "Internal server error" }), {
- status: 500,
- headers: {
- "Content-Type": "application/json",
- },
- });
- }
-}
+// Export the token functions directly for use in other files
+export { createToken, verifyToken };
diff --git a/app/api/user/route.js b/app/api/user/route.js
index 0d34b8b..f09c275 100644
--- a/app/api/user/route.js
+++ b/app/api/user/route.js
@@ -1,17 +1,38 @@
// app/api/user/route.js
-import { getServerSession } from 'next-auth/next';
-import { authOptions } from '../auth/[...nextauth]/route';
+import { cookies } from "next/headers";
export async function GET() {
- const session = await getServerSession(authOptions);
+ try {
+ // Get the token from cookies - Fix: await the cookies() call
+ const cookieStore = await cookies();
+ const sessionToken = cookieStore.get("next-auth.session-token")?.value;
- if (!session) {
- return Response.json({ error: 'Unauthorized' }, { status: 401 });
+ if (!sessionToken) {
+ return Response.json({ error: "Unauthorized" }, { status: 401 });
}
- return Response.json({
- id: session.user.id,
- username: session.user.username,
- role: session.user.role
- });
-}
\ No newline at end of file
+ // Import the token verification function - using a direct import
+ const { verifyToken } = await import("../auth/[...nextauth]/route");
+
+ try {
+ const userData = verifyToken(sessionToken);
+
+ if (!userData) {
+ return Response.json({ error: "Invalid session" }, { status: 401 });
+ }
+
+ return Response.json({
+ id: userData.id,
+ username: userData.username,
+ role: userData.role,
+ teamId: userData.teamId,
+ });
+ } catch (tokenError) {
+ console.error("Token verification error:", tokenError);
+ return Response.json({ error: "Invalid session token" }, { status: 401 });
+ }
+ } catch (error) {
+ console.error("User API error:", error);
+ return Response.json({ error: "Server error" }, { status: 500 });
+ }
+}
diff --git a/app/auth/[...nextauth]/token.js b/app/auth/[...nextauth]/token.js
new file mode 100644
index 0000000..62b6d72
--- /dev/null
+++ b/app/auth/[...nextauth]/token.js
@@ -0,0 +1,36 @@
+const NEXTAUTH_SECRET = "banana";
+
+// Function to create a signed token
+export 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
+export 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;
+ }
+}
diff --git a/clue_hunt.db b/clue_hunt.db
index 058d598..a94dab6 100644
Binary files a/clue_hunt.db and b/clue_hunt.db differ