Key changes Adds Entra OIDC redirect + callback endpoints under /auth/entra/* (token exchange only there). Upserts tenant users keyed by (entra_tenant_id = tid, entra_object_id = oid); regenerates session; never stores tokens. Blocks disabled / soft-deleted users with a generic error and safe logging. Membership-based post-login routing: 0 memberships → /admin/no-access 1 membership → tenant dashboard (via Filament URL helpers) >1 memberships → /admin/choose-tenant Adds Filament pages: /admin/choose-tenant (tenant selection + redirect) /admin/no-access (tenantless-safe) Both use simple layout to avoid tenant-required UI. Guards / tests Adds DbOnlyPagesDoNotMakeHttpRequestsTest to enforce DB-only render/hydration for: /admin/login, /admin/no-access, /admin/choose-tenant with Http::preventStrayRequests() Adds session separation smoke coverage to ensure tenant session doesn’t access system and vice versa. Runs: vendor/bin/sail artisan test --compact tests/Feature/Auth Co-authored-by: Ahmed Darrazi <ahmeddarrazi@MacBookPro.fritz.box> Reviewed-on: #76
6.5 KiB
6.5 KiB
| 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
/systemchanges 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.phpunder keymicrosoftusing env varsENTRA_CLIENT_ID,ENTRA_CLIENT_SECRET,ENTRA_REDIRECT_URI, and optionalENTRA_AUTHORITY_TENANT(defaultorganizations). - T002 Add
ENTRA_CLIENT_ID,ENTRA_CLIENT_SECRET,ENTRA_REDIRECT_URI, andENTRA_AUTHORITY_TENANTto.env.example(no break-glass vars in 063).
Phase 2: Foundational (Blocking Prerequisites)
- T003 Verify
usershasentra_tenant_idandentra_object_id(prefer existing types; if missing, add nullablestring(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.phpto handle/auth/entra/redirectand/auth/entra/callbackonly (NoAccess and Chooser are Filament pages). - T006 Add routes for
/auth/entra/redirectand/auth/entra/callbackinroutes/web.phpwith route namesauth.entra.redirectandauth.entra.callback. - T007 Define a dedicated rate limiter for
auth.entra.callbackinapp/Providers/AppServiceProvider.phpusingRateLimiter::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.phpallows writingentra_tenant_idandentra_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
/adminpanel to show only a "Sign in with Microsoft" action linking toroute('auth.entra.redirect')(no email/password inputs). - T010 [US1] Create feature test
tests/Feature/Auth/AdminLoginIsEntraOnlyTest.phpverifying 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@callbackupsert 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/loginwith generic error; logreason_code=user_disabled. - T013 [US2] Handle missing/invalid
tidoroid(or invalid state) by redirecting back to/admin/loginwith 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.phpto 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.phpthat 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, callPostLoginRedirectResolverafter upsert to determine redirect. - T020 [US3] Create a Filament page
app/Filament/Pages/ChooseTenant.phpmounted under the/adminpanel 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); persistusers.last_tenant_idif present. IMPORTANT: use Filament URL helpers (no hardcoded paths). - T022 [US3] Create feature test
tests/Feature/Auth/PostLoginRoutingByMembershipTest.phpto validate 0/1/>1 routing. - T023 [US3] Create feature test
tests/Feature/Auth/TenantChooserSelectionTest.phpensuring 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.phpunder the/adminpanel 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.phpto ensure tenant session cannot access/system; platform session cannot access/admintenant 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.phpto enforce DB-only render/hydration for/admin/login,/admin/no-access, and/admin/choose-tenantusingHttp::preventStrayRequests()+ render each page + assert no exceptions; optional hardening:Queue::fake()+Bus::fake()(and/orEvent::fake()) so render paths can’t silently dispatch.
Dependencies & Execution Order
- Phase 1–2 must complete before US1–US4.
- Implement in order: US1 → US2 → US3 → US4.
- Phase 7 is last.