Merge branch 'dev' into refactor/paginated-tables

This commit is contained in:
Fred KISSIE
2026-02-06 03:54:50 +01:00
62 changed files with 2377 additions and 2537 deletions

View File

@@ -1,6 +1,8 @@
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([
@@ -16,14 +18,18 @@ export function cleanRedirect(
input: string,
options: CleanRedirectOptions = {}
): string {
const { fallback = "/", maxRedirectDepth = 2 } = options;
const {
fallback = "/",
maxRedirectDepth = 2,
allowAllQueryParams = false
} = options;
if (!input || typeof input !== "string") {
return fallback;
}
try {
return sanitizeUrl(input, fallback, maxRedirectDepth);
return sanitizeUrl(input, fallback, maxRedirectDepth, allowAllQueryParams);
} catch {
return fallback;
}
@@ -32,7 +38,8 @@ export function cleanRedirect(
function sanitizeUrl(
input: string,
fallback: string,
remainingRedirectDepth: number
remainingRedirectDepth: number,
allowAllQueryParams: boolean = false
): string {
if (
input.startsWith("javascript:") ||
@@ -56,7 +63,7 @@ function sanitizeUrl(
const cleanParams = new URLSearchParams();
for (const [key, value] of url.searchParams.entries()) {
if (!ALLOWED_QUERY_PARAMS.has(key)) {
if (!allowAllQueryParams && !ALLOWED_QUERY_PARAMS.has(key)) {
continue;
}
@@ -68,7 +75,8 @@ function sanitizeUrl(
const cleanedRedirect = sanitizeUrl(
value,
"",
remainingRedirectDepth - 1
remainingRedirectDepth - 1,
allowAllQueryParams
);
if (cleanedRedirect) {

View File

@@ -0,0 +1,51 @@
import { cleanRedirect } from "@app/lib/cleanRedirect";
export const INTERNAL_REDIRECT_KEY = "internal_redirect";
/**
* Consumes the internal_redirect value from localStorage if present and valid
* (within TTL). Removes it from storage. Returns the path segment (with leading
* slash) to append to an orgId, or null if none/expired/invalid.
*/
export function consumeInternalRedirectPath(): string | null {
if (typeof window === "undefined") return null;
try {
const raw = window.localStorage.getItem(INTERNAL_REDIRECT_KEY);
if (raw == null || raw === "") return null;
window.localStorage.removeItem(INTERNAL_REDIRECT_KEY);
const { path: storedPath, expiresAt } = JSON.parse(raw) as {
path?: string;
expiresAt?: number;
};
if (
typeof storedPath !== "string" ||
storedPath === "" ||
typeof expiresAt !== "number" ||
Date.now() > expiresAt
) {
return null;
}
const cleaned = cleanRedirect(storedPath, {
fallback: "",
allowAllQueryParams: true
});
if (!cleaned) return null;
return cleaned.startsWith("/") ? cleaned : `/${cleaned}`;
} catch {
return null;
}
}
/**
* Returns the full redirect target for an org: either `/${orgId}` or
* `/${orgId}${path}` if a valid internal_redirect was stored. Consumes the
* stored value.
*/
export function getInternalRedirectTarget(orgId: string): string {
const path = consumeInternalRedirectPath();
return path ? `/${orgId}${path}` : `/${orgId}`;
}