Enabling the login screen when no admin user exists is refused by the guard added in #623, but the refusal is an InvalidOperationException (ConfigurationService.cs:451). ListenarrExceptionHandler.Map only maps the Application*Exception types to 4xx, so it falls through to the default and answers 500 internal_error with no detail (ListenarrExceptionHandler.cs:115-119).
SettingsView.vue treats only a 4xx as a server validation refusal (SettingsView.vue:865). Any other status is taken as a disk failure, and the view downloads the config as config.json for the user to save by hand. The comment there (SettingsView.vue:855-863) says a refused config must not be offered for download, because that would defeat the guard, and that the backend returns 400 with an actionable message. So today the guard's own refusal triggers the fallback it exists to prevent, and the user never sees the message.
Reproduced on a build of canary 192f5a9 with the startup config service mocked, so no config.json is touched: with the login screen off and no admin user, POST /api/v1/configuration/startupconfig with {"authenticationRequired":"true"} returns 500.
This change throws ApplicationValidationException with the code admin_user_required and the same message, so the existing handler answers 400 with the message as the detail, and adds [ProducesResponseType(400)] to the endpoint. The guard condition, its message and its position before SaveAsync are unchanged, so nothing new can be saved. No frontend change is needed, since the view already handles a 400 as a refusal. The only caller of the guard that surfaces its exception to a client is this endpoint; the two API-key paths that also call SaveStartupConfigAsync do not turn auth on.
Not done here: the refusal toast shows the raw API error: 400 {...} string rather than just the problem detail. That was already the case for other 4xx refusals, so I left it alone.
Tests:
- `...
Automated Canary build