mirror of
https://github.com/documenso/documenso.git
synced 2026-07-24 08:54:20 +10:00
138d663c25
Merge origin/main into feat/external-2fa-codes. Resolve formatting conflicts caused by biome rollout; preserve both feature streams: PR's external 2FA token + signing-session 2FA proof additions plus main's RateLimit/RecipientExpired/signingReminders/date-auto-insert. In complete-document-with-token.ts, drop the duplicate early field-fetching block introduced when main moved that logic later with date auto-insert support; keep the EXTERNAL_TWO_FACTOR_AUTH check using derivedRecipientActionAuth.
83 lines
1.8 KiB
TypeScript
83 lines
1.8 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
const ZIpSchema = z.string().ip();
|
|
|
|
/**
|
|
* Check whether a URL points to a known private/loopback address.
|
|
*
|
|
* Performs a synchronous check against known private hostnames and IP ranges.
|
|
* Works regardless of the URL protocol.
|
|
*/
|
|
export const isPrivateUrl = (url: string): boolean => {
|
|
try {
|
|
const parsed = new URL(url);
|
|
const hostname = parsed.hostname.toLowerCase();
|
|
|
|
// Strip IPv6 brackets.
|
|
const bare = hostname.startsWith('[') ? hostname.slice(1, -1) : hostname;
|
|
const normalizedHost = bare.replace(/\.+$/, '');
|
|
|
|
if (normalizedHost === 'localhost') {
|
|
return true;
|
|
}
|
|
|
|
const parsedIp = ZIpSchema.safeParse(normalizedHost);
|
|
|
|
if (!parsedIp.success) {
|
|
return false;
|
|
}
|
|
|
|
if (normalizedHost === '::1' || normalizedHost === '::') {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost === '0.0.0.0') {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('127.')) {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('10.')) {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('192.168.')) {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('169.254.')) {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('fe80:')) {
|
|
return true;
|
|
}
|
|
|
|
if (normalizedHost.startsWith('fc') || normalizedHost.startsWith('fd')) {
|
|
return true;
|
|
}
|
|
|
|
// 172.16.0.0/12
|
|
if (normalizedHost.startsWith('172.')) {
|
|
const second = parseInt(normalizedHost.split('.')[1], 10);
|
|
|
|
if (second >= 16 && second <= 31) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// IPv4-mapped IPv6 (e.g. ::ffff:127.0.0.1)
|
|
const v4Mapped = normalizedHost.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
|
|
|
|
if (v4Mapped) {
|
|
return isPrivateUrl(`http://${v4Mapped[1]}`);
|
|
}
|
|
|
|
return false;
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|