Add reauth flow (finding C7) #23

Open
mat wants to merge 1 commit from feat/c7-reauth into main
Owner

Summary

Closes finding C7.

Since PR #14 the coordinator already maps 401/403 to ConfigEntryAuthFailed, but without a reauth step the user has no way to recover short of deleting and re-adding the integration (which orphans every entity's history). This PR adds the missing async_step_reauth + async_step_reauth_confirm:

  • Trigger: HA calls async_step_reauth automatically when ConfigEntryAuthFailed is raised (or when the user clicks Reconfigure).
  • UX: prompts only for a new password; the username from the existing entry is shown in the description placeholder.
  • Validation: same validate_input helper as the user step — same invalid_auth / cannot_connect / unknown error surface.
  • Success: async_update_reload_and_abort writes the new password into entry.data, reloads the entry so the coordinator reconnects with the fresh creds, and aborts with reauth_successful.

Also cleaned up:

  • Dropped the unused host field from strings.json / translations/en.json — it referenced a schema entry that never existed.
  • validate_input now returns None and raises on error (was returning a {title} dict only used in one caller).

Test plan

  • 4 new config-flow tests:
    • test_reauth_prompts_for_new_password — form rendered, username in placeholders.
    • test_reauth_happy_path_updates_password — entry data updated, abort reason reauth_successful, authenticate called with new password.
    • test_reauth_invalid_auth_keeps_form_open — entry data unchanged on invalid_auth.
    • test_reauth_cannot_connect_keeps_form_open — entry data unchanged on cannot_connect.
  • pytest tests/ → 28 passed; ruff + mypy clean.
  • Hands-on: on your HA, trigger reauth by temporarily changing the stored password to something wrong, waiting for the next poll to hit ConfigEntryAuthFailed, then confirming the HA Settings → Repairs prompts for a new password and the integration comes back healthy after you enter the real one.
## Summary Closes finding C7. Since PR #14 the coordinator already maps 401/403 to `ConfigEntryAuthFailed`, but without a reauth step the user has no way to recover short of deleting and re-adding the integration (which orphans every entity's history). This PR adds the missing `async_step_reauth` + `async_step_reauth_confirm`: - **Trigger**: HA calls `async_step_reauth` automatically when `ConfigEntryAuthFailed` is raised (or when the user clicks *Reconfigure*). - **UX**: prompts only for a new password; the username from the existing entry is shown in the description placeholder. - **Validation**: same `validate_input` helper as the user step — same `invalid_auth` / `cannot_connect` / `unknown` error surface. - **Success**: `async_update_reload_and_abort` writes the new password into `entry.data`, reloads the entry so the coordinator reconnects with the fresh creds, and aborts with `reauth_successful`. Also cleaned up: - Dropped the unused `host` field from `strings.json` / `translations/en.json` — it referenced a schema entry that never existed. - `validate_input` now returns `None` and raises on error (was returning a `{title}` dict only used in one caller). ## Test plan - [x] 4 new config-flow tests: - `test_reauth_prompts_for_new_password` — form rendered, username in placeholders. - `test_reauth_happy_path_updates_password` — entry data updated, abort reason `reauth_successful`, `authenticate` called with new password. - `test_reauth_invalid_auth_keeps_form_open` — entry data **unchanged** on invalid_auth. - `test_reauth_cannot_connect_keeps_form_open` — entry data unchanged on cannot_connect. - [x] `pytest tests/` → 28 passed; `ruff` + `mypy` clean. - [ ] Hands-on: on your HA, trigger reauth by temporarily changing the stored password to something wrong, waiting for the next poll to hit `ConfigEntryAuthFailed`, then confirming the HA Settings → Repairs prompts for a new password and the integration comes back healthy after you enter the real one.
Add reauth flow (finding C7)
All checks were successful
Validate / validate-hacs (push) Has been skipped
Validate / validate-hassfest (push) Has been skipped
Validate / lint-ruff (push) Successful in 7s
Validate / test-pytest (push) Successful in 1m45s
Validate / type-check-mypy (push) Successful in 1m49s
Validate / validate-hacs (pull_request) Has been skipped
Validate / validate-hassfest (pull_request) Has been skipped
Validate / lint-ruff (pull_request) Successful in 7s
Validate / test-pytest (pull_request) Successful in 1m45s
Validate / type-check-mypy (pull_request) Successful in 1m48s
52934867cd
The coordinator already raises `ConfigEntryAuthFailed` on 401/403 from
the Comwatt backend (PR #14), but the integration had no reauth step
to collect fresh credentials — a password rotation required deleting
and re-adding the integration, which orphans every entity's history.

Add `async_step_reauth` + `async_step_reauth_confirm`:

- Triggered automatically whenever the coordinator raises
  `ConfigEntryAuthFailed` (i.e. session expired AND the stored
  password no longer works).
- Prompts the user for a new password only — the username from the
  existing entry is reused and shown to the user in the description
  placeholder.
- Validates against the backend using the same `validate_input`
  helper as the user step (pulls out its own `invalid_auth` /
  `cannot_connect` / `unknown` errors).
- On success, `async_update_reload_and_abort` writes the new
  password into `entry.data`, reloads the entry so the coordinator
  picks up the fresh creds, and aborts the flow with
  `reauth_successful` — the standard HA idiom.

## Changes

- `config_flow.py`:
  - `validate_input` now returns `None` and raises on error (used to
    return a `{"title": ...}` dict, but we need the same check from
    two places and the title was only used in one of them).
  - New `STEP_REAUTH_DATA_SCHEMA` with just the password field.
  - `async_step_reauth(entry_data)` delegates to
    `async_step_reauth_confirm`.
  - `async_step_reauth_confirm` renders the form, validates on
    submit, and calls `async_update_reload_and_abort` on success.
- `strings.json` / `translations/en.json`:
  - New `reauth_confirm` step with a description that renders the
    current username.
  - New `reauth_successful` abort string (reused from common keys).
  - Dropped the stale `host` field that didn't match the schema.
- Tests (`test_config_flow.py`):
  - Reauth shows the form with username in placeholders.
  - Valid new password → entry data updated, abort reason
    `reauth_successful`, authenticate called with the new password.
  - Invalid-auth / cannot-connect keep the form open; entry data
    unchanged.
mat force-pushed feat/c7-reauth from 52934867cd
All checks were successful
Validate / validate-hacs (push) Has been skipped
Validate / validate-hassfest (push) Has been skipped
Validate / lint-ruff (push) Successful in 7s
Validate / test-pytest (push) Successful in 1m45s
Validate / type-check-mypy (push) Successful in 1m49s
Validate / validate-hacs (pull_request) Has been skipped
Validate / validate-hassfest (pull_request) Has been skipped
Validate / lint-ruff (pull_request) Successful in 7s
Validate / test-pytest (pull_request) Successful in 1m45s
Validate / type-check-mypy (pull_request) Successful in 1m48s
to f58af6f23f
All checks were successful
Validate / validate-hacs (push) Has been skipped
Validate / validate-hassfest (push) Has been skipped
Validate / lint-ruff (push) Successful in 7s
Validate / test-pytest (push) Successful in 1m45s
Validate / type-check-mypy (push) Successful in 1m48s
Validate / validate-hacs (pull_request) Has been skipped
Validate / validate-hassfest (pull_request) Has been skipped
Validate / lint-ruff (pull_request) Successful in 7s
Validate / test-pytest (pull_request) Successful in 1m46s
Validate / type-check-mypy (pull_request) Successful in 1m49s
2026-04-24 21:53:11 +00:00
Compare
All checks were successful
Validate / validate-hacs (push) Has been skipped
Validate / validate-hassfest (push) Has been skipped
Validate / lint-ruff (push) Successful in 7s
Validate / test-pytest (push) Successful in 1m45s
Validate / type-check-mypy (push) Successful in 1m48s
Validate / validate-hacs (pull_request) Has been skipped
Validate / validate-hassfest (pull_request) Has been skipped
Validate / lint-ruff (pull_request) Successful in 7s
Validate / test-pytest (pull_request) Successful in 1m46s
Validate / type-check-mypy (pull_request) Successful in 1m49s
This pull request can be merged automatically.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/c7-reauth:feat/c7-reauth
git switch feat/c7-reauth

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff feat/c7-reauth
git switch feat/c7-reauth
git rebase main
git switch main
git merge --ff-only feat/c7-reauth
git switch feat/c7-reauth
git rebase main
git switch main
git merge --no-ff feat/c7-reauth
git switch main
git merge --squash feat/c7-reauth
git switch main
git merge --ff-only feat/c7-reauth
git switch main
git merge feat/c7-reauth
git push origin main
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
mat/homeassistant-comwatt!23
No description provided.