Implement auth middleware for API routes (P0.2)
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>
This commit is contained in:
75
src/middleware.ts
Normal file
75
src/middleware.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
// 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).*)",
|
||||
],
|
||||
};
|
||||
Reference in New Issue
Block a user