TenantAtlas/specs/063-entra-signin/tasks.md
2026-01-27 17:22:33 +01:00

6.5 KiB
Raw Blame History

description
Task list for feature 063 — Entra Sign-in (Tenant Panel) v1

Tasks: 063 — Entra Sign-in (Tenant Panel) v1

Input: Design documents from /specs/063-entra-signin/
Prerequisites: plan.md, spec.md

Tests: REQUIRED (Pest)

Notes / Guardrails

  • 063 covers /admin Entra sign-in only.
  • No /system changes and no break-glass UX on /admin/login.
  • Login is synchronous and must not require a queue worker.
  • DB-only render/hydration scope: /admin/login, /admin/no-access, /admin/choose-tenant.
  • Outbound HTTP is allowed only for the OIDC exchange on /auth/entra/* endpoints.

Phase 1: Setup (Shared Infrastructure)

  • T001 Configure Microsoft (Entra) OIDC provider in config/services.php under key microsoft using env vars ENTRA_CLIENT_ID, ENTRA_CLIENT_SECRET, ENTRA_REDIRECT_URI, and optional ENTRA_AUTHORITY_TENANT (default organizations).
  • T002 Add ENTRA_CLIENT_ID, ENTRA_CLIENT_SECRET, ENTRA_REDIRECT_URI, and ENTRA_AUTHORITY_TENANT to .env.example (no break-glass vars in 063).

Phase 2: Foundational (Blocking Prerequisites)

  • T003 Verify users has entra_tenant_id and entra_object_id (prefer existing types; if missing, add nullable string(255) columns) + a unique index on (entra_tenant_id, entra_object_id). Keep columns nullable in v1; enforce non-null only after explicit backfill/migration.
  • T004 Apply migrations via Sail: ./vendor/bin/sail artisan migrate.
  • T005 Create app/Http/Controllers/Auth/EntraController.php to handle /auth/entra/redirect and /auth/entra/callback only (NoAccess and Chooser are Filament pages).
  • T006 Add routes for /auth/entra/redirect and /auth/entra/callback in routes/web.php with route names auth.entra.redirect and auth.entra.callback.
  • T007 Define a dedicated rate limiter for auth.entra.callback in app/Providers/AppServiceProvider.php using RateLimiter::for('entra-callback', ...) and apply ->middleware('throttle:entra-callback') to the callback route; add a small feature test asserting 429 after excessive hits.
  • T008 Ensure app/Models/User.php allows writing entra_tenant_id and entra_object_id (fillable/guarded), without refactoring membership relationships.

Phase 3: US1 — Entra-only login UI on /admin/login (P1)

Goal: A tenant user can only start Microsoft sign-in from /admin/login.

  • T009 [US1] Override the Filament login page for the /admin panel to show only a "Sign in with Microsoft" action linking to route('auth.entra.redirect') (no email/password inputs).
  • T010 [US1] Create feature test tests/Feature/Auth/AdminLoginIsEntraOnlyTest.php verifying the Microsoft button exists and password inputs do not.

Phase 4: US2 — OIDC callback upserts tenant identity safely (P1)

Goal: The callback upserts a tenant user using Entra claims.

  • T011 [US2] Implement EntraController@callback upsert keyed by (entra_tenant_id=tid, entra_object_id=oid); regenerate session on success; never store tokens.
  • T012 [US2] Block login for disabled/soft-deleted users (Option B): redirect to /admin/login with generic error; log reason_code=user_disabled.
  • T013 [US2] Handle missing/invalid tid or oid (or invalid state) by redirecting back to /admin/login with a generic error + reason_code log (no claims/tokens dumped).
  • T014 [US2] Implement privacy-safe logging for login successes and failures (minimal identity; include correlation_id; never dump raw claims/tokens).
  • T015 [US2] Create feature test tests/Feature/Auth/EntraCallbackUpsertByTidOidTest.php to test upsert and session regeneration.
  • T016 [US2] Create feature test tests/Feature/Auth/DisabledUserLoginIsBlockedTest.php.
  • T017 [US2] Create feature test tests/Feature/Auth/OidcFailureRedirectsSafelyTest.php.

Phase 5: US3 — Post-login routing is membership-based (P1)

Goal: After login, routing depends on Suite tenant memberships.

  • T018 [P] [US3] Create app/Services/Auth/PostLoginRedirectResolver.php that resolves redirect targets for 0/1/>1 memberships (0 → /admin/no-access, 1 → TenantDashboard::getUrl(tenant: $tenant), >1 → /admin/choose-tenant). IMPORTANT: do not hardcode /admin/t/...; always use Filament page URL helpers so routing stays stable if prefixes/slugs change.
  • T019 [US3] In EntraController@callback, call PostLoginRedirectResolver after upsert to determine redirect.
  • T020 [US3] Create a Filament page app/Filament/Pages/ChooseTenant.php mounted under the /admin panel at /admin/choose-tenant.
  • T021 [US3] Implement tenant selection action on the chooser page: selecting a tenant sets Filament tenant context (session) and redirects to App\\Filament\\Pages\\TenantDashboard::getUrl(tenant: $tenant); persist users.last_tenant_id if present. IMPORTANT: use Filament URL helpers (no hardcoded paths).
  • T022 [US3] Create feature test tests/Feature/Auth/PostLoginRoutingByMembershipTest.php to validate 0/1/>1 routing.
  • T023 [US3] Create feature test tests/Feature/Auth/TenantChooserSelectionTest.php ensuring chooser selection sets tenant context and redirects (and stores last_tenant_id if present).

Phase 6: US4 — Filament-native “No access” page (P2)

  • T024 [US4] Create a Filament page app/Filament/Pages/NoAccess.php under the /admin panel with route /admin/no-access.
  • T025 [US4] Add feature test tests/Feature/Auth/NoAccessPageRendersTest.php.

Phase 7: Cross-cutting (Polish & Guards)

  • T026 Create tests/Feature/Auth/SessionSeparationSmokeTest.php to ensure tenant session cannot access /system; platform session cannot access /admin tenant routes.
  • T027 Run formatting: ./vendor/bin/sail bin pint --dirty.
  • T028 Run feature tests: ./vendor/bin/sail artisan test tests/Feature/Auth --stop-on-failure.
  • T029 Manual verification: run through /admin/login → OIDC → 0/1/>1 membership outcomes, chooser selection, and confirm no break-glass UI appears on /admin/login.
  • T030 Add tests/Feature/Auth/DbOnlyPagesDoNotMakeHttpRequestsTest.php to enforce DB-only render/hydration for /admin/login, /admin/no-access, and /admin/choose-tenant using Http::preventStrayRequests() + render each page + assert no exceptions; optional hardening: Queue::fake() + Bus::fake() (and/or Event::fake()) so render paths cant silently dispatch.

Dependencies & Execution Order

  • Phase 12 must complete before US1US4.
  • Implement in order: US1 → US2 → US3 → US4.
  • Phase 7 is last.