From 2c3d231d20ae81a88a9aa33472d8face15a8d6a4 Mon Sep 17 00:00:00 2001 From: deyvisonwanderley Date: Mon, 17 Nov 2025 16:27:15 -0300 Subject: [PATCH] refactor: enhance authentication and access control in ProtectedRoute component - Updated the ProtectedRoute component to improve access control logic, including a timeout mechanism for handling authentication checks. - Refactored the checkAccess function to streamline user access verification based on roles and authentication status. - Added comments for clarity on the authentication flow and the use of the convexClient plugin in the auth.ts file. - Improved the overall structure and readability of the code in auth.ts and ProtectedRoute.svelte. --- apps/web/src/lib/auth.ts | 5 + .../src/lib/components/ProtectedRoute.svelte | 61 +- apps/web/src/routes/(dashboard)/+page.svelte | 1698 +++++++++-------- .../routes/(dashboard)/perfil/+page.svelte | 542 +++++- packages/backend/convex/_generated/api.d.ts | 34 +- .../backend/convex/_generated/server.d.ts | 16 +- packages/backend/convex/_generated/server.js | 13 +- 7 files changed, 1399 insertions(+), 970 deletions(-) diff --git a/apps/web/src/lib/auth.ts b/apps/web/src/lib/auth.ts index 7881eb4..42ba6eb 100644 --- a/apps/web/src/lib/auth.ts +++ b/apps/web/src/lib/auth.ts @@ -8,6 +8,11 @@ import { createAuthClient } from "better-auth/svelte"; import { convexClient } from "@convex-dev/better-auth/client/plugins"; +// O baseURL deve apontar para o frontend (SvelteKit), não para o Convex diretamente +// O Better Auth usa as rotas HTTP do Convex que são acessadas via proxy do SvelteKit +// ou diretamente se configurado. Com o plugin convexClient, o token é gerenciado automaticamente. export const authClient = createAuthClient({ + // baseURL padrão é window.location.origin, que é o correto para SvelteKit + // O Better Auth será acessado via rotas HTTP do Convex registradas em http.ts plugins: [convexClient()], }); diff --git a/apps/web/src/lib/components/ProtectedRoute.svelte b/apps/web/src/lib/components/ProtectedRoute.svelte index 6bcf57a..3e41367 100644 --- a/apps/web/src/lib/components/ProtectedRoute.svelte +++ b/apps/web/src/lib/components/ProtectedRoute.svelte @@ -20,26 +20,32 @@ let isChecking = $state(true); let hasAccess = $state(false); + let timeoutId: ReturnType | null = null; const currentUser = useQuery(api.auth.getCurrentUser, {}); - onMount(() => { + // Usar $effect para reagir às mudanças na query + $effect(() => { checkAccess(); }); function checkAccess() { - isChecking = true; + // Limpar timeout anterior se existir + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } - // Aguardar um pouco para o authStore carregar do localStorage - setTimeout(() => { - // Verificar autenticação - if (requireAuth && !currentUser?.data) { - const currentPath = window.location.pathname; - window.location.href = `${redirectTo}?error=auth_required&redirect=${encodeURIComponent(currentPath)}`; - return; - } + // Se a query ainda está carregando (undefined), aguardar + if (currentUser === undefined) { + isChecking = true; + hasAccess = false; + return; + } + // Se a query retornou dados, verificar autenticação + if (currentUser?.data) { // Verificar roles - if (allowedRoles.length > 0 && currentUser?.data) { + if (allowedRoles.length > 0) { const hasRole = allowedRoles.includes(currentUser.data.role?.nome ?? ''); if (!hasRole) { const currentPath = window.location.pathname; @@ -49,19 +55,40 @@ } // Verificar nível - if ( - currentUser?.data && - currentUser.data.role?.nivel && - currentUser.data.role.nivel > maxLevel - ) { + if (currentUser.data.role?.nivel && currentUser.data.role.nivel > maxLevel) { const currentPath = window.location.pathname; window.location.href = `${redirectTo}?error=access_denied&route=${encodeURIComponent(currentPath)}`; return; } + // Se chegou aqui, permitir acesso hasAccess = true; isChecking = false; - }, 100); + return; + } + + // Se não tem dados e requer autenticação, aguardar um pouco antes de redirecionar + // (pode estar carregando ainda) + if (requireAuth && !currentUser?.data) { + isChecking = true; + hasAccess = false; + + // Aguardar 3 segundos antes de redirecionar (dar tempo para a query carregar) + timeoutId = setTimeout(() => { + // Verificar novamente antes de redirecionar + if (!currentUser?.data) { + const currentPath = window.location.pathname; + window.location.href = `${redirectTo}?error=auth_required&redirect=${encodeURIComponent(currentPath)}`; + } + }, 3000); + return; + } + + // Se não requer autenticação, permitir acesso + if (!requireAuth) { + hasAccess = true; + isChecking = false; + } } diff --git a/apps/web/src/routes/(dashboard)/+page.svelte b/apps/web/src/routes/(dashboard)/+page.svelte index 005b954..8330127 100644 --- a/apps/web/src/routes/(dashboard)/+page.svelte +++ b/apps/web/src/routes/(dashboard)/+page.svelte @@ -1,845 +1,853 @@ - - - -
- - {#if showAlert} - {@const alertData = getAlertMessage()} -
-
- {alertData.icon} -
-

{alertData.title}

-

{alertData.message}

- {#if alertType === "access_denied"} - - {/if} -
- -
-
- {/if} - - -
-
-
-

- {getSaudacao()}! 👋 -

-

- Bem-vindo ao Sistema de Gerenciamento da Secretaria de Esportes -

-

- {currentTime.toLocaleDateString("pt-BR", { - weekday: "long", - year: "numeric", - month: "long", - day: "numeric", - })} - {" - "} - {currentTime.toLocaleTimeString("pt-BR")} -

-
-
-
Sistema Online
-
Atualizado
-
-
-
- - - {#if statsQuery.isLoading} -
- -
- {:else if statsQuery.data} -
- -
-
-
-
-

- Total de Funcionários -

-

- {formatNumber(statsQuery.data.totalFuncionarios)} -

-

- {statsQuery.data.funcionariosAtivos} ativos -

-
-
- {calcPercentage( - statsQuery.data.funcionariosAtivos, - statsQuery.data.totalFuncionarios, - )}% -
-
-
-
- - -
-
-
-
-

- Solicitações Pendentes -

-

- {formatNumber(statsQuery.data.solicitacoesPendentes)} -

-

- de {statsQuery.data.totalSolicitacoesAcesso} total -

-
-
- - - -
-
-
-
- - -
-
-
-
-

- Símbolos Cadastrados -

-

- {formatNumber(statsQuery.data.totalSimbolos)} -

-

- {statsQuery.data.cargoComissionado} CC / {statsQuery.data - .funcaoGratificada} FG -

-
-
- - - -
-
-
-
- - - {#if activityQuery.data} -
-
-
-
-

- Atividade (24h) -

-

- {formatNumber( - activityQuery.data.funcionariosCadastrados24h + - activityQuery.data.solicitacoesAcesso24h, - )} -

-

- {activityQuery.data.funcionariosCadastrados24h} cadastros -

-
-
- - - -
-
-
-
- {/if} -
- - - {#if statusSistemaQuery.data && atividadeBDQuery.data && distribuicaoQuery.data} - {@const status = statusSistemaQuery.data} - {@const atividade = atividadeBDQuery.data} - {@const distribuicao = distribuicaoQuery.data} - -
-
-
- - - -
-
-

- Monitoramento em Tempo Real -

-

- Atualizado a cada segundo • {new Date( - status.ultimaAtualizacao, - ).toLocaleTimeString("pt-BR")} -

-
-
- - - LIVE -
-
- - -
- -
-
-
-
-

- Usuários Online -

-

- {status.usuariosOnline} -

-

- sessões ativas -

-
-
- - - -
-
-
-
- - -
-
-
-
-

- Total Registros -

-

- {status.totalRegistros.toLocaleString("pt-BR")} -

-

- no banco de dados -

-
-
- - - -
-
-
-
- - -
-
-
-
-

- Tempo Resposta -

-

- {status.tempoMedioResposta}ms -

-

média atual

-
-
- - - -
-
-
-
- - -
-
-
-

- Uso do Sistema -

-
-
-
- CPU - {status.cpuUsada}% -
- -
-
-
- Memória - {status.memoriaUsada}% -
- -
-
-
-
-
-
- - -
-
-
-
-

- Atividade do Banco de Dados -

-

- Entradas e saídas em tempo real (último minuto) -

-
-
- - Atualizando -
-
- -
- -
- {#each [10, 8, 6, 4, 2, 0] as val} - {val} - {/each} -
- - -
- - {#each Array.from({ length: 6 }) as _, i} -
- {/each} - - -
- {#each atividade.historico as ponto, idx} - {@const maxAtividade = Math.max( - ...atividade.historico.map((p) => - Math.max(p.entradas, p.saidas), - ), - )} -
- -
- -
- - -
-
↑ {ponto.entradas} entradas
-
↓ {ponto.saidas} saídas
-
-
- {/each} -
-
- - -
- - -
- -60s - -30s - agora -
-
- - -
-
-
- Entradas no BD -
-
-
- Saídas do BD -
-
-
-
- - -
-
-
-

- Tipos de Operações -

-
-
-
- Queries (Leituras) - {distribuicao.queries} -
- -
-
-
- Mutations (Escritas) - {distribuicao.mutations} -
- -
-
-
-
- -
-
-

- Operações no Banco -

-
-
-
- Leituras - {distribuicao.leituras} -
- -
-
-
- Escritas - {distribuicao.escritas} -
- -
-
-
-
-
-
- {/if} - - -
-
-
-

Status do Sistema

-
-
- Banco de Dados - Online -
-
- API - Operacional -
-
- Backup - Atualizado -
-
-
-
- - - -
-
-

Informações

-
-

- Versão: 1.0.0 -

-

- Última Atualização: - {new Date().toLocaleDateString("pt-BR")} -

-

- Suporte: TI SGSE -

-
-
-
-
- {/if} -
-
- - + + + +
+ + {#if showAlert} + {@const alertData = getAlertMessage()} +
+
+ {alertData.icon} +
+

{alertData.title}

+

{alertData.message}

+ {#if alertType === "access_denied"} + + {/if} +
+ +
+
+ {/if} + + +
+
+
+

+ {getSaudacao()}! 👋 +

+

+ Bem-vindo ao Sistema de Gerenciamento da Secretaria de Esportes +

+

+ {currentTime.toLocaleDateString("pt-BR", { + weekday: "long", + year: "numeric", + month: "long", + day: "numeric", + })} + {" - "} + {currentTime.toLocaleTimeString("pt-BR")} +

+
+
+
Sistema Online
+
Atualizado
+
+
+
+ + + {#if statsQuery.isLoading} +
+ +
+ {:else if statsQuery.data} +
+ +
+
+
+
+

+ Total de Funcionários +

+

+ {formatNumber(statsQuery.data.totalFuncionarios)} +

+

+ {statsQuery.data.funcionariosAtivos} ativos +

+
+
+ {calcPercentage( + statsQuery.data.funcionariosAtivos, + statsQuery.data.totalFuncionarios, + )}% +
+
+
+
+ + +
+
+
+
+

+ Solicitações Pendentes +

+

+ {formatNumber(statsQuery.data.solicitacoesPendentes)} +

+

+ de {statsQuery.data.totalSolicitacoesAcesso} total +

+
+
+ + + +
+
+
+
+ + +
+
+
+
+

+ Símbolos Cadastrados +

+

+ {formatNumber(statsQuery.data.totalSimbolos)} +

+

+ {statsQuery.data.cargoComissionado} CC / {statsQuery.data + .funcaoGratificada} FG +

+
+
+ + + +
+
+
+
+ + + {#if activityQuery.data} +
+
+
+
+

+ Atividade (24h) +

+

+ {formatNumber( + activityQuery.data.funcionariosCadastrados24h + + activityQuery.data.solicitacoesAcesso24h, + )} +

+

+ {activityQuery.data.funcionariosCadastrados24h} cadastros +

+
+
+ + + +
+
+
+
+ {/if} +
+ + + {#if statusSistemaQuery.data && atividadeBDQuery.data && distribuicaoQuery.data} + {@const status = statusSistemaQuery.data} + {@const atividade = atividadeBDQuery.data} + {@const distribuicao = distribuicaoQuery.data} + +
+
+
+ + + +
+
+

+ Monitoramento em Tempo Real +

+

+ Atualizado a cada segundo • {new Date( + status.ultimaAtualizacao, + ).toLocaleTimeString("pt-BR")} +

+
+
+ + + LIVE +
+
+ + +
+ +
+
+
+
+

+ Usuários Online +

+

+ {status.usuariosOnline} +

+

+ sessões ativas +

+
+
+ + + +
+
+
+
+ + +
+
+
+
+

+ Total Registros +

+

+ {status.totalRegistros.toLocaleString("pt-BR")} +

+

+ no banco de dados +

+
+
+ + + +
+
+
+
+ + +
+
+
+
+

+ Tempo Resposta +

+

+ {status.tempoMedioResposta}ms +

+

média atual

+
+
+ + + +
+
+
+
+ + +
+
+
+

+ Uso do Sistema +

+
+
+
+ CPU + {status.cpuUsada}% +
+ +
+
+
+ Memória + {status.memoriaUsada}% +
+ +
+
+
+
+
+
+ + +
+
+
+
+

+ Atividade do Banco de Dados +

+

+ Entradas e saídas em tempo real (último minuto) +

+
+
+ + Atualizando +
+
+ +
+ +
+ {#each [10, 8, 6, 4, 2, 0] as val} + {val} + {/each} +
+ + +
+ + {#each Array.from({ length: 6 }) as _, i} +
+ {/each} + + +
+ {#each atividade.historico as ponto, idx} + {@const maxAtividade = Math.max( + ...atividade.historico.map((p) => + Math.max(p.entradas, p.saidas), + ), + )} +
+ +
+ +
+ + +
+
↑ {ponto.entradas} entradas
+
↓ {ponto.saidas} saídas
+
+
+ {/each} +
+
+ + +
+ + +
+ -60s + -30s + agora +
+
+ + +
+
+
+ Entradas no BD +
+
+
+ Saídas do BD +
+
+
+
+ + +
+
+
+

+ Tipos de Operações +

+
+
+
+ Queries (Leituras) + {distribuicao.queries} +
+ +
+
+
+ Mutations (Escritas) + {distribuicao.mutations} +
+ +
+
+
+
+ +
+
+

+ Operações no Banco +

+
+
+
+ Leituras + {distribuicao.leituras} +
+ +
+
+
+ Escritas + {distribuicao.escritas} +
+ +
+
+
+
+
+
+ {/if} + + +
+
+
+

Status do Sistema

+
+
+ Banco de Dados + Online +
+
+ API + Operacional +
+
+ Backup + Atualizado +
+
+
+
+ + + +
+
+

Informações

+
+

+ Versão: 1.0.0 +

+

+ Última Atualização: + {new Date().toLocaleDateString("pt-BR")} +

+

+ Suporte: TI SGSE +

+
+
+
+
+ {/if} +
+
+ + diff --git a/apps/web/src/routes/(dashboard)/perfil/+page.svelte b/apps/web/src/routes/(dashboard)/perfil/+page.svelte index 2e0f1a7..0424489 100644 --- a/apps/web/src/routes/(dashboard)/perfil/+page.svelte +++ b/apps/web/src/routes/(dashboard)/perfil/+page.svelte @@ -1,7 +1,7 @@