Atualiza estrutura MVC e integração com banco de dados
This commit is contained in:
@@ -0,0 +1,515 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
class AuthController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Model responsável pelos usuários.
|
||||||
|
*/
|
||||||
|
private User $user;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model responsável pelas tentativas
|
||||||
|
* de autenticação.
|
||||||
|
*/
|
||||||
|
private LoginAttempt $loginAttempt;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Número máximo de tentativas inválidas
|
||||||
|
* permitidas para uma mesma conta.
|
||||||
|
*/
|
||||||
|
private const MAX_TENTATIVAS_CONTA = 5;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Número máximo de tentativas inválidas
|
||||||
|
* permitidas para um mesmo endereço IP.
|
||||||
|
*/
|
||||||
|
private const MAX_TENTATIVAS_IP = 20;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Período utilizado para contabilizar
|
||||||
|
* as tentativas inválidas.
|
||||||
|
*/
|
||||||
|
private const JANELA_MINUTOS = 10;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recebe os Models necessários para
|
||||||
|
* executar o processo de autenticação.
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
User $user,
|
||||||
|
LoginAttempt $loginAttempt
|
||||||
|
) {
|
||||||
|
$this->user = $user;
|
||||||
|
|
||||||
|
$this->loginAttempt = $loginAttempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processa o acesso à página de login.
|
||||||
|
*
|
||||||
|
* GET:
|
||||||
|
* Exibe o formulário.
|
||||||
|
*
|
||||||
|
* POST:
|
||||||
|
* Processa a tentativa de autenticação.
|
||||||
|
*/
|
||||||
|
public function login(): void
|
||||||
|
{
|
||||||
|
if ($this->usuarioAutenticado()) {
|
||||||
|
header('Location: /dashboard.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Garante que a sessão possua
|
||||||
|
* um token CSRF.
|
||||||
|
*/
|
||||||
|
$this->gerarTokenCsrf();
|
||||||
|
|
||||||
|
$erro = '';
|
||||||
|
|
||||||
|
$email = trim(
|
||||||
|
(string)($_POST['email'] ?? '')
|
||||||
|
);
|
||||||
|
|
||||||
|
$senha = (string)(
|
||||||
|
$_POST['senha'] ?? ''
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Obtém o endereço IP real do cliente.
|
||||||
|
*/
|
||||||
|
$ip = $this->obterIpCliente();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Processa somente requisições POST.
|
||||||
|
*/
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
|
|
||||||
|
$erro = $this->processarLogin(
|
||||||
|
$email,
|
||||||
|
$senha,
|
||||||
|
$ip
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Carrega a View responsável
|
||||||
|
* pela apresentação do formulário.
|
||||||
|
*/
|
||||||
|
require __DIR__ . '/../Views/auth/login.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica se existe um usuário autenticado
|
||||||
|
* na sessão atual.
|
||||||
|
*/
|
||||||
|
private function usuarioAutenticado(): bool
|
||||||
|
{
|
||||||
|
return isset(
|
||||||
|
$_SESSION['usuario_id']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria o token CSRF caso ainda
|
||||||
|
* não exista na sessão.
|
||||||
|
*/
|
||||||
|
private function gerarTokenCsrf(): void
|
||||||
|
{
|
||||||
|
if (empty($_SESSION['csrf_token'])) {
|
||||||
|
|
||||||
|
$_SESSION['csrf_token'] = bin2hex(
|
||||||
|
random_bytes(32)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processa todas as etapas de uma
|
||||||
|
* tentativa de autenticação.
|
||||||
|
*/
|
||||||
|
private function processarLogin(
|
||||||
|
string $email,
|
||||||
|
string $senha,
|
||||||
|
string $ip
|
||||||
|
): string {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Valida o token CSRF enviado
|
||||||
|
* pelo formulário.
|
||||||
|
*/
|
||||||
|
if (!$this->validarCsrf()) {
|
||||||
|
return 'E-mail ou senha inválidos.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Valida os dados básicos enviados
|
||||||
|
* pelo usuário.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
!$this->validarDadosLogin(
|
||||||
|
$email,
|
||||||
|
$senha
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return 'E-mail ou senha inválidos.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verifica o limite de tentativas
|
||||||
|
* para a conta.
|
||||||
|
*/
|
||||||
|
if ($this->contaBloqueada($email)) {
|
||||||
|
return 'E-mail ou senha inválidos.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verifica o limite de tentativas
|
||||||
|
* para o endereço IP.
|
||||||
|
*/
|
||||||
|
if ($this->ipBloqueado($ip)) {
|
||||||
|
return 'E-mail ou senha inválidos.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Solicita ao Model o usuário
|
||||||
|
* correspondente ao e-mail informado.
|
||||||
|
*/
|
||||||
|
$usuario = $this->user->buscarPorEmail(
|
||||||
|
$email
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verifica as credenciais fornecidas.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
$usuario !== null &&
|
||||||
|
(int)$usuario['ativo'] === 1 &&
|
||||||
|
password_verify(
|
||||||
|
$senha,
|
||||||
|
$usuario['senha']
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Registra a tentativa bem-sucedida.
|
||||||
|
*/
|
||||||
|
$this->loginAttempt->registrar(
|
||||||
|
$email,
|
||||||
|
$ip,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cria a sessão autenticada.
|
||||||
|
*/
|
||||||
|
$this->criarSessaoAutenticada(
|
||||||
|
$usuario
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Atualiza o hash da senha caso
|
||||||
|
* seja necessário utilizar parâmetros
|
||||||
|
* mais atuais.
|
||||||
|
*/
|
||||||
|
$this->atualizarHashSeNecessario(
|
||||||
|
$usuario,
|
||||||
|
$senha
|
||||||
|
);
|
||||||
|
|
||||||
|
header('Location: /dashboard.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Registra a tentativa inválida.
|
||||||
|
*/
|
||||||
|
$this->loginAttempt->registrar(
|
||||||
|
$email,
|
||||||
|
$ip,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pequeno atraso para dificultar
|
||||||
|
* ataques automatizados.
|
||||||
|
*/
|
||||||
|
usleep(300000);
|
||||||
|
|
||||||
|
return 'E-mail ou senha inválidos.';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valida o token CSRF enviado
|
||||||
|
* pelo formulário de login.
|
||||||
|
*/
|
||||||
|
private function validarCsrf(): bool
|
||||||
|
{
|
||||||
|
$token = (string)(
|
||||||
|
$_POST['csrf_token'] ?? ''
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
$token !== '' &&
|
||||||
|
isset($_SESSION['csrf_token']) &&
|
||||||
|
hash_equals(
|
||||||
|
$_SESSION['csrf_token'],
|
||||||
|
$token
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valida os dados básicos utilizados
|
||||||
|
* durante a autenticação.
|
||||||
|
*/
|
||||||
|
private function validarDadosLogin(
|
||||||
|
string $email,
|
||||||
|
string $senha
|
||||||
|
): bool {
|
||||||
|
|
||||||
|
if (strlen($email) > 150) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
$email === '' ||
|
||||||
|
$senha === ''
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
filter_var(
|
||||||
|
$email,
|
||||||
|
FILTER_VALIDATE_EMAIL
|
||||||
|
) === false
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtém o endereço IP real do cliente.
|
||||||
|
*
|
||||||
|
* Quando a requisição passa pelo Traefik,
|
||||||
|
* REMOTE_ADDR corresponde ao IP do Traefik.
|
||||||
|
*
|
||||||
|
* Nesse caso utilizamos X-Real-IP,
|
||||||
|
* conforme configurado no ambiente.
|
||||||
|
*/
|
||||||
|
private function obterIpCliente(): string
|
||||||
|
{
|
||||||
|
$remoteAddr =
|
||||||
|
$_SERVER['REMOTE_ADDR'] ?? '';
|
||||||
|
|
||||||
|
if ($remoteAddr === '172.18.0.3') {
|
||||||
|
|
||||||
|
$ip =
|
||||||
|
$_SERVER['HTTP_X_REAL_IP'] ?? '';
|
||||||
|
|
||||||
|
if (
|
||||||
|
filter_var(
|
||||||
|
$ip,
|
||||||
|
FILTER_VALIDATE_IP
|
||||||
|
) !== false
|
||||||
|
) {
|
||||||
|
return $ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
filter_var(
|
||||||
|
$remoteAddr,
|
||||||
|
FILTER_VALIDATE_IP
|
||||||
|
) !== false
|
||||||
|
) {
|
||||||
|
return $remoteAddr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return '0.0.0.0';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica se o endereço de e-mail
|
||||||
|
* atingiu o limite de tentativas inválidas.
|
||||||
|
*/
|
||||||
|
private function contaBloqueada(
|
||||||
|
string $email
|
||||||
|
): bool {
|
||||||
|
|
||||||
|
$quantidade =
|
||||||
|
$this->loginAttempt
|
||||||
|
->contarFalhasPorEmail(
|
||||||
|
$email,
|
||||||
|
self::JANELA_MINUTOS
|
||||||
|
);
|
||||||
|
|
||||||
|
return $quantidade
|
||||||
|
>= self::MAX_TENTATIVAS_CONTA;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica se o endereço IP atingiu
|
||||||
|
* o limite de tentativas inválidas.
|
||||||
|
*/
|
||||||
|
private function ipBloqueado(
|
||||||
|
string $ip
|
||||||
|
): bool {
|
||||||
|
|
||||||
|
$quantidade =
|
||||||
|
$this->loginAttempt
|
||||||
|
->contarFalhasPorIp(
|
||||||
|
$ip,
|
||||||
|
self::JANELA_MINUTOS
|
||||||
|
);
|
||||||
|
|
||||||
|
return $quantidade
|
||||||
|
>= self::MAX_TENTATIVAS_IP;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cria a sessão do usuário após
|
||||||
|
* uma autenticação bem-sucedida.
|
||||||
|
*
|
||||||
|
* A regeneração do ID impede ataques
|
||||||
|
* de session fixation.
|
||||||
|
*/
|
||||||
|
private function criarSessaoAutenticada(
|
||||||
|
array $usuario
|
||||||
|
): void {
|
||||||
|
|
||||||
|
session_regenerate_id(true);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Gera um novo token CSRF depois
|
||||||
|
* da autenticação.
|
||||||
|
*/
|
||||||
|
$_SESSION['csrf_token'] = bin2hex(
|
||||||
|
random_bytes(32)
|
||||||
|
);
|
||||||
|
|
||||||
|
$_SESSION['usuario_id'] =
|
||||||
|
$usuario['id'];
|
||||||
|
|
||||||
|
$_SESSION['usuario_nome'] =
|
||||||
|
$usuario['nome'];
|
||||||
|
|
||||||
|
$_SESSION['usuario_email'] =
|
||||||
|
$usuario['email'];
|
||||||
|
|
||||||
|
$_SESSION['usuario_perfil'] =
|
||||||
|
$usuario['perfil'];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Registra os tempos utilizados
|
||||||
|
* pelo controle de expiração da sessão.
|
||||||
|
*/
|
||||||
|
$_SESSION['login_time'] = time();
|
||||||
|
|
||||||
|
$_SESSION['last_activity'] = time();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atualiza o hash da senha caso o algoritmo
|
||||||
|
* ou seus parâmetros tenham evoluído.
|
||||||
|
*
|
||||||
|
* A operação de banco é realizada pelo
|
||||||
|
* Model User.
|
||||||
|
*/
|
||||||
|
private function atualizarHashSeNecessario(
|
||||||
|
array $usuario,
|
||||||
|
string $senha
|
||||||
|
): void {
|
||||||
|
|
||||||
|
if (
|
||||||
|
!password_needs_rehash(
|
||||||
|
$usuario['senha'],
|
||||||
|
PASSWORD_DEFAULT
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$novoHash = password_hash(
|
||||||
|
$senha,
|
||||||
|
PASSWORD_DEFAULT
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->user->atualizarSenha(
|
||||||
|
(int)$usuario['id'],
|
||||||
|
$novoHash
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encerra a sessão do usuário.
|
||||||
|
*
|
||||||
|
* Remove os dados da sessão, invalida
|
||||||
|
* o cookie de sessão e destrói a sessão
|
||||||
|
* existente no servidor.
|
||||||
|
*/
|
||||||
|
public function logout(): void
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Remove todos os dados armazenados
|
||||||
|
* na sessão atual.
|
||||||
|
*/
|
||||||
|
$_SESSION = [];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove o cookie de sessão
|
||||||
|
* do navegador.
|
||||||
|
*/
|
||||||
|
if (ini_get('session.use_cookies')) {
|
||||||
|
|
||||||
|
$params =
|
||||||
|
session_get_cookie_params();
|
||||||
|
|
||||||
|
setcookie(
|
||||||
|
session_name(),
|
||||||
|
'',
|
||||||
|
time() - 42000,
|
||||||
|
$params['path'],
|
||||||
|
$params['domain'],
|
||||||
|
(bool)$params['secure'],
|
||||||
|
(bool)$params['httponly']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Destrói a sessão no servidor.
|
||||||
|
*/
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Retorna o usuário para
|
||||||
|
* a tela de login.
|
||||||
|
*/
|
||||||
|
header('Location: /login.php');
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
cat > /home/opc/html/app/Controllers/Controller.php <<'EOF'
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller base da aplicação.
|
||||||
|
*
|
||||||
|
* Esta classe servirá como ponto de partida para
|
||||||
|
* os Controllers específicos do sistema.
|
||||||
|
*/
|
||||||
|
abstract class Controller
|
||||||
|
{
|
||||||
|
}
|
||||||
|
EOF
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
class DashboardController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Exibe a página principal do dashboard.
|
||||||
|
*
|
||||||
|
* A autenticação e o controle de expiração
|
||||||
|
* da sessão são realizados pelo security.php.
|
||||||
|
*/
|
||||||
|
public function index(): void
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Verifica se existe um usuário autenticado.
|
||||||
|
*
|
||||||
|
* Caso contrário, redireciona para o login.
|
||||||
|
*/
|
||||||
|
if (!isset($_SESSION['usuario_id'])) {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dados básicos do usuário autenticado.
|
||||||
|
*
|
||||||
|
* Posteriormente esses dados poderão vir
|
||||||
|
* de um Model ou de um Service.
|
||||||
|
*/
|
||||||
|
$usuario = [
|
||||||
|
'id' => $_SESSION['usuario_id'],
|
||||||
|
'nome' => $_SESSION['usuario_nome'] ?? '',
|
||||||
|
'email' => $_SESSION['usuario_email'] ?? '',
|
||||||
|
'perfil' => $_SESSION['usuario_perfil'] ?? ''
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Carrega a View do dashboard.
|
||||||
|
*
|
||||||
|
* A View recebe os dados através da variável
|
||||||
|
* $usuario e fica responsável somente pela
|
||||||
|
* apresentação das informações.
|
||||||
|
*/
|
||||||
|
require __DIR__ . '/../Views/dashboard/index.php';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller responsável pelos mecanismos de segurança
|
||||||
|
* relacionados à sessão e às requisições HTTP.
|
||||||
|
*
|
||||||
|
* Responsabilidades:
|
||||||
|
*
|
||||||
|
* - configurar os parâmetros de segurança da sessão;
|
||||||
|
* - iniciar a sessão com segurança;
|
||||||
|
* - aplicar headers HTTP de segurança;
|
||||||
|
* - controlar o tempo de inatividade da sessão;
|
||||||
|
* - controlar o tempo máximo de uma sessão;
|
||||||
|
* - destruir sessões expiradas;
|
||||||
|
* - redirecionar o usuário quando a sessão expirar.
|
||||||
|
*/
|
||||||
|
class SecurityController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Tempo máximo permitido sem atividade do usuário.
|
||||||
|
*
|
||||||
|
* 1800 segundos = 30 minutos.
|
||||||
|
*/
|
||||||
|
private const SESSAO_INATIVIDADE_SEGUNDOS = 1800;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tempo máximo absoluto de uma sessão.
|
||||||
|
*
|
||||||
|
* 28800 segundos = 8 horas.
|
||||||
|
*/
|
||||||
|
private const SESSAO_MAXIMA_SEGUNDOS = 28800;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configura os parâmetros de segurança
|
||||||
|
* utilizados pelo cookie da sessão.
|
||||||
|
*
|
||||||
|
* Essas configurações devem ser aplicadas
|
||||||
|
* antes de session_start().
|
||||||
|
*/
|
||||||
|
public function configurarSessao(): void
|
||||||
|
{
|
||||||
|
ini_set(
|
||||||
|
'session.use_strict_mode',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
|
||||||
|
ini_set(
|
||||||
|
'session.use_only_cookies',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
|
||||||
|
ini_set(
|
||||||
|
'session.cookie_httponly',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
|
||||||
|
ini_set(
|
||||||
|
'session.cookie_secure',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
|
||||||
|
ini_set(
|
||||||
|
'session.cookie_samesite',
|
||||||
|
'Lax'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inicia a sessão caso ainda não exista
|
||||||
|
* uma sessão ativa.
|
||||||
|
*/
|
||||||
|
public function iniciarSessao(): void
|
||||||
|
{
|
||||||
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aplica headers HTTP destinados a reduzir
|
||||||
|
* diferentes tipos de ataques no navegador.
|
||||||
|
*
|
||||||
|
* Os headers utilizados são:
|
||||||
|
*
|
||||||
|
* - X-Content-Type-Options;
|
||||||
|
* - X-Frame-Options;
|
||||||
|
* - Referrer-Policy;
|
||||||
|
* - Permissions-Policy;
|
||||||
|
* - Content-Security-Policy;
|
||||||
|
* - Strict-Transport-Security.
|
||||||
|
*/
|
||||||
|
public function aplicarHeadersSeguranca(): void
|
||||||
|
{
|
||||||
|
header(
|
||||||
|
'X-Content-Type-Options: nosniff'
|
||||||
|
);
|
||||||
|
|
||||||
|
header(
|
||||||
|
'X-Frame-Options: DENY'
|
||||||
|
);
|
||||||
|
|
||||||
|
header(
|
||||||
|
'Referrer-Policy: strict-origin-when-cross-origin'
|
||||||
|
);
|
||||||
|
|
||||||
|
header(
|
||||||
|
'Permissions-Policy: camera=(), microphone=(), geolocation=()'
|
||||||
|
);
|
||||||
|
|
||||||
|
header(
|
||||||
|
"Content-Security-Policy: "
|
||||||
|
. "default-src 'self'; "
|
||||||
|
. "style-src 'self'; "
|
||||||
|
. "script-src 'self'; "
|
||||||
|
. "img-src 'self' data:; "
|
||||||
|
. "font-src 'self'; "
|
||||||
|
. "form-action 'self'; "
|
||||||
|
. "frame-ancestors 'none'; "
|
||||||
|
. "base-uri 'self'; "
|
||||||
|
. "object-src 'none'"
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HSTS determina que o navegador deverá utilizar
|
||||||
|
* HTTPS durante o período definido.
|
||||||
|
*
|
||||||
|
* Esta aplicação já utiliza HTTPS através do Traefik.
|
||||||
|
*/
|
||||||
|
header(
|
||||||
|
'Strict-Transport-Security: max-age=31536000; includeSubDomains'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifica se a sessão autenticada ainda está dentro
|
||||||
|
* dos limites de tempo estabelecidos.
|
||||||
|
*
|
||||||
|
* São verificados dois limites:
|
||||||
|
*
|
||||||
|
* 1. tempo máximo de inatividade;
|
||||||
|
* 2. tempo máximo absoluto da sessão.
|
||||||
|
*/
|
||||||
|
public function verificarExpiracaoSessao(): void
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Se não existir usuário autenticado,
|
||||||
|
* não há sessão protegida para verificar.
|
||||||
|
*/
|
||||||
|
if (!isset($_SESSION['usuario_id'])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$agora = time();
|
||||||
|
|
||||||
|
$ultimoAcesso = (int) (
|
||||||
|
$_SESSION['last_activity'] ?? 0
|
||||||
|
);
|
||||||
|
|
||||||
|
$inicioSessao = (int) (
|
||||||
|
$_SESSION['login_time'] ?? 0
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verifica se o usuário ficou tempo demais
|
||||||
|
* sem realizar nenhuma atividade.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
$ultimoAcesso > 0
|
||||||
|
&& ($agora - $ultimoAcesso)
|
||||||
|
> self::SESSAO_INATIVIDADE_SEGUNDOS
|
||||||
|
) {
|
||||||
|
$this->encerrarSessao(
|
||||||
|
'inatividade'
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verifica se a sessão atingiu seu tempo
|
||||||
|
* máximo absoluto.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
$inicioSessao > 0
|
||||||
|
&& ($agora - $inicioSessao)
|
||||||
|
> self::SESSAO_MAXIMA_SEGUNDOS
|
||||||
|
) {
|
||||||
|
$this->encerrarSessao(
|
||||||
|
'tempo_maximo'
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Registra a última atividade do usuário.
|
||||||
|
*/
|
||||||
|
$_SESSION['last_activity'] = $agora;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encerra uma sessão expirada e redireciona
|
||||||
|
* o usuário para a tela de login.
|
||||||
|
*
|
||||||
|
* O motivo da expiração é enviado através
|
||||||
|
* de um parâmetro na URL.
|
||||||
|
*/
|
||||||
|
private function encerrarSessao(
|
||||||
|
string $motivo
|
||||||
|
): void {
|
||||||
|
/*
|
||||||
|
* Remove todos os dados armazenados
|
||||||
|
* na sessão atual.
|
||||||
|
*/
|
||||||
|
$_SESSION = [];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove o cookie da sessão do navegador.
|
||||||
|
*/
|
||||||
|
if (ini_get('session.use_cookies')) {
|
||||||
|
$params = session_get_cookie_params();
|
||||||
|
|
||||||
|
setcookie(
|
||||||
|
session_name(),
|
||||||
|
'',
|
||||||
|
time() - 42000,
|
||||||
|
$params['path'],
|
||||||
|
$params['domain'],
|
||||||
|
(bool) $params['secure'],
|
||||||
|
(bool) $params['httponly']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Destrói a sessão no servidor.
|
||||||
|
*/
|
||||||
|
session_destroy();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Redireciona para o login informando
|
||||||
|
* que a sessão foi encerrada.
|
||||||
|
*/
|
||||||
|
header(
|
||||||
|
'Location: /login.php?expirada=1&motivo='
|
||||||
|
. urlencode($motivo)
|
||||||
|
);
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executa a sequência básica de segurança
|
||||||
|
* necessária para uma requisição da aplicação.
|
||||||
|
*
|
||||||
|
* Este método permite que as páginas utilizem
|
||||||
|
* apenas uma chamada para inicializar a segurança.
|
||||||
|
*/
|
||||||
|
public function iniciarSeguranca(): void
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Configura o cookie antes de iniciar a sessão.
|
||||||
|
*/
|
||||||
|
$this->configurarSessao();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Inicia a sessão.
|
||||||
|
*/
|
||||||
|
$this->iniciarSessao();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Aplica os headers HTTP de segurança.
|
||||||
|
*/
|
||||||
|
$this->aplicarHeadersSeguranca();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verifica se uma sessão autenticada
|
||||||
|
* expirou por tempo.
|
||||||
|
*/
|
||||||
|
$this->verificarExpiracaoSessao();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model responsável pelas operações relacionadas
|
||||||
|
* às tentativas de autenticação.
|
||||||
|
*
|
||||||
|
* Responsabilidades:
|
||||||
|
*
|
||||||
|
* - verificar tentativas inválidas por e-mail;
|
||||||
|
* - verificar tentativas inválidas por endereço IP;
|
||||||
|
* - registrar tentativas de autenticação;
|
||||||
|
* - centralizar o acesso à tabela tentativas_login.
|
||||||
|
*/
|
||||||
|
class LoginAttempt
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Conexão com o banco de dados.
|
||||||
|
*/
|
||||||
|
private PDO $pdo;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recebe a conexão PDO utilizada pelo Model.
|
||||||
|
*/
|
||||||
|
public function __construct(PDO $pdo)
|
||||||
|
{
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conta as tentativas inválidas de um endereço
|
||||||
|
* de e-mail dentro da janela de tempo informada.
|
||||||
|
*/
|
||||||
|
public function contarFalhasPorEmail(
|
||||||
|
string $email,
|
||||||
|
int $janelaMinutos
|
||||||
|
): int {
|
||||||
|
|
||||||
|
$janelaMinutos = max(
|
||||||
|
1,
|
||||||
|
$janelaMinutos
|
||||||
|
);
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM tentativas_login
|
||||||
|
WHERE email = :email
|
||||||
|
AND sucesso = 0
|
||||||
|
AND criado_em >= DATE_SUB(
|
||||||
|
NOW(),
|
||||||
|
INTERVAL {$janelaMinutos} MINUTE
|
||||||
|
)
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
'email' => $email
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (int)$stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Conta as tentativas inválidas de um endereço
|
||||||
|
* IP dentro da janela de tempo informada.
|
||||||
|
*/
|
||||||
|
public function contarFalhasPorIp(
|
||||||
|
string $ip,
|
||||||
|
int $janelaMinutos
|
||||||
|
): int {
|
||||||
|
|
||||||
|
$janelaMinutos = max(
|
||||||
|
1,
|
||||||
|
$janelaMinutos
|
||||||
|
);
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM tentativas_login
|
||||||
|
WHERE ip_address = :ip
|
||||||
|
AND sucesso = 0
|
||||||
|
AND criado_em >= DATE_SUB(
|
||||||
|
NOW(),
|
||||||
|
INTERVAL {$janelaMinutos} MINUTE
|
||||||
|
)
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
'ip' => $ip
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (int)$stmt->fetchColumn();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registra uma tentativa de autenticação
|
||||||
|
* realizada pelo usuário.
|
||||||
|
*/
|
||||||
|
public function registrar(
|
||||||
|
string $email,
|
||||||
|
string $ip,
|
||||||
|
bool $sucesso
|
||||||
|
): bool {
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
INSERT INTO tentativas_login
|
||||||
|
(
|
||||||
|
email,
|
||||||
|
ip_address,
|
||||||
|
sucesso
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
:email,
|
||||||
|
:ip,
|
||||||
|
:sucesso
|
||||||
|
)
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([
|
||||||
|
'email' => $email,
|
||||||
|
'ip' => $ip,
|
||||||
|
'sucesso' => $sucesso ? 1 : 0
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model responsável pelas operações relacionadas
|
||||||
|
* aos usuários da aplicação.
|
||||||
|
*
|
||||||
|
* Responsabilidades:
|
||||||
|
*
|
||||||
|
* - localizar usuário pelo e-mail;
|
||||||
|
* - atualizar senha;
|
||||||
|
* - centralizar o acesso à tabela usuarios.
|
||||||
|
*/
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Conexão com o banco de dados.
|
||||||
|
*/
|
||||||
|
private PDO $pdo;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recebe a conexão PDO utilizada pelo Model.
|
||||||
|
*/
|
||||||
|
public function __construct(PDO $pdo)
|
||||||
|
{
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Localiza um usuário através do endereço de e-mail.
|
||||||
|
*
|
||||||
|
* Retorna os dados necessários para o processo
|
||||||
|
* de autenticação.
|
||||||
|
*
|
||||||
|
* Retorna null quando o usuário não existe.
|
||||||
|
*/
|
||||||
|
public function buscarPorEmail(string $email): ?array
|
||||||
|
{
|
||||||
|
$sql = "
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
nome,
|
||||||
|
email,
|
||||||
|
senha,
|
||||||
|
perfil,
|
||||||
|
ativo
|
||||||
|
FROM usuarios
|
||||||
|
WHERE email = :email
|
||||||
|
LIMIT 1
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
|
||||||
|
$stmt->execute([
|
||||||
|
'email' => $email
|
||||||
|
]);
|
||||||
|
|
||||||
|
$usuario = $stmt->fetch();
|
||||||
|
|
||||||
|
if ($usuario === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $usuario;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atualiza o hash da senha de um usuário.
|
||||||
|
*
|
||||||
|
* Utilizado quando password_needs_rehash()
|
||||||
|
* identificar que o algoritmo ou os parâmetros
|
||||||
|
* utilizados para criar o hash foram atualizados.
|
||||||
|
*/
|
||||||
|
public function atualizarSenha(
|
||||||
|
int $id,
|
||||||
|
string $novoHash
|
||||||
|
): bool {
|
||||||
|
|
||||||
|
$sql = "
|
||||||
|
UPDATE usuarios
|
||||||
|
SET senha = :senha
|
||||||
|
WHERE id = :id
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $this->pdo->prepare($sql);
|
||||||
|
|
||||||
|
return $stmt->execute([
|
||||||
|
'senha' => $novoHash,
|
||||||
|
'id' => $id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="pt-BR">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0"
|
||||||
|
>
|
||||||
|
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Acesso ao sistema Finanças+"
|
||||||
|
>
|
||||||
|
|
||||||
|
<title>Acesso - Finanças+</title>
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="/css/style.css"
|
||||||
|
>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="header">
|
||||||
|
|
||||||
|
<nav class="nav container">
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="brand"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
|
||||||
|
<span class="mark">
|
||||||
|
F+
|
||||||
|
</span>
|
||||||
|
|
||||||
|
Finanças<span>+</span>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="container"
|
||||||
|
style="max-width: 480px;"
|
||||||
|
>
|
||||||
|
|
||||||
|
<div class="heading">
|
||||||
|
|
||||||
|
<small class="eyebrow">
|
||||||
|
ACESSO AO SISTEMA
|
||||||
|
</small>
|
||||||
|
|
||||||
|
<!--<h1>
|
||||||
|
Entrar
|
||||||
|
</h1>-->
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Acesse sua área financeira.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($erro !== ''): ?>
|
||||||
|
|
||||||
|
<div
|
||||||
|
role="alert" class="login-error"
|
||||||
|
>
|
||||||
|
|
||||||
|
<?= htmlspecialchars(
|
||||||
|
$erro,
|
||||||
|
ENT_QUOTES | ENT_SUBSTITUTE,
|
||||||
|
'UTF-8'
|
||||||
|
) ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<div class="login-card">
|
||||||
|
<form
|
||||||
|
method="post"
|
||||||
|
action="/login.php"
|
||||||
|
autocomplete="on"
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="hidden"
|
||||||
|
name="csrf_token"
|
||||||
|
value="<?= htmlspecialchars(
|
||||||
|
$_SESSION['csrf_token'],
|
||||||
|
ENT_QUOTES | ENT_SUBSTITUTE,
|
||||||
|
'UTF-8'
|
||||||
|
) ?>"
|
||||||
|
>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
E-mail
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
maxlength="150"
|
||||||
|
required
|
||||||
|
autocomplete="username"
|
||||||
|
value="<?= htmlspecialchars(
|
||||||
|
$email,
|
||||||
|
ENT_QUOTES | ENT_SUBSTITUTE,
|
||||||
|
'UTF-8'
|
||||||
|
) ?>"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Senha
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="senha"
|
||||||
|
maxlength="128"
|
||||||
|
required
|
||||||
|
autocomplete="current-password"
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="btn primary full"
|
||||||
|
>
|
||||||
|
Entrar
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
|
||||||
|
<div class="container footer">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="brand"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
|
||||||
|
<span class="mark">
|
||||||
|
F+
|
||||||
|
</span>
|
||||||
|
|
||||||
|
Finanças<span>+</span>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Conhecimento para decisões financeiras melhores.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<small>
|
||||||
|
© 2026 Finanças+.
|
||||||
|
</small>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="pt-BR">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0"
|
||||||
|
>
|
||||||
|
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Dashboard financeiro do Finanças+"
|
||||||
|
>
|
||||||
|
|
||||||
|
<title>Dashboard - Finanças+</title>
|
||||||
|
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="/css/style.css"
|
||||||
|
>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="header">
|
||||||
|
|
||||||
|
<nav class="nav container">
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="brand"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
|
||||||
|
<span class="mark">
|
||||||
|
F+
|
||||||
|
</span>
|
||||||
|
|
||||||
|
Finanças<span>+</span>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="links">
|
||||||
|
|
||||||
|
<a href="/dashboard.php">
|
||||||
|
Dashboard
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="/logout.php">
|
||||||
|
Sair
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="heading">
|
||||||
|
|
||||||
|
<small class="eyebrow">
|
||||||
|
ÁREA DO USUÁRIO
|
||||||
|
</small>
|
||||||
|
|
||||||
|
<h1>
|
||||||
|
Olá, <?= htmlspecialchars(
|
||||||
|
$usuario['nome'],
|
||||||
|
ENT_QUOTES | ENT_SUBSTITUTE,
|
||||||
|
'UTF-8'
|
||||||
|
) ?>!
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Bem-vindo ao seu painel financeiro.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cards">
|
||||||
|
|
||||||
|
<article class="card">
|
||||||
|
|
||||||
|
<div class="icon">
|
||||||
|
??
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<small>
|
||||||
|
PATRIMÔNIO
|
||||||
|
</small>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
R$ 0,00
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Seu patrimônio será exibido aqui.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="card">
|
||||||
|
|
||||||
|
<div class="icon">
|
||||||
|
??
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<small>
|
||||||
|
RECEITAS
|
||||||
|
</small>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
R$ 0,00
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Acompanhe suas receitas mensais.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="card">
|
||||||
|
|
||||||
|
<div class="icon">
|
||||||
|
??
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<small>
|
||||||
|
INVESTIMENTOS
|
||||||
|
</small>
|
||||||
|
|
||||||
|
<h3>
|
||||||
|
R$ 0,00
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Acompanhe a evolução dos seus investimentos.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
|
||||||
|
<div class="container footer">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="brand"
|
||||||
|
href="/"
|
||||||
|
>
|
||||||
|
|
||||||
|
<span class="mark">
|
||||||
|
F+
|
||||||
|
</span>
|
||||||
|
|
||||||
|
Finanças<span>+</span>
|
||||||
|
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Conhecimento para decisões financeiras melhores.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<small>
|
||||||
|
© 2026 Finanças+.
|
||||||
|
</small>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once '/home/opc/database.php';
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -0,0 +1,350 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configurações centralizadas de segurança da aplicação.
|
||||||
|
*
|
||||||
|
* Responsabilidades:
|
||||||
|
*
|
||||||
|
* - configurar parâmetros seguros da sessão;
|
||||||
|
* - configurar headers HTTP de segurança;
|
||||||
|
* - iniciar a sessão;
|
||||||
|
* - controlar expiração por inatividade;
|
||||||
|
* - controlar tempo máximo da sessão.
|
||||||
|
*
|
||||||
|
* Este arquivo deve ser carregado antes de qualquer
|
||||||
|
* saída HTML ou envio de headers HTTP.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =========================================================
|
||||||
|
* CONFIGURAÇÕES DA SESSÃO
|
||||||
|
* =========================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tempo máximo de inatividade permitido.
|
||||||
|
*
|
||||||
|
* 1800 segundos = 30 minutos.
|
||||||
|
*/
|
||||||
|
const SESSAO_INATIVIDADE_SEGUNDOS = 1800;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tempo máximo absoluto de uma sessão autenticada.
|
||||||
|
*
|
||||||
|
* 28800 segundos = 8 horas.
|
||||||
|
*/
|
||||||
|
const SESSAO_MAXIMA_SEGUNDOS = 28800;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =========================================================
|
||||||
|
* CONFIGURAÇÃO DOS COOKIES DA SESSÃO
|
||||||
|
* =========================================================
|
||||||
|
*
|
||||||
|
* Estas configurações precisam ser realizadas
|
||||||
|
* antes de session_start().
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Impede a utilização de um identificador de sessão
|
||||||
|
* que não tenha sido criado pelo servidor.
|
||||||
|
*/
|
||||||
|
ini_set(
|
||||||
|
'session.use_strict_mode',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permite que o identificador da sessão seja enviado
|
||||||
|
* somente através de cookie.
|
||||||
|
*
|
||||||
|
* Isso impede o uso de IDs de sessão na URL.
|
||||||
|
*/
|
||||||
|
ini_set(
|
||||||
|
'session.use_only_cookies',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Impede que JavaScript tenha acesso ao cookie
|
||||||
|
* da sessão.
|
||||||
|
*/
|
||||||
|
ini_set(
|
||||||
|
'session.cookie_httponly',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permite o envio do cookie somente através
|
||||||
|
* de conexões HTTPS.
|
||||||
|
*/
|
||||||
|
ini_set(
|
||||||
|
'session.cookie_secure',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reduz o risco de envio automático do cookie
|
||||||
|
* em requisições originadas de outros sites.
|
||||||
|
*/
|
||||||
|
ini_set(
|
||||||
|
'session.cookie_samesite',
|
||||||
|
'Lax'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =========================================================
|
||||||
|
* CONFIGURAÇÕES GERAIS DO PHP
|
||||||
|
* =========================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Não informa ao navegador que a aplicação
|
||||||
|
* está utilizando PHP.
|
||||||
|
*/
|
||||||
|
ini_set(
|
||||||
|
'expose_php',
|
||||||
|
'0'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =========================================================
|
||||||
|
* HEADERS HTTP DE SEGURANÇA
|
||||||
|
* =========================================================
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Impede que o navegador interprete um conteúdo
|
||||||
|
* utilizando um MIME type diferente daquele declarado.
|
||||||
|
*/
|
||||||
|
header(
|
||||||
|
'X-Content-Type-Options: nosniff'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Impede que a aplicação seja carregada
|
||||||
|
* dentro de frames.
|
||||||
|
*
|
||||||
|
* Proteção contra ataques de clickjacking.
|
||||||
|
*/
|
||||||
|
header(
|
||||||
|
'X-Frame-Options: DENY'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controla quais informações de referência
|
||||||
|
* podem ser enviadas pelo navegador.
|
||||||
|
*/
|
||||||
|
header(
|
||||||
|
'Referrer-Policy: strict-origin-when-cross-origin'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Desabilita recursos do navegador que não são
|
||||||
|
* utilizados pela aplicação.
|
||||||
|
*/
|
||||||
|
header(
|
||||||
|
'Permissions-Policy: camera=(), microphone=(), geolocation=()'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =========================================================
|
||||||
|
* CONTENT SECURITY POLICY
|
||||||
|
* =========================================================
|
||||||
|
*
|
||||||
|
* Permite somente recursos provenientes da própria
|
||||||
|
* aplicação.
|
||||||
|
*
|
||||||
|
* A aplicação utiliza:
|
||||||
|
*
|
||||||
|
* /css/style.css
|
||||||
|
* /js/app.js
|
||||||
|
*
|
||||||
|
* Não são permitidos scripts ou estilos externos.
|
||||||
|
*/
|
||||||
|
header(
|
||||||
|
"Content-Security-Policy: "
|
||||||
|
. "default-src 'self'; "
|
||||||
|
. "style-src 'self'; "
|
||||||
|
. "script-src 'self'; "
|
||||||
|
. "img-src 'self' data:; "
|
||||||
|
. "font-src 'self'; "
|
||||||
|
. "form-action 'self'; "
|
||||||
|
. "frame-ancestors 'none'; "
|
||||||
|
. "base-uri 'self'; "
|
||||||
|
. "object-src 'none'"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =========================================================
|
||||||
|
* HTTP STRICT TRANSPORT SECURITY
|
||||||
|
* =========================================================
|
||||||
|
*
|
||||||
|
* Informa ao navegador que a aplicação deve ser
|
||||||
|
* acessada exclusivamente através de HTTPS.
|
||||||
|
*
|
||||||
|
* O domínio já está funcionando através de HTTPS,
|
||||||
|
* portanto este header pode ser utilizado.
|
||||||
|
*/
|
||||||
|
header(
|
||||||
|
'Strict-Transport-Security: max-age=31536000; includeSubDomains'
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =========================================================
|
||||||
|
* INICIALIZAÇÃO DA SESSÃO
|
||||||
|
* =========================================================
|
||||||
|
*
|
||||||
|
* A sessão somente será iniciada se ainda não estiver
|
||||||
|
* ativa.
|
||||||
|
*
|
||||||
|
* Isso evita:
|
||||||
|
*
|
||||||
|
* "session_start(): Ignoring session_start()
|
||||||
|
* because a session is already active"
|
||||||
|
*/
|
||||||
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =========================================================
|
||||||
|
* CONTROLE DE EXPIRAÇÃO DA SESSÃO
|
||||||
|
* =========================================================
|
||||||
|
*
|
||||||
|
* O controle abaixo somente é executado para usuários
|
||||||
|
* que já estejam autenticados.
|
||||||
|
*/
|
||||||
|
if (isset($_SESSION['usuario_id'])) {
|
||||||
|
|
||||||
|
$agora = time();
|
||||||
|
|
||||||
|
$ultimoAcesso =
|
||||||
|
(int)($_SESSION['last_activity'] ?? 0);
|
||||||
|
|
||||||
|
$inicioSessao =
|
||||||
|
(int)($_SESSION['login_time'] ?? 0);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =====================================================
|
||||||
|
* EXPIRAÇÃO POR INATIVIDADE
|
||||||
|
* =====================================================
|
||||||
|
*
|
||||||
|
* Se o usuário permanecer mais de 30 minutos
|
||||||
|
* sem realizar nenhuma requisição autenticada,
|
||||||
|
* a sessão será encerrada.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
$ultimoAcesso > 0 &&
|
||||||
|
($agora - $ultimoAcesso)
|
||||||
|
> SESSAO_INATIVIDADE_SEGUNDOS
|
||||||
|
) {
|
||||||
|
|
||||||
|
destruirSessao();
|
||||||
|
|
||||||
|
header(
|
||||||
|
'Location: /login.php?expirada=1'
|
||||||
|
);
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =====================================================
|
||||||
|
* EXPIRAÇÃO PELO TEMPO MÁXIMO
|
||||||
|
* =====================================================
|
||||||
|
*
|
||||||
|
* Mesmo que o usuário continue ativo,
|
||||||
|
* a sessão não poderá ultrapassar 8 horas.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
$inicioSessao > 0 &&
|
||||||
|
($agora - $inicioSessao)
|
||||||
|
> SESSAO_MAXIMA_SEGUNDOS
|
||||||
|
) {
|
||||||
|
|
||||||
|
destruirSessao();
|
||||||
|
|
||||||
|
header(
|
||||||
|
'Location: /login.php?expirada=1'
|
||||||
|
);
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =====================================================
|
||||||
|
* ATUALIZAÇÃO DA ATIVIDADE
|
||||||
|
* =====================================================
|
||||||
|
*
|
||||||
|
* A cada requisição autenticada, registra o momento
|
||||||
|
* do último acesso.
|
||||||
|
*/
|
||||||
|
$_SESSION['last_activity'] = $agora;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* =========================================================
|
||||||
|
* FUNÇÃO DE DESTRUIÇÃO DA SESSÃO
|
||||||
|
* =========================================================
|
||||||
|
*
|
||||||
|
* Centraliza o procedimento utilizado quando uma sessão
|
||||||
|
* precisa ser encerrada.
|
||||||
|
*/
|
||||||
|
function destruirSessao(): void
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Remove todos os dados armazenados na sessão.
|
||||||
|
*/
|
||||||
|
$_SESSION = [];
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove o cookie da sessão no navegador.
|
||||||
|
*/
|
||||||
|
if (ini_get('session.use_cookies')) {
|
||||||
|
|
||||||
|
$params =
|
||||||
|
session_get_cookie_params();
|
||||||
|
|
||||||
|
setcookie(
|
||||||
|
session_name(),
|
||||||
|
'',
|
||||||
|
time() - 42000,
|
||||||
|
$params['path'],
|
||||||
|
$params['domain'],
|
||||||
|
(bool)$params['secure'],
|
||||||
|
(bool)$params['httponly']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Destrói a sessão existente no servidor.
|
||||||
|
*/
|
||||||
|
session_destroy();
|
||||||
|
}
|
||||||
+356
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/config/security.php';
|
||||||
|
require_once __DIR__ . '/app/Controllers/DashboardController.php';
|
||||||
|
|
||||||
|
$controller = new DashboardController();
|
||||||
|
|
||||||
|
$controller->index();
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
if (session_status() !== PHP_SESSION_ACTIVE) {
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function usuarioAutenticado(): bool
|
||||||
|
{
|
||||||
|
return isset($_SESSION['usuario_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
function exigirLogin(): void
|
||||||
|
{
|
||||||
|
if (!usuarioAutenticado()) {
|
||||||
|
header('Location: /login.php');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
+456
-7
@@ -1,7 +1,456 @@
|
|||||||
<!DOCTYPE html><html lang="pt-BR"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><meta name="description" content="Finanças+ — educação financeira e simulador de juros compostos"><title>Finanças+ | Educação Financeira</title><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"><link rel="stylesheet" href="css/style.css"></head><body>
|
<!DOCTYPE html>
|
||||||
<header class="header"><nav class="nav container"><a class="brand" href="#inicio"><span class="mark">F+</span>Finanças<span>+</span></a><button class="menu" aria-label="Menu">☰</button><div class="links"><a href="#inicio">Início</a><a href="#aprenda">Aprenda</a><a href="#simulador">Simulador</a><a href="#metas">Metas</a></div></nav></header>
|
|
||||||
<main><section class="hero" id="inicio"><div class="orb one"></div><div class="orb two"></div><div class="container hero-grid"><div class="hero-copy reveal"><small class="eyebrow">EDUCAÇÃO FINANCEIRA</small><h1>Seu dinheiro deve <em>trabalhar por você.</em></h1><p>Aprenda a organizar suas finanças, tomar decisões melhores e construir um futuro financeiro mais tranquilo.</p><div class="actions"><a class="btn primary" href="#simulador">Começar a simular →</a><a class="btn outline" href="#aprenda">Quero aprender</a></div><div class="trust">✓ Conteúdo educativo ✓ Simulação gratuita ✓ Sem cadastro</div></div><div class="visual reveal delay1"><div class="dashboard"><div class="dash-top"><span>Patrimônio projetado</span><b>●</b></div><strong>R$ 84.750,00</strong><div class="mini"><svg viewBox="0 0 500 180" preserveAspectRatio="none"><path class="fill" d="M0 160 C55 150 75 130 120 138 S180 115 220 120 S270 88 310 98 S360 60 395 72 S450 30 500 22 L500 180 L0 180Z"/><path class="line" d="M0 160 C55 150 75 130 120 138 S180 115 220 120 S270 88 310 98 S360 60 395 72 S450 30 500 22"/></svg></div><div class="dash-bottom"><span>+18,4%</span><span>12 meses</span></div></div><div class="float goal">🎯 <div><small>Meta mensal</small><b>R$ 500</b></div></div><div class="float saved">💰 <div><small>Economizado</small><b>R$ 7.280</b></div></div></div></div></section>
|
<html lang="pt-BR">
|
||||||
<section class="section" id="aprenda"><div class="container"><div class="heading reveal"><small class="eyebrow">PRIMEIROS PASSOS</small><h2>Uma vida financeira melhor começa com três hábitos.</h2><p>Conhecimento, organização e constância fazem mais diferença do que procurar uma fórmula mágica.</p></div><div class="cards"><article class="card reveal"><div class="icon">💰</div><small>01</small><h3>Organize</h3><p>Saiba quanto entra, quanto sai e para onde seu dinheiro está indo.</p></article><article class="card reveal delay1"><div class="icon">📊</div><small>02</small><h3>Planeje</h3><p>Transforme objetivos em metas financeiras claras e mensuráveis.</p></article><article class="card reveal delay2"><div class="icon">📈</div><small>03</small><h3>Invista</h3><p>Conheça as opções de investimento e o efeito dos juros compostos.</p></article></div></div></section>
|
<head>
|
||||||
<section class="sim-section" id="simulador"><div class="container sim-grid"><div class="sim-copy reveal"><small class="eyebrow">SIMULADOR</small><h2>Veja o poder dos juros compostos.</h2><p>Faça uma projeção educativa do crescimento do seu patrimônio. Não é recomendação de investimento.</p><div class="tip">💡 <div><b>Uma regra importante</b><p>Tempo e aportes consistentes podem fazer grande diferença no longo prazo.</p></div></div></div><div class="sim-card reveal delay1"><form id="form"><div class="inputs"><label>Investimento inicial<div class="input"><span>R$</span><input id="initial" type="number" value="1000" min="0"></div></label><label>Aporte mensal<div class="input"><span>R$</span><input id="monthly" type="number" value="300" min="0"></div></label><label>Taxa mensal<div class="input"><input id="rate" type="number" value="0.8" min="0" step="0.1"><span>%</span></div></label><label>Período<div class="input"><input id="months" type="number" value="60" min="1"><span>meses</span></div></label></div><button class="btn primary full">Calcular projeção →</button></form><div class="results"><div><small>Total investido</small><b id="invested">R$ 0,00</b></div><div><small>Rendimentos</small><b id="earnings">R$ 0,00</b></div><div class="highlight"><small>Valor projetado</small><b id="future">R$ 0,00</b></div></div><canvas id="growth"></canvas></div></div></section>
|
<meta charset="UTF-8">
|
||||||
<section class="section" id="metas"><div class="container goal-grid"><div class="heading reveal"><small class="eyebrow">METAS FINANCEIRAS</small><h2>Transforme sonhos em números.</h2><p>Uma meta clara facilita acompanhar o progresso e manter a disciplina.</p></div><div class="goal-card reveal delay1"><div class="goal-head"><b>Reserva de emergência</b><strong id="percent">64%</strong></div><div class="progress"><span id="progress"></span></div><div class="goal-values"><span>R$ 6.400</span><span>R$ 10.000</span></div><div class="goal-foot"><span>Faltam R$ 3.600</span><button id="advance">Registrar progresso</button></div></div></div></section><section class="quote"><div class="container reveal"><span>“</span><p>Educação financeira não é sobre ficar rico rapidamente. É sobre fazer escolhas conscientes com o dinheiro que você tem.</p></div></section></main>
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<footer><div class="container footer"><div><a class="brand" href="#inicio"><span class="mark">F+</span>Finanças<span>+</span></a><p>Conhecimento para decisões financeiras melhores.</p></div><div class="footer-links"><a href="#aprenda">Aprenda</a><a href="#simulador">Simulador</a><a href="#metas">Metas</a></div><small>© <span id="year"></span> Finanças+.</small></div></footer><script src="js/app.js"></script></body></html>
|
<meta name="description" content="Finanças+ — educação financeira e simulador de juros compostos">
|
||||||
|
<title>Finanças+ | Educação Financeira</title>
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="header">
|
||||||
|
<nav class="nav container">
|
||||||
|
|
||||||
|
|
||||||
|
<a class="brand" href="#inicio">
|
||||||
|
<span class="mark">F+</span>Finanças<span>+</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button class="menu" aria-label="Menu">☰</button>
|
||||||
|
|
||||||
|
<div class="links">
|
||||||
|
<a href="#inicio">Início</a>
|
||||||
|
<a href="#aprenda">Aprenda</a>
|
||||||
|
<a href="#simulador">Simulador</a>
|
||||||
|
<a href="login.php">Entrar</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<section class="hero" id="inicio">
|
||||||
|
|
||||||
|
<div class="orb one"></div>
|
||||||
|
<div class="orb two"></div>
|
||||||
|
|
||||||
|
<div class="container hero-grid">
|
||||||
|
|
||||||
|
<div class="hero-copy reveal">
|
||||||
|
|
||||||
|
<small class="eyebrow">EDUCAÇÃO FINANCEIRA</small>
|
||||||
|
|
||||||
|
<h1>
|
||||||
|
Seu dinheiro deve <em>trabalhar por você.</em>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Aprenda a organizar suas finanças, tomar decisões melhores
|
||||||
|
e construir um futuro financeiro mais tranquilo.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<a class="btn primary" href="#simulador">
|
||||||
|
Começar a simular →
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="btn outline" href="#aprenda">
|
||||||
|
Quero aprender
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="trust">
|
||||||
|
✓ Conteúdo educativo
|
||||||
|
✓ Simulação gratuita
|
||||||
|
✓ Área do aluno
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="visual reveal delay1">
|
||||||
|
|
||||||
|
<div class="dashboard">
|
||||||
|
|
||||||
|
<div class="dash-top">
|
||||||
|
<span>Patrimônio projetado</span>
|
||||||
|
<b>●</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<strong>R$ 84.750,00</strong>
|
||||||
|
|
||||||
|
<div class="mini">
|
||||||
|
<svg viewBox="0 0 500 180" preserveAspectRatio="none">
|
||||||
|
<path
|
||||||
|
class="fill"
|
||||||
|
d="M0 160 C55 150 75 130 120 138 S180 115 220 120 S270 88 310 98 S360 60 395 72 S450 30 500 22 L500 180 L0 180Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="line"
|
||||||
|
d="M0 160 C55 150 75 130 120 138 S180 115 220 120 S270 88 310 98 S360 60 395 72 S450 30 500 22"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dash-bottom">
|
||||||
|
<span>+18,4%</span>
|
||||||
|
<span>12 meses</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="float goal">
|
||||||
|
🎯
|
||||||
|
<div>
|
||||||
|
<small>Meta mensal</small>
|
||||||
|
<b>R$ 500</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="float saved">
|
||||||
|
💰
|
||||||
|
<div>
|
||||||
|
<small>Economizado</small>
|
||||||
|
<b>R$ 7.280</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section" id="aprenda">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="heading reveal">
|
||||||
|
|
||||||
|
<small class="eyebrow">PRIMEIROS PASSOS</small>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
Uma vida financeira melhor começa com três hábitos.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Conhecimento, organização e constância fazem mais diferença
|
||||||
|
do que procurar uma fórmula mágica.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cards">
|
||||||
|
|
||||||
|
<article class="card reveal">
|
||||||
|
|
||||||
|
<div class="icon">💰</div>
|
||||||
|
|
||||||
|
<small>01</small>
|
||||||
|
|
||||||
|
<h3>Organize</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Saiba quanto entra, quanto sai e para onde seu dinheiro está indo.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="card reveal delay1">
|
||||||
|
|
||||||
|
<div class="icon">📊</div>
|
||||||
|
|
||||||
|
<small>02</small>
|
||||||
|
|
||||||
|
<h3>Planeje</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Transforme objetivos em metas financeiras claras e mensuráveis.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="card reveal delay2">
|
||||||
|
|
||||||
|
<div class="icon">📈</div>
|
||||||
|
|
||||||
|
<small>03</small>
|
||||||
|
|
||||||
|
<h3>Invista</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Conheça as opções de investimento e o efeito dos juros compostos.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sim-section" id="simulador">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container sim-grid">
|
||||||
|
|
||||||
|
<div class="sim-copy reveal">
|
||||||
|
|
||||||
|
<small class="eyebrow">SIMULADOR</small>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
Veja o poder dos juros compostos.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Faça uma projeção educativa do crescimento do seu patrimônio.
|
||||||
|
Não é recomendação de investimento.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="tip">
|
||||||
|
|
||||||
|
💡
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<b>Uma regra importante</b>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Tempo e aportes consistentes podem fazer grande diferença
|
||||||
|
no longo prazo.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="sim-card reveal delay1">
|
||||||
|
|
||||||
|
<form id="form">
|
||||||
|
|
||||||
|
<div class="inputs">
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Investimento inicial
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
<span>R$</span>
|
||||||
|
<input
|
||||||
|
id="initial"
|
||||||
|
type="number"
|
||||||
|
value="1000"
|
||||||
|
min="0"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Aporte mensal
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
<span>R$</span>
|
||||||
|
<input
|
||||||
|
id="monthly"
|
||||||
|
type="number"
|
||||||
|
value="300"
|
||||||
|
min="0"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Taxa mensal
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
<input
|
||||||
|
id="rate"
|
||||||
|
type="number"
|
||||||
|
value="0.8"
|
||||||
|
min="0"
|
||||||
|
step="0.1"
|
||||||
|
>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Período
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
<input
|
||||||
|
id="months"
|
||||||
|
type="number"
|
||||||
|
value="60"
|
||||||
|
min="1"
|
||||||
|
>
|
||||||
|
<span>meses</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn primary full">
|
||||||
|
Calcular projeção →
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="results">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<small>Total investido</small>
|
||||||
|
<b id="invested">R$ 0,00</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<small>Rendimentos</small>
|
||||||
|
<b id="earnings">R$ 0,00</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="highlight">
|
||||||
|
<small>Valor projetado</small>
|
||||||
|
<b id="future">R$ 0,00</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<canvas id="growth"></canvas>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section" id="metas">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container goal-grid">
|
||||||
|
|
||||||
|
<div class="heading reveal">
|
||||||
|
|
||||||
|
<small class="eyebrow">METAS FINANCEIRAS</small>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
Transforme sonhos em números.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Uma meta clara facilita acompanhar o progresso e manter a disciplina.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="goal-card reveal delay1">
|
||||||
|
|
||||||
|
<div class="goal-head">
|
||||||
|
<b>Reserva de emergência</b>
|
||||||
|
<strong id="percent">64%</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress">
|
||||||
|
<span id="progress"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="goal-values">
|
||||||
|
<span>R$ 6.400</span>
|
||||||
|
<span>R$ 10.000</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="goal-foot">
|
||||||
|
<span>Faltam R$ 3.600</span>
|
||||||
|
<button id="advance">
|
||||||
|
Registrar progresso
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="quote">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container reveal">
|
||||||
|
|
||||||
|
<span>“</span>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Educação financeira não é sobre ficar rico rapidamente.
|
||||||
|
É sobre fazer escolhas conscientes com o dinheiro que você tem.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container footer">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<a class="brand" href="#inicio">
|
||||||
|
<span class="mark">F+</span>Finanças<span>+</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Conhecimento para decisões financeiras melhores.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer-links">
|
||||||
|
|
||||||
|
<a href="#aprenda">Aprenda</a>
|
||||||
|
<a href="#simulador">Simulador</a>
|
||||||
|
<a href="#metas">Metas</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<small>
|
||||||
|
© <span id="year"></span> Finanças+. by Romerito Melo
|
||||||
|
</small>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|||||||
@@ -0,0 +1,472 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/config/security.php';
|
||||||
|
|
||||||
|
$usuarioAutenticado = isset($_SESSION['usuario_id']);
|
||||||
|
?>
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html lang="pt-BR">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<meta name="description" content="Finanças+ — educação financeira e simulador de juros compostos">
|
||||||
|
<title>Finanças+ | Educação Financeira</title>
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="header">
|
||||||
|
<nav class="nav container">
|
||||||
|
|
||||||
|
|
||||||
|
<a class="brand" href="#inicio">
|
||||||
|
<span class="mark">F+</span>Finanças<span>+</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button class="menu" aria-label="Menu">☰</button>
|
||||||
|
|
||||||
|
<div class="links">
|
||||||
|
<a href="#inicio">Início</a>
|
||||||
|
<a href="#aprenda">Aprenda</a>
|
||||||
|
<a href="#simulador">Simulador</a>
|
||||||
|
<!-- <a href="login.php">Entrar</a> -->
|
||||||
|
|
||||||
|
<?php if ($usuarioAutenticado): ?>
|
||||||
|
<a href="/dashboard.php">Dashboard</a>
|
||||||
|
<a href="/logout.php">Sair</a>
|
||||||
|
<?php else: ?>
|
||||||
|
<a href="/login.php">Entrar</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<section class="hero" id="inicio">
|
||||||
|
|
||||||
|
<div class="orb one"></div>
|
||||||
|
<div class="orb two"></div>
|
||||||
|
|
||||||
|
<div class="container hero-grid">
|
||||||
|
|
||||||
|
<div class="hero-copy reveal">
|
||||||
|
|
||||||
|
<small class="eyebrow">EDUCAÇÃO FINANCEIRA</small>
|
||||||
|
|
||||||
|
<h1>
|
||||||
|
Seu dinheiro deve <em>trabalhar por você.</em>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Aprenda a organizar suas finanças, tomar decisões melhores
|
||||||
|
e construir um futuro financeiro mais tranquilo.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<a class="btn primary" href="#simulador">
|
||||||
|
Começar a simular →
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="btn outline" href="#aprenda">
|
||||||
|
Quero aprender
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="trust">
|
||||||
|
✓ Conteúdo educativo
|
||||||
|
✓ Simulação gratuita
|
||||||
|
✓ Área do aluno
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="visual reveal delay1">
|
||||||
|
|
||||||
|
<div class="dashboard">
|
||||||
|
|
||||||
|
<div class="dash-top">
|
||||||
|
<span>Patrimônio projetado</span>
|
||||||
|
<b>●</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<strong>R$ 84.750,00</strong>
|
||||||
|
|
||||||
|
<div class="mini">
|
||||||
|
<svg viewBox="0 0 500 180" preserveAspectRatio="none">
|
||||||
|
<path
|
||||||
|
class="fill"
|
||||||
|
d="M0 160 C55 150 75 130 120 138 S180 115 220 120 S270 88 310 98 S360 60 395 72 S450 30 500 22 L500 180 L0 180Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="line"
|
||||||
|
d="M0 160 C55 150 75 130 120 138 S180 115 220 120 S270 88 310 98 S360 60 395 72 S450 30 500 22"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dash-bottom">
|
||||||
|
<span>+18,4%</span>
|
||||||
|
<span>12 meses</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="float goal">
|
||||||
|
🎯
|
||||||
|
<div>
|
||||||
|
<small>Meta mensal</small>
|
||||||
|
<b>R$ 500</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="float saved">
|
||||||
|
💰
|
||||||
|
<div>
|
||||||
|
<small>Economizado</small>
|
||||||
|
<b>R$ 7.280</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section" id="aprenda">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="heading reveal">
|
||||||
|
|
||||||
|
<small class="eyebrow">PRIMEIROS PASSOS</small>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
Uma vida financeira melhor começa com três hábitos.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Conhecimento, organização e constância fazem mais diferença
|
||||||
|
do que procurar uma fórmula mágica.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="cards">
|
||||||
|
|
||||||
|
<article class="card reveal">
|
||||||
|
|
||||||
|
<div class="icon">💰</div>
|
||||||
|
|
||||||
|
<small>01</small>
|
||||||
|
|
||||||
|
<h3>Organize</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Saiba quanto entra, quanto sai e para onde seu dinheiro está indo.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="card reveal delay1">
|
||||||
|
|
||||||
|
<div class="icon">📊</div>
|
||||||
|
|
||||||
|
<small>02</small>
|
||||||
|
|
||||||
|
<h3>Planeje</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Transforme objetivos em metas financeiras claras e mensuráveis.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article class="card reveal delay2">
|
||||||
|
|
||||||
|
<div class="icon">📈</div>
|
||||||
|
|
||||||
|
<small>03</small>
|
||||||
|
|
||||||
|
<h3>Invista</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Conheça as opções de investimento e o efeito dos juros compostos.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</article>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sim-section" id="simulador">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container sim-grid">
|
||||||
|
|
||||||
|
<div class="sim-copy reveal">
|
||||||
|
|
||||||
|
<small class="eyebrow">SIMULADOR</small>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
Veja o poder dos juros compostos.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Faça uma projeção educativa do crescimento do seu patrimônio.
|
||||||
|
Não é recomendação de investimento.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="tip">
|
||||||
|
|
||||||
|
💡
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<b>Uma regra importante</b>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Tempo e aportes consistentes podem fazer grande diferença
|
||||||
|
no longo prazo.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="sim-card reveal delay1">
|
||||||
|
|
||||||
|
<form id="form">
|
||||||
|
|
||||||
|
<div class="inputs">
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Investimento inicial
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
<span>R$</span>
|
||||||
|
<input
|
||||||
|
id="initial"
|
||||||
|
type="number"
|
||||||
|
value="1000"
|
||||||
|
min="0"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Aporte mensal
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
<span>R$</span>
|
||||||
|
<input
|
||||||
|
id="monthly"
|
||||||
|
type="number"
|
||||||
|
value="300"
|
||||||
|
min="0"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Taxa mensal
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
<input
|
||||||
|
id="rate"
|
||||||
|
type="number"
|
||||||
|
value="0.8"
|
||||||
|
min="0"
|
||||||
|
step="0.1"
|
||||||
|
>
|
||||||
|
<span>%</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Período
|
||||||
|
|
||||||
|
<div class="input">
|
||||||
|
<input
|
||||||
|
id="months"
|
||||||
|
type="number"
|
||||||
|
value="60"
|
||||||
|
min="1"
|
||||||
|
>
|
||||||
|
<span>meses</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn primary full">
|
||||||
|
Calcular projeção →
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="results">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<small>Total investido</small>
|
||||||
|
<b id="invested">R$ 0,00</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<small>Rendimentos</small>
|
||||||
|
<b id="earnings">R$ 0,00</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="highlight">
|
||||||
|
<small>Valor projetado</small>
|
||||||
|
<b id="future">R$ 0,00</b>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<canvas id="growth"></canvas>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="section" id="metas">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container goal-grid">
|
||||||
|
|
||||||
|
<div class="heading reveal">
|
||||||
|
|
||||||
|
<small class="eyebrow">METAS FINANCEIRAS</small>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
Transforme sonhos em números.
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Uma meta clara facilita acompanhar o progresso e manter a disciplina.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="goal-card reveal delay1">
|
||||||
|
|
||||||
|
<div class="goal-head">
|
||||||
|
<b>Reserva de emergência</b>
|
||||||
|
<strong id="percent">64%</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="progress">
|
||||||
|
<span id="progress"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="goal-values">
|
||||||
|
<span>R$ 6.400</span>
|
||||||
|
<span>R$ 10.000</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="goal-foot">
|
||||||
|
<span>Faltam R$ 3.600</span>
|
||||||
|
<button id="advance">
|
||||||
|
Registrar progresso
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="quote">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container reveal">
|
||||||
|
|
||||||
|
<span>“</span>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Educação financeira não é sobre ficar rico rapidamente.
|
||||||
|
É sobre fazer escolhas conscientes com o dinheiro que você tem.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container footer">
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<a class="brand" href="#inicio">
|
||||||
|
<span class="mark">F+</span>Finanças<span>+</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Conhecimento para decisões financeiras melhores.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="footer-links">
|
||||||
|
|
||||||
|
<a href="#aprenda">Aprenda</a>
|
||||||
|
<a href="#simulador">Simulador</a>
|
||||||
|
<a href="#metas">Metas</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<small>
|
||||||
|
© <span id="year"></span> Finanças+. by Romerito Melo
|
||||||
|
</small>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/config/security.php';
|
||||||
|
|
||||||
|
require_once __DIR__ . '/config/database.php';
|
||||||
|
|
||||||
|
require_once __DIR__ . '/app/Models/User.php';
|
||||||
|
|
||||||
|
require_once __DIR__ . '/app/Models/LoginAttempt.php';
|
||||||
|
|
||||||
|
require_once __DIR__ . '/app/Controllers/AuthController.php';
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cria o Model responsável pelos usuários.
|
||||||
|
*/
|
||||||
|
$user = new User($pdo);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cria o Model responsável pelas tentativas
|
||||||
|
* de autenticação.
|
||||||
|
*/
|
||||||
|
$loginAttempt = new LoginAttempt($pdo);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cria o Controller responsável pelo processo
|
||||||
|
* de autenticação.
|
||||||
|
*
|
||||||
|
* O Controller recebe os Models que necessita
|
||||||
|
* para executar suas responsabilidades.
|
||||||
|
*/
|
||||||
|
$controller = new AuthController(
|
||||||
|
$user,
|
||||||
|
$loginAttempt
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Processa a requisição de login.
|
||||||
|
*/
|
||||||
|
$controller->login();
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require_once __DIR__ . '/config/security.php';
|
||||||
|
|
||||||
|
require_once __DIR__ . '/config/database.php';
|
||||||
|
|
||||||
|
require_once __DIR__ . '/app/Models/User.php';
|
||||||
|
|
||||||
|
require_once __DIR__ . '/app/Models/LoginAttempt.php';
|
||||||
|
|
||||||
|
require_once __DIR__ . '/app/Controllers/AuthController.php';
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cria o Model responsável pelos usuários.
|
||||||
|
*/
|
||||||
|
$user = new User($pdo);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cria o Model responsável pelas tentativas
|
||||||
|
* de autenticação.
|
||||||
|
*/
|
||||||
|
$loginAttempt = new LoginAttempt($pdo);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cria o Controller responsável pelo processo
|
||||||
|
* de autenticação.
|
||||||
|
*/
|
||||||
|
$controller = new AuthController(
|
||||||
|
$user,
|
||||||
|
$loginAttempt
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Encerra a sessão do usuário.
|
||||||
|
*/
|
||||||
|
$controller->logout();
|
||||||
Vendored
+10
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
header('Content-Type: text/plain');
|
||||||
|
|
||||||
|
echo "REMOTE_ADDR: " . ($_SERVER['REMOTE_ADDR'] ?? 'N/A') . PHP_EOL;
|
||||||
|
echo "X-Real-IP: " . ($_SERVER['HTTP_X_REAL_IP'] ?? 'N/A') . PHP_EOL;
|
||||||
|
echo "X-Forwarded-For: " . ($_SERVER['HTTP_X_FORWARDED_FOR'] ?? 'N/A') . PHP_EOL;
|
||||||
|
echo "X-Forwarded-Proto: " . ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'N/A') . PHP_EOL;
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -1 +0,0 @@
|
|||||||
testando o push deste arquivo para a conta romeritomelo no gitea na OCI.
|
|
||||||
Reference in New Issue
Block a user