Skip to content

Commit bf61de4

Browse files
Thales-Chagasclaude
andcommitted
Login: e-mail/senha a cada 24h e PIN a cada 1h
- Tela da nuvem (e-mail/senha) so e repedida a cada 24h: guarda o horario do ultimo login real (cloudAuth.entrar/atualizarSenha) e, ao abrir, se passou 24h encerra a sessao e volta pro portao. Sessoes ja existentes sao "adotadas" (contagem comeca agora). - Tranca por PIN: a janela de graca de 1h agora SEMPRE vale (mesmo com "pedir sempre"), sobrevive a recarregar E reabrir -> dentro de 1h entra direto; so repede o PIN depois de 1h. - Blindagem: eventos INITIAL_SESSION/refresh de token nao decidem mais a tela (evita furar a checagem de 24h); handler so reage a login feito na tela de entrada ou a logout. - sair() zera o contador de 24h. Rotulo do PIN atualizado. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 795f96f commit bf61de4

2 files changed

Lines changed: 70 additions & 11 deletions

File tree

src/App.jsx

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ import {
7474
sairNuvem,
7575
sessaoAtual,
7676
aoMudarAuth,
77+
marcarLoginNuvem,
78+
limparLoginNuvem,
79+
loginNuvemTs,
7780
} from "./cloudAuth";
7881
import { carregarTudo, sincronizar, migrarLocalParaNuvem } from "./cloudData";
7982
import { gerarCodigoTelegram, statusTelegram, desconectarTelegram, BOT_URL, BOT_USERNAME } from "./telegramLink";
@@ -104,9 +107,12 @@ const MIGR_KEY = "financas_app_migrado_"; // + userId: marca que já perguntamos
104107
const URL_RECUPERACAO =
105108
typeof window !== "undefined" && /[#&?]type=recovery/.test(window.location.hash || "");
106109

107-
// Janela de desbloqueio: por 1h após desbloquear, recarregar não pede PIN/biometria.
110+
// Janela de desbloqueio: por 1h após desbloquear, recarregar/reabrir não pede
111+
// PIN/biometria (pede de novo só depois de 1h).
108112
const DESBLOQUEIO_KEY = "financas_app_desbloqueio";
109113
const GRACA_MS = 60 * 60 * 1000; // 1 hora
114+
// Login na nuvem (e-mail/senha) só é repedido a cada 24h.
115+
const NUVEM_MS = 24 * 60 * 60 * 1000; // 24 horas
110116
function marcarDesbloqueio() {
111117
try {
112118
localStorage.setItem(DESBLOQUEIO_KEY, String(Date.now()));
@@ -2581,7 +2587,7 @@ function TelaLogin({ modo, nome, foto, onCriar, onDesbloquear, onEsqueci, onBiom
25812587
onChange={(e) => setPedirSempre(e.target.checked)}
25822588
className="h-4 w-4 accent-emerald-600"
25832589
/>
2584-
Pedir o PIN sempre que o app abrir
2590+
Proteger com PIN (pede a cada 1 hora)
25852591
</label>
25862592
{erro && <p className="text-sm text-red-600 dark:text-red-400">{erro}</p>}
25872593
<button type="submit" className="w-full rounded-xl bg-emerald-600 py-2.5 text-sm font-semibold text-white transition hover:bg-emerald-700">
@@ -3063,13 +3069,12 @@ export default function App() {
30633069
}, [escuro]);
30643070

30653071
// Decide a tela de entrada quando JÁ existe sessão na nuvem:
3066-
// se este aparelho tem tranca rápida (PIN/biometria), pede; senão, abre.
3072+
// se este aparelho tem tranca rápida (PIN/biometria) E já passou 1h desde o
3073+
// último desbloqueio, pede o PIN; senão (dentro de 1h), abre direto.
30673074
function estadoComSessao(conf) {
30683075
const precisaTranca = conf?.pinHash && (conf.pedirSempre || conf.bioCredId);
3069-
// A janela de 1h evita repedir a tranca a cada recarregar — MAS quem
3070-
// marcou "pedir sempre" quer justamente ser pedido sempre: aí a graça
3071-
// não vale (só vale pra desbloqueio por biometria de conveniência).
3072-
const graca = desbloqueioRecente() && !conf?.pedirSempre;
3076+
// Janela de 1h: recarregar OU reabrir dentro de 1h não repede a tranca.
3077+
const graca = desbloqueioRecente();
30733078
if (precisaTranca && !graca) return "lock";
30743079
return "open";
30753080
}
@@ -3089,8 +3094,28 @@ export default function App() {
30893094
// que já exista sessão de recuperação, NÃO entrar direto).
30903095
// Sem sessão na nuvem → portão externo (entrar/criar conta).
30913096
// Com sessão → tranca rápida deste aparelho (ou abre direto).
3092-
if (URL_RECUPERACAO) setAuth("recuperar");
3093-
else setAuth(!sess ? "auth" : estadoComSessao(conf));
3097+
if (URL_RECUPERACAO) {
3098+
setAuth("recuperar");
3099+
} else if (!sess) {
3100+
setAuth("auth"); // sem sessão → portão externo (entrar/criar conta)
3101+
} else {
3102+
// Tem sessão. Ela só vale por 24h: passou disso → repede o login.
3103+
let ts = loginNuvemTs();
3104+
if (!ts) {
3105+
// sessão que já existia antes desta regra → começa a contar agora
3106+
marcarLoginNuvem();
3107+
ts = Date.now();
3108+
}
3109+
if (Date.now() - ts >= NUVEM_MS) {
3110+
sairNuvem().catch(() => {}); // encerra a sessão antiga
3111+
limparLoginNuvem();
3112+
setSessao(null);
3113+
setUserId(null);
3114+
setAuth("auth");
3115+
} else {
3116+
setAuth(estadoComSessao(conf)); // dentro das 24h → PIN (se >1h) ou abre
3117+
}
3118+
}
30943119
} catch {
30953120
setAuth(URL_RECUPERACAO ? "recuperar" : "auth");
30963121
}
@@ -3232,6 +3257,7 @@ export default function App() {
32323257
/* segue mesmo se a rede falhar */
32333258
}
32343259
limparDesbloqueio(); // "Sair" encerra a janela de 1h → pede tranca no próximo acesso
3260+
limparLoginNuvem(); // e zera o contador de 24h → pede e-mail/senha de novo
32353261
setSessao(null);
32363262
setUserId(null);
32373263
setNuvemPronta(false);
@@ -3258,8 +3284,13 @@ export default function App() {
32583284
setUserId(sess?.user?.id ?? null);
32593285
// Chegou pelo link de "Esqueci a senha" → força a tela de senha nova.
32603286
if (evento === "PASSWORD_RECOVERY") return setAuth("recuperar");
3261-
if (!sess) setAuth("auth");
3262-
else setAuth((prev) => (prev === "auth" || prev === "init" ? estadoComSessao(login) : prev));
3287+
// A restauração da sessão ao abrir (INITIAL_SESSION) e o refresh de token
3288+
// NÃO decidem a tela: quem decide o estado inicial é a carga (com a regra
3289+
// das 24h). Aqui só reagimos a um login FEITO na tela de entrada
3290+
// (prev === "auth") ou a um logout — assim nada fura a checagem de 24h.
3291+
if (evento === "INITIAL_SESSION") return;
3292+
if (!sess) return setAuth("auth");
3293+
setAuth((prev) => (prev === "auth" ? estadoComSessao(login) : prev));
32633294
});
32643295
return parar;
32653296
// eslint-disable-next-line react-hooks/exhaustive-deps

src/cloudAuth.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,32 @@
1010

1111
import { supabase } from "./supabaseClient";
1212

13+
// Marca de tempo do ÚLTIMO login real na nuvem (e-mail/senha). Serve para o
14+
// app repedir o login a cada 24h, mesmo com a sessão do Supabase persistida.
15+
const LOGIN_NUVEM_KEY = "financas_app_login_nuvem";
16+
export function marcarLoginNuvem() {
17+
try {
18+
localStorage.setItem(LOGIN_NUVEM_KEY, String(Date.now()));
19+
} catch {
20+
/* sem armazenamento */
21+
}
22+
}
23+
export function limparLoginNuvem() {
24+
try {
25+
localStorage.removeItem(LOGIN_NUVEM_KEY);
26+
} catch {
27+
/* sem armazenamento */
28+
}
29+
}
30+
// timestamp do último login (0 se nunca).
31+
export function loginNuvemTs() {
32+
try {
33+
return Number(localStorage.getItem(LOGIN_NUVEM_KEY) || 0);
34+
} catch {
35+
return 0;
36+
}
37+
}
38+
1339
// Cadastro por e-mail/senha. Guarda o nome nos metadados do usuário
1440
// (o trigger handle_new_user copia isso pro profiles).
1541
export async function cadastrar(email, senha, nome) {
@@ -31,6 +57,7 @@ export async function entrar(email, senha) {
3157
password: senha,
3258
});
3359
if (error) throw error;
60+
marcarLoginNuvem(); // conta os 24h a partir de agora
3461
return data.session;
3562
}
3663

@@ -55,6 +82,7 @@ export async function redefinirSenha(email) {
5582
export async function atualizarSenha(novaSenha) {
5683
const { error } = await supabase.auth.updateUser({ password: novaSenha });
5784
if (error) throw error;
85+
marcarLoginNuvem(); // acabou de entrar pelo link → conta os 24h
5886
}
5987

6088
// Encerra a sessão na nuvem (logout de verdade).

0 commit comments

Comments
 (0)