Files
usememos--memos/web/tests/redirect-safety.test.ts
wehub-resource-sync 4cddfcf2f3
Backend Tests / Tests (other) (push) Failing after 1s
Backend Tests / Static Checks (push) Failing after 2s
Backend Tests / Tests (internal) (push) Failing after 2s
Backend Tests / Tests (server) (push) Failing after 1s
Backend Tests / Tests (store) (push) Failing after 1s
Build Canary Image / build-frontend (push) Failing after 1s
Frontend Tests / Lint (push) Failing after 1s
Build Canary Image / build-push (linux/amd64) (push) Has been skipped
Build Canary Image / build-push (linux/arm64) (push) Has been skipped
Build Canary Image / merge (push) Has been skipped
Frontend Tests / Build (push) Failing after 1s
Release Please / release-please (push) Failing after 0s
Proto Linter / Lint Protos (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:02:24 +08:00

105 lines
4.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
AUTH_REDIRECT_PARAM,
buildAuthRoute,
getSafeRedirectPath,
isPublicRoute,
shouldGatePrivateInstance,
} 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);
});
});
describe("shouldGatePrivateInstance", () => {
it("never gates on an open (public) instance", () => {
expect(shouldGatePrivateInstance({ isPrivateInstance: false, isAuthenticated: false, pathname: "/" })).toBe(false);
expect(shouldGatePrivateInstance({ isPrivateInstance: false, isAuthenticated: false, pathname: "/explore" })).toBe(false);
});
it("never gates an authenticated visitor", () => {
expect(shouldGatePrivateInstance({ isPrivateInstance: true, isAuthenticated: true, pathname: "/explore" })).toBe(false);
});
it("gates anonymous visitors to non-share pages on a private instance", () => {
for (const pathname of ["/", "/explore", "/about", "/memos/abc", "/u/steven", "/setting"]) {
expect(shouldGatePrivateInstance({ isPrivateInstance: true, isAuthenticated: false, pathname })).toBe(true);
}
});
it("keeps share links reachable for anonymous visitors on a private instance", () => {
expect(shouldGatePrivateInstance({ isPrivateInstance: true, isAuthenticated: false, pathname: "/memos/shares/token123" })).toBe(false);
});
});