diff --git a/src/main.jsx b/src/main.jsx index 1135be6..e032138 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,8 +2,11 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { RouterProvider } from 'react-router-dom'; import { router } from './routes/router'; +import { installAuthFetchInterceptor } from './shared/services/authFetch'; import './shared/styles/global.css'; +installAuthFetchInterceptor(); + ReactDOM.createRoot(document.getElementById('root')).render( diff --git a/src/modules/auth/hooks/useLogin.js b/src/modules/auth/hooks/useLogin.js index 2a50f20..ef21588 100644 --- a/src/modules/auth/hooks/useLogin.js +++ b/src/modules/auth/hooks/useLogin.js @@ -7,6 +7,13 @@ import { storeAuthSession, } from '../services/authService'; +function decodeAuthPayload(payload) { + const base64 = payload.replace(/-/g, '+').replace(/_/g, '/'); + const paddedBase64 = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '='); + + return JSON.parse(atob(paddedBase64)); +} + export function useLogin() { const navigate = useNavigate(); const [isSubmitting, setIsSubmitting] = useState(false); @@ -20,6 +27,21 @@ export function useLogin() { }, []); useEffect(() => { + const hashParams = new URLSearchParams(window.location.hash.replace(/^#/, '')); + const authPayload = hashParams.get('auth'); + + if (authPayload) { + try { + storeAuthSession(decodeAuthPayload(authPayload)); + window.history.replaceState({}, document.title, window.location.pathname); + navigate('/home', { replace: true }); + return; + } catch { + window.history.replaceState({}, document.title, window.location.pathname); + setError('Não foi possível concluir o login Microsoft.'); + } + } + const params = new URLSearchParams(window.location.search); const token = params.get('token'); const rawUser = params.get('user'); diff --git a/src/modules/auth/services/authService.js b/src/modules/auth/services/authService.js index fc68b52..8ad5a7b 100644 --- a/src/modules/auth/services/authService.js +++ b/src/modules/auth/services/authService.js @@ -30,6 +30,8 @@ export function startMicrosoftLogin() { } export function storeAuthSession(authResult) { - window.localStorage.setItem('authToken', authResult.token); - window.localStorage.setItem('authUser', JSON.stringify(authResult.user)); + window.sessionStorage.setItem('authToken', authResult.token); + window.sessionStorage.setItem('authUser', JSON.stringify(authResult.user)); + window.localStorage.removeItem('authToken'); + window.localStorage.removeItem('authUser'); } diff --git a/src/modules/auth/services/sessionService.js b/src/modules/auth/services/sessionService.js index 6adbcb8..2e04998 100644 --- a/src/modules/auth/services/sessionService.js +++ b/src/modules/auth/services/sessionService.js @@ -8,16 +8,8 @@ const PROFILE_ALIASES = { agent: 'agent', }; -const DEMO_PROFILE_BY_USERNAME = { - admin: 'admin', - 'lucas.admin': 'admin', - supervisor: 'supervisor', - 'marina.alves': 'supervisor', - 'rafael.nunes': 'supervisor', -}; - function readStoredUser() { - const rawUser = window.localStorage.getItem('authUser'); + const rawUser = window.sessionStorage.getItem('authUser'); if (!rawUser) { return null; @@ -68,6 +60,14 @@ export function getCurrentUser() { return readStoredUser(); } +export function getAuthToken() { + return window.sessionStorage.getItem('authToken'); +} + +export function isAuthenticated() { + return Boolean(getAuthToken() && getCurrentUser()); +} + export function getCurrentUserDisplay() { const user = getCurrentUser(); const fullName = user?.name || user?.nome || user?.username || 'Ana Camolesi'; @@ -102,7 +102,7 @@ export function getCurrentUserProfile() { const user = getCurrentUser(); if (!user) { - return 'agent'; + return null; } if (user.accessStatus === 'unassigned') { @@ -119,17 +119,12 @@ export function getCurrentUserProfile() { return backendProfile; } - const username = String(user.username || user.email || user.name || '').trim().toLowerCase(); - const demoProfile = DEMO_PROFILE_BY_USERNAME[username]; - - if (demoProfile) { - return demoProfile; - } - return 'agent'; } export function clearSession() { + window.sessionStorage.removeItem('authToken'); + window.sessionStorage.removeItem('authUser'); window.localStorage.removeItem('authToken'); window.localStorage.removeItem('authUser'); } diff --git a/src/modules/management/pages/WhatsappAdminPage.jsx b/src/modules/management/pages/WhatsappAdminPage.jsx index 6f252d6..3ca98cb 100644 --- a/src/modules/management/pages/WhatsappAdminPage.jsx +++ b/src/modules/management/pages/WhatsappAdminPage.jsx @@ -1,14 +1,18 @@ import React, { useEffect, useState } from 'react'; import { io } from 'socket.io-client'; import { API_BASE_URL, WHATSAPP_SOCKET_URL } from '../../../shared/services/apiConfig'; +import { getAuthToken } from '../../auth/services/sessionService'; export const WhatsappAdminPage = () => { const [qrCode, setQrCode] = useState(null); const [status, setStatus] = useState('DISCONNECTED'); useEffect(() => { - // Conecta ao namespace /whatsapp - const socket = io(WHATSAPP_SOCKET_URL); + const socket = io(WHATSAPP_SOCKET_URL, { + auth: { + token: getAuthToken(), + }, + }); socket.on('connect', () => { console.log('Connected to WhatsApp WebSocket'); @@ -38,10 +42,13 @@ export const WhatsappAdminPage = () => { return (

Configuração do WhatsApp

- +
-

Status da Conexão: {status}

- +

+ Status da Conexão:{' '} + {status} +

+ {status === 'AWAITING_QR' && qrCode && (

Escaneie o QR Code abaixo com seu WhatsApp:

diff --git a/src/routes/ProtectedRoute.jsx b/src/routes/ProtectedRoute.jsx new file mode 100644 index 0000000..4043c8d --- /dev/null +++ b/src/routes/ProtectedRoute.jsx @@ -0,0 +1,10 @@ +import { Navigate } from 'react-router-dom'; +import { isAuthenticated } from '../modules/auth/services/sessionService'; + +export function ProtectedRoute({ children }) { + if (!isAuthenticated()) { + return ; + } + + return children; +} diff --git a/src/routes/router.jsx b/src/routes/router.jsx index 12c3a6f..8a9efda 100644 --- a/src/routes/router.jsx +++ b/src/routes/router.jsx @@ -7,6 +7,11 @@ import { ChatPage } from '../modules/chat/pages/ChatPage'; import { CallPage } from '../modules/call/pages/CallPage'; import { AgentNewAttendancePage } from '../modules/attendance/pages/AgentNewAttendancePage'; import { WhatsappAdminPage } from '../modules/management/pages/WhatsappAdminPage'; +import { ProtectedRoute } from './ProtectedRoute'; + +function privatePage(page) { + return {page}; +} export const router = createBrowserRouter([ { @@ -19,30 +24,30 @@ export const router = createBrowserRouter([ }, { path: '/home', - element: , + element: privatePage(), }, { path: '/chat', - element: , + element: privatePage(), }, { path: '/call', - element: , + element: privatePage(), }, { path: '/new-attendance', - element: , + element: privatePage(), }, { path: '/mass-message', - element: , + element: privatePage(), }, { path: '/contacts', - element: , + element: privatePage(), }, { path: '/admin/whatsapp', - element: , + element: privatePage(), }, ]); diff --git a/src/shared/hooks/useWhatsappSocket.js b/src/shared/hooks/useWhatsappSocket.js index 462011f..e483a7c 100644 --- a/src/shared/hooks/useWhatsappSocket.js +++ b/src/shared/hooks/useWhatsappSocket.js @@ -1,6 +1,7 @@ import { useEffect, useState, useRef } from 'react'; import io from 'socket.io-client'; import { API_BASE_URL, WHATSAPP_SOCKET_URL } from '../services/apiConfig'; +import { getAuthToken } from '../../modules/auth/services/sessionService'; export function useWhatsappSocket() { const [socket, setSocket] = useState(null); @@ -15,6 +16,9 @@ export function useWhatsappSocket() { // Conectar ao namespace /whatsapp const newSocket = io(WHATSAPP_SOCKET_URL, { reconnectionAttempts: 5, + auth: { + token: getAuthToken(), + }, }); socketRef.current = newSocket; diff --git a/src/shared/services/authFetch.js b/src/shared/services/authFetch.js new file mode 100644 index 0000000..fb1e149 --- /dev/null +++ b/src/shared/services/authFetch.js @@ -0,0 +1,35 @@ +import { API_BASE_URL } from './apiConfig'; + +function getStoredToken() { + return window.sessionStorage.getItem('authToken'); +} + +function shouldAttachToken(resource) { + const url = typeof resource === 'string' ? resource : resource?.url; + + return Boolean(url && url.startsWith(API_BASE_URL) && getStoredToken()); +} + +export function installAuthFetchInterceptor() { + window.localStorage.removeItem('authToken'); + window.localStorage.removeItem('authUser'); + + const nativeFetch = window.fetch.bind(window); + + window.fetch = (resource, options = {}) => { + if (!shouldAttachToken(resource)) { + return nativeFetch(resource, options); + } + + const headers = new Headers(options.headers || {}); + + if (!headers.has('Authorization')) { + headers.set('Authorization', `Bearer ${getStoredToken()}`); + } + + return nativeFetch(resource, { + ...options, + headers, + }); + }; +}