pos login with the ph number and pin
This commit is contained in:
@@ -29,10 +29,10 @@ told.**
|
||||
```bash
|
||||
BASE=https://fiesta.nearle.app/live/api/v1/pos
|
||||
|
||||
# 1. Sign in
|
||||
# 1. Sign in — a mobile number and a 4-digit PIN
|
||||
curl -s -X POST $BASE/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"authname":"rsselvapuram@gmail.com","password":"…","terminal_id":"T5EDD"}'
|
||||
-d '{"contactno":"9876543210","pin":"4821","terminal_id":"T5EDD"}'
|
||||
|
||||
# 2. Use the token on everything else
|
||||
curl -s $BASE/session -H "Authorization: Bearer $TOKEN"
|
||||
@@ -44,9 +44,9 @@ curl -s $BASE/session -H "Authorization: Bearer $TOKEN"
|
||||
|
||||
These steps are in order, and the order matters.
|
||||
|
||||
**1. Sign in.** `POST /login` with the operator's own credentials — the same
|
||||
`app_users` account they use for the web console. There is no separate POS
|
||||
password.
|
||||
**1. Sign in.** `POST /login` with the operator's **mobile number and 4-digit
|
||||
PIN** — the pair the back office issued them. Both are held on their own
|
||||
`app_users` row; there is no separate POS credential store.
|
||||
|
||||
**2. Read `store_id` out of the response.** Do not ask anyone to type it. It is
|
||||
whatever the back office says that account's outlet is.
|
||||
@@ -74,8 +74,8 @@ The only unauthenticated route. It is where a token comes from.
|
||||
|
||||
```json
|
||||
{
|
||||
"authname": "rsselvapuram@gmail.com",
|
||||
"password": "…",
|
||||
"contactno": "9876543210",
|
||||
"pin": "4821",
|
||||
"terminal_id": "T5EDD",
|
||||
"device_id": "a5f3…",
|
||||
"location_id": 1135,
|
||||
@@ -85,15 +85,15 @@ The only unauthenticated route. It is where a token comes from.
|
||||
|
||||
| Field | Required | Notes |
|
||||
|---|---|---|
|
||||
| `authname` | yes* | Email. **Or** send `contactno` instead. |
|
||||
| `contactno` | yes* | Mobile number, as an alternative to `authname`. |
|
||||
| `password` | yes | |
|
||||
| `contactno` | yes | Mobile number. Send it as typed — `+91 98765 43210`, `098765-43210` and `9876543210` all reach the same account. |
|
||||
| `pin` | yes | Exactly 4 digits, never starting with `0`. |
|
||||
| `terminal_id` | no | This till's short code, e.g. `T5EDD`. Recorded on the session. |
|
||||
| `device_id` | no | The device's stable UUID. |
|
||||
| `location_id` | no | **Only** meaningful for a multi-outlet account. A request, not an assertion — it is checked against what the account may reach. |
|
||||
| `configid` | no | Inferred when absent. Send it only if you get the ambiguity error below. |
|
||||
| `authname` + `password` | no | The previous way in. Still accepted, so a shop whose numbers have not been backfilled is not stranded — see [POS_PHONE_PIN_LOGIN_HANDOVER.md](POS_PHONE_PIN_LOGIN_HANDOVER.md). |
|
||||
|
||||
\* one of `authname` or `contactno`.
|
||||
`pin` wins if a password is sent as well; `authname` wins over `contactno`.
|
||||
|
||||
### Response — `200`
|
||||
|
||||
@@ -128,7 +128,7 @@ The only unauthenticated route. It is where a token comes from.
|
||||
|
||||
"staff": [
|
||||
{ "user_id": 1148, "full_name": "Ragul Kannan",
|
||||
"role": "Super admin", "pin": "1111", "status": "Active" }
|
||||
"role": "Super admin", "status": "Active" }
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,10 @@ sign-in.
|
||||
**`locations`** — every outlet this account may open a till at. Length 1 is the
|
||||
normal case.
|
||||
|
||||
**`staff`** — see [Staff and PINs](#staff-and-pins). **Often empty.**
|
||||
**`staff`** — see [Staff and PINs](#staff-and-pins). **Often empty**, and it
|
||||
**no longer carries `pin`**: a PIN is now half of the sign-in, so a list of them
|
||||
is a list of working credentials for the outlet. Switch operator through
|
||||
`POST /login/pin` instead.
|
||||
|
||||
---
|
||||
|
||||
@@ -191,9 +194,13 @@ Requires the token. Returns `401` when there isn't one.
|
||||
Who may ring a bill at this terminal's outlet. For pulling down somebody hired
|
||||
mid-shift without signing the terminal out.
|
||||
|
||||
**Takes no parameters.** The answer carries PINs, so the outlet comes from the
|
||||
caller's own token — a till must not be able to ask who works at the shop next
|
||||
door. A request without a token is refused whatever the enforcement setting is.
|
||||
**Takes no parameters.** The outlet comes from the caller's own token — a till
|
||||
must not be able to ask who works at the shop next door. A request without a
|
||||
token is refused whatever the enforcement setting is.
|
||||
|
||||
Names and roles only; **`pin` is not returned here either**, for the same reason
|
||||
it left the login session. A supervisor who needs to see or change one uses
|
||||
`GET /pos/users`, which is role-gated.
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -258,14 +265,18 @@ login, it is simply not found.
|
||||
one, 22 live accounts have it including a delivery rider, and it grants nothing
|
||||
on either side.
|
||||
|
||||
### Every till account gets its own username and password
|
||||
### Every till account gets its own number and PIN
|
||||
|
||||
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.
|
||||
Both roles. A PIN now opens a *closed* terminal too, paired with the account's
|
||||
mobile number — which is what removed the old deadlock: `/pos/login/pin` needs a
|
||||
session that already exists, so before this a PIN-only account could not unlock
|
||||
a till at all. 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.
|
||||
|
||||
**So a till account needs both `contactno` and `pin` set.** One without the
|
||||
other cannot sign in. A username and password still work, and every account
|
||||
created before this still has them.
|
||||
|
||||
So a Cashier signs in exactly like a Supervisor does, and the *role* decides
|
||||
what they get — not which credential they used:
|
||||
@@ -293,9 +304,10 @@ outlet — becomes `cashier2.1185@pos.nearle.in`; a name **you** supplied is nev
|
||||
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.
|
||||
**The PIN is no longer optional in practice.** The field still is — creation
|
||||
accepts an account without one — but an account with no PIN cannot sign a
|
||||
terminal in, and is told so by name: *"this account has no PIN set; ask your
|
||||
supervisor to set one in the web console first."*
|
||||
|
||||
---
|
||||
|
||||
@@ -303,11 +315,12 @@ around — so it is set deliberately, never by default.
|
||||
|
||||
For a cashier taking over a counter a supervisor has already opened.
|
||||
|
||||
**Requires an existing valid token.** That is the security model, not an
|
||||
oversight: four digits is ten thousand guesses, which is no barrier at all to an
|
||||
anonymous caller. Tying it to a session means a supervisor has opened the
|
||||
terminal with a real password first, and the guesses are confined to that one
|
||||
outlet's staff.
|
||||
**Requires an existing valid token.** A PIN alone is four digits — ten thousand
|
||||
guesses, and no barrier to an anonymous caller. Tying it to a session confines
|
||||
the guesses to one outlet's staff, at a terminal somebody has already opened.
|
||||
That is why this route takes a bare PIN and `/login` does not: there, the PIN is
|
||||
checked against one mobile number, and the number is what makes the pair worth
|
||||
anything.
|
||||
|
||||
```bash
|
||||
curl -s -X POST $BASE/login/pin \
|
||||
@@ -486,23 +499,29 @@ GET /catalogue?store_id=1185 → 403 {"message":"this session cannot reach o
|
||||
|
||||
| Code | Meaning | What the till should do |
|
||||
|---|---|---|
|
||||
| `400` | Body unreadable, or neither `authname` nor `contactno` sent | Fix the request |
|
||||
| `401` | `those sign-in details were not recognised` | Ask them to re-type. **Wrong email and wrong password give the same message** — deliberately, so the endpoint isn't a directory of who banks here |
|
||||
| `400` | Body unreadable, or `a mobile number is required` / `a PIN is required` — one of the two fields is empty | Fix the request; do not send it again unchanged |
|
||||
| `401` | `those sign-in details were not recognised` | Ask them to re-type. **A wrong number and a wrong PIN give the same message** — deliberately, so the endpoint isn't a directory of who banks here. A number that cannot be ten digits, and a PIN that is not four, answer the same way |
|
||||
| `403` | Real account, but it can't open this till | Show the message; re-typing won't help |
|
||||
|
||||
The `403` messages, verbatim:
|
||||
|
||||
- `this account is not set up for the till; ask your store admin to add you as a Supervisor or Cashier in the web console`
|
||||
- `this account is inactive; contact your administrator`
|
||||
- `this account has no PIN set; ask your supervisor to set one in the web console first`
|
||||
- `this account has no password set; set one in the web console first`
|
||||
- `this account is not attached to a tenant and cannot open a till`
|
||||
- `no active outlet is registered for this account`
|
||||
- `this account cannot open a till at outlet 1185`
|
||||
- `more than one account uses these sign-in details; ask your administrator for the configid and send it with the login`
|
||||
|
||||
That last one is real, not theoretical: `authname` is not unique in this schema.
|
||||
Live data has the same address twice. We refuse rather than pick one, because
|
||||
picking wrong means billing into another tenant's books.
|
||||
That last one is real, not theoretical: neither `authname` nor `contactno` is
|
||||
unique in this schema. Live data has the same address twice, and 34 mobile
|
||||
numbers shared by 104 accounts. We refuse rather than pick one, because picking
|
||||
wrong means billing into another tenant's books.
|
||||
|
||||
Sign-in only ever looks at **till accounts** (roleid 7 and 8), which is what
|
||||
makes a number workable as a credential: it has to be unique among a tenant's
|
||||
own till staff, not across all 608 users on the platform.
|
||||
|
||||
The **first** one is the common case now, and it is deliberately specific where a
|
||||
bad password is deliberately vague. By the time it fires the caller has already
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# POS sign-in by mobile number, and shift assignment — handover to the terminal team
|
||||
|
||||
> ## ⚠️ Superseded by [POS_PHONE_PIN_LOGIN_HANDOVER.md](POS_PHONE_PIN_LOGIN_HANDOVER.md)
|
||||
>
|
||||
> `POST /pos/login` now takes a mobile number and a **PIN**, not a mobile number
|
||||
> and a password, and the session no longer carries `staff[].pin`. Read the new
|
||||
> handover instead — build against this one and the sign-in screen will be
|
||||
> wrong.
|
||||
>
|
||||
> Still accurate here, and not repeated there: the `createposuser` /
|
||||
> `updateposuser` / `getposusers` field changes (§2), the number format rules
|
||||
> (§3), and shifts (§4). **Not** accurate here: §2's claim that `shift_*`
|
||||
> appears on `staff[]` in the `/pos/login` session — it never did.
|
||||
|
||||
Backend is done and builds clean. **Nothing about this breaks the current app** —
|
||||
username sign-in keeps working exactly as it does today. Read §5 before you ship
|
||||
anything, because the switch has one ordering rule that will lock out every
|
||||
|
||||
311
docs/POS_PHONE_PIN_LOGIN_HANDOVER.md
Normal file
311
docs/POS_PHONE_PIN_LOGIN_HANDOVER.md
Normal file
@@ -0,0 +1,311 @@
|
||||
# POS sign-in by mobile number and PIN — handover to the terminal team
|
||||
|
||||
`POST /pos/login` now takes a **mobile number and a four-digit PIN**. Backend is
|
||||
done, builds clean, and is proved end to end against a real database (§8).
|
||||
|
||||
Two things need reading before you ship: **§5**, because shipping the new screen
|
||||
before the back office has filled in numbers and PINs locks out every cashier in
|
||||
every shop; and **§4**, because the login response no longer carries staff PINs
|
||||
and any terminal switching operator from that array will stop working.
|
||||
|
||||
Supersedes `POS_PHONE_LOGIN_HANDOVER.md`, which described the same endpoint
|
||||
taking a number and a *password*.
|
||||
|
||||
---
|
||||
|
||||
## 1. What changed, in one line
|
||||
|
||||
A till signs in with the two things a person standing at a counter can actually
|
||||
type — their number and their PIN — and the response stops handing out everyone
|
||||
else's PIN.
|
||||
|
||||
---
|
||||
|
||||
## 2. The endpoint
|
||||
|
||||
`POST https://fiesta.nearle.app/live/api/v1/pos/login`
|
||||
|
||||
Unauthenticated. It is where a token comes from. Everything else on the POS
|
||||
group sits behind the session it issues.
|
||||
|
||||
### Payload
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"contactno": "9876543210", // required
|
||||
"pin": "4821", // required — exactly 4 digits, never starts with 0
|
||||
"terminal_id": "T5EDD", // optional, recorded on the session
|
||||
"device_id": "a5f3…", // optional
|
||||
"location_id": 1135, // optional, multi-outlet accounts only
|
||||
"configid": 1 // optional, only if you are told to send it
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Required | Notes |
|
||||
|---|---|---|
|
||||
| `contactno` | **yes** | Send it as typed. `+91 98765 43210`, `098765-43210` and `9876543210` all reach the same account — the server reduces every number to ten digits before matching. |
|
||||
| `pin` | **yes** | Exactly 4 digits. `0451` is not a PIN here and never was — see §3. |
|
||||
| `terminal_id` | no | This till's short code. Recorded on the session so a stolen token can be told apart from the terminal it was issued to. |
|
||||
| `device_id` | no | The device's stable UUID. |
|
||||
| `location_id` | no | Means something **only** for an account entitled to more than one outlet. It is a request, not an assertion: checked against what the account may reach, and refused if it is not one of them. |
|
||||
| `configid` | no | Inferred when absent. Send it only after the ambiguity error in §6. |
|
||||
| `authname` + `password` | no | The previous way in. **Still works** — see §5. |
|
||||
|
||||
If both are sent, `pin` is used over `password` and `authname` over `contactno`.
|
||||
|
||||
### Response — `200`, unchanged in shape
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"code": 200,
|
||||
"status": true,
|
||||
"message": "Login successful",
|
||||
"details": {
|
||||
"token": "eyJ1aWQiOjQwMDEsInRpZCI6MTA4Nywi….PKmoMn92BMs",
|
||||
"expires_at": "2026-09-11T11:31:08Z",
|
||||
|
||||
"user_id": 4001,
|
||||
"full_name": "Meena Sundaram",
|
||||
"email": "meena@example.com",
|
||||
"role_id": 7,
|
||||
"role": "Supervisor",
|
||||
"can_manage_staff": true,
|
||||
|
||||
"tenant_id": 1087,
|
||||
"tenant_name": "R Mart",
|
||||
|
||||
"store_id": "1135",
|
||||
"location_id": 1135,
|
||||
"location_name": "Selvapuram",
|
||||
"gstin": "33AABCU9603R1ZM",
|
||||
"address": "4 Trichy Road",
|
||||
"phone": "04422334455",
|
||||
|
||||
"locations": [
|
||||
{ "location_id": 1135, "location_name": "Selvapuram",
|
||||
"address": "4 Trichy Road", "city": "Coimbatore", "status": "Active" }
|
||||
],
|
||||
|
||||
"staff": [
|
||||
{ "user_id": 4002, "full_name": "Priya Raman",
|
||||
"role": "Cashier", "status": "Active" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**The only change to the response is that `staff[].pin` is gone.** Everything
|
||||
else — `token`, `expires_at`, `store_id`, `locations[]`, `can_manage_staff` —
|
||||
is byte-for-byte what it was. §4 is why, and what to do instead.
|
||||
|
||||
Two corrections to what the previous handover promised about this response:
|
||||
`staff[]` has **no** `shift_id` / `shift_name` / `shift_start` / `shift_end`
|
||||
fields, and never did. The shift fields exist on `GET /web/tenants/getposusers`.
|
||||
If the terminal needs the shift on the sign-in screen, say so and it can be
|
||||
added — do not write code against it today.
|
||||
|
||||
---
|
||||
|
||||
## 3. What counts as a PIN, and the trap in it
|
||||
|
||||
Four digits, and **never a leading zero**. `app_users.pin` is a `bigint`, so
|
||||
`0451` is stored as `451` and read back as three digits — somebody would type
|
||||
four and be refused for ever. Creation refuses those, so this only matters for
|
||||
what you let a person type: accept four digits, send them as a string.
|
||||
|
||||
**Do not add a client-side guessable-PIN check.** The console refuses to *issue*
|
||||
`1234`, `1111`, `9999` and a handful more, but live data already holds `1234` on
|
||||
eleven accounts and `1111` on nine — issued before that rule existed. Sign-in
|
||||
deliberately accepts them, because refusing them would lock twenty real people
|
||||
out of terminals this platform signed them up to. A rule about what may be
|
||||
created is not a rule about what may be typed.
|
||||
|
||||
**Number format.** Non-digits are stripped, then a leading `91` or `0` is
|
||||
dropped once. Anything that is not ten digits afterwards is rejected. Do not
|
||||
add a country code the user did not type, and do not reject `+91` locally — let
|
||||
it through and let the server reduce it.
|
||||
|
||||
---
|
||||
|
||||
## 4. 🔴 `staff[].pin` is gone — read this if you switch operators offline
|
||||
|
||||
The login session used to carry every colleague's PIN so the till could switch
|
||||
operator without a round trip. That was defensible while a PIN was *shift
|
||||
attribution*: the token decided which books a terminal could reach, and the PIN
|
||||
only decided whose name went on the bill.
|
||||
|
||||
That stopped being true the moment the PIN became half of the sign-in. The array
|
||||
would now be a list of working credentials for the whole outlet — including the
|
||||
supervisor's, which carries `can_manage_staff`. Any cashier could read it and
|
||||
sign back in as their own manager.
|
||||
|
||||
So `pin` no longer appears in `staff[]`, and no longer appears in `GET
|
||||
/pos/staff` either. `staff[]` still carries `user_id`, `full_name`, `role` and
|
||||
`status`, so the operator picker still works.
|
||||
|
||||
**Switch operator through `POST /pos/login/pin`** — an existing session, a bare
|
||||
PIN, and you get a fresh session with the new person's role:
|
||||
|
||||
```bash
|
||||
curl -s -X POST "$B/pos/login/pin" \
|
||||
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
|
||||
-d '{"pin":"7391"}'
|
||||
```
|
||||
|
||||
**The cost, stated plainly: this needs the network.** A till that switched
|
||||
operators offline against the cached array can no longer do so. If that matters
|
||||
for your shops, tell us — the options are a short-lived cache of hashed PINs on
|
||||
the device, or a separate endpoint a supervisor calls once to warm one. Neither
|
||||
is built, and neither should be built on a guess about how shops actually work.
|
||||
|
||||
One wart we did **not** change: `staff[]` can contain a back-office account that
|
||||
has a PIN and sits at that location — the query filters on having a PIN, not on
|
||||
role. Tapping them and typing their PIN answers *"this account is not set up for
|
||||
the till"*. Filtering by role would look tidier and would empty the staff list
|
||||
for a great many real shops, whose people carry role ids that are not POS roles
|
||||
at all. Show the message; it names the fix.
|
||||
|
||||
---
|
||||
|
||||
## 5. 🔴 Sequencing — read this before shipping
|
||||
|
||||
**Every till account on the platform predates the number it now signs in with.**
|
||||
When the previous handover was written, all 12 live POS accounts had
|
||||
`contactno = ""`. Some also have no PIN. An account needs **both** to sign in.
|
||||
|
||||
If the app ships number-and-PIN **only**, every cashier in every shop is locked
|
||||
out on the next update.
|
||||
|
||||
**Required order:**
|
||||
|
||||
1. **Backend deploys.** Nothing changes for the app — username and password
|
||||
still work, unchanged.
|
||||
2. **Back office fills in a mobile number and a PIN on every existing till
|
||||
account** through the console (`PUT /web/tenants/updateposuser`). New
|
||||
accounts already require a number.
|
||||
3. **Only then** does the app make number-and-PIN the primary sign-in.
|
||||
|
||||
**Recommendation for the app:** ship the new screen, and keep a small
|
||||
"sign in with a username instead" link behind it until step 2 is confirmed
|
||||
finished for every shop. One un-backfilled account then means one awkward login,
|
||||
not a shop that cannot open.
|
||||
|
||||
**Verify step 2 is actually done** before you flip anything — a shop is ready
|
||||
only when every one of its till accounts has both fields:
|
||||
|
||||
```bash
|
||||
curl -s "$B/web/tenants/getposusers?tenantid=1087&locationid=1135" \
|
||||
-H "Authorization: Bearer $CONSOLE_TOKEN" | jq '.details.users[] | {full_name, contactno, pin}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Errors you should handle
|
||||
|
||||
| Code | Message | What it means | What the app should do |
|
||||
|---|---|---|---|
|
||||
| `400` | `a mobile number is required` | The field was empty, or held no digits at all | Fix locally; do not resend unchanged |
|
||||
| `400` | `a PIN is required` | Neither `pin` nor `password` was sent | Fix locally |
|
||||
| `401` | `those sign-in details were not recognised` | Wrong number, wrong PIN, a number that cannot be ten digits, a PIN that is not four, or somebody who has left | *"Check your number and PIN"* — do **not** say which was wrong |
|
||||
| `403` | `this account has no PIN set; ask your supervisor to set one in the web console first` | Provisioned without one | Show verbatim; it names the fix |
|
||||
| `403` | `this account is not set up for the till; ask your store admin to add you as a Supervisor or Cashier in the web console` | A back-office login was used | Show verbatim |
|
||||
| `403` | `more than one account uses these sign-in details; ask your administrator for the configid and send it with the login` | Ambiguous match | Show verbatim; it needs the back office |
|
||||
| `403` | `this account cannot open a till at outlet 1185` | `location_id` named an outlet this account cannot reach | Drop `location_id` and retry, then show the picker from `locations[]` |
|
||||
|
||||
The `401` is deliberately one message for several causes. Distinguishing them
|
||||
turns an unauthenticated endpoint into a directory of who banks here.
|
||||
|
||||
---
|
||||
|
||||
## 7. 🔴 One thing this endpoint cannot fix: rate limiting
|
||||
|
||||
A four-digit PIN is ten thousand guesses. On `/pos/login/pin` that is contained,
|
||||
because the route needs a valid session and can only reach one outlet's staff.
|
||||
On `/pos/login` it is **not** — the route is unauthenticated by necessity, and
|
||||
the pair is only strong while an attacker cannot sit and try every PIN against a
|
||||
number they know.
|
||||
|
||||
Nothing in this change adds a limiter, and the endpoint is the wrong place for
|
||||
one. **Before this becomes the only way into a till, `/pos/login` needs a rate
|
||||
limit at the edge** — per source and per `contactno`, with a lockout after a
|
||||
few failures. Please raise it with whoever owns the ingress; it is not the
|
||||
terminal team's job, but shipping the screen without it is what would make it
|
||||
somebody's incident.
|
||||
|
||||
---
|
||||
|
||||
## 8. How this was verified
|
||||
|
||||
Not against production — there are no live credentials in this working copy, so
|
||||
nothing here was run against the real database. Proved instead against a
|
||||
throwaway Postgres seeded with a shop and six accounts, running the **real**
|
||||
repository, service and SQL:
|
||||
|
||||
```bash
|
||||
docker run -d --rm --name nearle-posproof -e POSTGRES_PASSWORD=proof \
|
||||
-e POSTGRES_DB=proof -p 55432:5432 postgres:16-alpine
|
||||
|
||||
POS_PROOF_DSN='postgres://postgres:proof@localhost:55432/proof?sslmode=disable' \
|
||||
POS_TOKEN_SECRET=proof-secret-at-least-16 \
|
||||
go run ./scratch/posphonepinproof # 18 passed, 0 failed
|
||||
```
|
||||
|
||||
Covered: sign-in by number and PIN; the same account typed four different ways;
|
||||
a cashier getting a cashier's session; a weak-but-issued PIN still admitted; a
|
||||
wrong PIN, an unknown number, an account with no PIN, a back-office account, a
|
||||
malformed number, a malformed PIN and a leaver all refused with the right
|
||||
answer; username-and-password still working; `POST /login/pin` still switching
|
||||
operator; no PIN anywhere in the response; a token minted.
|
||||
|
||||
Unit tests for the same rules: `go test ./repositories/` —
|
||||
`repositories/posLogin_test.go`.
|
||||
|
||||
**Still to do against live data, by whoever has the credentials:** confirm the
|
||||
regression check below, and count how many till accounts still lack a number or
|
||||
a PIN (§5, step 2).
|
||||
|
||||
```bash
|
||||
B=https://fiesta.nearle.app/live/api/v1
|
||||
|
||||
# regression: username sign-in must still work — run this first
|
||||
curl -s -X POST "$B/pos/login" -H 'Content-Type: application/json' \
|
||||
-d '{"authname":"supervisor.1135@pos.nearle.in","password":"…"}' \
|
||||
| grep -o '"can_manage_staff":[a-z]*'
|
||||
# expect: "can_manage_staff":true
|
||||
|
||||
# the new way in, once that account has a number and a PIN
|
||||
curl -s -X POST "$B/pos/login" -H 'Content-Type: application/json' \
|
||||
-d '{"contactno":"9876543210","pin":"4821"}' \
|
||||
| grep -o '"can_manage_staff":[a-z]*'
|
||||
|
||||
# and no PIN in the response
|
||||
curl -s -X POST "$B/pos/login" -H 'Content-Type: application/json' \
|
||||
-d '{"contactno":"9876543210","pin":"4821"}' | grep -c '"pin"'
|
||||
# expect: 0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. What did **not** change
|
||||
|
||||
- The response shape, apart from `staff[].pin` leaving
|
||||
- `token` format, TTL (30 days) and the `PosAuth` guard
|
||||
- `POST /pos/login/pin` — same request, same answer, and it still needs a session
|
||||
- `POST /pos/orders`, `/pos/customers`, `/pos/health`, `GET /pos/catalogue`
|
||||
- `GET /pos/users`, which still shows PINs to a supervisor and blanks them for a
|
||||
cashier — a supervisor sets those PINs, so seeing them tells them nothing they
|
||||
could not already change
|
||||
- `POS_AUTH_REQUIRED` still defaults to off
|
||||
|
||||
---
|
||||
|
||||
## 10. Where this lives, if you need to read it
|
||||
|
||||
| What | Where |
|
||||
|---|---|
|
||||
| Which credential was offered, and how a row is checked against it | `repositories/posAuthRepository.go` — `posLoginSecret` |
|
||||
| Which column names the account, and number normalisation | `repositories/posAuthRepository.go` — `posLoginIdentity` |
|
||||
| PIN format at sign-in vs. the rule for issuing one | `repositories/posUserRepository.go` — `posLoginPin`, `validatePosPin` |
|
||||
| The staff list, and why `Pin` is `json:"-"` | `models/pos.go` — `PosStaffMember` |
|
||||
| Request/response shapes | `models/pos.go` — `PosLoginRequest`, `PosSession` |
|
||||
| Full endpoint reference | `docs/POS_LOGIN.md` |
|
||||
Reference in New Issue
Block a user