feat: add sign-out redirect URL and enhance sign-in redirect handling
This commit is contained in:
44
frontend/src/auth/redirects.test.ts
Normal file
44
frontend/src/auth/redirects.test.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { resolveSignInRedirectUrl } from "@/auth/redirects";
|
||||
|
||||
describe("resolveSignInRedirectUrl", () => {
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("uses env fallback when redirect is missing", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL", "/boards");
|
||||
|
||||
expect(resolveSignInRedirectUrl(null)).toBe("/boards");
|
||||
});
|
||||
|
||||
it("defaults to /dashboard when no env fallback is set", () => {
|
||||
expect(resolveSignInRedirectUrl(null)).toBe("/dashboard");
|
||||
});
|
||||
|
||||
it("allows safe relative paths", () => {
|
||||
expect(resolveSignInRedirectUrl("/dashboard?tab=ops#queue")).toBe(
|
||||
"/dashboard?tab=ops#queue",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects protocol-relative urls", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL", "/activity");
|
||||
|
||||
expect(resolveSignInRedirectUrl("//evil.example.com/path")).toBe("/activity");
|
||||
});
|
||||
|
||||
it("rejects external absolute urls", () => {
|
||||
vi.stubEnv("NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL", "/activity");
|
||||
|
||||
expect(resolveSignInRedirectUrl("https://evil.example.com/steal")).toBe(
|
||||
"/activity",
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts same-origin absolute urls and normalizes to path", () => {
|
||||
const url = `${window.location.origin}/boards/new?src=invite#top`;
|
||||
expect(resolveSignInRedirectUrl(url)).toBe("/boards/new?src=invite#top");
|
||||
});
|
||||
});
|
||||
31
frontend/src/auth/redirects.ts
Normal file
31
frontend/src/auth/redirects.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
const DEFAULT_SIGN_IN_REDIRECT = "/dashboard";
|
||||
|
||||
function isSafeRelativePath(value: string): boolean {
|
||||
return value.startsWith("/") && !value.startsWith("//");
|
||||
}
|
||||
|
||||
export function resolveSignInRedirectUrl(rawRedirect: string | null): string {
|
||||
const fallback =
|
||||
process.env.NEXT_PUBLIC_CLERK_SIGN_IN_FALLBACK_REDIRECT_URL ??
|
||||
DEFAULT_SIGN_IN_REDIRECT;
|
||||
|
||||
if (!rawRedirect) return fallback;
|
||||
|
||||
if (isSafeRelativePath(rawRedirect)) {
|
||||
return rawRedirect;
|
||||
}
|
||||
|
||||
if (typeof window === "undefined") {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = new URL(rawRedirect, window.location.origin);
|
||||
if (parsed.origin !== window.location.origin) {
|
||||
return fallback;
|
||||
}
|
||||
return `${parsed.pathname}${parsed.search}${parsed.hash}`;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user