From 04971ca82e91a430365a46321fc723d5e0aabdef Mon Sep 17 00:00:00 2001 From: Rafael Lopes Date: Mon, 1 Jun 2026 16:56:09 -0300 Subject: [PATCH] REFACTOR: Removido Mock e assegurado login com JWT --- src/modules/auth/hooks/useLogin.js | 24 ++++++------- src/modules/auth/services/authService.js | 4 +++ src/modules/auth/services/sessionService.js | 40 +++++++++++++++++++-- src/routes/ProtectedRoute.jsx | 8 +++-- src/routes/router.jsx | 6 ++-- src/shared/services/authFetch.js | 15 ++++++-- 6 files changed, 73 insertions(+), 24 deletions(-) diff --git a/src/modules/auth/hooks/useLogin.js b/src/modules/auth/hooks/useLogin.js index ef21588..1cec748 100644 --- a/src/modules/auth/hooks/useLogin.js +++ b/src/modules/auth/hooks/useLogin.js @@ -6,6 +6,7 @@ import { startMicrosoftLogin, storeAuthSession, } from '../services/authService'; +import { isAuthenticated } from '../services/sessionService'; function decodeAuthPayload(payload) { const base64 = payload.replace(/-/g, '+').replace(/_/g, '/'); @@ -21,10 +22,15 @@ export function useLogin() { const [providers, setProviders] = useState({ ldap: true, microsoft: false }); useEffect(() => { + if (!window.location.hash.includes('auth=') && isAuthenticated()) { + navigate('/home', { replace: true }); + return; + } + getAuthConfig() .then((config) => setProviders(config.providers || { ldap: true, microsoft: false })) .catch(() => setProviders({ ldap: true, microsoft: false })); - }, []); + }, [navigate]); useEffect(() => { const hashParams = new URLSearchParams(window.location.hash.replace(/^#/, '')); @@ -38,23 +44,13 @@ export function useLogin() { return; } catch { window.history.replaceState({}, document.title, window.location.pathname); - setError('Não foi possível concluir o login Microsoft.'); + setError('Nao foi possivel concluir o login Microsoft.'); } } - const params = new URLSearchParams(window.location.search); - const token = params.get('token'); - const rawUser = params.get('user'); - - if (!token || !rawUser) return; - - try { - const user = JSON.parse(rawUser); - storeAuthSession({ token, user }); + if (window.location.search.includes('token=') || window.location.search.includes('user=')) { window.history.replaceState({}, document.title, window.location.pathname); - navigate('/home', { replace: true }); - } catch { - setError('Não foi possível concluir o login Microsoft.'); + setError('Fluxo de login expirado. Tente novamente.'); } }, [navigate]); diff --git a/src/modules/auth/services/authService.js b/src/modules/auth/services/authService.js index 8ad5a7b..b539d3f 100644 --- a/src/modules/auth/services/authService.js +++ b/src/modules/auth/services/authService.js @@ -30,6 +30,10 @@ export function startMicrosoftLogin() { } export function storeAuthSession(authResult) { + if (!authResult?.token || !authResult?.user) { + throw new Error('Sessao de autenticacao invalida.'); + } + window.sessionStorage.setItem('authToken', authResult.token); window.sessionStorage.setItem('authUser', JSON.stringify(authResult.user)); window.localStorage.removeItem('authToken'); diff --git a/src/modules/auth/services/sessionService.js b/src/modules/auth/services/sessionService.js index 2e04998..3d251e7 100644 --- a/src/modules/auth/services/sessionService.js +++ b/src/modules/auth/services/sessionService.js @@ -22,6 +22,33 @@ function readStoredUser() { } } +function decodeJwtPayload(token) { + const [, payload] = String(token || '').split('.'); + + if (!payload) { + return null; + } + + try { + const base64 = payload.replace(/-/g, '+').replace(/_/g, '/'); + const paddedBase64 = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '='); + + return JSON.parse(window.atob(paddedBase64)); + } catch { + return null; + } +} + +function isTokenExpired(token) { + const payload = decodeJwtPayload(token); + + if (!payload?.exp) { + return true; + } + + return payload.exp * 1000 <= Date.now(); +} + function normalizeProfile(value) { if (!value) { return null; @@ -61,7 +88,14 @@ export function getCurrentUser() { } export function getAuthToken() { - return window.sessionStorage.getItem('authToken'); + const token = window.sessionStorage.getItem('authToken'); + + if (!token || isTokenExpired(token)) { + clearSession(); + return null; + } + + return token; } export function isAuthenticated() { @@ -70,7 +104,7 @@ export function isAuthenticated() { export function getCurrentUserDisplay() { const user = getCurrentUser(); - const fullName = user?.name || user?.nome || user?.username || 'Ana Camolesi'; + const fullName = user?.name || user?.nome || user?.username || user?.email || 'Usuario'; const nameParts = fullName.split(' ').filter(Boolean); const name = nameParts.length > 1 ? `${nameParts[0]} ${nameParts[nameParts.length - 1]}` : fullName; @@ -94,7 +128,7 @@ export function getCurrentUserDisplay() { return { name, subtitle, - initials: initials || 'AM', + initials: initials || 'US', }; } diff --git a/src/routes/ProtectedRoute.jsx b/src/routes/ProtectedRoute.jsx index 4043c8d..cd0a586 100644 --- a/src/routes/ProtectedRoute.jsx +++ b/src/routes/ProtectedRoute.jsx @@ -1,10 +1,14 @@ import { Navigate } from 'react-router-dom'; -import { isAuthenticated } from '../modules/auth/services/sessionService'; +import { getCurrentUserProfile, isAuthenticated } from '../modules/auth/services/sessionService'; -export function ProtectedRoute({ children }) { +export function ProtectedRoute({ children, allowedProfiles = [] }) { if (!isAuthenticated()) { return ; } + if (allowedProfiles.length && !allowedProfiles.includes(getCurrentUserProfile())) { + return ; + } + return children; } diff --git a/src/routes/router.jsx b/src/routes/router.jsx index 8a9efda..9032af2 100644 --- a/src/routes/router.jsx +++ b/src/routes/router.jsx @@ -9,8 +9,8 @@ import { AgentNewAttendancePage } from '../modules/attendance/pages/AgentNewAtte import { WhatsappAdminPage } from '../modules/management/pages/WhatsappAdminPage'; import { ProtectedRoute } from './ProtectedRoute'; -function privatePage(page) { - return {page}; +function privatePage(page, allowedProfiles = []) { + return {page}; } export const router = createBrowserRouter([ @@ -48,6 +48,6 @@ export const router = createBrowserRouter([ }, { path: '/admin/whatsapp', - element: privatePage(), + element: privatePage(, ['admin']), }, ]); diff --git a/src/shared/services/authFetch.js b/src/shared/services/authFetch.js index fb1e149..512709d 100644 --- a/src/shared/services/authFetch.js +++ b/src/shared/services/authFetch.js @@ -1,4 +1,5 @@ import { API_BASE_URL } from './apiConfig'; +import { clearSession } from '../../modules/auth/services/sessionService'; function getStoredToken() { return window.sessionStorage.getItem('authToken'); @@ -16,7 +17,7 @@ export function installAuthFetchInterceptor() { const nativeFetch = window.fetch.bind(window); - window.fetch = (resource, options = {}) => { + window.fetch = async (resource, options = {}) => { if (!shouldAttachToken(resource)) { return nativeFetch(resource, options); } @@ -27,9 +28,19 @@ export function installAuthFetchInterceptor() { headers.set('Authorization', `Bearer ${getStoredToken()}`); } - return nativeFetch(resource, { + const response = await nativeFetch(resource, { ...options, headers, }); + + if (response.status === 401) { + clearSession(); + + if (window.location.pathname !== '/login') { + window.location.assign('/login'); + } + } + + return response; }; }