From e0bd764e440f7cb23ad8297d2f68ca62388a9a0d Mon Sep 17 00:00:00 2001 From: Suriyakumarvijayanayagam Date: Tue, 29 Sep 2026 15:31:21 +0530 Subject: [PATCH] Document the self-service password change Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj --- API.md | 29 +++++++++++++++++++++++++++++ CLAUDE.md | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/API.md b/API.md index 9c6446b..731c097 100644 --- a/API.md +++ b/API.md @@ -43,6 +43,7 @@ user; the tenant is always taken from the session and never from the request. |---|---| | `POST /api/auth/login` `refresh` · `GET /api/auth/invitation` · `POST /api/auth/register` | **no auth** | | `POST /api/auth/logout` · `GET /api/auth/me` · `/api/auth/sessions*` | authed | +| `POST /api/auth/password` — change your OWN | authed (platform admins too) | | `GET /api/visits` · `GET /api/visits/stream` | authed | | `GET /api/visitors` · `GET /api/visitors/{id}/history` · `GET /api/visitors/{id}/image` · `GET /api/faces/{id}` | authed | | `PUT /api/visitors/{id}/profile` · `POST /api/purchases` | staff | @@ -477,6 +478,34 @@ deactivate them (§4); that revokes every session they hold. --- +### `POST /api/auth/password` — any signed-in account + +Change your own password. Works for **every** account including a platform +admin, who has no company and therefore cannot be reached by the team routes. + +```json +{ "current_password": "...", "new_password": "..." } +``` + +```json +{ "changed": true, "sessions_revoked": 3 } +``` + +- **The current password is required.** An access token lives twelve hours and + travels on shop-floor PCs and staff phones; without this a stolen one would + own the account permanently rather than until it expires. Wrong current + password is **403 `wrong_password`** and changes nothing. +- **Every other session is revoked; the caller's is kept.** Somebody changing + their password because they think it is known must not wonder whether the + device that already had it is still signed in — and must not be signed out of + the one in their hand while dealing with it. +- A new password under the floor is **400**, and so is reusing the current one. + +This is the route to use rather than asking an administrator. `POST +/api/team/{id}/password` remains what a manager uses on somebody *else*. + +--- + ## 4. The team ### `GET /api/team` — anyone in the company diff --git a/CLAUDE.md b/CLAUDE.md index 4966231..65d44e2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2470,6 +2470,47 @@ carrying no host or username, another merchant's shop and camera both 404, malformed identifiers 404 rather than 500, one real sale (INR 1000, V-1) read back by id, and every tenant route still 200 for an ordinary tenant account. +## Nobody could change their own password + +`POST /api/auth/password`. The cost of its absence was measured rather than +argued. Rotating the three production accounts took a shell on the host, three +round trips, and briefly left the **platform admin** — the account that reads +every company on the estate — with the password `PASTE_IT_HERE`, because a +placeholder in a pasted command was taken literally and there was no way to +correct it from the product itself. + +A manager could always reset somebody *else's* password. A platform admin could +be reset by nobody: they have no client, so the team routes are not theirs, and +`provision user` on the host was the only route. For software that puts +accounts on shop-floor PCs and staff phones, this is not a feature — it is what +makes every other credential decision recoverable. + +- **`authed`, not `tenantOnly`.** A session is not a company's data, and the + account with no company is precisely the one that had no route. Scoping it by + client would have reproduced the hole it exists to close — which is also why + `SetUserPassword` is not client-scoped the way `ResetMemberPassword` beside + it is. The user id comes from the verified session, never the request. +- **The current password is required**, or an access token alone takes an + account over permanently instead of for the rest of the day. +- **Every other session is revoked and the caller's is kept.** A failure there + is logged, not returned: the password is already changed, and an error would + send the user to retry with a current password that no longer exists. + +Verified live against production: wrong current password 403 and nothing +changed, a change revoking **45** stale sessions while the caller's own +survived, the new password in and the old one out, then changed back. + +### And a shell quoting trap worth not repeating + +The first attempt to rotate the admin password from the operator's terminal +ran with the literal string `PASTE_IT_HERE`. The second, reading the value out +of a file, produced **no output at all** and changed nothing — `~` was not +expanded in that eval context, `awk` could not open the file, returned +non-zero, and `&&` short-circuited silently. Absolute paths and `;` instead of +`&&` fixed it, and echoing the password *length* first is what proved the +third attempt was about to set something real. A command handed to somebody to +paste should contain nothing to edit and should fail loudly. + ## Setting up on a new machine 1. Copy the `Behavision` folder **including `.env`** (gitignored, holds