diff --git a/backend/index.js b/backend/index.js index 2469f33..bf90683 100644 --- a/backend/index.js +++ b/backend/index.js @@ -217,6 +217,30 @@ apiRouter.post('/auth/login', async (req, res) => { } }); +// God Mode (Impersonate Tenant) +apiRouter.post('/impersonate/:tenantId', requireRole(['super_admin']), async (req, res) => { + try { + // Buscar o primeiro admin (ou qualquer usuário) do tenant para assumir a identidade + const [users] = await pool.query("SELECT * FROM users WHERE tenant_id = ? AND role = 'admin' LIMIT 1", [req.params.tenantId]); + + if (users.length === 0) { + return res.status(404).json({ error: 'Nenhum administrador encontrado nesta organização para assumir a identidade.' }); + } + + const user = users[0]; + + if (user.status !== 'active') { + return res.status(403).json({ error: 'A conta do admin desta organização está inativa.' }); + } + + // Gerar um token JWT como se fôssemos o admin do tenant + const token = jwt.sign({ id: user.id, tenant_id: user.tenant_id, role: user.role, team_id: user.team_id, slug: user.slug }, JWT_SECRET, { expiresIn: '2h' }); + res.json({ token, user: { id: user.id, name: user.name, email: user.email, role: user.role, tenant_id: user.tenant_id, team_id: user.team_id, slug: user.slug } }); + } catch (error) { + res.status(500).json({ error: error.message }); + } +}); + // Forgot Password apiRouter.post('/auth/forgot-password', async (req, res) => { const { email } = req.body; diff --git a/pages/SuperAdmin.tsx b/pages/SuperAdmin.tsx index a7cbe14..0b84408 100644 --- a/pages/SuperAdmin.tsx +++ b/pages/SuperAdmin.tsx @@ -1,13 +1,16 @@ import React, { useState, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Building2, Users, MessageSquare, Plus, Search, - Edit, Trash2, ChevronDown, ChevronUp, ChevronsUpDown, X, CheckCircle2, Loader2 -} from 'lucide-react';import { getTenants, createTenant, deleteTenant, updateTenant } from '../services/dataService'; + Edit, Trash2, ChevronDown, ChevronUp, ChevronsUpDown, X, CheckCircle2, Loader2, LogIn +} from 'lucide-react'; +import { getTenants, createTenant, deleteTenant, updateTenant, impersonateTenant } from '../services/dataService'; import { Tenant } from '../types'; import { DateRangePicker } from '../components/DateRangePicker'; import { KPICard } from '../components/KPICard'; export const SuperAdmin: React.FC = () => { + const navigate = useNavigate(); const [dateRange, setDateRange] = useState({ start: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000), end: new Date() @@ -90,6 +93,20 @@ export const SuperAdmin: React.FC = () => { } }; + const handleImpersonate = async (tenantId: string) => { + try { + if (tenantId === 'system') { + alert('Você já está na organização do sistema.'); + return; + } + await impersonateTenant(tenantId); + // Force a full reload to clear any cached context/state in the React app + window.location.href = '/'; + } catch (err: any) { + alert(err.message || 'Erro ao tentar entrar na organização.'); + } + }; + const [successMessage, setSuccessMessage] = useState(''); const [errorMessage, setErrorMessage] = useState(''); const [isSaving, setIsSaving] = useState(false); @@ -232,8 +249,13 @@ export const SuperAdmin: React.FC = () => { {tenant.attendance_count?.toLocaleString()}
- - + {tenant.id !== 'system' && ( + + )} + +
diff --git a/services/dataService.ts b/services/dataService.ts index 7975821..8588ceb 100644 --- a/services/dataService.ts +++ b/services/dataService.ts @@ -387,6 +387,50 @@ export const login = async (credentials: any): Promise => { return data; }; +export const impersonateTenant = async (tenantId: string): Promise => { + const response = await fetch(`${API_URL}/impersonate/${tenantId}`, { + method: 'POST', + headers: getHeaders() + }); + + const contentType = response.headers.get("content-type"); + const isJson = contentType && contentType.indexOf("application/json") !== -1; + + if (!response.ok) { + const errorData = isJson ? await response.json() : { error: 'Erro no servidor' }; + throw new Error(errorData.error || 'Erro ao assumir identidade'); + } + + const data = await response.json(); + const oldToken = localStorage.getItem('ctms_token'); + if (oldToken) { + localStorage.setItem('ctms_super_admin_token', oldToken); + } + + localStorage.setItem('ctms_token', data.token); + localStorage.setItem('ctms_user_id', data.user.id); + localStorage.setItem('ctms_tenant_id', data.user.tenant_id || ''); + return data; +}; + +export const returnToSuperAdmin = (): boolean => { + const superAdminToken = localStorage.getItem('ctms_super_admin_token'); + if (superAdminToken) { + try { + const payload = JSON.parse(atob(superAdminToken.split('.')[1])); + localStorage.setItem('ctms_token', superAdminToken); + localStorage.setItem('ctms_user_id', payload.id); + localStorage.setItem('ctms_tenant_id', payload.tenant_id || 'system'); + localStorage.removeItem('ctms_super_admin_token'); + return true; + } catch (e) { + console.error("Failed to restore super admin token", e); + return false; + } + } + return false; +}; + export const register = async (userData: any): Promise => { const response = await fetch(`${API_URL}/auth/register`, { method: 'POST',