TenantAtlas/specs/072-managed-tenants-workspace-enforcement/spec.md
ahmido 38d9826f5e feat: workspace context enforcement + ownership safeguards (#86)
Implements workspace-first enforcement and UX:
- Workspace selected before tenant flows; /admin routes into choose-workspace/choose-tenant
- Tenant lists and default tenant selection are scoped to current workspace
- Workspaces UI is tenantless at /admin/workspaces

Security hardening:
- Workspaces can never have 0 owners (blocks last-owner removal/demotion)
- Blocked attempts are audited with action_id=workspace_membership.last_owner_blocked + required metadata
- Optional break-glass recovery page to re-assign workspace owner (audited)

Tests:
- Added/updated Pest feature tests covering redirects, scoping, tenantless workspaces, last-owner guards, and break-glass recovery.

Notes:
- Filament v5 strict Page property signatures respected in RepairWorkspaceOwners.

Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box>
Reviewed-on: #86
2026-02-02 23:00:56 +00:00

2.1 KiB

Spec — 072 Managed Tenants workspace context enforcement

Problem

Managed Tenant pages exist in an unscoped URL space (/admin/managed-tenants/*) while Managed Tenants are product-scoped to a Workspace (MSP portfolio). This makes workspace context feel optional and allows confusing / insecure navigation patterns where tenant context and workspace context can drift.

Mental model (source of truth)

  • Managed Tenant = the Entra/Intune tenant. All policy/backup/drift/inventory features are always scoped to a Managed Tenant.
    • In code: Filament tenancy (/admin/t/{tenant_external_id}/...).
  • Workspace = portfolio container. Controls which Managed Tenants a user can see + portfolio-level settings.
    • In code: session + last_workspace_id + middleware (not Filament tenancy).

Goals

  • Workspace becomes a real, enforced context for all tenant-scoped pages.
  • Keep Filament tenancy URL space unchanged: /admin/t/{tenant_external_id}/....
  • Keep Workspaces UI tenantless: /admin/workspaces/* (never under /admin/t/{tenant}/...).
  • Introduce / use a workspace-scoped landing space for portfolio UX: /admin/w/{workspace}/....
  • Eliminate or redirect legacy unscoped Managed Tenants routes under /admin/managed-tenants/*.

Non-goals

  • Redesigning all navigation IA or introducing a second Filament panel.
  • Migrating existing tenant data beyond enforcing tenants.workspace_id consistency.

Hard rule (security / enterprise)

When accessing /admin/t/{tenant} routes:

  • current_workspace_id must be set, and
  • tenant.workspace_id == current_workspace_id, and
  • user must be a member of the workspace (and/or tenant, per current auth model). Otherwise: deny as not found (404).

Acceptance criteria

  • /admin/managed-tenants/* does not act as a primary UX entry point anymore (redirects to workspace-scoped UX).
  • /admin/w/{workspace}/managed-tenants exists as the primary portfolio landing for Managed Tenants.
  • Tenant switcher only shows tenants from the current workspace.
  • Visiting /admin/t/{tenant} with missing or mismatched workspace context results in 404.
  • Pest tests cover redirects + workspace/tenant mismatch denial.