fixed a bit of stuff the current but API is broken again for logging in so will be fixing that with the next push

This commit is contained in:
bobbert committed 2025-03-14 19:33:13 +00:00
1 parent 6ad943a868
commit ea38775a82
20 files changed
+1643 -1027

No files matched your search

+99 -357
View File
@@ -1,378 +1,83 @@
export const runtime = "nodejs";
import NextAuth from "next-auth"; // <-- Changed from "next-auth/next"
import CredentialsProvider from "next-auth/providers/credentials";
import { validateCredentials } from "@/actions/auth";
import jwt from "jsonwebtoken";
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 a consistent secret for JWT signing and verification
const JWT_SECRET = process.env.NEXTAUTH_SECRET || "banana";
console.log("Using NextAuth Secret:", JWT_SECRET);
// 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: [
// Correct syntax: no function call, just reference the imported object
{
id: "credentials",
name: "Credentials",
type: "credentials",
authorize: async (credentials) => {
if (!credentials?.username || !credentials?.password) return null;
return await validateCredentials(
credentials.username,
credentials.password
);
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials) {
try {
if (!credentials?.username || !credentials?.password) {
console.log("Missing credentials");
return null;
}
// Validate credentials against our database
const user = await validateCredentials(
credentials.username,
credentials.password
);
if (user) {
console.log("User authenticated:", user.username);
return user;
}
console.log("Invalid credentials for:", credentials.username);
return null;
} catch (error) {
console.error("Authentication error:", error);
return null;
}
},
},
],
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
jwt: {
secret: JWT_SECRET,
encode: async ({ secret, token }) => {
return jwt.sign(token, secret);
},
decode: async ({ secret, token }) => {
try {
return jwt.verify(token, secret);
} catch (error) {
console.error("JWT decode error:", error);
return null;
}
},
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.role = user.role;
token.username = user.username;
token.teamId = user.teamId;
token.teamId = user.team_id || user.teamId;
}
return token;
},
async session({ session, token }) {
if (token) {
session.user = session.user || {};
@@ -384,9 +89,46 @@ export const authOptions = {
return session;
},
},
session: { strategy: "jwt", maxAge: 30 * 24 * 60 * 60 },
secret: NEXTAUTH_SECRET,
pages: {
signIn: "/login",
error: "/login?error=true",
},
// Add debugging to help identify issues with API responses
debug: process.env.NODE_ENV === "development",
// Add logger to catch issues
logger: {
error(code, metadata) {
console.error(`NextAuth error: ${code}`, metadata);
},
warn(code) {
console.warn(`NextAuth warning: ${code}`);
},
debug(code, metadata) {
console.log(`NextAuth debug: ${code}`, metadata);
},
},
};
// Export the token functions directly for use in other files
export { createToken, verifyToken };
// Token helpers
export function createToken(payload) {
return jwt.sign(payload, JWT_SECRET, { expiresIn: "30d" });
}
export function verifyToken(token) {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
console.error("Token verification error:", error);
return null;
}
}
// Set session runtime to nodejs (not edge for better compatibility)
export const runtime = "nodejs";
// Create the handler with correct NextAuth instantiation
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };