From c334d477a2c59e6683e9fbbc88526b94ec6ff106 Mon Sep 17 00:00:00 2001 From: hedun Date: Fri, 26 Jun 2026 10:22:28 +0100 Subject: [PATCH 1/2] fix: remove legacy auth route and jwt secret fallback --- .github/workflows/test.yml | 9 ++++ app/app/(auth)/logout/route.ts | 7 +-- app/app/(auth)/session/route.ts | 80 --------------------------------- app/lib/jwt.ts | 20 +++------ app/tests/auth.test.ts | 9 ++-- app/tests/jwt-config.test.ts | 25 +++++++++++ 6 files changed, 45 insertions(+), 105 deletions(-) delete mode 100644 app/app/(auth)/session/route.ts create mode 100644 app/tests/jwt-config.test.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3048941..79e4565 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,10 +58,19 @@ jobs: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/geev_test run: npx prisma migrate deploy + - name: Check production config secrets + run: | + if [ -z "${{ secrets.NEXTAUTH_SECRET }}" ]; then + echo "Error: NEXTAUTH_SECRET is missing from repository secrets." + echo "It must be present for production deployments." + exit 1 + fi + - name: Run tests env: DATABASE_URL: postgresql://postgres:postgres@localhost:5432/geev_test JWT_SECRET: test-secret-key + NEXTAUTH_SECRET: test-secret-key NODE_ENV: test run: npm run test:ci diff --git a/app/app/(auth)/logout/route.ts b/app/app/(auth)/logout/route.ts index 3e37abb..520f2d3 100644 --- a/app/app/(auth)/logout/route.ts +++ b/app/app/(auth)/logout/route.ts @@ -37,13 +37,10 @@ export async function POST(request: Request) { expires: new Date(0), // Set to epoch to ensure expiration }; - // 1. Clear the legacy auth-token - response.cookies.set("auth-token", "", cookieOptions); - - // 2. Clear Auth.js session token (standard) + // 1. Clear Auth.js session token (standard) response.cookies.set("next-auth.session-token", "", cookieOptions); - // 3. Clear Auth.js session token (secure version used in production/HTTPS) + // 2. Clear Auth.js session token (secure version used in production/HTTPS) response.cookies.set("__Secure-next-auth.session-token", "", cookieOptions); return response; diff --git a/app/app/(auth)/session/route.ts b/app/app/(auth)/session/route.ts deleted file mode 100644 index 4400e57..0000000 --- a/app/app/(auth)/session/route.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { getTokenFromRequest, verifyToken } from "@/lib/jwt"; - -import { NextResponse } from "next/server"; -import { prisma } from "@/lib/prisma"; - -/** - * Checks for an active session. - * - * @deprecated This endpoint is legacy. For new implementations, use Auth.js `auth()` - * or `useSession()` instead. - * - * @param request - The incoming Request object - * @returns A NextResponse with session data - */ -export async function GET(request: Request) { - try { - // Get token from cookies - const token = getTokenFromRequest(request); - - if (!token) { - return NextResponse.json( - { error: "No authentication token found" }, - { status: 401 }, - ); - } - - // Verify and decode token - const payload = await verifyToken(token); - console.log("Session payload:", payload); - // Find user in database - const user = await prisma.user.findUnique({ - where: { id: payload.userId }, - }); - - if (!user) { - return NextResponse.json({ error: "User not found" }, { status: 404 }); - } - - // Return session data - return NextResponse.json( - { - success: true, - user: { - id: user.id, - walletAddress: user.walletAddress, - username: user.name, - email: null, - avatar: user.avatarUrl, - bio: user.bio, - joinDate: user.createdAt, - }, - token: { - expiresAt: payload.exp - ? new Date(payload.exp * 1000).toISOString() - : null, - }, - }, - { - headers: { - // RFC 299: Miscellaneous persistent warning - Warning: - '299 - "Deprecated: This endpoint is legacy. Use Auth.js session instead."', - }, - }, - ); - } catch (error: any) { - if (error.message === "Invalid token") { - return NextResponse.json( - { error: "Invalid or expired token" }, - { status: 401 }, - ); - } - - console.error("Session check error:", error); - return NextResponse.json( - { error: "Internal server error" }, - { status: 500 }, - ); - } -} diff --git a/app/lib/jwt.ts b/app/lib/jwt.ts index e236584..d8bdf03 100644 --- a/app/lib/jwt.ts +++ b/app/lib/jwt.ts @@ -1,8 +1,10 @@ import { SignJWT, jwtVerify } from "jose"; -const secret = new TextEncoder().encode( - process.env.NEXTAUTH_SECRET || "your-secret-key-change-in-production", -); +if (!process.env.NEXTAUTH_SECRET) { + throw new Error("NEXTAUTH_SECRET is missing. It must be set in production config."); +} + +const secret = new TextEncoder().encode(process.env.NEXTAUTH_SECRET); export interface JWTPayload { userId: string; @@ -47,17 +49,5 @@ export function getTokenFromRequest(request: Request): string | null { return authHeader.slice(7); } - // Try to get from cookies - const cookieHeader = request.headers.get("cookie"); - if (cookieHeader) { - const cookies = cookieHeader.split(";"); - for (const cookie of cookies) { - const [name, value] = cookie.trim().split("="); - if (name === "token" || name === "auth-token") { - return value; - } - } - } - return null; } diff --git a/app/tests/auth.test.ts b/app/tests/auth.test.ts index b9b320f..ac65e32 100644 --- a/app/tests/auth.test.ts +++ b/app/tests/auth.test.ts @@ -1,4 +1,7 @@ -import { test, expect, describe } from "vitest"; +import { test, expect, describe, vi } from "vitest"; + +vi.stubEnv("NEXTAUTH_SECRET", "test-secret-key"); + import { createToken, verifyToken } from "@/lib/jwt"; describe("Authentication System", () => { @@ -58,13 +61,9 @@ describe("Authentication System", () => { const response = await POST(request); const cookies = response.cookies.getAll(); - expect(cookies.some(c => c.name === "auth-token")).toBe(true); expect(cookies.some(c => c.name === "next-auth.session-token")).toBe(true); expect(response.status).toBe(200); }); - test.skip("GET /api/auth/session should return 401 without token", async () => { - // Skipped - }); }); describe("Authentication Middleware", () => { diff --git a/app/tests/jwt-config.test.ts b/app/tests/jwt-config.test.ts new file mode 100644 index 0000000..63dbdef --- /dev/null +++ b/app/tests/jwt-config.test.ts @@ -0,0 +1,25 @@ +import { test, expect, describe, vi, beforeEach } from "vitest"; + +describe("JWT Configuration Fail-Fast", () => { + beforeEach(() => { + vi.resetModules(); // Ensure we get a fresh import of jwt.ts each time + }); + + test("should throw an Error on import if NEXTAUTH_SECRET is missing", async () => { + // Explicitly unset the secret to simulate missing production config + vi.stubEnv("NEXTAUTH_SECRET", ""); + + await expect(async () => { + await import("@/lib/jwt"); + }).rejects.toThrow("NEXTAUTH_SECRET is missing. It must be set in production config."); + }); + + test("should successfully import if NEXTAUTH_SECRET is provided", async () => { + // Provide a valid secret + vi.stubEnv("NEXTAUTH_SECRET", "valid-secret-for-test"); + + const jwt = await import("@/lib/jwt"); + expect(jwt.createToken).toBeDefined(); + expect(jwt.verifyToken).toBeDefined(); + }); +}); From 314f51410cff5fda99f2e44c44a398fa2b75953c Mon Sep 17 00:00:00 2001 From: hedun Date: Fri, 26 Jun 2026 10:23:40 +0100 Subject: [PATCH 2/2] fix: resolve typescript narrowing error for NEXTAUTH_SECRET --- app/lib/jwt.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/lib/jwt.ts b/app/lib/jwt.ts index d8bdf03..360b07b 100644 --- a/app/lib/jwt.ts +++ b/app/lib/jwt.ts @@ -1,10 +1,11 @@ import { SignJWT, jwtVerify } from "jose"; -if (!process.env.NEXTAUTH_SECRET) { +const secretValue = process.env.NEXTAUTH_SECRET; +if (!secretValue) { throw new Error("NEXTAUTH_SECRET is missing. It must be set in production config."); } -const secret = new TextEncoder().encode(process.env.NEXTAUTH_SECRET); +const secret = new TextEncoder().encode(secretValue); export interface JWTPayload { userId: string;