mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 21:39:54 +02:00
* refactor(auth): add sanitizeNextUrl helper in @multica/core/auth Extracts a reusable helper that returns a post-login redirect URL only when it's a safe single-slash relative path, and null otherwise. Rejects absolute URLs, protocol-relative URLs, backslashes, and control characters so call sites can safely pass the result to router.push(). Keeping the rule in a single helper (with direct unit tests) avoids each consumer re-implementing the validation and drifting. * fix(auth): validate next= redirect target to prevent open redirect Closes #1116 Next.js router.push accepts absolute URLs, so a crafted `/login?next=https://evil.example` would send the user off-origin after a successful login. The Google OAuth callback has the same vector via the `state=next:<url>` payload. Sanitize both entry points through `sanitizeNextUrl` from `@multica/core/auth` so only safe single-slash relative paths survive; null results fall through to the existing workspace-list-based default without any hard-coded path. --------- Co-authored-by: JunghwanNA <70629228+shaun0927@users.noreply.github.com>
46 lines
1.6 KiB
TypeScript
46 lines
1.6 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { sanitizeNextUrl } from "./utils";
|
|
|
|
describe("sanitizeNextUrl", () => {
|
|
it("accepts single-slash relative paths", () => {
|
|
expect(sanitizeNextUrl("/issues")).toBe("/issues");
|
|
expect(sanitizeNextUrl("/invite/123")).toBe("/invite/123");
|
|
expect(sanitizeNextUrl("/issues?tab=assigned#top")).toBe(
|
|
"/issues?tab=assigned#top",
|
|
);
|
|
});
|
|
|
|
it("returns null for null or empty input", () => {
|
|
expect(sanitizeNextUrl(null)).toBeNull();
|
|
expect(sanitizeNextUrl("")).toBeNull();
|
|
});
|
|
|
|
it("rejects absolute URLs", () => {
|
|
expect(sanitizeNextUrl("https://evil.example")).toBeNull();
|
|
expect(sanitizeNextUrl("http://evil.example/path")).toBeNull();
|
|
});
|
|
|
|
it("rejects javascript: and other non-http schemes", () => {
|
|
// Caught by the leading-slash rule, but named here so future edits
|
|
// to the regex don't silently drop protection against this vector.
|
|
expect(sanitizeNextUrl("javascript:alert(1)")).toBeNull();
|
|
expect(sanitizeNextUrl("data:text/html,<script>")).toBeNull();
|
|
});
|
|
|
|
it("rejects protocol-relative URLs", () => {
|
|
expect(sanitizeNextUrl("//evil.example")).toBeNull();
|
|
expect(sanitizeNextUrl("//evil.example/path")).toBeNull();
|
|
});
|
|
|
|
it("rejects paths containing backslashes", () => {
|
|
expect(sanitizeNextUrl("/\\evil.example")).toBeNull();
|
|
expect(sanitizeNextUrl("\\\\evil.example")).toBeNull();
|
|
});
|
|
|
|
it("rejects paths containing control characters", () => {
|
|
expect(sanitizeNextUrl("/safe\u0000bad")).toBeNull();
|
|
expect(sanitizeNextUrl("/safe\tbad")).toBeNull();
|
|
expect(sanitizeNextUrl("/safe\r\nbad")).toBeNull();
|
|
});
|
|
});
|