A standalone portal with "Sign in with iVendNext" — the recipe
This recipe builds a web app of your own, on your own server, for people who already have accounts in a shop: a head-office view, a franchise portal, a manager's dashboard. The person signs in with their iVendNext account, and the portal shows only what that person's own permissions allow. Reference implementation: samples/s13_portal/ (the portal, its tests).
When to use it. When the screen cannot live inside the shop. If it can, use the shop's own screens first (guide chapter 13a, rung 1–4); a portal is rung 5.
Steps
-
Ask the shop to register your app (
recipes/sign_in.md§1): an OAuth Client with your exact redirect address (https://your.server/callback), scopesopenid all. They give you the client id. To let only some roles into your app, they list them under Allowed Roles: the shop then stops anyone else at its own Allow step. -
Run the portal with three settings:
PORTAL_SHOP=https://shop.example PORTAL_CLIENT_ID=… PORTAL_REDIRECT=https://your.server/callback python3 portal.pyPut it behind HTTPS and set
PORTAL_SECURE=1, so the session cookie is never sent in the clear. -
Give each person the permissions you want them to have — in the shop. The portal does no filtering of its own. A store user is fenced to their store by a User Permission on the store (the same way S8 and S14 do); the portal's list then holds only that store's sales. Change who sees what in the shop and the portal follows.
-
Make your own pages with the kit. The sample's pages carry the kit's classes only, no colour or font of your own. Run the screen check on every page you ship:
kit/design/tools/check_kit.sh your-page.html. -
Decide what to list. The sample lists the latest 20 sales (POS Invoice) with a link into the shop's Desk record. Replace it with the records your people need; ask only for the fields you show.
What is refused, and why
- A sign-in that did not start in this browser: the portal refuses the callback (400) and asks the shop for nothing.
- A stolen code: useless without the verifier the portal made, which never leaves its memory.
- A person the shop does not allow into your app: the shop stops them; your server never sees a code.
- A person who may not see sales: the shop answers 403 to the portal's request; the page says so in a sentence.
- A signed-out or ended sign-in: the page is the sign-in page again; nothing of the shop is shown.
Your duties
- Keep tokens on your server; never in a page, cookie, URL or log. (The sample keeps none in a log: it writes no access log.)
- Revoke the token when the person signs out (the sample does).
- A restart signs everyone out in the sample (memory only). Keep sessions in a store you control if that is not enough, and expire them.
- Ask for consent on your side for anything you store about a person.
Technical notes
What was proven, and where
- Laptop, 27 tests (
samples/s13_portal/tests/test_s13.py): a guest costs the shop nothing; the sign-in sends PKCE S256, a state and the exact redirect; the callback exchanges the code with the verifier that matches the challenge and no client secret; the page uses the person's own token and never shows it; a wrong state, a callback from another browser and a reused callback are refused with nothing exchanged; a refused person reads nothing and the shop's error text is not echoed; markup in a record name is escaped; sign-out is a POST and revokes; an ended sign-in goes back to the sign-in page; the shop unreachable is a clean page; the sessions are capped, and a flood of sign-in starts cannot sign a real person out; the session id is new after sign-in; an HTTPS portal's cookie isSecureby itself; a wrong state spends nothing; a shop's redirect is not followed with the token. - Our test bench, 23 checks (run
pp-20261008T030544Z-85817): the portal ran as a real process, and a script played each person's browser. The Gateway store user saw exactly the 20 latest sales the shop holds for that store and none of the other store's; the other store's user saw exactly their store's 4 and none of the first's; the record behind the link opened for the person's own store's sale and was refused for the other store's; a desk user holding an unrelated role signed in and was told in words they may not see sales; the portal's code, exchanged with no verifier or a wrong one, was refused; a callback from another browser, one with the wrong state and one used twice were refused and issued no token; the session id was new after sign-in; sign-out cut the shop's active tokens by exactly one; everything the run made was removed. - The screen check (
kit/design/tools/check_kit.shon the portal's seven pages — signed out, signed out with a note, sales, sales with no name, none, refused, error): tokens only, kit version 0.1.0, contrast, right-to-left: PASS.
Measured
- A website user (no desk role) cannot sign in to any app: the shop stops them at its Allow step with "Invalid client_id parameter value" (400).
- With Allowed Roles on the client, a desk user without the role is stopped the same way; the user with the role signs in.
Not proven
The shop's token ending during an 8-hour session (proven on the laptop only); the refresh token; HTTPS (the bench is plain HTTP on its own network); two portals on one client; many users at once; a person's browser was played by a script (the pages were checked by the screen check, not looked at by a person).