Skip to content

v0.4.12 — Identity SPA, overview command centre, and check-in hardening

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 06 Jul 14:22
88a9ade

[0.4.12] - 2026-07-06

Added

  • Vitest code coverage (npm run coverage) across workspaces with LCOV upload to Codecov on CI — reporting only, no coverage gate yet.
  • CI PR pipeline shortened: lint merged into build-test, Semgrep and Docker build smoke run on main merge (not every PR); SECURITY.md documents when each control runs.
  • Identity providers + Cloudflare Access JSON API (/api/admin/identity/providers*, /api/admin/identity/cf-access*) for the SPA Settings → Identity migration: list, get, create, update, toggle, discover, test, and CF Access get/update/test endpoints. Reuses @admitto/auth logic unchanged; gated by requireAdminAccess (superadmin) to match the legacy HTML routes. PUT contract: mappings is required on every PUT (replace-all, mirroring the HTML form; omitting it returns mappings_required so editing other fields can never silently delete mappings) and omitting login_button_label preserves the stored value (null/"" clears); create defaults omitted mappings to []. mappingSchema enforces scope_id for organization/event scopes. Toggle uses a conditional updateMany and returns 409 on a concurrent toggle (TOCTOU-safe). CF Access test endpoint shares the adminAuthProviderOpsRateLimit bucket with OIDC discover/test. Schemas use z.strictObject() (zod v4). Legacy HTML routes remain until the SPA editor lands (#266).
  • Settings → Identity & SSO SPA overview (#266 slice 2): new /admin/settings/identity/* routes under the existing InstanceSettingsShell render the OIDC providers list (with optimistic enable/disable toggle) and a Cloudflare Access summary card. The Settings "Identity" tab and the legacy ?tab=identity query now hand off to the canonical /admin/settings/identity/providers route, keeping the SPA shell consistent instead of jumping to raw HTML. Add/Edit provider and CF Access Manage still bridge to the legacy HTML editors until the SPA editor lands in slices 3–4.
  • Identity provider SPA editor — Basics, Endpoints, Claims, login button label (#266 slice 3a): new /admin/settings/identity/providers/new and /identity/providers/:providerId routes render a full form editor under the SPA shell. Create POSTs a new provider; edit loads by id and PUTs the full form (mappings carried through unchanged until the repeater lands in slice 3b). Client-side validation mirrors the slice-1 Zod contract; the stored client secret is preserved on edit when left blank; a dirty guard warns on navigation. The providers list Add/Edit actions now SPA-navigate to these routes instead of the legacy HTML bridge.
  • Identity provider SPA editor — group→role mapping repeater, Discover/Test, SSO preview (#266 slice 3b): the editor gains an editable mapping repeater (add/remove rows, role + scope selects, conditional scope_id) with replace-all save semantics; Discover autofills OIDC endpoints from the issuer's .well-known config and Test probes the connection (edit mode only, both with 401 routing to login); a live SSO login button preview reflects the custom label or the product default. Mapping validation (group required, scope_id required for organization/event scopes) blocks save with inline row errors.
  • Identity provider SPA editor — draft test and discover in create mode (#266): POST /api/admin/identity/providers/test and POST /api/admin/identity/providers/discover-preview are stateless endpoints that probe OIDC connectivity and autofill endpoints without requiring a saved provider record; Discover and Test connection are now available in create mode (previously edit-only). Partial endpoint sets (fewer than all three of auth/token/jwks) fall back to discovery so the test always reflects the same endpoint resolution as save. Issuer URL is validated against the same SSRF guard as the save path before any explicit-endpoint test. Save and Cancel buttons are disabled while discovery is in flight; stale discover and test responses are silently discarded when the issuer or endpoint draft diverges mid-flight.
  • Cloudflare Access SPA editor (#266 slice 4): new /admin/settings/identity/cloudflare route renders the CF Zero Trust config editor under the SPA Identity sub-tab, replacing the slice-2 placeholder. Edits team URL, Application Audience (AUD), and protected URL paths (comma-separated lists), toggles enabled, and Test connection probes the team domain's JWKS endpoint (sends the draft team URL so operators can test before saving). Per-field env locks disable the locked inputs and show a "Locked by env" badge; the PUT body omits locked fields so the server keeps the env-managed value. A dirty guard (router useBlocker + beforeunload) and 401 routing to login match the OIDC provider editor patterns. The Identity overview "Manage" action now SPA-navigates here instead of bridging to the legacy HTML editor.
  • HTTP access log on app container stdout (LOG_HTTP_REQUESTS, on by default in deploy compose): one JSON line per request with method, redacted path, status, and duration — no IPs, query strings, or ticket/QR tokens; successful health probes are skipped. Documented per-container log expectations in deploy/README.md (#237).
  • Password strength meter on first-run /setup, forced /change-password, and admin Account password change — text label plus segmented bar (not color-only); confirm fields show match feedback on setup and change-password pages (#226).
  • Overview page: per-event Pinned note — short operational sticky visible to all admins; editable inline, highlighted in the right column (#291).
  • Overview page: per-event Key contacts — list of on-site contacts with name, role, phone and email action links; add/edit/delete inline (#291).
  • Overview page: per-event Important links & files — list of linked documents and URLs with title and optional description; add/edit/delete inline, shows first 4 with "View all" toggle (#291).
  • Audit log: all mutations to pinned note, key contacts, and important links are recorded in AdminAuditLog with actor, session, IP, and action type — visible to superadmins in the Audit viewer (#291).

Changed

  • Settings shell unified for Identity IA (#266 slice 7b): SettingsLayout wraps /admin/settings/* so the primary tabs (General | Mail | Security | Archiving | Identity) remain visible on the Identity overview and on all detail views (add/edit provider, Cloudflare Access editor). The second tab row (Providers | Cloudflare Access) is removed — Identity overview is a single page with both cards. The ?tab=identity legacy query still redirects to the canonical /admin/settings/identity/providers route. Editor dirty guards (useBlocker) still trigger when switching primary tabs or leaving detail views.
  • Docs: sync CI/security narrative (Semgrep on main, Codecov data note, required merge checks), fix stale deploy examples (Node 24), README documentation map, identity SPA status in admin README, contributor coverage commands.
  • Overview page: redesigned as an event command center — "Quick actions" nav grid removed; new two-column layout with Needs attention alerts (email failures, queued tickets, missing operators), Event readiness checklist (attendees, tickets, delivery, operators), Email delivery breakdown, live Recent check-ins feed (SSE), and compact Event info block (#276).
  • Account page: sidebar nav structure now consistent with AdminShell — "My account" link is in the nav area (not the footer), back link stays in the footer; empty aria-hidden placeholder div removed (#267).
  • Reports: tile/panel spacing is now correct — --space-md/sm/lg and --surface-elevated were undefined tokens resolving to 0/transparent; replaced with --space-4/3/5 and --surface-sunken throughout reports-page.css. Progress bars in "By ticket type" use --primary consistently (removed threshold-based traffic-light coloring that flagged <50% admission as a warning at the start of every event). Hourly chart is accessible by keyboard and screen reader — bars have individual aria-label with hour and count; role="img" removed so the accessibility tree is not flattened (#269).
  • Topbar: mailer status indicator now uses the shared Badge component (pill with dot) instead of bespoke markup — visually consistent with every other status badge in the app; label still hides at narrow viewports (#275).
  • Check-in: AttendeeCard status display now differentiates positive, warning, and blocking-error states — VALID/PREVIEW show status inline in the identity header; ALREADY_CHECKED_IN uses a compact warning strip; REVOKED/INVALID show a unified tinted alert block with status and reason text merged into one message. Item action buttons (Give gift bag, Return headset) use chip-matching geometry without a border or background (#270).
  • Toast notifications: unified design-system stack (Tabler icons, deduplicated messages, bottom-right placement); admin pages and settings panels use useToast() for save/load feedback instead of inline status text.
  • Account page: profile, password, and MFA success/error feedback uses toasts instead of inline status text; locale-change reminder stays until dismissed (#239).
  • Toast stack z-index sits below check-in camera overlay so mobile lookup warnings do not cover overlay controls.
  • Setup wizard mail step: provider select order/labels, per-provider field grouping (SMTP username+port grid), and test-send row aligned with design mockup.
  • First-run routing: unauthenticated staff entry (/, /login, /admin, /operator, and related HTML gates) redirects to /setup until the first user exists; login form is shown only after bootstrap.
  • Setup wizard system check: allow http://127.0.0.1 / localhost BASE_URL in production (local Docker smoke); non-loopback HTTP still fails.
  • First-run mail wizard: ignore deploy env placeholders for field locks and test send until setup wizard completes (setup_complete).
  • Setup SSR (/setup): mockup-aligned copy, login-aligned autocomplete="username" on email, confirm password, and passwordrules for password managers.
  • Setup wizard shell: “Set up your instance” header, numbered stepper with labels, Continue arrow on primary CTA; custom date picker and timezone combobox; ready step summary chips.
  • Password strength meter: jsdom test executes the generated inline script end-to-end (meter, aria-label, confirm match); shared sample passwords move to the @admitto/auth/password-strength-fixtures test-only export (#254).
  • Settings page: the active in-page tab is persisted in the URL (?tab=general|mail|security|archiving, merged via replace so tab clicks don't stack history or wipe unrelated params) — Back from the Identity sub-section now restores the operator's tab instead of resetting to General (closes the #296 TODO); ?tab=identity still redirects to the canonical Identity route. A SPA-side catch-all inside /admin redirects any unmatched /admin/* (including removed legacy /admin/auth/* URLs) to the events picker so old bookmarks/docs links don't land on a blank outlet (#266 slice 5).

Removed

  • Identity providers migration cleanup (#266 slice 5): the legacy server-rendered identity admin is removed — /admin/auth/providers* and /admin/auth/cf-access* HTML routes, their handlers (auth-providers-routes.ts, cf-access-routes.ts), the HTML renderers (auth-providers-html.ts, cf-access-html.ts), and the renderAdminShell sidebar + SETTINGS_SUBNAV_ITEMS + ADMIN_PAGE_CSS block in shared-auth-styles.ts. The SPA at /admin/settings/identity/* is now the only identity admin surface; the JSON API (/api/admin/identity/*, slice 1) is unaffected. The cf-access-routes integration test was retargeted from the deleted HTML routes to the /admin SPA shell (and /api/admin/identity/providers for the CF no-role 403 message); the oidc-admin-routes and auth-providers-html unit tests were removed with the code they covered. Docs/env examples and SECURITY-CONTROLS.md updated to reference Settings → Identity; the stale /admin/auth Vite dev-proxy rule was removed.

Security

  • Login and MFA pages (verify, enroll, backup codes) ship inline scripts gated by a per-response CSP nonce instead of script-src 'unsafe-inline' (MFA) or a policy that blocked them in strict browsers (login) (#253).

Fixed

  • Admin: modal and panel backgrounds (confirm dialog, note modal, create-event modal, attendee drawer, requirements, reports, users) were transparent — var(--surface) was referenced but never defined in the token set; replaced with var(--surface-card) (#286).
  • Check-in: scan input stays enabled and queues submissions while a previous scan/lookup is still processing, instead of disabling the field and silently dropping keyboard-wedge keystrokes for the next attendee (#261).
  • Check-in: duplicate-scan debounce and buffer-clear-on-accept are now measured at the moment a scan or manual-lookup query is accepted, not once it reaches the front of the FIFO queue — a slow first request could otherwise let a genuine duplicate through, or leave stale query text for a later wedge scan's keystrokes to land on (producing a corrupted, unmatchable scan payload). The mobile camera overlay's own manual-entry field had the same gap (no disabled state at all) and is fixed the same way. Auto-advance also no longer clears an unrelated, still-in-progress scan's buffer, and Confirm check-in, item actions, notes, manual-lookup select, and Undo now all queue behind an in-flight scan instead of racing it — previously a slower response from one of these could overwrite the card of an attendee scanned afterward, or (for Undo) roll back the wrong check-in (#277 review follow-up).
  • Mail settings: SMTP_HOST/MAIL_FROM_ADDRESS left at their shipped deploy/.env.example placeholder values (smtp.example.com / events@example.com) no longer falsely report as "managed by environment" — Settings → Mail is editable for deployments that configure the transport from the admin UI instead of env (#264).
  • Check-in: manual lookup no longer misfires as a QR scan for queries over 20 characters — this now covers pressing Enter/Search after slowly typing a long name or email (not just the auto-submit debounce timer) and any single-event bulk insert into an empty field (paste, browser autofill/autocomplete, drag-and-drop, IME composition, voice dictation), in addition to the original mid-typing case; wedge auto-submit still requires burst-speed keystrokes arriving one character at a time, not just buffer length. Burst detection also now uses the input event's own timestamp instead of wall-clock time at handler execution, so a busy main thread (e.g. an unrelated scan's response resolving) can no longer misclassify a genuinely fast wedge scan as manual typing (#262).
  • Admin shell: entering an event from the events picker (or right after creating one) no longer re-fetches the whole events list and no longer flashes a blank spinner screen in place of the sidebar/topbar — the picker passes the already-loaded event along, so the event shell renders instantly; the passed-along event is consumed once and cleared from that history entry, so a later browser back/forward revisit still re-validates event access from the server instead of trusting a stale snapshot. Deep links and refreshes still resolve the event from the API as before (#274).
  • Reports: hourly admissions chart and peak hour bucket check-ins in the event timezone — previously shifted by the UTC offset for non-UTC events (#268).
  • Sidebar: unreleased lifecycle sections (Approval, Passes, Fulfilment, Post-event) render as plain disabled items — stale “Soon v0.4.9”-style release badges removed; placeholder pages drop internal jargon (#263).
  • New events seed default event items (gift bag, badge, headset) at creation, so Requirements → Event items is populated before the first check-in (#238).
  • Setup wizard step 1: Retry on failed check load, Run checks again after results, inline fix hints; single-column check list with status on the right (#223).
  • Setup wizard system check: four rows like mockup (Database includes migration status; no separate Migrations row).
  • Setup wizard steps 2–5: mockup parity — mail test row, branding logo zone/toasts, typed date picker, timezone list layout, ready screen footer; step labels no longer truncated (#243).
  • Setup wizard: restore last step after browser refresh; unsaved-refresh notice only when a dirty form was lost (saved mail/branding kept).
  • POST /api/admin/setup/complete requires passing system checks (409 setup_not_ready when checks fail).
  • Mail transport test: actionable admin error messages for TLS hostname mismatch, auth, and port mode (no hostnames in API responses) (#244).
  • Check-in: server-connected status moves to a compact page-header pill; full-width green banner only for connection problems (#234).
  • Check-in: persistent screen-reader live region announces connection recovery after offline/degraded states.
  • Check-in search fields: suppress password-manager autofill hints on scan bar and manual lookup (#231).
  • Check-in manual lookup: warning toast when search returns no attendees (#232).

Deploy

  • Container image: ghcr.io/solarssk/admitto:0.4.12 (rolling :0.4)
  • Migrations apply automatically on container start (with pre-migration backup when pending). No manual migrate deploy.

Full changelog