Files
techxcar/docs/superpowers/specs/2026-06-29-super-admin-tenant-access.md
Luciano Milani 5de37bb512 Inicial
2026-07-02 12:47:55 +01:00

3.7 KiB

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_<uuid-with-hyphens>" but schemas are named tenant_<uuid-with-underscores>. 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):

// 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=<id> 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: <tenant name> [← 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:

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: <user.name>  [← 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)