feat(auth): proactive session expiry redirect across all delivery paths
- Split auth config into auth.config.ts (edge-safe, no argon2) and auth-edge.ts for middleware use; auth.ts now spreads the shared config - Middleware wraps with auth() to redirect unauthenticated requests to /auth/signin before any page render; passes through /auth/, /api/, /invite/ paths - SessionGuard client component watches useSession() and redirects on status=unauthenticated, closing the SPA navigation gap - QueryCache + MutationCache in TRPCProvider redirect on UNAUTHORIZED tRPC errors without retrying; SessionProvider polls session state every 5 minutes - Middleware tests updated for async auth wrapper and auth-edge mock Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -1,4 +1,17 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "./server/auth-edge.js";
|
||||
|
||||
// Paths that are accessible without a session.
|
||||
// Everything else requires a valid JWT session.
|
||||
const PUBLIC_PREFIXES = [
|
||||
"/auth/", // signin, forgot-password, reset-password
|
||||
"/api/", // tRPC, health, auth endpoints — these manage their own auth
|
||||
"/invite/", // public invite acceptance flow
|
||||
];
|
||||
|
||||
function isPublicPath(pathname: string): boolean {
|
||||
return PUBLIC_PREFIXES.some((prefix) => pathname.startsWith(prefix));
|
||||
}
|
||||
|
||||
function buildCsp(nonce: string, isProd: boolean): string {
|
||||
const scriptSrc = isProd
|
||||
@@ -20,7 +33,16 @@ function buildCsp(nonce: string, isProd: boolean): string {
|
||||
].join("; ");
|
||||
}
|
||||
|
||||
export function middleware(request: NextRequest): NextResponse {
|
||||
export default auth(function middleware(request) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
// Redirect unauthenticated requests for protected routes to signin
|
||||
if (!isPublicPath(pathname) && !request.auth) {
|
||||
const signInUrl = new URL("/auth/signin", request.url);
|
||||
signInUrl.searchParams.set("callbackUrl", request.url);
|
||||
return NextResponse.redirect(signInUrl);
|
||||
}
|
||||
|
||||
// Generate a cryptographically random nonce for this request
|
||||
const nonceBytes = new Uint8Array(16);
|
||||
crypto.getRandomValues(nonceBytes);
|
||||
@@ -38,7 +60,7 @@ export function middleware(request: NextRequest): NextResponse {
|
||||
response.headers.set("Content-Security-Policy", csp);
|
||||
|
||||
return response;
|
||||
}
|
||||
});
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
|
||||
Reference in New Issue
Block a user