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
105 lines
4.3 KiB
TypeScript
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);
|
|
});
|
|
});
|