Add authentication infrastructure for protected routes: - withAuth() wrapper for API route handlers (src/lib/auth-middleware.ts) - Next.js middleware for page protection (src/middleware.ts) withAuth() loads auth from cookies, validates session, and passes user context to handlers. Returns 401 for unauthenticated requests. Page middleware redirects unauthenticated users to /login, while allowing public routes (/login), API routes (handled separately), and static assets through. Tests: 18 new tests (6 for withAuth, 12 for page middleware) Total test count: 60 tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
76 lines
2.0 KiB
TypeScript
76 lines
2.0 KiB
TypeScript
// ABOUTME: Next.js middleware for page route protection.
|
|
// ABOUTME: Redirects unauthenticated users to login for protected pages.
|
|
import type { NextRequest } from "next/server";
|
|
import { NextResponse } from "next/server";
|
|
|
|
/**
|
|
* Public paths that don't require authentication.
|
|
*/
|
|
const PUBLIC_PATHS = ["/login"];
|
|
|
|
/**
|
|
* Paths to skip middleware entirely (API routes, static assets).
|
|
*/
|
|
const SKIP_PATHS = ["/api", "/_next", "/favicon.ico"];
|
|
|
|
/**
|
|
* Checks if a request path should skip middleware.
|
|
*/
|
|
function shouldSkipMiddleware(pathname: string): boolean {
|
|
return SKIP_PATHS.some((path) => pathname.startsWith(path));
|
|
}
|
|
|
|
/**
|
|
* Checks if a request path is public (doesn't require auth).
|
|
*/
|
|
function isPublicPath(pathname: string): boolean {
|
|
return PUBLIC_PATHS.some((path) => pathname === path);
|
|
}
|
|
|
|
/**
|
|
* Next.js middleware function for page protection.
|
|
* Checks for pb_auth cookie and redirects to login if missing.
|
|
*/
|
|
export async function middleware(request: NextRequest): Promise<NextResponse> {
|
|
const { pathname } = request.nextUrl;
|
|
|
|
// Skip middleware for API routes and static assets
|
|
if (shouldSkipMiddleware(pathname)) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// Allow public paths without authentication
|
|
if (isPublicPath(pathname)) {
|
|
return NextResponse.next();
|
|
}
|
|
|
|
// Check for authentication cookie
|
|
const authCookie = request.cookies.get("pb_auth");
|
|
|
|
// If no valid auth cookie, redirect to login
|
|
if (!authCookie || !authCookie.value) {
|
|
const loginUrl = new URL("/login", request.nextUrl.origin);
|
|
return NextResponse.redirect(loginUrl);
|
|
}
|
|
|
|
// User has auth cookie, allow through
|
|
return NextResponse.next();
|
|
}
|
|
|
|
/**
|
|
* Matcher configuration for Next.js middleware.
|
|
* Matches all paths except static files and public assets.
|
|
*/
|
|
export const config = {
|
|
matcher: [
|
|
/*
|
|
* Match all request paths except:
|
|
* - _next/static (static files)
|
|
* - _next/image (image optimization files)
|
|
* - favicon.ico (favicon file)
|
|
* - public folder
|
|
*/
|
|
"/((?!_next/static|_next/image|favicon.ico|public).*)",
|
|
],
|
|
};
|