feat: Add 'atas' (minutes/records) management feature, and implement various improvements across UI, backend logic, and authentication.

This commit is contained in:
2025-12-02 16:37:48 -03:00
parent 05e7f1181d
commit 4bd9e21748
265 changed files with 29156 additions and 26460 deletions

View File

@@ -8,144 +8,143 @@
* Considera headers como X-Forwarded-For, X-Real-IP, etc.
*/
export function getClientIP(request: Request): string | undefined {
// Headers que podem conter o IP do cliente (case-insensitive)
const getHeader = (name: string): string | null => {
// Tentar diferentes variações de case
const variations = [
name,
name.toLowerCase(),
name.toUpperCase(),
name.charAt(0).toUpperCase() + name.slice(1).toLowerCase(),
];
for (const variation of variations) {
const value = request.headers.get(variation);
if (value) return value;
}
// As variações de case já cobrem a maioria dos casos
// Se não encontrou, retorna null
return null;
};
const forwardedFor = getHeader("x-forwarded-for");
const realIP = getHeader("x-real-ip");
const cfConnectingIP = getHeader("cf-connecting-ip"); // Cloudflare
const trueClientIP = getHeader("true-client-ip"); // Cloudflare Enterprise
const xClientIP = getHeader("x-client-ip");
const forwarded = getHeader("forwarded");
const remoteAddr = getHeader("remote-addr");
// Log para debug
console.log("Procurando IP nos headers:", {
"x-forwarded-for": forwardedFor,
"x-real-ip": realIP,
"cf-connecting-ip": cfConnectingIP,
"true-client-ip": trueClientIP,
"x-client-ip": xClientIP,
"forwarded": forwarded,
"remote-addr": remoteAddr,
});
// Prioridade: X-Forwarded-For pode conter múltiplos IPs (proxy chain)
// O primeiro IP é geralmente o IP original do cliente
if (forwardedFor) {
const ips = forwardedFor.split(",").map((ip) => ip.trim());
// Pegar o primeiro IP válido
for (const ip of ips) {
if (isValidIP(ip)) {
console.log("IP encontrado em X-Forwarded-For:", ip);
return ip;
}
}
}
// Forwarded header (RFC 7239)
if (forwarded) {
// Formato: for=192.0.2.60;proto=http;by=203.0.113.43
const forMatch = forwarded.match(/for=([^;,\s]+)/i);
if (forMatch && forMatch[1]) {
const ip = forMatch[1].replace(/^\[|\]$/g, ''); // Remove brackets de IPv6
if (isValidIP(ip)) {
console.log("IP encontrado em Forwarded:", ip);
return ip;
}
}
}
// Outros headers com IP único
if (realIP && isValidIP(realIP)) {
console.log("IP encontrado em X-Real-IP:", realIP);
return realIP;
}
if (cfConnectingIP && isValidIP(cfConnectingIP)) {
console.log("IP encontrado em CF-Connecting-IP:", cfConnectingIP);
return cfConnectingIP;
}
if (trueClientIP && isValidIP(trueClientIP)) {
console.log("IP encontrado em True-Client-IP:", trueClientIP);
return trueClientIP;
}
if (xClientIP && isValidIP(xClientIP)) {
console.log("IP encontrado em X-Client-IP:", xClientIP);
return xClientIP;
}
if (remoteAddr && isValidIP(remoteAddr)) {
console.log("IP encontrado em Remote-Addr:", remoteAddr);
return remoteAddr;
}
// Tentar extrair do URL (último recurso)
try {
const url = new URL(request.url);
// Se o servidor estiver configurado para passar IP via query param
const ipFromQuery = url.searchParams.get("ip");
if (ipFromQuery && isValidIP(ipFromQuery)) {
console.log("IP encontrado em query param:", ipFromQuery);
return ipFromQuery;
}
} catch {
// Ignorar erro de parsing do URL
}
console.log("Nenhum IP válido encontrado nos headers");
return undefined;
// Headers que podem conter o IP do cliente (case-insensitive)
const getHeader = (name: string): string | null => {
// Tentar diferentes variações de case
const variations = [
name,
name.toLowerCase(),
name.toUpperCase(),
name.charAt(0).toUpperCase() + name.slice(1).toLowerCase()
];
for (const variation of variations) {
const value = request.headers.get(variation);
if (value) return value;
}
// As variações de case já cobrem a maioria dos casos
// Se não encontrou, retorna null
return null;
};
const forwardedFor = getHeader('x-forwarded-for');
const realIP = getHeader('x-real-ip');
const cfConnectingIP = getHeader('cf-connecting-ip'); // Cloudflare
const trueClientIP = getHeader('true-client-ip'); // Cloudflare Enterprise
const xClientIP = getHeader('x-client-ip');
const forwarded = getHeader('forwarded');
const remoteAddr = getHeader('remote-addr');
// Log para debug
console.log('Procurando IP nos headers:', {
'x-forwarded-for': forwardedFor,
'x-real-ip': realIP,
'cf-connecting-ip': cfConnectingIP,
'true-client-ip': trueClientIP,
'x-client-ip': xClientIP,
forwarded: forwarded,
'remote-addr': remoteAddr
});
// Prioridade: X-Forwarded-For pode conter múltiplos IPs (proxy chain)
// O primeiro IP é geralmente o IP original do cliente
if (forwardedFor) {
const ips = forwardedFor.split(',').map((ip) => ip.trim());
// Pegar o primeiro IP válido
for (const ip of ips) {
if (isValidIP(ip)) {
console.log('IP encontrado em X-Forwarded-For:', ip);
return ip;
}
}
}
// Forwarded header (RFC 7239)
if (forwarded) {
// Formato: for=192.0.2.60;proto=http;by=203.0.113.43
const forMatch = forwarded.match(/for=([^;,\s]+)/i);
if (forMatch && forMatch[1]) {
const ip = forMatch[1].replace(/^\[|\]$/g, ''); // Remove brackets de IPv6
if (isValidIP(ip)) {
console.log('IP encontrado em Forwarded:', ip);
return ip;
}
}
}
// Outros headers com IP único
if (realIP && isValidIP(realIP)) {
console.log('IP encontrado em X-Real-IP:', realIP);
return realIP;
}
if (cfConnectingIP && isValidIP(cfConnectingIP)) {
console.log('IP encontrado em CF-Connecting-IP:', cfConnectingIP);
return cfConnectingIP;
}
if (trueClientIP && isValidIP(trueClientIP)) {
console.log('IP encontrado em True-Client-IP:', trueClientIP);
return trueClientIP;
}
if (xClientIP && isValidIP(xClientIP)) {
console.log('IP encontrado em X-Client-IP:', xClientIP);
return xClientIP;
}
if (remoteAddr && isValidIP(remoteAddr)) {
console.log('IP encontrado em Remote-Addr:', remoteAddr);
return remoteAddr;
}
// Tentar extrair do URL (último recurso)
try {
const url = new URL(request.url);
// Se o servidor estiver configurado para passar IP via query param
const ipFromQuery = url.searchParams.get('ip');
if (ipFromQuery && isValidIP(ipFromQuery)) {
console.log('IP encontrado em query param:', ipFromQuery);
return ipFromQuery;
}
} catch {
// Ignorar erro de parsing do URL
}
console.log('Nenhum IP válido encontrado nos headers');
return undefined;
}
/**
* Valida se uma string é um endereço IP válido (IPv4 ou IPv6)
*/
function isValidIP(ip: string): boolean {
if (!ip || ip.length === 0) {
return false;
}
// Validar IPv4
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipv4Regex.test(ip)) {
const parts = ip.split(".");
return parts.every((part) => {
const num = parseInt(part, 10);
return num >= 0 && num <= 255;
});
}
// Validar IPv6 (formato simplificado)
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
if (ipv6Regex.test(ip)) {
return true;
}
// Validar IPv6 comprimido (com ::)
const ipv6CompressedRegex = /^([0-9a-fA-F]{0,4}:)*::([0-9a-fA-F]{0,4}:)*[0-9a-fA-F]{0,4}$/;
if (ipv6CompressedRegex.test(ip)) {
return true;
}
return false;
}
if (!ip || ip.length === 0) {
return false;
}
// Validar IPv4
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipv4Regex.test(ip)) {
const parts = ip.split('.');
return parts.every((part) => {
const num = parseInt(part, 10);
return num >= 0 && num <= 255;
});
}
// Validar IPv6 (formato simplificado)
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
if (ipv6Regex.test(ip)) {
return true;
}
// Validar IPv6 comprimido (com ::)
const ipv6CompressedRegex = /^([0-9a-fA-F]{0,4}:)*::([0-9a-fA-F]{0,4}:)*[0-9a-fA-F]{0,4}$/;
if (ipv6CompressedRegex.test(ip)) {
return true;
}
return false;
}