Inicial
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
# 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):
|
||||
|
||||
```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=<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:
|
||||
```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: <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)
|
||||
Reference in New Issue
Block a user