Document the self-service password change

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
2026-09-29 15:31:21 +05:30
parent 6068b2c3c7
commit e0bd764e44
2 changed files with 70 additions and 0 deletions

29
API.md
View File

@@ -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

View File

@@ -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