feat(status): public /status discovery endpoint — advertise auth methods for mobile SSO#3664
Conversation
…ods for mobile SSO The mobile app calls GET /status (unauthenticated) at connect time to discover which login methods an instance offers, so it can show the right affordances — notably the OIDC 'Sign in with SSO' flow, which stays DORMANT in the app until a backend advertises it here. This is the discovery half of the mobile OIDC bridge (#3637); without it the SSO button never appears even when the bridge is deployed. - app/Domain/Status/Controllers/Index.php — GET /status returns the SAFE public tier: authMethods (password / ldap / oidc, from config), oidcLoginUrl when OIDC is enabled, instanceName, core version, minAppVersion, and an empty ssoProviders that AdvancedAuth can populate via the 'publicStatus' filter. Deliberately OMITS plugin inventory / versions / dbVersion — unauthenticated that is a recon gift (CVE matching); those stay behind auth (the authenticated mobileStatus). - AuthCheck: allow-list 'status' / 'status.index' as public. - Tests: IndexTest (password-only, oidc-advertised + login URL, no-inventory-leak) + AuthCheck public-route test. Pint clean. Answers Q5 in docs/backend-mobile-auth-bridge-plan.md. Follow-up to #3637/#3662/#3663. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NtfhjPwUEEHJb4Jf7714eR
There was a problem hiding this comment.
Pull request overview
Adds a new unauthenticated /status discovery endpoint in the core backend to support the mobile app’s connect-time capability detection (notably advertising OIDC SSO availability), while keeping sensitive “inventory” details behind authenticated status endpoints.
Changes:
- Introduces
Status\Controllers\IndexwithGET /statusreturning a minimal, public-safe JSON payload (auth methods, instance name, version, optionaloidcLoginUrl, andssoProviders). - Allow-lists
status/status.indexas public routes inAuthCheck. - Adds unit tests to pin the public contract and validate the route is public.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| app/Domain/Status/Controllers/Index.php | Implements the public /status discovery JSON response and plugin filter hook. |
| app/Core/Middleware/AuthCheck.php | Marks status and status.index as public actions so /status is reachable unauthenticated. |
| tests/Unit/app/Domain/Status/Controllers/IndexTest.php | Verifies payload contract (auth methods, oidcLoginUrl) and absence of sensitive inventory keys. |
| tests/Unit/app/Core/Middleware/AuthCheckTest.php | Ensures status routes are treated as public by AuthCheck. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| $payload = self::dispatchFilter('publicStatus', $payload, ['request' => $this->request]); | ||
|
|
||
| return new JsonResponse($payload); |
|
|
||
| private IncomingRequest $request; | ||
|
|
||
| public function init(Environment $config, AppSettings $appSettings, IncomingRequest $request): void |
| $this->request = $request; | ||
| } | ||
|
|
||
| public function get(array $params): Response |
…-filter + docblocks - Defense in depth: after the publicStatus filter runs, strip a denylist of known-sensitive keys (plugins / plugin versions / dbVersion) so a misbehaving filter (or a future edit) can't leak the recon-risk inventory into this unauthenticated response, even if a plugin gets the public-safe contract wrong. - Add init()/get() method docblocks to match the controller doc style. IndexTest green (3 tests / 10 assertions); Pint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NtfhjPwUEEHJb4Jf7714eR
|
Status: triaged · Priority: P2 · Next action: human review · Owner: @marcelfolaron Picked up in the daily sweep — new since the last pass. This is the discovery half of the mobile OIDC bridge (follow-up to #3637 / #3662 / #3663), adding the unauthenticated One thing for the reviewer to keep front-of-mind, since it's the security crux of the PR: the endpoint is public, so the "safe tier" contract is load-bearing — verify it never leaks plugin inventory / plugin versions / |
The discovery half of the mobile OIDC bridge (#3637). Answers Q5 in
docs/backend-mobile-auth-bridge-plan.md("build the general status endpoint in core with auth-tiered disclosure").Why
The mobile app calls
GET /status(unauthenticated) at connect time to discover which login methods an instance offers, so it shows the right affordances — most importantly the OIDC "Sign in with SSO" flow. That flow ships gated: the button stays dormant until a backend advertises OIDC here. Without this endpoint, SSO never appears even when the bridge (#3637) is deployed. (Mobile side:connectionService::fetchPublicStatus.)What
New
app/Domain/Status/Controllers/Index.php—GET /statusreturns the safe public tier:{ "mobileAuthEnabled": true, "instanceName": "…", // config sitename "version": "3.9.8", // AppSettings::appVersion "minAppVersion": null, "authMethods": ["password", "oidc"], // password always; ldap/oidc when configured "oidcLoginUrl": "https://host/oidc/login", // only when OIDC enabled "ssoProviders": [] // AdvancedAuth appends via the 'publicStatus' filter }AuthCheck:status/status.indexallow-listed public.publicStatusfilter, which must preserve the safe contract.Tests
IndexTest(3): password-only when no SSO configured, OIDC advertised +oidcLoginUrlwhen enabled, and never leaks a plugin/version inventory.AuthCheckTest:/statusis a public route. Pint clean.Note for review
authMethodsreadsconfig->oidcEnable/config->useLdap(mirrorsAuth\Controllers\Login+Auth\Services\Auth).ssoProvidersis empty in core by design; AdvancedAuth's provider labels + login URLs come through thepublicStatusfilter (keeps core provider-agnostic — Q1 in the plan doc).Follow-up to #3637 / #3662 / #3663.
🤖 Generated with Claude Code