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