Skip to content

Releases: solarssk/admitto

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

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 `autocompl...
Read more

v0.4.11 — CI stability patch

Pre-release

Choose a tag to compare

@github-actions github-actions released this 02 Jul 18:48
b8268e7

[0.4.11] - 2026-07-02

Fixed

  • Redis rate-limit integration test: wait for fresh fixed window before asserting block (flaky build-test on main after v0.4.10)

Deploy

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

Full changelog

v0.4.10 — Multiple superadmins, OIDC floor-guard, emergency CLI

Choose a tag to compare

@github-actions github-actions released this 02 Jul 18:24
7edca72

[0.4.10] - 2026-07-02

Changed

  • Remove instance-superadmin ceiling: multiple active superadmin@instance assignments are allowed; OIDC group→superadmin grants are no longer capped at one. The dropped index was redundant with the Serializable user.count() guard in POST /setup; first-run bootstrap protection is unchanged.
  • Consolidate rate-limit factories into declarative policy registry (no behavior change)
  • Centralize MFA enroll rate-limit constants in RATE_POLICIES; add registry edge-case and wiring tests
  • Extract inline-only rate limits to INLINE_RATE_LIMITS (excluded from RatePolicyName; compile-time guard against rateLimit() misuse)
  • Remove unused SSE message variant and stale nginx metrics location; fix dangling ADR links in deploy docs
  • Docs: superadmin runbook for multiple instance admins and OIDC offboarding prerequisites (SECURITY-CONTROLS.md, deploy/README.md)

Security

  • Fix event-settings GET authz-order oracle (404→403 for cross-org probing of non-existent events); add defense-in-depth assertEventManageAccess to handlePatchEvent handler body (route wrapper already enforced scope); reduce QR image cache TTL from 24h to 5min
  • Emergency CLI attendee export: enforce mode 0600 on overwrite; writable emergency-exports bind mount; reject --out under public UPLOAD_DIR or outside EMERGENCY_EXPORT_DIR when those env vars are set; reject EMERGENCY_EXPORT_DIR when it is a public alias under UPLOAD_DIR raw or realpath (including symlinked upload roots); require raw --out under raw EMERGENCY_EXPORT_DIR (not only canonical realpath); resolve symlinks before path checks; write via validated canonical path with O_NOFOLLOW (loop until full buffer is written)

Fixed

  • POST /setup maps Serializable transaction conflicts (P2034) to 409 already_initialized when two first-run submissions race with different emails
  • bootstrap-superadmin --force recovery path works after removing the single-superadmin partial unique index
  • OIDC group-sync cannot revoke the last active instance superadmin (floor-guard with audit event auth.oidc.superadmin_revoke_blocked; Serializable transaction on active instance-superadmin revoke; inactive owners skip the floor check; retries P2034 serialization losers so concurrent OIDC logins do not fail)

Added

  • CLI: admitto emergency ops binary (apps/cli) — checkin admit/lookup, attendees export, mail retry-failed, auth bootstrap-superadmin/reset-mfa, sessions revoke/purge, retention run
  • Automated retention cron (auth sessions, mail delivery snapshots) and nightly pg_dump backup sidecar in deploy compose

Deploy

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

Full changelog

v0.4.9 — SSE live check-in and multi-template mail

Choose a tag to compare

@github-actions github-actions released this 02 Jul 09:38
29ce939

[0.4.9] - 2026-07-02

Added

  • Admin: Instance URL setting (Settings → General) for email logo absolute URLs when BASE_URL env is unset
  • Backend: live check-in SSE at GET /api/checkin/events/:eventId/stream (operator/admin canPerformCheckIn auth)
  • Backend: multiple email templates per event (name/label, CRUD under /api/admin/events/:eventId/templates)
  • Backend: bulk send POST /api/admin/events/:eventId/send with templateId, recipient filters, and dryRun recipient count
  • Backend: send batch status GET /api/admin/events/:eventId/send/status/:batchId
  • Backend: per-template test send POST /api/admin/events/:eventId/templates/:templateId/test-send
  • EmailDelivery.template_id foreign key to MailTemplate (delivery audit per template)
  • Admin SPA: useEventStream hook for live check-in SSE with reconnect and auth-error heuristic
  • Admin SPA: Check-in page live feed (prepend history, dedup, offline banner)
  • Admin SPA: Event overview optimistic admitted_count from SSE
  • Admin SPA: Communication page multi-template editor, bulk send dialog with dry-run and batch polling

Changed

  • Docs: align contributor roadmap in AGENTS.md, README.md, and VERSIONING.md (v0.5 ingest API → v0.6 Wallet → v0.7 RSVP); AGENTS.md points at CHANGELOG.md and the open GitHub milestone instead of a hardcoded active milestone

Fixed

  • Admin: corrupt uploaded logo files clear the upload value in LogoUploadZone and show an error; external HTTPS URLs keep the value for manual correction
  • Admin: attendee resend and bulk resend use DB instance URL for ticket links when BASE_URL env is unset
  • Instance URL validation rejects bare ? or # delimiters (prevents malformed ticket and QR link paths)
  • Admin SPA: event overview reuses check-in TTL dedup map for SSE admits (no full clear on server refresh; TTL prune on poll keeps map bounded)
  • Admin SPA: communication page refetches inherited ticket template on each virtual-ticket selection (avoids stale legacy cache)
  • Admin SPA: communication page clears editor actions after delete when ticket fallback load fails (avoids targeting deleted template; re-select or create reloads editor)
  • Admin: revoke and restore pass from attendees list
  • Email templates (reminder and custom) can be deleted even after deliveries were sent (delivery log keeps rows; template reference cleared)

Deploy

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

Full changelog

v0.4.8 — capacity enforcement, branding upload, and admin UX

Choose a tag to compare

@github-actions github-actions released this 01 Jul 08:39
50503da

[0.4.8] - 2026-07-01

Fixed

  • Event capacity: pass restore (status: registered) respects capacity limits; manual create and import share an advisory lock to prevent concurrent over-capacity writes
  • PATCH reactivation from cancelled or revoked to registered enforces capacity the same way as manual create
  • Event overview attendee_count excludes revoked attendees (aligned with capacity enforcement)
  • Event overview admitted_count uses the same active scope as attendee_count (excludes revoked/cancelled)
  • Event capacity counts exclude cancelled as well as revoked passes
  • CSV import capacity override (?force=1) records forced: true in attendees_imported audit metadata
  • Import overwrite-only commits allowed when toCreate === 0 even if event is already over capacity
  • Event overview email card surfaces email_bounced separately from failed deliveries
  • Branding upload validates magic bytes and uses async filesystem I/O

Added

  • Admin: logo upload zone in setup wizard (server upload or external HTTPS URL)
  • Admin: bounce alert on Communication page with link to delivery log
  • Backend: saveEventUpload helper for event-scoped branding paths (/uploads/{orgId}/events/{eventId}/…; no HTTP endpoint yet)
  • Branding save accepts validated /uploads/… logo paths in addition to HTTPS URLs
  • Email template render absolutizes /uploads/… branding assets using BASE_URL (required for logo in outbound mail)
  • Admin: revoke and restore pass on attendee detail (PATCH status, capacity-aware restore)
  • Admin: CSV import shows event_full capacity banner; superadmin can override with force commit
  • Admin: TOTP enrollment QR code on Account page
  • Admin: device label pre-filled from browser user agent
  • Admin: sidebar pin/unpin (desktop), lifecycle nav labels (Passes, Post-event), Administration section
  • Event capacity enforcement on manual attendee create and CSV import commit: returns 409 event_full when the limit would be exceeded; instance superadmin may override with ?force=1 (audited)
  • PATCH /api/admin/events/:eventId/attendees/:id supports status: registered | revoked with pass_revoked / pass_restored attendee action log entries
  • Local branding upload API: POST /api/admin/uploads (PNG/JPG/WebP, max 2 MB, superadmin-only) and GET /uploads/* static serve; Docker Compose volume for ./uploads
  • Event overview: separate email_bounced count distinct from email_failed (failed + rejected only)
  • Attendee status revoked in database (migration) — revoked passes are not admittable at check-in

Deploy

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

Full changelog

v0.4.7 — overview, locale, requirements runtime, and export hardening

Choose a tag to compare

@github-actions github-actions released this 30 Jun 13:41
a1c706e

[0.4.7] - 2026-06-30

Security

  • Rate-limit admin export endpoints: PII export 5/h, attendees/reports export 10/h per user per route (global across events)

Added

  • Bulk Send tickets on the attendees list: POST .../attendees/bulk-resend with target unsent (default, purpose: initial with atomic claim) or all (resend, max 500 per request); rate limit 3 requests per 10 minutes per admin; response reports provider-accepted (queued), skipped, and failed counts; confirmation modal in admin SPA; audit via mail_bulk_resend in attendee action log
  • CSV import preview: first 20 valid rows returned as sampleRows with attributeFieldLabels; admin Import page shows a scrollable "Data preview" table before commit (dynamic optional columns + event custom attributes)
  • Requirements v2 (admin): Tabler icon picker on event items (EventItem.icon); contents metadata (type, required, options); ops-config flags allow_manual_lookup and auto_advance_on_valid (defaults true)
  • Check-in runtime: enforces allow_manual_lookup (403 on lookup API; UI hides manual lookup and blocks short-query lookup); auto_advance_on_valid clears scan state after VALID admission; GET /api/checkin/ops-config; item icons on AttendeeCard (Tabler)
  • Contents metadata runtime: enforce type/select/boolean/required/options on attendee create and patch; type-aware admin fields; required markers and formatted values on check-in item detail
  • CSV import: dynamic event-item attribute columns (source_field slugs) validated and stored in custom_data; template includes configured fields; export-style label headers accepted on re-import
  • Per-user preferred locale (User.preferred_locale) with date-format picker on Account page; admin SPA date displays respect the stored locale via module-level locale store
  • Per-event IANA timezone on events — create/settings/wizard picker, reports/exports/mail preview use event timezone
  • Event overview dashboard at /admin/events/:id/overview with admission rate, email delivery stats, event countdown, and dedicated GET /api/admin/events/:eventId/overview endpoint

Fixed

  • Event settings PATCH: audit_failed 500 uses { error } shape
  • Communication template editor: cursor restored after inserting placeholders into subject/body fields
  • Event overview: auto-refresh stats every 30s during event
  • CSV import: ignore source_field slugs that collide with standard import columns (email, company, etc.); event item contents API and admin form reject those reserved slugs on save
  • CSV import: validate merged custom_data at commit (including overwrite with existing attributes); return 400 when event attribute config has conflicting select options
  • Settings tabs preserve in-progress panel state (drafts, filters) when switching tabs without eager-loading every panel on first visit

Changed

  • Settings: replace mixed SPA/SSR horizontal tabs with four grouped in-app tabs (General, Security, Archiving, Identity); OIDC and Cloudflare Access remain server-rendered manage links
  • Admin timestamp display clarity: event operational times use event timezone with abbreviation; admin/system times (audit log, mail deliveries, sessions, archived_at) always show UTC with label
  • client-ip / healthz rate-limit helpers import resolveTrustProxy from lightweight env-flags module (avoids flaky CI load of @admitto/auth barrel → @admitto/tickets → Prisma singleton)
  • Shared @admitto/shared locale whitelist (SUPPORTED_LOCALE_TAGS) used by API validation and Account picker; invalid DB values sanitized on read
  • Audit log date filters use UTC calendar-day bounds (aligned with UTC table display)
  • Existing events migrated to UTC — update timezone in Event Settings after deploy
  • Known limitation: Account page TOTP enrollment still shows an otpauth:// URI string (HTML /mfa/enroll shows QR; SPA QR deferred to v0.5)

Deploy

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

Full changelog

v0.4.6 — audit remediation, privacy retention, release polish

Choose a tag to compare

@solarssk solarssk released this 27 Jun 22:42
v0.4.6
be06717

[0.4.6] - 2026-06-27

Changed

  • Admin and operator check-in: desktop camera renders inline in the main panel (stats sidebar stays visible); mobile keeps fullscreen camera overlay; operator mobile autostart with "Use camera" button to reopen after close; desktop inline shows lookup errors in the scan bar area and renders full AttendeeCard (items, notes, undo) below the camera preview
  • Attendee note modal includes a one-line reminder not to record medical, dietary, or other sensitive personal data
  • Staff SPA Content-Security-Policy allows HTTPS branding logo URLs in setup wizard and settings preview (aligned with existing font-src https: for theme fonts)
  • Privacy docs: retention tables distinguish product-automated cleanup (sessions, email snapshots) from operator-controlled data (IP logs, attendee lists); OIDC IdP group membership documented; attendee erasure documented as API-only (no SPA delete button)
  • SECURITY.md: Trivy HIGH remediation SLA (30 days when fix available); blocking HIGH gate deferred to v1.0
  • deploy/README.md: container startup documents auth-state and email snapshot retention purge steps
  • README: MFA first-login flow notes backup-code acknowledgment is persisted in the database

Added

  • Account self-service at /account for all signed-in staff: profile, password change (re-auth), TOTP enrollment/reset, and session management (/api/account/*)
  • Nullable User.password_hash for OIDC-only accounts (additive migration; existing rows unchanged)
  • IAM Users & roles page at /admin/users: staff user table (search, filters, pagination), invite-user modal, edit-user modal with role management, reset MFA/password/sessions, and role-assignments tab
  • GET/POST/PATCH /api/admin/users and role grant/revoke, reset-2fa, reset-password, revoke-sessions endpoints with anti-lockout guards and audit logging (superadmin; org admin may grant/revoke operator@event only)
  • GET /api/admin/role-assignments for non-instance role grants
  • must_change_password on User with server-rendered /change-password flow after login or admin password reset
  • First-run setup: server-rendered /setup for empty-database superadmin bootstrap; 5-step React onboarding wizard (system checks, mail, org branding, first event, completion) gated by setup_complete in SystemSettings
  • Settings audit log viewer: superadmin-only paginated table of AdminAuditLog entries with action-type and date filters (GET /api/admin/audit-log)
  • Event reports page at /admin/events/:id/reports: admission stats, hourly CSS chart, ticket-type breakdown, paginated admission log, CSV export, and printable HTML/PDF export via GET /api/admin/events/:eventId/reports and /reports/export; exports write reports_exported to the event audit log
  • Admin attendee erasure now has a GDPR-ready DELETE /api/admin/events/:eventId/attendees/:id path that removes dependent delivery, wallet, and check-in rows in one transaction while preserving an event-scoped audit entry (PRIV-001, PRIV-004)

Fixed

  • Check-in camera result panel shows the actual admission timestamp on repeat scans instead of a hardcoded "Entered earlier today" subtitle
  • Attendee note modal privacy hint is linked to the textarea via aria-describedby for screen readers
  • Ops health/readiness rate limiters import audit logging via @admitto/auth/audit so unit tests do not load the full auth barrel (flaky PrismaClient init in CI)
  • Account page (/account): profile Save disabled when unchanged; live password-confirm mismatch feedback; Spinner loading states; SPA Link navigation to account from staff/operator shells
  • @admitto/auth runInTransaction no longer value-imports PrismaClient, avoiding flaky healthz-rate-limit unit tests when the auth barrel loads before prisma generate
  • First-run POST /setup bootstrap race: Serializable transaction re-checks empty user table so only one superadmin can be created
  • IAM anti-lockout guards (last_superadmin on role revoke and superadmin deactivation) run atomically in Serializable transactions; idempotent role DELETE returns 204 instead of 500 when the assignment is already gone; 404 when the assignment exists under a different user id in the URL
  • Setup wizard no longer bypassed on /operatorsetup_complete included on /api/auth/me for instance superadmins
  • Event slug helper truncates before trimming trailing dashes; wizard step 4 disables Continue when slug is empty and uses max slug length 80 (aligned with API and CreateEventModal)
  • DB partial unique index enforces at most one instance-scoped superadmin RoleAssignment
  • Admin mutations that write AdminAuditLog now persist audit rows in the same database transaction as the primary change (users IAM routes, mail settings PUT); audit failure rolls back the mutation instead of leaving inconsistent state (BE-001, BE-002)
  • Concurrent attendee import commits for the same event are serialized with a PostgreSQL advisory lock so duplicate bulk audit rows cannot be written (BE-004)
  • Agency public_ref backfill processes attendees in bounded batches instead of loading all rows at once (BE-005)
  • User deactivation revokes sessions after the Serializable user-update transaction commits, avoiding serialization conflicts with concurrent last_seen_at updates (Bugbot)
  • Import commit transaction timeout raised to 120s so a second commit can wait on the per-event advisory lock without aborting mid-queue (Bugbot)
  • Attendee.rsvp_status now has a database CHECK constraint matching the application enum, preventing invalid RSVP states from raw SQL or future scripts (DATA-002)
  • OidcRoleGrant now uses partial unique indexes for scoped and instance grants, correctly enforcing uniqueness when scope_id is NULL (DATA-004)
  • Attendee, check-in, and event item state status columns now have database CHECK constraints matching persisted application values (DATA-003)
  • Destructive migration scanning now flags DELETE FROM DML in new Prisma migrations unless explicitly approved (DATA-006)
  • Staff account creation audit metadata and import CLI skipped-row output now redact email addresses, and privacy/DSAR docs now document attendee-note special-category risk plus manual erasure FK ordering (PRIV-001, PRIV-002, PRIV-003, PRIV-004, PRIV-006)
  • Container startup now wraps the agency public_ref backfill in a 120-second timeout, and PR-Agent comments are limited to collaborators/members/owners to prevent public API-credit drain (INFRA-002, INFRA-003)
  • Container startup now attempts a best-effort purge of expired/revoked Session and TrustedDevice rows after migrations/backfills with a 120-second timeout; operators can also run npm run cli -w @admitto/auth -- purge-auth-retention --dry-run to preview counts, reducing stale auth-state retention (DATA-001, PRIV-005)
  • Container startup now nullifies stale EmailDelivery.rendered_html / rendered_subject snapshots on terminal deliveries older than 60 days (configurable via EMAIL_DELIVERY_SNAPSHOT_RETENTION_DAYS); operators can preview counts with npm run cli -w @admitto/mail-delivery -- nullify-delivery-snapshots --dry-run (DATA-005, PRIV-001)

Security

  • Forced password change is now enforced as a dedicated change_password_required session stage: a user whose password was reset by an admin cannot reach any protected route (API or UI) until they set a new password — the previous next: change_password hint was a UI directive only and could be ignored by any HTTP client (IAM-001)
  • Backup recovery codes must be acknowledged before a full session is granted, even after a fresh login or when the completion request lands on a different process: acknowledgment is now persisted on UserMfaMethod (backup_codes_acknowledged_at) instead of an in-memory stash, closing a bypass where a returning user could skip saving recovery codes (IAM-002)
  • Forced password-change form now enforces the same 12-character minimum as all other self-set passwords (was 8) (IAM-003)
  • Granting superadmin@instance to a second user now returns HTTP 409 single_superadmin_limit instead of an unhandled 500 (IAM-004)
  • Known limitation: OIDC group→role mappings are fully reconciled at OIDC login only; deployments using OIDC-managed admin/superadmin roles should set admin session TTL to 8h or less and follow the documented offboarding runbook (accepted risk, IAM-005)
  • Attendee CSV export now uses the shared CSV formula-injection sanitizer (covers newline-prefixed formulas and whitespace-padded =) across attendee, event-settings PII, and reports exports (SEC-001)
  • Printable HTML/PDF event report export now sends a restrictive Content-Security-Policy header (SEC-003)
  • EmailDelivery.error sanitization now redacts URLs (e.g. Power Automate webhooks) before persistence (BE-006)
  • REVOKED check-in audit rows are written inside a transaction for consistency with other check-in paths (BE-003)
  • Known limitation: automated post-event attendee PII purge deferred to v1.0 — use Attendees export + per-attendee DELETE API (no SPA delete button yet)
  • Known limitation: OIDC group→role reconciliation runs at OIDC login only — shorten admin session TTL and follow offboarding guidance in SECURITY-CONTROLS.md
  • Known limitation: Account page TOTP enrollment shows an otpauth:// URI string; HTML /mfa/enroll shows a QR code — SPA QR deferred to v0.4.7
  • Known limitation: ticket token may appear in server access logs when operators open ticket URLs — accepted risk; control via reverse-proxy log retention

Deploy

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

Full changelog

v0.4.5 — settings, sessions, and event admin

Choose a tag to compare

@solarssk solarssk released this 24 Jun 16:27
v0.4.5
9ed5ed9

[0.4.5] - 2026-06-24

Added

  • MFA enrollment split into three steps: TOTP QR/setup-key confirm → dedicated backup-codes page → full session; new backup_codes_required session stage and DB migration
  • POST /mfa/enroll/backup-codes route and handleTotpBackupCodesComplete API endpoint; session promoted to FULL only after backup-codes acknowledgment
  • 6-digit centered OTP input field with auto-focus, paste support, keyboard navigation, and backup-recovery-code toggle
  • GET / smart redirect: authenticated users land on /admin (or /operator), unauthenticated users on /login
  • Admitto favicon set: SVG, 32 × 32 PNG, Apple touch icon, and ICO fallback served from /favicon.*
  • SSO button label per identity provider (login_button_label); default falls back to "Continue with SSO"
  • Admin sidebar: checkmark brand mark (correct admitto-mark.svg), All events + Settings items always pinned to footer
  • Settings sub-navigation: horizontal tab bar (General · Identity providers · Cloudflare Access) rendered on both SPA and SSR settings pages
  • Identity-provider and Cloudflare Access admin pages rendered with full sidebar shell matching the SPA layout (sticky layout, design-system CSS tokens)
  • Event card: date icon, location pin icon, attendee count stat with user icon; hover lift effect
  • Event overview landing page at /admin/events/:id/overview with quick stats and navigation links to live admin sections
  • Shared admin shell (StaffShell): sidebar with independent scroll, slim topbar, optional settings subnav, and mobile drawer navigation
  • Staff topbar: mailer status indicator (configured dot + provider label), role badge (SA/AD/OP), icon-only sign out
  • Sidebar footer: Users & roles link for org admins and superadmins (page ships in a follow-up PR)
  • Dev-only demo bar (import.meta.env.DEV) to trigger sample toasts from the admin shell
  • Admin check-in v2: split layout with scan bar, connection banner, stats/progress sidebar, color-dot recent scans, and fullscreen camera overlay with QR viewfinder
  • Create event UI: POST /api/admin/events, New event modal (title, slug, date, location), redirect to attendees after create
  • Events picker visual polish: active status badge, responsive 2/3-column grid, empty state with Create event CTA
  • Attendees v2: rsvp_status migration, wider list table (STATUS/MAIL/CHECK-IN/actions), manual POST /attendees, full attendee detail page with activity log, Add attendee modal
  • Event settings page at /admin/events/:id/settings: edit title, date, location, capacity; superadmin PII CSV export; archive/unarchive from danger zone; capacity field on Event

Changed

  • GET /api/admin/me includes mailer_status (provider presence only — no credentials); /api/auth/me unchanged for operator sessions
  • Toast stack position: top-right below the staff topbar (--topbar-h) instead of bottom-right
  • Admin check-in: keyboard wedge inputMode="none", auto-submit for long tokens, Esc to clear result state
  • README: local dev onboarding (Run locally), Node engines alignment, infra/ vs deploy/ distinction; new apps/admin/README.md
  • Admin Vite dev proxy: forward /mfa to @admitto/web so MFA enrollment works on :5173
  • Login page <title> fixed to "Admitto"; added application-name, og:site_name, and description meta tags for password-manager naming
  • MFA page heading changed from <h1> to <p class="auth-page-action"> to preserve correct document semantics; Admitto brand uses <h1>
  • CSRF fix: Nginx forwards $http_host (with port) in Host and X-Forwarded-Host headers so 127.0.0.1:8080 logins no longer return 403
  • Superadmin instance settings at /admin/settings with branding panel — live theme preview, anti-lockout guards, and links to OIDC / Cloudflare Access admin pages (#96)
  • Settings → Mail transport panel: configure provider, masked secrets, env-locked fields, and test send (#99)
  • Settings → Sessions and Security panels: list/revoke staff sessions, bulk operator revoke, session TTL and MFA policy; GET/PATCH /api/admin/system-settings (#112)
  • Event archiving: Event.archived_at hides completed events from default lists; superadmin archive/unarchive; archived events read-only on admin mutating APIs; Active/Archived tabs; check-in stays available (ADR 0022) (#116)
  • Admin shell layout: single main scroll region (subnav + page content), 100dvh viewport, Overview in live event sidebar segments; events picker opens archived tab when no active events remain; active event cards are fully clickable
  • MFA enrollment and verify: step progress indicator (Step X of 3), no OTP autofocus on the QR step, auto-submit after six digits, and submit loading state on auth forms
  • Settings horizontal subnav uses consistent styling across SPA and SSR; Identity providers and Cloudflare Access open via full-page navigation to SSR admin pages
  • Admin sidebar chrome trimmed: redundant context labels and duplicate Instance settings header action removed
  • @admitto/ui design system: Spinner, EmptyState, Skeleton, and ToastProvider / useToast (#120)
  • Admin app root wrapped with ToastProvider; recoverable ErrorBoundary on render errors (#97, #120)
  • Admin UX micro-fixes: import column reference table and CSV template download; delivery log purpose filter; compose dirty-state guard; attendee drawer discard confirmation; check-in stats admitted/total (#121)
  • POST /mfa/enroll/download-codes — backup codes as .txt during enrollment (#117)
  • Check-in: NoteModal replaces window.prompt for attendee notes (#114)
  • Runtime upgraded to Node 24 LTS; React 18 → 19 across admin and web (#111, #110)
  • Login, MFA, and superadmin identity-provider HTML pages aligned with Admitto design tokens (#117)
  • OIDC group mapping role picker uses a select; provider list supports inline enable/disable (#117)
  • Cloudflare Access admin form shows status badge, fall-through explanation, and enable warning (#117)
  • Requirements and Communication panels use ConfirmDialog instead of native window.confirm (#97)

Fixed

  • Guest ticket page now prints correctly (white background, no wallet buttons, no shadows)
  • Export: sanitize dynamic attribute column headers against formula injection (#97)
  • Local dev: login/MFA CSRF when Origin/Referer absent (Safari); admin SPA dist path after npm run build -w @admitto/admin (#115)
  • Admin page shell document title uses event name prefix without regressing the visible h1 (#118)
  • SSO failure on /login shows a dedicated fallback banner; removed placeholder “SSO coming soon” when no IdP is configured (#117)
  • Check-in card coloured left border per scan status (#114)
  • Login page title and heading: “Sign in to Admitto” (#114)
  • Sidebar Overview section shows “Soon” until built (v1.0) (#114)
  • Settings subnav active tab uses path prefix matching instead of exact pathname equality
  • Sidebar “Soon” badges render with correct styling (.nav-item--soon, .nav-item__badge)
  • Global link hover underline no longer appears on sidebar brand, navigation items, or button-styled links (SPA and SSR settings shell)
  • OIDC admin form: URL fields use type="url"; group→role mapping rows can be added/removed; scope type select; SSO button live preview on provider form; failed save re-renders submitted mapping drafts
  • Cloudflare Access settings: clearer operator copy and field hints; test action labeled “Test connection”
  • Events picker: search by title/location; content width capped at 1100px; grid capped at three columns on wide screens
  • Check-in camera: removed fullscreen toggle (browser instability); debounce repeated ZXing decodes; extract ticket token from QR URLs with trailing slash or query (packages/tickets)
  • Check-in invalid/revoked scans show dedicated feedback card instead of silent failure
  • Requirements: @admitto/ui Switch missing thumb restored; item table uses name + auto-generated key; drawer layout cleanup
  • Event picker cards: removed hover lift/underline noise
  • Vercel Git deploys disabled via root vercel.json (self-hosted Docker only)

Security

  • Branding font_family_name allowlist on save and ticket-page render (blocks CSS/HTML injection via custom fonts) (#96)
  • deploy/validate-env.sh pre-flight for deploy/.env; production boot fails fast when REDIS_URL is missing, unauthenticated, or ENCRYPTION_KEY is invalid
  • PENtest hardening: structured audit events for rate limits, MFA, OIDC login, logout, admin 403, and settings changes — ISO ts on each event (#123)
  • Production BASE_URL must use https:// (except localhost / 127.0.0.1 smoke) (#123)
  • OIDC ID token verification restricts JWT algorithms to RS/ES/PS family (no none) (#123)
  • Deploy Redis requires REDIS_PASSWORD; compose wires authenticated REDIS_URL (#123)
  • Nginx proxy baseline security headers (HSTS, nosniff, frame deny); Docker bridge gateway for RealIP behind NPM (#123)
  • /healthz and /readyz responses include baseline security headers (#123)
  • GitHub Releases for v0.x.y are pre-release until v1.0.0; publish-container sets the flag automatically (#123)
  • PENtest follow-up: rate limits on MFA enroll, /healthz, admin import/template preview, and OIDC provider discover/test; OIDC outbound fetch resolves DNS before connect; malformed X-Forwarded-For falls back to socket IP (no shared unknown bucket)
  • docs/SECURITY-CONTROLS.md: rate-limit matrix, TRUST_PROXY trust model, SSRF/DNS-rebind guards, PEN retest checklist for operators

Deploy

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

Full changelog

v0.4.4 — undici security and container workflow fix

Choose a tag to compare

@solarssk solarssk released this 19 Jun 20:30
v0.4.4
0c8a912

[0.4.4] - 2026-06-19

Security

Fixed

  • Container publish workflow: workflow_dispatch on branch refs runs scan-only (Trivy SARIF, CRITICAL gate) without SBOM path or Docker metadata failures (#93)
  • GHCR push, provenance attestation, and release SBOM restricted to refs/tags/v*.*.* semver refs; semver-shaped branch names can no longer trigger a publish

Deploy

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

Full changelog

v0.4.3 — admin export, dynamic attributes, corp readiness

Choose a tag to compare

@solarssk solarssk released this 19 Jun 13:59
v0.4.3
95c95f7

[0.4.3] - 2026-06-19

Added

  • Attendee list export to CSV, XLSX, and PDF with check-off column and formula-injection sanitization (#88)
  • Dynamic custom attributes in admin drawer: edit fields driven by EventItem.config.contents instead of hardcoded shirt_size (ADR 0030, #91)
  • Export columns follow EventItem.config.contents definitions; check-in parity preserved
  • SECURITY-CONTROLS.md: configurable security capabilities table with TOTP and OIDC implementation detail
  • CORPORATE-DEPLOYMENT.md: self-hosted model, customer-hosted stack, no SaaS
  • ARCHITECTURE-FOR-AUDITORS.md: scope, generic exposure overview, roadmap flows
  • GDPR-ONE-PAGER.md, SUBPROCESSORS.md: purposes, retention, subprocessor template, DSAR options
  • DSAR-PROCEDURE.md: organizer-mediated access and erasure template (Option B)
  • INCIDENT-RESPONSE.md: rotation, rollback, severity template; GDPR Art. 33/34 72-hour breach notification
  • Zod validation on custom_data_fields keys; stable export column order (orderBy: key)
  • Duplicate Excel headers disambiguated as Label (source_field); PDF column widths scale down when many attributes exceed printable width
  • Export integration tests use isolated events

Changed

  • DATA-PROTECTION.md updated with legal basis note (LIA for legitimate interest)

Fixed

  • Drawer degrades gracefully when event-items API fails

Security

  • Removed || true from Semgrep CI step — SAST findings now block pull requests (baseline verified at 0 findings before merge)

Deploy

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

Full changelog