POST /api/auth/password. The cost of its absence was measured today rather than argued: rotating three production accounts took a shell on the host, three round trips, and briefly left a 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. 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. Three decisions: - **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 this by client would have reproduced the hole it exists to close, which is also why SetUserPassword is not scoped by client the way ResetMemberPassword beside it is. The user id comes from the verified session, never the request, so there is nothing to point at anyone else. - **The current password is required.** An access token lives twelve hours and travels on devices that get lost and shared; without this a stolen one owns the account permanently instead of until it expires. - **Every OTHER session is revoked, and the caller's is kept.** Somebody changing their password because they believe it is known must not have to 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 failure there is logged, not returned: the password IS changed by then, and reporting an error would send them to retry with a current password that no longer exists. The suite's login() helper fatals on anything but 200, which is right everywhere else and useless here - half of what these tests assert is that a password has STOPPED working. loginCode() returns the status. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
31 KiB
31 KiB