Patch notes — Document Portal: customer login provisioning + SSO handoff (2026-06-15)
Ethica.no 2d18e58 · EthicaDocServer 9948766. The folder-per-company integration
already created each company’s docs folder and the portal rendered it as an access-gated
section — but no human was ever granted access. The “provision a portal login” half was
only a settings panel and a Test connection button. This wires up the missing half, for
both password and SSO customers. Full design:
../../ethica-no/components/docs-integration.md.
Ethica.no Docker smoke 157/0.
1 — Password customers: auto-provisioned login ✅
New ethica/core/portal.py — a stdlib token-auth client for the portal machine API
(POST /api/admin/{provision,sections,deprovision}, Bearer READPAGE_ADMIN_TOKEN).
Best-effort: every call returns a bool and never raises, so portal trouble never blocks user
management. Mirrors the Secbitz platform’s lib/portal.ts.
Hooked into the customer-user lifecycle (admin Users page + the company-admin Add team
member on /account):
| Event | Call | Effect |
|---|---|---|
| Customer created | provision | portal login (same password) granted [company name] |
| Password changed | provision | password re-synced (idempotent upsert) |
| Disabled / role off customer | sections [] | grants revoked |
| Re-enabled | sections [section] | company section re-granted |
| Deleted | deprovision | login removed |
The grant section is the exact company name — the same string as the docs folder, so it lines up with the portal’s slug-matched access check.
2 — SSO customers: a signed handoff ✅
An SSO (PocketID) customer has only a random throwaway password in Ethica.no, and the portal has no SSO of its own — so the password mirror can’t admit them. Added a signed single-sign-on bridge instead:
- ethica.no —
portal_sso_url()mints a short-lived (~2 min) HMAC-SHA256 token over{u, exp, s|a}, signed with the sharedREADPAGE_ADMIN_TOKEN(no new secret). Sections are taken from the user’s company server-side, never from the browser./accountshows “Open documentation portal ↗” →GET /account/docs-portalredirects todokument.ethica.no/sso?token=…. - portal (
EthicaDocServer) —GET /sso→verify_sso_token()(constant-time signature- expiry check) → upserts the login (no password needed), grants the section, opens a
session, lands them in. Admin/staff carry
a:1→ every section.
- expiry check) → upserts the login (no password needed), grants the section, opens a
session, lands them in. Admin/staff carry
One button serves password and SSO users. The mint↔verify pair was round-trip verified byte-for-byte (tampered signature, wrong key, and expired tokens all rejected).
3 — Configuration
None added. Both halves reuse the existing Document Portal URL + admin token under Settings → Docs. Security: server-derived sections, ~2-minute token expiry, constant-time verify, TLS-only session cookies.
Verification
py_compile clean across both repos; token mint/verify round-trip green; Ethica.no Docker
smoke 157/0 (no regression in settings / account / dispatch).