# Super Admin — Tenant Access Design **Date:** 2026-06-29 **Status:** Approved **Scope:** Bug fix (TenantMiddleware schema name) + feature (super admin impersonation with session restore) --- ## Problem 1. **Bug:** `TenantMiddleware` constructs schema name as `"tenant_"` but schemas are named `tenant_`. All tenant user requests fail with SET search_path error. Independent of the feature — must always be fixed. 2. **Feature gap:** Super admin can list and create tenants but cannot access or manage data within individual tenant workspaces. --- ## Solution ### Bug Fix — TenantMiddleware Replace raw UUID concatenation with normalized schema name (hyphens → underscores): ```go // Before schema := `"tenant_` + claims.TenantID + `"` // After schema := `"tenant_` + strings.ReplaceAll(claims.TenantID, "-", "_") + `"` ``` ### Feature — Tenant Impersonation with Session Restore **Flow:** 1. Super admin clicks "Gerir" on any tenant row in TenantsPage 2. Frontend calls `POST /api/v1/admin/tenants/:id/access` 3. Backend validates tenant (exists + active), generates a 15-min access token with `tenantID=` and `role="tenant_admin"` 4. Frontend saves current session to `previousSession` in authStore, sets new token+user 5. Navigates to `/app` — super admin now sees the tenant workspace 6. AppLayout shows a banner: `[TechXCar Admin] A gerir: [← Voltar ao painel]` 7. Clicking "Voltar" restores the previous session and navigates to `/admin` --- ## Backend ### New endpoint `POST /api/v1/admin/tenants/:id/access` - Auth: `RequireAuth` + `RequireRole("super_admin")` - Validates: tenant ID format, tenant exists, tenant status = "active" - Returns: `{ "data": { "access_token": "...", "tenant": { "id", "name", "slug" } }, "error": null }` - Token: standard 15-min access token, `userID = super_admin_id`, `tenantID = tenant.ID`, `role = "tenant_admin"` - No new refresh token — the impersonation session is access-token only ### Route registration Added to `tenant.RegisterRoutes` under the existing `admin` group: ``` POST /api/v1/admin/tenants/:id/access ``` --- ## Frontend ### authStore changes New fields: ```typescript previousSession?: { token: string; user: AuthUser } ``` New actions: - `impersonateTenant(token, user)` — saves current `{ token, user }` to `previousSession`, sets new token+user - `restoreSession()` — restores `previousSession` into token+user, clears `previousSession` `previousSession` is NOT persisted to localStorage (impersonation does not survive page refresh — intentional). ### TenantsPage Each tenant row gains a "Gerir" button (secondary/outline variant). On click: 1. POST `/admin/tenants/:id/access` 2. On success: `impersonateTenant(data.access_token, { ...data.tenant, role: 'tenant_admin' })` 3. Invalidate TanStack Query cache (avoid stale super-admin-scoped data) 4. Navigate to `/app` ### AppLayout When `authStore.previousSession` is defined, render a fixed banner at the top: ``` [TechXCar Admin] A gerir: [← Voltar ao painel] ``` - Background: amber/yellow to visually distinguish from normal tenant UI - "Voltar" button: calls `restoreSession()`, navigates to `/admin` - Banner height: ~40px; main content padding-top adjusts accordingly --- ## Error Handling - Tenant not found or not active → 404 "oficina não encontrada ou inativa" - `impersonateTenant` failure (network) → show error toast, do not change session - Restoring session never fails (purely client-side state) --- ## Out of Scope - Refresh token for impersonation sessions (15-min window is intentional) - Audit log of impersonation events (future) - Super admin creating/editing data within tenant as themselves (they appear as tenant_admin)