@@ -74,6 +74,9 @@ import {
7474 sairNuvem ,
7575 sessaoAtual ,
7676 aoMudarAuth ,
77+ marcarLoginNuvem ,
78+ limparLoginNuvem ,
79+ loginNuvemTs ,
7780} from "./cloudAuth" ;
7881import { carregarTudo , sincronizar , migrarLocalParaNuvem } from "./cloudData" ;
7982import { gerarCodigoTelegram , statusTelegram , desconectarTelegram , BOT_URL , BOT_USERNAME } from "./telegramLink" ;
@@ -104,9 +107,12 @@ const MIGR_KEY = "financas_app_migrado_"; // + userId: marca que já perguntamos
104107const URL_RECUPERACAO =
105108 typeof window !== "undefined" && / [ # & ? ] t y p e = r e c o v e r y / . 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).
108112const DESBLOQUEIO_KEY = "financas_app_desbloqueio" ;
109113const 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
110116function 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
0 commit comments