/**
* オープンリダイレクト対策の固定。
* ★`next` はクエリ経由=利用者が自由に書ける値なので、ここが唯一の防御★
*/
import { describe, expect, it } from "vitest";
import { DEFAULT_NEXT_PATH, safeNextPath } from "@/lib/auth/next-path";
describe("safeNextPath()", () => {
const allowed: Array<[string, string]> = [
["/dashboard", "/dashboard"],
["/detail-log", "/detail-log"],
["/reports?period=weekly", "/reports?period=weekly"],
["/ds-alerts", "/ds-alerts"],
];
for (const [input, expected] of allowed) {
it(`自サイト内の絶対パスは通す: ${input}`, () => {
expect(safeNextPath(input)).toBe(expected);
});
}
const blocked = [
"//evil.example",
"//evil.example/path",
"/\\evil.example",
"https://evil.example",
"http://evil.example",
"javascript:alert(1)",
"evil.example",
"../../etc/passwd",
];
for (const input of blocked) {
it(`★外部サイト・相対パスは既定へ落とす: ${input}★`, () => {
expect(safeNextPath(input)).toBe(DEFAULT_NEXT_PATH);
});
}
it("null / undefined / 空文字は既定", () => {
expect(safeNextPath(null)).toBe(DEFAULT_NEXT_PATH);
expect(safeNextPath(undefined)).toBe(DEFAULT_NEXT_PATH);
expect(safeNextPath("")).toBe(DEFAULT_NEXT_PATH);
});
it("★/login へは戻さない(ログインループを防ぐ)★", () => {
expect(safeNextPath("/login")).toBe(DEFAULT_NEXT_PATH);
expect(safeNextPath("/login/")).toBe(DEFAULT_NEXT_PATH);
expect(safeNextPath("/login?next=/dashboard")).toBe(DEFAULT_NEXT_PATH);
});
it("既定は /dashboard", () => {
expect(DEFAULT_NEXT_PATH).toBe("/dashboard");
});
});