Patch notes — per-company API + SSO tabs (2026-06-15, commit `5107579`)
Adds two functional tabs next to Access on the company profile
(/admin/company?id=X): an API tab (a per-company, scoped API token) and an
SSO tab (per-company OpenID Connect group mapping). On main at commit
5107579, Docker smoke 116 / 0.
1 — API tab: a per-company, scoped API token ✅
Each company can now hold its own API token — high-entropy, separate from the
global platform token in Settings → API. Generate / Reset / Revoke from
the tab; it’s shown in a copyable field with a ready-to-paste curl example.
The token unlocks one new, read-only, scoped endpoint:
GET /api/company
auth: X-Company-Token: <token> (or ?company_token=<token>)
-> { "company": { id, name, org_no, website, active },
"systems": { online, degraded, offline, total, uptime_pct },
"open_tickets": <n> }
- The token resolves to exactly one company, so a holder can only ever read that company’s own profile, system health and open-ticket count — never another tenant’s data.
- Lookup is constant-time (
hmac.compare_digestover the small set of companies that have a token); an empty/blank token never matches. - The global
X-API-Tokengate (api_full) is untouched — this is an additive, parallel path for company-scoped machine access.
2 — SSO tab: explicit per-company group mapping ✅
An admin can map an OpenID Connect group straight to membership of this company.
Set a Customer group and/or a Company-admin group; a federated user carrying
that group in their groups claim is provisioned as a customer (or
company-admin) of this company on sign-in.
- Complements the existing global prefixes (
customer:/companyadmin:in Settings → SSO), which still resolve by company id or name — the tab lists the group names those prefixes already map here, for reference. _sso_resolvenow checks the explicit per-company groups first at each privilege tier (company-admin before plain customer), then falls back to the prefixes, so the established precedence is preserved: admin > staff > company-admin > customer > reader.- A banner on the tab flags when SSO is disabled (the mappings simply take effect once it’s enabled in Settings → SSO).
3 — Under the hood
- Schema: three idempotent columns on
crm_companies—api_token,sso_group,sso_admin_group(ALTER … ADD COLUMN IF NOT EXISTS; no semicolons in the SCHEMA comments, per the init-db split rule). - Both mutations (
/admin/company/api,/admin/company/sso) go through the existing single CSRF check in the admin POST gate and are audited (company.api.reset/company.api.revoke/company.sso.save). - Access to the tabs themselves is gated by the existing
can_access_companycheck on the company page.
Known limitation (unchanged by this patch)
can_access_company still treats all staff as having access to every company (the
“Staff with access” assignments on the Access tab remain informational — see the
spec-sheet’s open offer). The new API token is correctly scoped to one company;
tightening staff scoping is a separate, future change.
Verification
py_compile clean · Docker smoke 116 / 0 — including the new tabs render, token
generate / persist (48-hex) / revoke, /api/company returning scoped data, 401
without a token and 401 after revoke, and the SSO mapping saving + showing on the
tab.