mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-04 02:04:06 +00:00
125 lines
3.5 KiB
TypeScript
125 lines
3.5 KiB
TypeScript
import { extractUtmParams, type UtmParams } from "@/lib/utm";
|
|
|
|
const DEFAULT_CALLBACK_PATH = "/";
|
|
const INVITATION_TOKEN_PARAM = "invitation_token";
|
|
// Origin used only to resolve relative paths; never part of the returned value.
|
|
const INTERNAL_ORIGIN = "http://localhost";
|
|
|
|
type CallbackSearchParams = {
|
|
get(name: string): string | null;
|
|
};
|
|
|
|
export const getSafeCallbackPathFromValue = (
|
|
value: string | null | undefined,
|
|
) => {
|
|
if (!value || !value.startsWith("/") || value.startsWith("//")) {
|
|
return DEFAULT_CALLBACK_PATH;
|
|
}
|
|
|
|
// A prefix check is not enough: the URL parser normalizes backslashes and
|
|
// control characters, so "/\evil.com" or "/\t/evil.com" pass the check above
|
|
// yet resolve to an external origin. Resolve against a fixed origin and
|
|
// confirm it stayed internal before trusting the path.
|
|
try {
|
|
const url = new URL(value, INTERNAL_ORIGIN);
|
|
if (url.origin !== INTERNAL_ORIGIN) {
|
|
return DEFAULT_CALLBACK_PATH;
|
|
}
|
|
|
|
return `${url.pathname}${url.search}${url.hash}`;
|
|
} catch (_error) {
|
|
return DEFAULT_CALLBACK_PATH;
|
|
}
|
|
};
|
|
|
|
export const getSafeCallbackPath = (
|
|
searchParams: CallbackSearchParams,
|
|
key = "state",
|
|
) => getSafeCallbackPathFromValue(searchParams.get(key));
|
|
|
|
export const appendCallbackState = (authUrl: string, callbackPath: string) => {
|
|
const safeCallbackPath = getSafeCallbackPathFromValue(callbackPath);
|
|
if (safeCallbackPath === DEFAULT_CALLBACK_PATH) {
|
|
return authUrl;
|
|
}
|
|
|
|
try {
|
|
const url = new URL(authUrl);
|
|
url.searchParams.set("state", safeCallbackPath);
|
|
return url.toString();
|
|
} catch (_error) {
|
|
return authUrl;
|
|
}
|
|
};
|
|
|
|
export const getInvitationTokenFromCallbackPath = (callbackPath: string) => {
|
|
const safeCallbackPath = getSafeCallbackPathFromValue(callbackPath);
|
|
if (safeCallbackPath === DEFAULT_CALLBACK_PATH) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const url = new URL(safeCallbackPath, "http://localhost");
|
|
return url.searchParams.get(INVITATION_TOKEN_PARAM);
|
|
} catch (_error) {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export const appendAttributionToCallbackPath = (
|
|
callbackPath: string,
|
|
attribution: UtmParams,
|
|
): string => {
|
|
const safeCallbackPath = getSafeCallbackPathFromValue(callbackPath);
|
|
if (Object.keys(attribution).length === 0) {
|
|
return safeCallbackPath;
|
|
}
|
|
|
|
try {
|
|
const url = new URL(safeCallbackPath, INTERNAL_ORIGIN);
|
|
for (const [key, value] of Object.entries(attribution)) {
|
|
if (!url.searchParams.has(key)) {
|
|
url.searchParams.set(key, value);
|
|
}
|
|
}
|
|
return `${url.pathname}${url.search}${url.hash}`;
|
|
} catch (_error) {
|
|
return safeCallbackPath;
|
|
}
|
|
};
|
|
|
|
export const getAttributionParamsFromCallbackPath = (
|
|
callbackPath: string,
|
|
): UtmParams => {
|
|
const safeCallbackPath = getSafeCallbackPathFromValue(callbackPath);
|
|
|
|
try {
|
|
const url = new URL(safeCallbackPath, INTERNAL_ORIGIN);
|
|
return extractUtmParams(url.searchParams);
|
|
} catch (_error) {
|
|
return {};
|
|
}
|
|
};
|
|
|
|
const SELF_REGISTRATION_DISABLED_CODE = "self_registration_disabled";
|
|
|
|
// The API answers a social login from a brand-new user with this error code
|
|
// when the deployment only allows invited users.
|
|
export const isSelfRegistrationDisabledResponse = async (
|
|
response: Response,
|
|
): Promise<boolean> => {
|
|
if (response.status !== 403) return false;
|
|
try {
|
|
const body = (await response.json()) as {
|
|
errors?: Array<{ code?: string }>;
|
|
};
|
|
return (
|
|
body.errors?.some(
|
|
(error) => error.code === SELF_REGISTRATION_DISABLED_CODE,
|
|
) ?? false
|
|
);
|
|
} catch (_error) {
|
|
return false;
|
|
}
|
|
};
|