first commit

This commit is contained in:
Claudecio Martins
2025-11-04 18:22:02 +01:00
commit c1184d2878
4394 changed files with 444123 additions and 0 deletions

View File

@@ -0,0 +1,346 @@
<?php
namespace Zampet\Module\Auth\v0\Services;
use DateTime;
use Exception;
use Ramsey\Uuid\Uuid;
use Zampet\Services\DB;
use Zampet\Services\JWTService;
use Zampet\Helpers\DataValidator;
use Zampet\Module\Auth\v0\DTO\UsuarioDTO;
use Zampet\Module\Auth\v0\Models\UsuarioModel;
class AuthService {
/**
* Registra um novo usuário no sistema após realizar validações rigorosas.
*
* Este método orquestra o processo de criação de um novo usuário, incluindo validação de dados, checagem de duplicidade de e-mail e persistência no banco de dados. A operação é atômica e utiliza uma transação de banco de dados (`DB::beginTransaction()`).
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados.
* 2. **Validação de Dados:** Um `DataValidator` é usado para validar campos obrigatórios (`nome_completo`, `email`, `senha`) e a complexidade da senha (mínimo de 8 caracteres, maiúsculas, números e símbolos).
* 3. **Checagem de Duplicidade:** O método `getUsuarioByIdentifier` é chamado para verificar se o e-mail já está em uso por outro usuário.
* 4. **Encerramento em Caso de Falha na Validação/Duplicidade:** Se houver erros de validação (código **400**) ou se o e-mail já estiver em uso (código **409**), a transação é revertida (`DB::rollBack()`) e o erro é retornado.
* 5. **Preparação Final dos Dados:** Se a validação passar, o **UUID** do usuário e o timestamp de criação são definidos. A senha é criptografada usando **`PASSWORD_ARGON2ID`** antes de ser persistida.
* 6. **Persistência:** O método `store` do `UsuarioModel` é chamado para inserir o novo registro.
* 7. **Confirmação:** Se a persistência for bem-sucedida (código **201**), a transação é confirmada (`DB::commit()`) e o resultado de sucesso é retornado.
* 8. **Tratamento de Erro de Persistência:** Se o `store` falhar por um motivo inesperado (código **500**), a transação é revertida.
*
* @param UsuarioDTO $usuarioDTO O objeto DTO contendo os dados do novo usuário, incluindo nome, email e senha.
* @return array Um array associativo contendo o código HTTP, status da operação, mensagem e dados de saída.
*/
public function register(UsuarioDTO $usuarioDTO): array {
// Inicia conexão com o banco de dados
DB::beginTransaction();
// Valida os dados do DTO
$dataValidador = new DataValidator(inputs: $usuarioDTO->toArray());
$dataValidador->validate(field: 'nome_completo', rule: 'notEmpty', message: 'Nome completo é obrigatório');
$dataValidador->validate(field: 'email', rule: 'notEmpty', message: 'Email é obrigatório');
$dataValidador->validate(field: 'email', rule: 'validateEmail', message: 'Email inválido');
$dataValidador->validate(field: 'senha', rule: 'notEmpty', message: 'Senha é obrigatória');
$dataValidador->validate(field: 'documentcpf', rule: 'notEmpty', message: 'CPF é obrigatório');
$dataValidador->validate(field: 'documentcpf', rule: 'validateCpf', message: 'CPF informado é inválido.');
$dataValidador->validate(field: 'senha', rule: 'validatePassword', message: 'Senha inválida', additionalParams: [
[
'minLength' => 8,
'upper' => true,
'digit' => true,
'symbol' => true
]
]
);
// Se houver erros de validação, retornar resposta de erro
if(!$dataValidador->passes()) {
DB::rollBack();
return [
'response_code' => 400,
'status' => 'error',
'message' => 'Validation errors',
'output' => [
'errors' => $dataValidador->getErrors()
]
];
}
// Verifica se o email já está em uso
$existingEmailUser = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'email', value: $usuarioDTO->getEmail());
if($existingEmailUser['status'] === 'success' && !empty($existingEmailUser['output']['data'])) {
DB::rollBack();
return [
'response_code' => 409,
'status' => 'error',
'message' => 'E-mail já está em uso por outro usuário.',
'output' => []
];
}
// Verifica se o CPF já está em uso
$existingCpfUser = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'documentcpf', value: $usuarioDTO->getDocumentCpf());
if($existingCpfUser['status'] === 'success' && !empty($existingCpfUser['output']['data'])) {
DB::rollBack();
return [
'response_code' => 409,
'status' => 'error',
'message' => 'CPF já está em uso por outro usuário.',
'output' => []
];
}
// Define dados adicionais
$usuarioDTO->setUuid(uuid: Uuid::uuid7()->toString());
$usuarioDTO->setCreatedAt(created_at: (new DateTime())->format(format: 'Y-m-d H:i:s'));
$usuarioDTO->setSenha(senha: password_hash(password: $usuarioDTO->getSenha(), algo: PASSWORD_ARGON2ID));
// Persiste o novo usuário no banco de dados
$storeUsuario = (new UsuarioModel())->store(usuarioDTO: $usuarioDTO);
if($storeUsuario['status'] !== 'success') {
DB::rollBack();
return [
'response_code' => 500,
'status' => 'error',
'message' => 'Erro ao registrar usuário. Tente novamente mais tarde.',
'output' => $storeUsuario['output'] ?? []
];
}
DB::commit();
return [
'response_code' => $storeUsuario['response_code'],
'status' => $storeUsuario['status'],
'message' => $storeUsuario['message'],
'output' => $storeUsuario['output']
];
}
/**
* Realiza a autenticação de um usuário por e-mail e senha, gerando e registrando um token JWT.
*
* Este método gerencia todo o fluxo de login, incluindo a verificação de credenciais, revogação de tokens antigos, geração de um novo JWT e persistência desse novo token no banco de dados. A operação é atômica e utiliza uma transação de banco de dados para garantir a consistência dos dados.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`).
* 2. **Consulta do Usuário:** Busca o usuário pelo e-mail. Se o usuário não for encontrado, uma `Exception` é lançada com o código `401 Unauthorized`.
* 3. **Validação da Senha:** Verifica a senha fornecida contra o hash armazenado. Se a senha for inválida, uma `Exception` é lançada com o código `401 Unauthorized`.
* 4. **Revogação de Tokens Antigos:** Chama o método `revokeOldTokens` do `UsuarioModel` para invalidar todas as sessões anteriores do usuário.
* 5. **Geração do JWT:** Monta o payload do JWT com os dados essenciais do usuário e os timestamps `iat` (emitido em) e `exp` (expira em), gerando o novo token.
* 6. **Registro do Token:** Armazena o novo token no banco de dados usando `storeToken`. Se a operação falhar, uma `Exception` é lançada com o código `500 Internal Server Error`.
* 7. **Confirmação e Retorno:** Se todas as etapas forem bem-sucedidas, a transação é confirmada (`DB::commit()`), e o token gerado é retornado com `status` 'success' e código `200 OK`.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` capturada durante o processo faz com que a transação seja revertida (`DB::rollBack()`).
* - Retorna um array com `status` 'error', o código de erro da exceção (ou 500 como fallback) e uma mensagem descritiva.
*
* @param string $email O e-mail do usuário para autenticação.
* @param string $password A senha em texto plano fornecida pelo usuário.
* @return array Um array associativo contendo o status da operação, o código de resposta HTTP e o token JWT em caso de sucesso.
*/
public function login(string $email, string $password): array {
try {
// Inicia Transação no Banco de dados
DB::beginTransaction();
// Consulta as informações do usuário pelo email
$userResult = (new UsuarioModel())->getUsuarioByIdentifier(identifier: 'email', value: $email);
if($userResult['status'] !== 'success' || empty($userResult['output']['data'])) {
throw new Exception(message: 'E-mail ou Senha incorretos.', code: 401);
}
// Valida a senha fornecida com a armazenada
if(!password_verify(password: $password, hash: $userResult['output']['data']['senha'])) {
throw new Exception(message: 'E-mail ou Senha incorretos.', code: 401);
}
// Revoga os tokens antigos do usuário
$revokeOldTokens = (new UsuarioModel())->revokeOldTokens(user_id: $userResult['output']['data']['id']);
if($revokeOldTokens['status'] !== 'success') {
throw new Exception(message: 'Erro ao revogar tokens antigos.', code: 500);
}
// Prepara o Payload do JWT
$jwtPayload = [
'user_uuid' => $userResult['output']['data']['uuid'],
'email' => $userResult['output']['data']['email'],
'nome_completo' => $userResult['output']['data']['nome_completo'],
'iat' => (new DateTime())->getTimestamp(),
'exp' => (new DateTime())->modify(modifier: '+8 hour')->getTimestamp()
];
$token = JWTService::generateToken(payload: $jwtPayload);
// Armazena o token no banco de dados
$storeToken = (new UsuarioModel())->storeToken(user_id: $userResult['output']['data']['id'], token: $token);
if($storeToken['status'] !== 'success') {
throw new Exception(message: 'Erro ao armazenar token de autenticação.', code: 500);
}
DB::commit();
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Login successful.',
'output' => [
'data' => [
'token' => $token
]
]
];
} catch(Exception $e) {
DB::rollBack();
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred during login.'
];
}
}
/**
* Finaliza a sessão do usuário revogando um token JWT específico.
*
* Este método é o responsável por orquestrar o processo de logout seguro, garantindo que o token de autenticação seja validado e, em seguida, invalidado no banco de dados.
*
* #### Fluxo de Operação:
* 1. **Validação do Token:** O método chama `validateToken` para verificar se o token existe e ainda é válido (não expirado ou revogado no DB). Se a validação falhar, uma `Exception` é lançada com o código de erro apropriado.
* 2. **Revogação no Banco de Dados:** Tenta revogar o token usando `UsuarioModel::revokeToken()`. Se a revogação falhar (ex: erro de banco de dados), uma `Exception` é lançada.
* 3. **Retorno de Sucesso:** Se ambas as operações (validação e revogação) forem bem-sucedidas, retorna um array com **`status` 'success'**, código **200 OK** e uma mensagem de êxito.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo é capturada, e o método retorna um array de erro formatado, contendo o código HTTP e a mensagem da exceção.
*
* @param string $token O token JWT a ser revogado.
* @return array Um array associativo contendo o status da operação ("success" ou "error"), o código de resposta HTTP e uma mensagem.
*/
public function logout(string $token): array {
try {
// Validar se o token existe e não está revogado
$validateToken = $this->validateToken(token: $token);
if($validateToken['status'] !== 'success') {
throw new Exception(message: $validateToken['message'], code: $validateToken['response_code']);
}
// Revoga o token fornecido
$revokeToken = (new UsuarioModel())->revokeToken(token: $token);
if($revokeToken['status'] !== 'success') {
throw new Exception(message: 'Erro ao revogar token.', code: $revokeToken['response_code']);
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Logout successful.',
'output' => []
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred during logout.'
];
}
}
/**
* Valida um token de autenticação verificando sua presença no banco de dados e seu status de expiração.
*
* Este método realiza uma validação de duas etapas: 1) Checa se o token existe e não está revogado no banco de dados, e 2) verifica se o timestamp de expiração (exp) no payload do token já passou. Se o token estiver expirado, ele é revogado no banco de dados.
*
* #### Fluxo de Operação:
* 1. **Início da Transação:** Inicia uma transação no banco de dados (`DB::beginTransaction()`) para garantir que a revogação, se necessária, seja atômica.
* 2. **Busca no DB:** Tenta encontrar o token usando o `UsuarioModel`. Se a busca falhar (token não encontrado ou revogado), uma `Exception` é lançada com o código `401 Unauthorized`.
* 3. **Decodificação:** O token é decodificado usando `JWTService::decode()` para acessar o payload e o timestamp de expiração (`exp`).
* 4. **Verificação de Expiração:** Compara o timestamp atual com o `exp` do payload.
* * **Se Expirado:** O método tenta revogar o token no DB. Se a revogação for bem-sucedida, a transação é confirmada e o método retorna um erro com código `401`. Se a revogação falhar, a transação é revertida e um erro `500` é retornado.
* * **Se Válido:** A transação é confirmada e o método retorna `status` 'success' com o código `200 OK` e os dados do payload.
*
* #### Tratamento de Erros:
* - Qualquer `Exception` lançada durante o processo (falha de busca, falha de decodificação JWT, falha de revogação) é capturada. Se a falha ocorrer após o início da transação, um `DB::rollBack()` é implícito ou explicitamente chamado antes do retorno.
* - Retorna um array com `status` 'error', o código de erro (ou `500` como fallback) e uma mensagem descritiva.
*
* @param string $token O token JWT a ser validado.
* @return array Um array associativo contendo o status da operação, um código de resposta HTTP e os dados do token, se válido.
*/
public function validateToken(string $token): array {
// Inicia transação no banco de dados
DB::beginTransaction();
try {
$dbTokenData = (new UsuarioModel())->getTokenByidentifier(identifier: 'token', value: $token);
if($dbTokenData['status'] !== 'success' || empty($dbTokenData['output']['data'])) {
throw new Exception(message: 'Token inválido ou expirado.', code: 401);
}
// Decodifica o token para verificar sua validade
$decodedToken = JWTService::decode(token: $dbTokenData['output']['data']['token']);
// Verifica se o token expirou
$currentTimestamp = (new DateTime())->getTimestamp();
switch (true) {
case $currentTimestamp > $decodedToken['exp']:
// Revoga o token expirado
$revokeToken = (new UsuarioModel())->revokeToken(token: $dbTokenData['output']['data']['token']);
if($revokeToken['status'] !== 'success') {
DB::rollBack();
throw new Exception(message: 'Erro ao revogar token expirado.', code: 500);
}
DB::commit();
return [
'response_code' => 401,
'status' => 'error',
'message' => 'Token expirado.',
'output' => []
];
case $dbTokenData['output']['data']['revoked'] === 1:
DB::commit();
return [
'response_code' => 401,
'status' => 'error',
'message' => 'Token inválido ou expirado.',
'output' => []
];
}
DB::commit();
return [
'response_code' => 200,
'status' => 'success',
'message' => 'Token is valid.',
'output' => ['token_data' => $decodedToken]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred during token validation.'
];
}
}
public function getUserData(string $token): array {
try {
// Validar se o token existe e não está revogado
$validateToken = $this->validateToken(token: $token);
if($validateToken['status'] !== 'success') {
throw new Exception(message: $validateToken['message'], code: $validateToken['response_code']);
}
return [
'response_code' => 200,
'status' => 'success',
'message' => 'User data retrieved successfully.',
'output' => [
'data' => $validateToken['output']['token_data']
]
];
} catch(Exception $e) {
return [
'response_code' => $e->getCode() ?: 500,
'status' => 'error',
'message' => $e->getMessage() ?: 'An unexpected error occurred while retrieving user data.'
];
}
}
}