S13 — a standalone portal with "Sign in with iVendNext"
A small web app on your own server. A person from the shop opens it, signs in with their own iVendNext account, and sees the shop's records that their own account may see — a store user sees their store's sales and no other store's; someone with no right sees nothing. It is for a developer, or the CitiXsys Consulting Team, who needs a screen that is not inside the shop's frame (a head-office or franchise portal, say).
When to use it: a screen of your own, outside the shop, for people who already have shop accounts. If the screen can live inside the shop, use the native screens first (guide chapter 13a).
What happens
- The person opens the portal. It shows a Sign in with iVendNext button.
- The portal sends their browser to the shop. The person signs in to the shop with their own account and presses Allow on the shop's page that names your app.
- The shop sends the browser back with a one-time code. The portal swaps the code for a token — proving it is the app that started the sign-in — and keeps the token on its server.
- The portal asks the shop for the latest sales with that token. The shop answers as that person: only their store's sales. The page lists them; each sale links into the same record in the shop's Desk.
- Sign out revokes the token at the shop.
A person whose account has no right to sales gets a plain sentence, not a list. A person the shop does not let into your app never reaches the portal at all.
The files
| File | What it is |
|---|---|
portal.py |
the whole portal (standard library only): the sign-in, the page, the sign-out |
tests/test_s13.py |
27 laptop tests against a fake shop |
The pages use the kit's own stylesheet (ivn-kit.css) and no colour or font of their own. The portal serves the kit's file from the kit's folder, or from next to portal.py if you copy it there. Recipe: recipes/portal.md; the sign-in itself: recipes/sign_in.md.
What is refused, and why
- A callback that was not started from this browser, has the wrong state, or is used twice. The portal keeps the state against the session id of the browser that started the sign-in and spends it on the first callback that carries it. A wrong state spends nothing, so a forged link cannot burn someone's sign-in.
- A code with no verifier, or the wrong one. The shop refuses it; the portal always sends the verifier it made. (The shop does not check the client secret, so the portal never sends one.)
- A page that shows the token, or an address that carries one. The token stays in the portal's memory; the browser holds only a random session id, replaced by a new one at sign-in (an id planted beforehand is worth nothing). On an HTTPS portal the cookie is
Secureand__Host--bound without being told. - A flood of sign-in starts. The portal keeps at most 1,000 sessions; at the cap it drops the expired ones, then the oldest unfinished sign-in, never a signed-in person.
- A record name that carries markup. Every value is escaped.
Technical notes
Tested on our test bench on 2026-10-08 (23 checks; the run id is in the recipe): two store users with one store each, a desk user holding an unrelated role, a website user, and an OAuth client with and without Allowed Roles. Measured: a website user (no desk role) is stopped by the shop at its Allow step with "Invalid client_id parameter value" (400), and so is a desk user without the role when the client lists Allowed Roles; the portal never receives a code for either. Bounds, recorded: starting a new sign-in in a browser that is already signed in leaves the old session until it expires (8 hours) or is signed out; a shop's refusal at sign-in always reads "Your account may not use this portal"; a restart signs everyone out. Not tried: the refresh token, HTTPS (the bench is plain HTTP on its own network), two portals on one client, many users at once.