mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-25 18:53:36 +00:00
95 lines
2.1 KiB
TypeScript
95 lines
2.1 KiB
TypeScript
type CleanRedirectOptions = {
|
|
fallback?: string;
|
|
maxRedirectDepth?: number;
|
|
/** When true, preserve all query params on the path (for internal redirects). Default false. */
|
|
allowAllQueryParams?: boolean;
|
|
};
|
|
|
|
const ALLOWED_QUERY_PARAMS = new Set([
|
|
"forceLogin",
|
|
"code",
|
|
"token",
|
|
"redirect"
|
|
]);
|
|
|
|
const DUMMY_BASE = "https://internal.local";
|
|
|
|
export function cleanRedirect(
|
|
input: string,
|
|
options: CleanRedirectOptions = {}
|
|
): string {
|
|
const {
|
|
fallback = "/",
|
|
maxRedirectDepth = 2,
|
|
allowAllQueryParams = false
|
|
} = options;
|
|
|
|
if (!input || typeof input !== "string") {
|
|
return fallback;
|
|
}
|
|
|
|
try {
|
|
return sanitizeUrl(input, fallback, maxRedirectDepth, allowAllQueryParams);
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function sanitizeUrl(
|
|
input: string,
|
|
fallback: string,
|
|
remainingRedirectDepth: number,
|
|
allowAllQueryParams: boolean = false
|
|
): string {
|
|
if (
|
|
input.startsWith("javascript:") ||
|
|
input.startsWith("data:") ||
|
|
input.startsWith("//")
|
|
) {
|
|
return fallback;
|
|
}
|
|
|
|
const url = new URL(input, DUMMY_BASE);
|
|
|
|
// Must be a relative/internal path
|
|
if (url.origin !== DUMMY_BASE) {
|
|
return fallback;
|
|
}
|
|
|
|
if (!url.pathname.startsWith("/")) {
|
|
return fallback;
|
|
}
|
|
|
|
const cleanParams = new URLSearchParams();
|
|
|
|
for (const [key, value] of url.searchParams.entries()) {
|
|
if (!allowAllQueryParams && !ALLOWED_QUERY_PARAMS.has(key)) {
|
|
continue;
|
|
}
|
|
|
|
if (key === "redirect") {
|
|
if (remainingRedirectDepth <= 0) {
|
|
continue;
|
|
}
|
|
|
|
const cleanedRedirect = sanitizeUrl(
|
|
value,
|
|
"",
|
|
remainingRedirectDepth - 1,
|
|
allowAllQueryParams
|
|
);
|
|
|
|
if (cleanedRedirect) {
|
|
cleanParams.set("redirect", cleanedRedirect);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
cleanParams.set(key, value);
|
|
}
|
|
|
|
const queryString = cleanParams.toString();
|
|
return queryString ? `${url.pathname}?${queryString}` : url.pathname;
|
|
}
|