ecourt+groups: N8 เปิดเอกสารจากลิงก์ ?fileUrl=&fileId=&typeId= (metadata-only + allowlist + ดึงไฟล์สด + login วนกลับ + การ์ดจำลองลิงก์ + path dev /samples) + N9 กลุ่มเอกสารตาม typeId — หน้ารายการจัด section ต่อกลุ่ม + "ไม่มีกลุ่ม" ท้ายสุด + หน้า /groups/[typeId] พร้อมหมายเหตุ "อาจไม่ครบทุกฉบับ" + docs sync ครบ (2026-09-23)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-09-23 10:44:14 +07:00
parent 0aef3ff028
commit 329ae8e956
31 changed files with 2766 additions and 155 deletions

View file

@ -0,0 +1,28 @@
import { describe, expect, it } from "vitest";
import { sanitizeNextPath } from "@/utils/external-file";
// N8 D5 — path วนกลับหลัง login ต้องเป็น path ใน app เท่านั้น (กัน open-redirect)
describe("sanitizeNextPath", () => {
it("path ใน app (มี query ก็ได้) ผ่านตรง ๆ", () => {
expect(sanitizeNextPath("/")).toBe("/");
expect(sanitizeNextPath("/annotate")).toBe("/annotate");
expect(sanitizeNextPath("/annotate/abc-123")).toBe("/annotate/abc-123");
expect(sanitizeNextPath("/?fileUrl=https://x&fileId=1")).toBe(
"/?fileUrl=https://x&fileId=1",
);
});
it("absolute URL / protocol-relative / backslash → null กัน open-redirect", () => {
expect(sanitizeNextPath("//evil.com")).toBeNull();
expect(sanitizeNextPath("/\\evil.com")).toBeNull(); // browser ตีความ \ เป็น /
expect(sanitizeNextPath("https://evil.com")).toBeNull();
expect(sanitizeNextPath("http://evil.com/path")).toBeNull();
expect(sanitizeNextPath("/\\//evil.com")).toBeNull();
});
it("ค่าว่าง/ไม่ขึ้นต้น / ขาด ๆ เกิน ๆ → null", () => {
expect(sanitizeNextPath("")).toBeNull();
expect(sanitizeNextPath("annotate")).toBeNull(); // ไม่มี / นำหน้า
expect(sanitizeNextPath(" /annotate")).toBeNull(); // space นำหน้าไม่ผ่าน startsWith
});
});