Give a cashier their own till login, not just a PIN behind a supervisor
A PIN cannot open a closed terminal. The PIN route needs a session that already exists, so a PIN-only account works only while somebody else is standing there to unlock the till first. For a supervisor that was an outright deadlock and was fixed last commit. For a cashier it is subtler and just as wrong: the shop cannot open until two people have arrived, and whoever gets in at seven is as often the cashier as the supervisor. So every till account now gets a username and a password, and the role decides the shell rather than the credential deciding it. A cashier signs in exactly the way a supervisor does and is still held to billing only, because that comes from roleid 8 and not from how they got in. The earlier reasoning — that a second password is one more credential to leak for no capability gained — was measuring the wrong thing. It counted the cost of the credential and not the cost of the shop that cannot open without one. CreatePosUser generates both when the request omits them, so provisioning is one call per person and nobody has to invent a naming scheme. An explicit value always wins. A generated name that collides walks to the next free one, because a second cashier at one counter is ordinary rather than an error; a name the caller supplied is refused instead, because silently signing somebody in as another person's address is worse than a message. Uniqueness is checked against authname and email together, since the insert writes the same value to both and app_users_email_unique would otherwise fail the transaction rather than return something anyone can act on. The password comes back exactly once, in the creation response. Listing till users still reports only has_password, so an admin who loses it reissues rather than looks it up — the right shape even while the column behind it is plaintext. The domain is deliberately unroutable. These are till credentials, never a mailbox, and an address that looks deliverable invites somebody to try sending a reset to it. Verified against live rows by scratch/posseparation, which now checks the cashier path too: cashier.1185@pos.nearle.in opens a closed terminal alone and comes back can_manage_staff=false. All five outlets that stock products have both accounts, each proved by an actual sign-in. Also drops a stray `print(queryBuilder.String())` from GetAllUsers, which was writing the whole SQL statement to stderr on every call. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -258,15 +258,44 @@ login, it is simply not found.
|
||||
one, 22 live accounts have it including a delivery rider, and it grants nothing
|
||||
on either side.
|
||||
|
||||
### A Supervisor needs a password, a Cashier does not
|
||||
### Every till account gets its own username and password
|
||||
|
||||
A PIN cannot open a *closed* terminal — `/pos/login/pin` requires a session that
|
||||
already exists. So a Supervisor is provisioned with an `authname` **and** a
|
||||
`password` as well as a PIN, and a Cashier gets only a PIN: a cashier signs on
|
||||
at a counter a Supervisor has already opened, so a second password would be one
|
||||
more credential to leak for no capability gained.
|
||||
Both roles. A PIN cannot open a *closed* terminal — `/pos/login/pin` requires a
|
||||
session that already exists — so a PIN-only account works only while somebody
|
||||
else is standing there to unlock the till first. For a Supervisor that was an
|
||||
outright deadlock; for a Cashier it meant a shop that could not open until two
|
||||
people had arrived, and whoever gets in at seven is as often the cashier as the
|
||||
supervisor.
|
||||
|
||||
Provisioning a Cashier and nobody else leaves an outlet with no way in at all.
|
||||
So a Cashier signs in exactly like a Supervisor does, and the *role* decides
|
||||
what they get — not which credential they used:
|
||||
|
||||
```
|
||||
POST /v1/pos/login supervisor.1185@pos.nearle.in -> full shell
|
||||
POST /v1/pos/login cashier.1185@pos.nearle.in -> billing only
|
||||
```
|
||||
|
||||
`POST /pos/users` generates both when the request omits them, and returns the
|
||||
password **once**, in the creation response only:
|
||||
|
||||
```json
|
||||
{ "user_id": 1452, "role": "Cashier",
|
||||
"authname": "cashier.1185@pos.nearle.in",
|
||||
"password": "9tWx2KUJksM5Rm", "pin": "4513", "has_password": true }
|
||||
```
|
||||
|
||||
`GET /pos/users` never returns a password, only `has_password`. An admin who
|
||||
loses it reissues rather than looks it up.
|
||||
|
||||
Send `authname` and `password` explicitly if the shop wants its people signing
|
||||
in as themselves. A generated name that collides — a second cashier at one
|
||||
outlet — becomes `cashier2.1185@pos.nearle.in`; a name **you** supplied is never
|
||||
adjusted, it is refused, because silently signing somebody in as another
|
||||
person's address is worse than an error.
|
||||
|
||||
The PIN stays optional. It switches operator at an open counter, which not every
|
||||
shop does, and it is the one credential the till holds in plaintext to hand
|
||||
around — so it is set deliberately, never by default.
|
||||
|
||||
---
|
||||
|
||||
@@ -321,11 +350,16 @@ own store id.
|
||||
|---|---|
|
||||
| `full_name` | required; split across `firstname`/`lastname` |
|
||||
| `role` | `"supervisor"` or `"cashier"`. Anything else is refused — never defaulted |
|
||||
| `pin` | 4 digits. See the rules below |
|
||||
| `password` + `authname` | optional; for someone who also signs the terminal in |
|
||||
| `pin` | optional, 4 digits. See the rules below |
|
||||
| `authname` | optional. **Generated if omitted** — `cashier.1185@pos.nearle.in`, or `cashier2.…` if that is taken |
|
||||
| `password` | optional. **Generated if omitted**, and returned once in this response |
|
||||
|
||||
**At least one of `pin` or `password` is required.** Creating a person who can
|
||||
sign in by neither would look like it worked right up until somebody tried.
|
||||
**Everyone gets a username and a password, cashiers included**, because a PIN
|
||||
cannot open a closed terminal. Omit both fields and they are generated for you,
|
||||
so provisioning a shop is one call per person.
|
||||
|
||||
The response is the only time the password is returned; `GET /pos/users` reports
|
||||
`has_password` and nothing more.
|
||||
|
||||
:warning: **PIN rules, and why**
|
||||
|
||||
|
||||
Reference in New Issue
Block a user