{"record":{"id":"6eece3a3ec87e73e","repo":"home-assistant/core","slug":"system-generated-users-cannot-enable-multi-factor","errorCode":null,"errorMessage":"System generated users cannot enable multi-factor auth module.","messagePattern":"System generated users cannot enable multi-factor auth module\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"homeassistant/auth/__init__.py","lineNumber":420,"sourceCode":"        await self._store.async_deactivate_user(user)\n        for refresh_token in list(user.refresh_tokens.values()):\n            self.async_remove_refresh_token(refresh_token)\n\n    async def async_remove_credentials(self, credentials: models.Credentials) -> None:\n        \"\"\"Remove credentials.\"\"\"\n        provider = self._async_get_auth_provider(credentials)\n\n        if provider is not None and hasattr(provider, \"async_will_remove_credentials\"):\n            await provider.async_will_remove_credentials(credentials)\n\n        await self._store.async_remove_credentials(credentials)\n\n    async def async_enable_user_mfa(\n        self, user: models.User, mfa_module_id: str, data: Any\n    ) -> None:\n        \"\"\"Enable a multi-factor auth module for user.\"\"\"\n        if user.system_generated:\n            raise ValueError(\n                \"System generated users cannot enable multi-factor auth module.\"\n            )\n\n        if (module := self.get_auth_mfa_module(mfa_module_id)) is None:\n            raise ValueError(f\"Unable find multi-factor auth module: {mfa_module_id}\")\n\n        await module.async_setup_user(user.id, data)\n\n    async def async_disable_user_mfa(\n        self, user: models.User, mfa_module_id: str\n    ) -> None:\n        \"\"\"Disable a multi-factor auth module for user.\"\"\"\n        if user.system_generated:\n            raise ValueError(\n                \"System generated users cannot disable multi-factor auth module.\"\n            )\n\n        if (module := self.get_auth_mfa_module(mfa_module_id)) is None:","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/home-assistant/core/blob/58a3fdb3ea0538617f0a07efcfba6294de64fd59/homeassistant/auth/__init__.py#L402-L438","documentation":"ConfigEntryNotReady-style auth signal: brunt's coordinator maps an HTTP 403 from the Brunt cloud API to ConfigEntryAuthFailed, meaning the stored account credentials/token were rejected and HA should launch the re-auth flow rather than retry data updates.","triggerScenarios":"Awaiting self.bapi.async_get_things(force=True) raises aiohttp ClientResponseError with status == 403 — the Brunt API refuses the authenticated request because the session token/API key is invalid, expired, or the account denied access.","commonSituations":"Password changed on the Brunt account after the token was issued, the OAuth/session token expired server-side, the Brunt cloud service invalidated old tokens after an API change, or rarely the account itself was deactivated.","solutions":["Use the re-authenticate flow HA shows for the Brunt entry: Settings > Devices & Services > Brunt > Reauthenticate, and sign in with current credentials.","If re-auth fails too, verify the username/password work in the Brunt app; change the password if the account is compromised or you suspect a stale credential.","Check for known issues/updates in the brunt integration and its aionbrt library — an API-side change can make every request 403 until the library is patched."],"exampleFix":"// before\nexcept ClientResponseError as err:\n    if err.status == 403:\n        raise ConfigEntryAuthFailed from err\n// after: unchanged — correct handling. Optionally add a clearer message:\nraise ConfigEntryAuthFailed(\"Brunt API rejected the stored credentials (HTTP 403)\") from err","handlingStrategy":"try-catch","validationCode":"# There is no cheap pre-check for a 403 — the token's validity is only knowable\n# by making the request. Best pre-validation: confirm credentials recently re-authed.\nfrom datetime import datetime, timedelta\n\ndef token_likely_fresh(last_auth: datetime) -> bool:\n    return datetime.now(UTC) - last_auth < timedelta(days=30)","typeGuard":"from aiohttp import ClientResponseError\n\ndef is_forbidden(err: BaseException) -> bool:\n    \"\"\"True when the cloud API rejected our credentials (HTTP 403).\"\"\"\n    return isinstance(err, ClientResponseError) and err.status == 403","tryCatchPattern":"try:\n    things = await self.bapi.async_get_things(force=True)\nexcept ClientResponseError as err:\n    if err.status == 403:\n        raise ConfigEntryAuthFailed from err  # reauth flow\n    raise UpdateFailed from err               # transient, retry next interval","preventionTips":["Map only 403 to ConfigEntryAuthFailed; mapping 401/5xx too causes spurious reauth prompts.","Avoid changing the account password without immediately re-authing the integration.","Log the HTTP status alongside the exception so 403 (auth) is distinguishable from 5xx (outage) in production logs."],"tags":["home-assistant","authentication","brunt","http-403","cloud-api"],"backgroundTag":null,"analyzedSha":"58a3fdb3ea0538617f0a07efcfba6294de64fd59","analyzedAt":"2026-08-14T20:54:38.818Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}