diff --git a/.gitignore b/.gitignore index 3cb5c1a..d370763 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ venv/ .env .env.* +# Ignora configuração de acesso ao banco de dados Firebird +config/database/firebird.json + # Bytecode compilado __pycache__/ *.py[cod] diff --git a/actions/security/security.py b/actions/security/security.py new file mode 100644 index 0000000..e23c3a6 --- /dev/null +++ b/actions/security/security.py @@ -0,0 +1,43 @@ +# core/security.py + +# Importa CryptContext da biblioteca passlib para operações de hash de senha +from passlib.context import CryptContext + +# Cria uma instância do contexto de criptografia +# O esquema usado é 'bcrypt', que é seguro e amplamente aceito +# O parâmetro 'deprecated="auto"' marca versões antigas como inseguras, se aplicável +CRYPTO = CryptContext(schemes=['bcrypt'], deprecated='auto') + + +class Security: + + # Verifica se a senha tem um hash válido + @staticmethod + def is_hash(senha: str) -> bool: + """ + Verifica se a string fornecida é um hash reconhecido pelo CryptContext. + """ + return CRYPTO.identify(senha) + + + # Verifica se uma senha fornecida corresponde ao hash armazenado + def verify_senha_api(plain_senha_api: str, hashed_senha_api: str) -> bool: + """ + Compara a senha fornecida em texto puro com o hash armazenado. + + :param plain_senha_api: Senha digitada pelo usuário + :param hashed_senha_api: Hash da senha armazenado no banco de dados + :return: True se corresponder, False se não + """ + return CRYPTO.verify(plain_senha_api, hashed_senha_api) + + + # Gera o hash de uma senha fornecida + def hash_senha_api(plain_senha_api: str) -> str: + """ + Gera e retorna o hash da senha fornecida. + + :param plain_senha_api: Senha em texto puro fornecida pelo usuário + :return: Hash da senha + """ + return CRYPTO.hash(plain_senha_api) diff --git a/actions/validations/cpf.py b/actions/validations/cpf.py new file mode 100644 index 0000000..2083800 --- /dev/null +++ b/actions/validations/cpf.py @@ -0,0 +1,34 @@ +import re + + +class CPF: + + @staticmethod + def is_valid_cpf(data: str) -> bool: + # Remove caracteres não numéricos + data = re.sub(r'\D', '', data) + + # Verifica se tem 11 dígitos + if len(data) != 11: + return False + + # CPFs com todos os dígitos iguais são inválidos + if data == data[0] * 11: + return False + + # Calcula o primeiro e segundo dígitos verificadores + def calcular_digito(digitos, peso): + soma = sum(int(a) * b for a, b in zip(digitos, peso)) + resto = soma % 11 + return '0' if resto < 2 else str(11 - resto) + + # Primeiro dígito verificador + peso1 = range(10, 1, -1) + digito1 = calcular_digito(data[:9], peso1) + + # Segundo dígito verificador + peso2 = range(11, 1, -1) + digito2 = calcular_digito(data[:10], peso2) + + # Verifica se os dígitos batem + return data[-2:] == digito1 + digito2 \ No newline at end of file diff --git a/actions/validations/email.py b/actions/validations/email.py index 4a31523..4839b95 100644 --- a/actions/validations/email.py +++ b/actions/validations/email.py @@ -4,8 +4,6 @@ import re class Email: @staticmethod - def validate(data: str) -> str: - # Validação de email - default = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$' - if not re.match(default, data): - raise f"Email inválido: {data}" \ No newline at end of file + def is_valid_email(email: str) -> bool: + """Check if email has a valid structure""" + return bool(re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email)) \ No newline at end of file diff --git a/actions/validations/text.py b/actions/validations/text.py index 36f7aab..51438bc 100644 --- a/actions/validations/text.py +++ b/actions/validations/text.py @@ -4,16 +4,60 @@ import re class Text: - @staticmethod - def sanitize(data: str) -> str: - """Trim spaces, escape HTML entities, collapse multiple spaces.""" - data = data.strip() - data = html.escape(data) - data = re.sub(r"\s+", " ", data) - return data - + # Remove as mascaras de números @staticmethod def just_numbers(data: str) -> str: """ Mantêm apenas os numeros """ data = re.sub(r"[^\d]", "", data) + return data + + # Verifica se um e-mail é válido + @staticmethod + def is_valid_email(email: str) -> bool: + """Check if email has a valid structure""" + return bool(re.match(r"^[\w\.-]+@[\w\.-]+\.\w+$", email)) + + + """ + Sanitiza entradas de texto contra XSS e SQL Injection básicos. + - Remove espaços extras + - Escapa entidades HTML + - Remove padrões suspeitos de XSS e SQL Injection + - Normaliza múltiplos espaços em um só + """ + @staticmethod + def sanitize_input(data: str) -> str: + + if not data: + return data + + # 1) Remove espaços no início e no fim + data = data.strip() + + # 2) Escapa entidades HTML (< > & ") + data = html.escape(data) + + # 3) Remove múltiplos espaços seguidos + data = re.sub(r"\s+", " ", data) + + # 4) Remove tags