Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions backend/src/common/client-ip.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Trusted-proxy-aware client IP resolution (#1489).
*
* The client IP was read straight from `X-Forwarded-For`, which is spoofable
* unless the app is explicitly configured behind a trusted proxy. This helper
* only honours the header when the caller opts in, and otherwise falls back to
* the socket address.
*/
export interface ClientIpOptions {
trustProxy: boolean;
}

export function getClientIp(
headers: Record<string, string | string[] | undefined>,
socketAddress: string | undefined,
options: ClientIpOptions = { trustProxy: false },
): string | null {
if (options.trustProxy) {
const forwarded = headers["x-forwarded-for"];
const value = Array.isArray(forwarded) ? forwarded[0] : forwarded;
if (value) {
const first = value.split(",")[0]?.trim();
if (first) {
return first;
}
}
}
return socketAddress ?? null;
}
38 changes: 38 additions & 0 deletions backend/src/common/pagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Bounded pagination parsing (#1491).
*
* `GET /disputes` parsed `limit`/`offset` with `parseInt` and no bounds, so
* `NaN`, negative and huge values reached the database. This helper clamps both
* values to a safe range.
*/
export interface Pagination {
limit: number;
offset: number;
}

export const DEFAULT_LIMIT = 20;
export const MAX_LIMIT = 100;

export function parsePagination(
rawLimit: unknown,
rawOffset: unknown,
): Pagination {
return {
limit: toBoundedInt(rawLimit, DEFAULT_LIMIT, 1, MAX_LIMIT),
offset: toBoundedInt(rawOffset, 0, 0, Number.MAX_SAFE_INTEGER),
};
}

function toBoundedInt(
value: unknown,
fallback: number,
min: number,
max: number,
): number {
const parsed =
typeof value === "number" ? value : parseInt(String(value ?? ""), 10);
if (!Number.isFinite(parsed)) {
return fallback;
}
return Math.min(Math.max(Math.trunc(parsed), min), max);
}
Loading