feat(auth): restrict public routes
This commit is contained in:
parent
ddb2a7b627
commit
a0e6da97b1
55
middleware.ts
Normal file
55
middleware.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import type { NextRequest } from "next/server";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import { SESSION_COOKIE_NAME } from "./src/lib/authGateway";
|
||||
|
||||
const PUBLIC_EXACT_PATHS = new Set([
|
||||
"/",
|
||||
"/services",
|
||||
"/login",
|
||||
"/register",
|
||||
"/email-verification",
|
||||
"/logout",
|
||||
"/404",
|
||||
"/500",
|
||||
]);
|
||||
|
||||
function isDocsPath(pathname: string): boolean {
|
||||
return pathname === "/docs" || pathname.startsWith("/docs/");
|
||||
}
|
||||
|
||||
function isPublicPath(pathname: string): boolean {
|
||||
return PUBLIC_EXACT_PATHS.has(pathname) || isDocsPath(pathname);
|
||||
}
|
||||
|
||||
function buildRedirectTarget(request: NextRequest): string {
|
||||
const query = request.nextUrl.search;
|
||||
return `${request.nextUrl.pathname}${query}`;
|
||||
}
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
if (isPublicPath(pathname)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const token = request.cookies.get(SESSION_COOKIE_NAME)?.value?.trim();
|
||||
if (token) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const loginUrl = new URL("/login", request.url);
|
||||
const redirect = buildRedirectTarget(request);
|
||||
if (redirect && redirect !== "/login") {
|
||||
loginUrl.searchParams.set("redirect", redirect);
|
||||
}
|
||||
|
||||
return NextResponse.redirect(loginUrl);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
"/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|.*\\.(?:css|gif|ico|jpg|jpeg|js|map|png|svg|txt|webp|woff|woff2|xml)$).*)",
|
||||
],
|
||||
};
|
||||
57
src/middleware.test.ts
Normal file
57
src/middleware.test.ts
Normal file
@ -0,0 +1,57 @@
|
||||
// @vitest-environment node
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
import { middleware } from "../middleware";
|
||||
|
||||
describe("middleware public route policy", () => {
|
||||
it("keeps homepage public", () => {
|
||||
const response = middleware(new NextRequest("https://console.svc.plus/"));
|
||||
|
||||
expect(response).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps docs routes public", () => {
|
||||
const response = middleware(
|
||||
new NextRequest("https://console.svc.plus/docs/getting-started"),
|
||||
);
|
||||
|
||||
expect(response).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps only the top-level services page public", () => {
|
||||
const publicResponse = middleware(
|
||||
new NextRequest("https://console.svc.plus/services"),
|
||||
);
|
||||
const protectedResponse = middleware(
|
||||
new NextRequest("https://console.svc.plus/services/openclaw"),
|
||||
);
|
||||
|
||||
expect(publicResponse).toBeUndefined();
|
||||
expect(protectedResponse?.status).toBe(307);
|
||||
expect(protectedResponse?.headers.get("location")).toContain(
|
||||
"/login?redirect=%2Fservices%2Fopenclaw",
|
||||
);
|
||||
});
|
||||
|
||||
it("redirects protected pages to login when no session cookie exists", () => {
|
||||
const response = middleware(
|
||||
new NextRequest("https://console.svc.plus/panel?tab=agent"),
|
||||
);
|
||||
|
||||
expect(response?.status).toBe(307);
|
||||
expect(response?.headers.get("location")).toContain(
|
||||
"/login?redirect=%2Fpanel%3Ftab%3Dagent",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows protected pages when a session cookie exists", () => {
|
||||
const request = new NextRequest("https://console.svc.plus/support");
|
||||
request.cookies.set("xc_session", "token");
|
||||
|
||||
const response = middleware(request);
|
||||
|
||||
expect(response).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user