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:
76
src/lib/auth-middleware.ts
Normal file
76
src/lib/auth-middleware.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
// ABOUTME: Authentication middleware wrapper for protected API routes.
|
||||
// ABOUTME: Provides withAuth HOF that validates session and injects user context.
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import type { User } from "@/types";
|
||||
|
||||
import {
|
||||
createPocketBaseClient,
|
||||
getCurrentUser,
|
||||
isAuthenticated,
|
||||
loadAuthFromCookies,
|
||||
} from "./pocketbase";
|
||||
|
||||
/**
|
||||
* Route handler function type that receives the authenticated user.
|
||||
*/
|
||||
export type AuthenticatedHandler<T = unknown> = (
|
||||
request: NextRequest,
|
||||
user: User,
|
||||
context?: { params?: T },
|
||||
) => Promise<NextResponse>;
|
||||
|
||||
/**
|
||||
* Higher-order function that wraps an API route handler with authentication.
|
||||
* Loads auth from cookies, validates the session, and passes the user to the handler.
|
||||
*
|
||||
* @param handler - The route handler that requires authentication
|
||||
* @returns A wrapped handler that checks auth before calling the original handler
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* export const GET = withAuth(async (request, user) => {
|
||||
* return NextResponse.json({ email: user.email });
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function withAuth<T = unknown>(
|
||||
handler: AuthenticatedHandler<T>,
|
||||
): (request: NextRequest, context?: { params?: T }) => Promise<NextResponse> {
|
||||
return async (
|
||||
request: NextRequest,
|
||||
context?: { params?: T },
|
||||
): Promise<NextResponse> => {
|
||||
try {
|
||||
// Create a fresh PocketBase client for this request
|
||||
const pb = createPocketBaseClient();
|
||||
|
||||
// Load auth state from cookies
|
||||
const cookieStore = await cookies();
|
||||
loadAuthFromCookies(pb, cookieStore);
|
||||
|
||||
// Check if the user is authenticated
|
||||
if (!isAuthenticated(pb)) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
// Get the current user
|
||||
const user = getCurrentUser(pb);
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
// Call the original handler with the user context
|
||||
return await handler(request, user, context);
|
||||
} catch (error) {
|
||||
console.error("Auth middleware error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user