You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
memos/web/tests/redirect-safety.test.ts

78 lines
3.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { AUTH_REDIRECT_PARAM, buildAuthRoute, getSafeRedirectPath, isPublicRoute } from "@/utils/redirect-safety";
describe("getSafeRedirectPath", () => {
it("accepts safe same-origin internal paths", () => {
expect(getSafeRedirectPath("/home")).toBe("/home");
expect(getSafeRedirectPath("/setting")).toBe("/setting");
expect(getSafeRedirectPath("/memos/abc")).toBe("/memos/abc");
expect(getSafeRedirectPath("/explore?foo=1")).toBe("/explore?foo=1");
expect(getSafeRedirectPath("/explore#anchor")).toBe("/explore#anchor");
});
it("rejects empty and non-string input", () => {
expect(getSafeRedirectPath(undefined)).toBeUndefined();
expect(getSafeRedirectPath(null)).toBeUndefined();
expect(getSafeRedirectPath("")).toBeUndefined();
});
it("rejects non-internal targets", () => {
expect(getSafeRedirectPath("//evil.example")).toBeUndefined();
expect(getSafeRedirectPath("https://evil.example")).toBeUndefined();
expect(getSafeRedirectPath("http://evil.example/home")).toBeUndefined();
expect(getSafeRedirectPath("javascript:alert(1)")).toBeUndefined();
expect(getSafeRedirectPath("home")).toBeUndefined();
});
it("rejects auth-family targets", () => {
expect(getSafeRedirectPath("/auth")).toBeUndefined();
expect(getSafeRedirectPath("/auth/callback")).toBeUndefined();
expect(getSafeRedirectPath("/auth/signup")).toBeUndefined();
expect(getSafeRedirectPath("/auth?code=abc")).toBeUndefined();
});
it("does not false-match auth-like paths", () => {
expect(getSafeRedirectPath("/authors")).toBe("/authors");
});
});
describe("buildAuthRoute", () => {
it("embeds only safe redirect targets", () => {
expect(buildAuthRoute({ redirect: "/home" })).toBe("/auth?redirect=%2Fhome");
expect(buildAuthRoute({ redirect: "//evil.example" })).toBe("/auth");
expect(buildAuthRoute({ redirect: "/auth/callback" })).toBe("/auth");
expect(buildAuthRoute({ redirect: null })).toBe("/auth");
});
it("preserves the reason parameter", () => {
expect(buildAuthRoute({ reason: "protected-memo" })).toBe("/auth?reason=protected-memo");
expect(buildAuthRoute({ redirect: "/memos/abc", reason: "protected-memo" })).toBe(
"/auth?redirect=%2Fmemos%2Fabc&reason=protected-memo",
);
});
it("exposes the canonical redirect query key", () => {
expect(AUTH_REDIRECT_PARAM).toBe("redirect");
});
});
describe("isPublicRoute", () => {
it("identifies anonymous-accessible page prefixes", () => {
expect(isPublicRoute("/auth")).toBe(true);
expect(isPublicRoute("/auth/signup")).toBe(true);
expect(isPublicRoute("/about")).toBe(true);
expect(isPublicRoute("/explore")).toBe(true);
expect(isPublicRoute("/memos/abc")).toBe(true);
expect(isPublicRoute("/memos/shares/abc")).toBe(true);
expect(isPublicRoute("/u/steven")).toBe(true);
});
it("treats authenticated-only pages as non-public", () => {
expect(isPublicRoute("/home")).toBe(false);
expect(isPublicRoute("/setting")).toBe(false);
expect(isPublicRoute("/inbox")).toBe(false);
expect(isPublicRoute("/attachments")).toBe(false);
expect(isPublicRoute("/archived")).toBe(false);
});
});