Fixed the login again?

This commit is contained in:
2026-06-26 22:30:54 +02:00
parent cd37d5a987
commit 39fb39db12
5 changed files with 51 additions and 58 deletions
+29 -21
View File
@@ -1,4 +1,4 @@
import { NextRequest, NextResponse } from "next/server" import { NextRequest } from "next/server"
import { import {
comparePassword, comparePassword,
getUserByEmail, getUserByEmail,
@@ -13,6 +13,13 @@ import {
SESSION_COOKIE, SESSION_COOKIE,
} from "@/lib/auth" } from "@/lib/auth"
function jsonResponse(data: unknown, status: number) {
return new Response(JSON.stringify(data), {
status,
headers: { "Content-Type": "application/json" },
})
}
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const { email, username, password } = await request.json() const { email, username, password } = await request.json()
@@ -20,16 +27,16 @@ export async function POST(request: NextRequest) {
const credential = email || username const credential = email || username
if (!credential || !password) { if (!credential || !password) {
return NextResponse.json( return jsonResponse(
{ error: "Email/Username and password are required." }, { error: "Email/Username and password are required." },
{ status: 400 } 400
) )
} }
if (credential.trim().length === 0 || password.trim().length === 0) { if (credential.trim().length === 0 || password.trim().length === 0) {
return NextResponse.json( return jsonResponse(
{ error: "Credentials cannot be empty." }, { error: "Credentials cannot be empty." },
{ status: 400 } 400
) )
} }
@@ -59,9 +66,9 @@ export async function POST(request: NextRequest) {
false, false,
"User not found" "User not found"
) )
return NextResponse.json( return jsonResponse(
{ error: "Invalid email/username or password." }, { error: "Invalid email/username or password." },
{ status: 401 } 401
) )
} }
@@ -75,9 +82,9 @@ export async function POST(request: NextRequest) {
false, false,
lockStatus.reason lockStatus.reason
) )
return NextResponse.json( return jsonResponse(
{ error: lockStatus.reason }, { error: lockStatus.reason },
{ status: 423 } 423
) )
} }
@@ -92,9 +99,9 @@ export async function POST(request: NextRequest) {
false, false,
"Invalid password" "Invalid password"
) )
return NextResponse.json( return jsonResponse(
{ error: "Invalid email/username or password." }, { error: "Invalid email/username or password." },
{ status: 401 } 401
) )
} }
@@ -112,19 +119,20 @@ export async function POST(request: NextRequest) {
const user = mapDbUserToSessionUser(dbUser) const user = mapDbUserToSessionUser(dbUser)
const response = NextResponse.json({ user }, { status: 200 }) const cookieStr = `${SESSION_COOKIE}=${token}; HttpOnly; SameSite=Strict; Path=/; Max-Age=86400${process.env.NODE_ENV === "production" ? "; Secure" : ""}`
response.cookies.set(SESSION_COOKIE, token, {
httpOnly: true, return new Response(JSON.stringify({ user }), {
secure: process.env.NODE_ENV === "production", status: 200,
sameSite: "strict", headers: {
path: "/", "Content-Type": "application/json",
"Set-Cookie": cookieStr,
},
}) })
return response
} catch (error) { } catch (error) {
console.error("Login error:", error) console.error("Login error:", error)
return NextResponse.json( return new Response(
{ error: "Authentication service unavailable." }, JSON.stringify({ error: "Authentication service unavailable." }),
{ status: 503 } { status: 503, headers: { "Content-Type": "application/json" } }
) )
} }
} }
+9 -12
View File
@@ -1,22 +1,19 @@
import { NextResponse } from "next/server"
import { SESSION_COOKIE } from "@/lib/auth" import { SESSION_COOKIE } from "@/lib/auth"
export async function POST() { export async function POST() {
try { try {
const response = NextResponse.json({ success: true }, { status: 200 }) return new Response(JSON.stringify({ success: true }), {
response.cookies.set(SESSION_COOKIE, "", { status: 200,
httpOnly: true, headers: {
secure: process.env.NODE_ENV === "production", "Content-Type": "application/json",
sameSite: "strict", "Set-Cookie": `${SESSION_COOKIE}=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0`,
path: "/", },
maxAge: 0,
}) })
return response
} catch (error) { } catch (error) {
console.error("Logout error:", error) console.error("Logout error:", error)
return NextResponse.json( return new Response(
{ error: "Logout failed." }, JSON.stringify({ error: "Logout failed." }),
{ status: 500 } { status: 500, headers: { "Content-Type": "application/json" } }
) )
} }
} }
+1 -1
View File
@@ -62,7 +62,7 @@ export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: Side
> >
{/* Logo */} {/* Logo */}
<div className={cn("flex h-16 items-center border-b border-sidebar-border px-4", collapsed ? "justify-center" : "justify-between")}> <div className={cn("flex h-16 items-center border-b border-sidebar-border px-4", collapsed ? "justify-center" : "justify-between")}>
<Link href="/" className="flex items-center gap-3 overflow-hidden"> <Link href="/dashboard" className="flex items-center gap-3 overflow-hidden">
{collapsed ? ( {collapsed ? (
<div className="bc-logo" style={{padding: "8px 3px", fontSize: "16px"}}>B<span className="accent">C</span></div> <div className="bc-logo" style={{padding: "8px 3px", fontSize: "16px"}}>B<span className="accent">C</span></div>
) : ( ) : (
+11 -6
View File
@@ -2,14 +2,19 @@ import { SignJWT, jwtVerify } from "jose";
import bcrypt from "bcryptjs"; import bcrypt from "bcryptjs";
import { query } from "@/lib/db"; import { query } from "@/lib/db";
import { cookies } from "next/headers"; import { cookies } from "next/headers";
import crypto from "crypto";
const RAW_SECRET = process.env.NODE_ENV === "production" function randomHex(length: number): string {
? process.env.JWT_SECRET const chars = "abcdef0123456789";
: crypto.randomUUID(); let result = "";
if (!RAW_SECRET) { for (let i = 0; i < length; i++) {
throw new Error("JWT_SECRET environment variable is required"); result += chars[Math.floor(Math.random() * chars.length)];
} }
return result;
}
const g = globalThis as typeof globalThis & { __JWT_RAW_SECRET?: string };
const RAW_SECRET = process.env.JWT_SECRET || (g.__JWT_RAW_SECRET ??= randomHex(32));
if (!RAW_SECRET) throw new Error("JWT_SECRET environment variable is required");
const JWT_SECRET = new TextEncoder().encode(RAW_SECRET); const JWT_SECRET = new TextEncoder().encode(RAW_SECRET);
export const SESSION_COOKIE = "session"; export const SESSION_COOKIE = "session";
-17
View File
@@ -1,12 +1,5 @@
import { NextResponse } from "next/server" import { NextResponse } from "next/server"
import type { NextRequest } from "next/server" import type { NextRequest } from "next/server"
import { jwtVerify } from "jose"
const RAW_SECRET = process.env.JWT_SECRET
if (!RAW_SECRET) {
throw new Error("JWT_SECRET environment variable is required")
}
const JWT_SECRET = new TextEncoder().encode(RAW_SECRET)
const publicRoutes = [ const publicRoutes = [
"/login", "/login",
@@ -41,17 +34,7 @@ export async function middleware(request: NextRequest) {
return NextResponse.redirect(loginUrl) return NextResponse.redirect(loginUrl)
} }
try {
await jwtVerify(sessionCookie, JWT_SECRET)
return NextResponse.next() return NextResponse.next()
} catch {
if (pathname.startsWith("/api/")) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}
const loginUrl = new URL("/login", request.url)
loginUrl.searchParams.set("redirect", pathname)
return NextResponse.redirect(loginUrl)
}
} }
export const config = { export const config = {