Atualiza estrutura MVC e integração com banco de dados

This commit is contained in:
2026-08-30 22:18:31 -03:00
parent 6231cc46a8
commit 966f6b7587
26 changed files with 3265 additions and 8 deletions
+515
View File
@@ -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;
}
}
+15
View File
@@ -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
+47
View File
@@ -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';
}
}
+285
View File
@@ -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();
}
}
?>
View File
+136
View File
@@ -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
]);
}
}
+96
View File
@@ -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
]);
}
}
View File
View File
+202
View File
@@ -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>
View File
+200
View File
@@ -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>
View File
+7
View File
@@ -0,0 +1,7 @@
<?php
declare(strict_types=1);
require_once '/home/opc/database.php';
?>
+350
View File
@@ -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
View File
File diff suppressed because one or more lines are too long
+10
View File
@@ -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();
+21
View File
@@ -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
View File
@@ -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 &nbsp; ✓ Simulação gratuita &nbsp; ✓ 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 &nbsp;
✓ Simulação gratuita &nbsp;
✓ Á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>
+472
View File
@@ -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 &nbsp;
✓ Simulação gratuita &nbsp;
✓ Á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>
+45
View File
@@ -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
View File
@@ -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();
View File
View File
+10
View File
@@ -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
View File
@@ -1 +0,0 @@
testando o push deste arquivo para a conta romeritomelo no gitea na OCI.