{"record":{"id":"8e643fdc61653600","repo":"home-assistant/core","slug":"username-not-normalized","errorCode":"username_not_normalized","errorMessage":"username_not_normalized","messagePattern":"username_not_normalized","errorType":"exception","errorClass":"InvalidUsername","httpStatus":null,"severity":"error","filePath":"homeassistant/auth/providers/homeassistant.py","lineNumber":254,"sourceCode":"\n        for user in self.users:\n            if self.normalize_username(user[\"username\"]) == username:\n                user[\"password\"] = self.hash_password(new_password, True).decode()\n                break\n        else:\n            raise InvalidUser(translation_key=\"user_not_found\")\n\n    @callback\n    def _validate_new_username(self, new_username: str) -> None:\n        \"\"\"Validate that username is normalized and unique.\n\n        Raises InvalidUsername if the new username is invalid.\n        \"\"\"\n        normalized_username = self.normalize_username(\n            new_username, force_normalize=True\n        )\n        if normalized_username != new_username:\n            raise InvalidUsername(\n                translation_key=\"username_not_normalized\",\n                translation_placeholders={\"new_username\": new_username},\n            )\n\n        if any(\n            self.normalize_username(user[\"username\"]) == normalized_username\n            for user in self.users\n        ):\n            raise InvalidUsername(\n                translation_key=\"username_already_exists\",\n                translation_placeholders={\"username\": new_username},\n            )\n\n    @callback\n    def change_username(self, username: str, new_username: str) -> None:\n        \"\"\"Update the username.\n\n        Raises InvalidUser if user cannot be found.","sourceCodeStart":236,"sourceCodeEnd":272,"githubUrl":"https://github.com/home-assistant/core/blob/58a3fdb3ea0538617f0a07efcfba6294de64fd59/homeassistant/auth/providers/homeassistant.py#L236-L272","documentation":"InvalidUsername with translation_key username_not_normalized, raised by Data._validate_new_username (homeassistant/auth/providers/homeassistant.py:254) when the proposed username differs from its force-normalized form (username.strip().casefold()). The provider refuses to store names with leading/trailing whitespace or uppercase letters so lookups stay deterministic; the offending value is passed as the new_username placeholder.","triggerScenarios":"Calling add_auth or change_username with e.g. \" Alice \", \"Bob\", or any mixed-case/whitespace name; usernames sourced from external directories that preserve case.","commonSituations":"Programmatic user creation using display names; migrating old user lists; input from forms that don't trim/casefold before submission.","solutions":["Normalize before calling: `username = username.strip().casefold()`","Enforce trim + lowercase in the UI/form layer for new usernames"],"exampleFix":"// before\nprovider.data.add_auth(\"Bob\", password)  # raises username_not_normalized\n\n# after\nprovider.data.add_auth(\"bob\", password)","handlingStrategy":"validation","validationCode":"new_username = new_username.strip().casefold()\nprovider.data.add_auth(new_username, password)","typeGuard":"def is_normalized(username: str) -> bool:\n    return username == username.strip().casefold()","tryCatchPattern":"from homeassistant.auth.providers.homeassistant import InvalidUsername\ntry:\n    provider.data.add_auth(username, password)\nexcept InvalidUsername as err:\n    if err.translation_key != \"username_not_normalized\":\n        raise\n    provider.data.add_auth(username.strip().casefold(), password)","preventionTips":["Apply strip().casefold() to every new username before submit","Enforce trimming and lowercase in form validation layers","Note legacy-mode stores may hold non-normalized names — address the repair issue first"],"tags":["auth","python","home-assistant","username","validation","local-provider"],"backgroundTag":null,"analyzedSha":"58a3fdb3ea0538617f0a07efcfba6294de64fd59","analyzedAt":"2026-08-14T20:54:38.818Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}