visitor sign-in · for the workshop's actual front door

visitas.world

A small, honest visitor sign-in app built for a VFX workshop — not for a 50-floor office tower, not for a security-theater corporate lobby, not for a SaaS that wants to license your reception desk.

Apache-2.0 · Node · SQLite · single-container deploy · API-first

View on GitHub →

What it is

visitas.world (the name is from the Spanish noun visitas — “visits”) is a self-hosted visitor sign-in app. An iPad parked at reception runs the kiosk, a visitor taps in (name, company, host, purpose), the host gets pinged, an AirPrint badge prints, and an active-visitors view tells you who's on-site if the fire alarm goes off.

It runs as a single container next to your other infrastructure. There's no SaaS dependency, no “talk to sales”, no per-seat licensing. The data file lives on your disk; you can scp it.

Sister project to cambiar.world — same workshop, same stack, same philosophy.

Who it's for

What shipped (the v0 series → 1.0)

Shipped 2026-05-06 as 1.0.0: scaffold, kiosk sign-in, active visitors + wall view, security role, host notifications, multi-iPad with AirPrint badges, NDA + safety acknowledgments with drawn signature, returning-visitor pre-fill with 1-year NDA cache, pre-registration via QR, opt-in photo capture with 30-day retention, and Active Directory host lookup. Each card below was its own minor release on the v0 track.

v0.1 — scaffold

Express + better-sqlite3 + Vite + JWT, multi-stage Docker, GH Actions, bootstrap admin/admin, host CRUD, branding settings, configurable visitor-form schema (config/visitor-form.json).

Shipped.

v0.2 — kiosk sign-in

The iPad-facing sign-in flow: visitor types name / company / host / purpose, active-visitors admin page, public wall view at /active for fire drills, sign-out at the same kiosk. Full audit log on every transition. Adds a second security role for reception staff.

Shipped.

v0.3 — host notifications

Email via SMTP (nodemailer) and SMS via a small Twilio REST adapter. Per-channel events filter; secrets in env, transport config in config/notifications.json. Settings page test buttons for both channels.

Shipped.

v0.4 — multi-iPad + AirPrint badges

Each entrance gets its own kiosk URL (/kiosk/<slug>) with a default-printer-name hint (MDM enforces the actual AirPrint default per iPad). After sign-in the kiosk auto-pops a printable AirPrint badge. Wall view filters per kiosk for multiple muster points.

Shipped.

v0.5 — NDA & safety acknowledgments

Admin-editable, versioned NDA and safety briefing. Visitor must scroll to the bottom before acknowledging; NDA needs a drawn signature on the iPad (canvas → PNG, stored against the visit + version). A signed-NDA copy is emailed to the visitor.

Shipped.

v0.6 — visitor records & NDA cache

Visitors are first-class: keyed by email, returning visitors get their name / company / phone pre-filled from a previous visit. If the same visitor acknowledged the current NDA version in the last 365 days, the kiosk skips the NDA step on this visit.

Shipped.

v0.7 — pre-registration via QR

Hosts pre-book expected visitors; the visitor gets an email invite with a QR code. They scan the QR at the kiosk to claim the booking and skip the form fields they’re already on file for.

Shipped.

v0.8 — photo capture

The iPad’s front camera captures a visitor photo at sign-in. It prints on the badge and is retained for 30 days against the visit record, then auto-purged by a daily sweep. Off by default — privacy is opt-in.

Shipped.

v0.9 — AD hosts

Optional ldapts lookup for hosts from the visitas-world AD group. Same env-driven on/off pattern as cambiar; if auth.ad.enabled = true and AD_BIND_PASSWORD is set the host list pulls from AD, otherwise local-only.

Shipped.

Branding

Admin-uploadable logo (PNG / SVG / JPEG / WebP) and app name. Renders on the topbar, the login screen, the kiosk welcome card, and the printed badge. Light and dark themes, persisted per browser.

API-first

Every UI action is a documented HTTP endpoint. Build your own integrations, scripts, or in-house tools against the same surface the kiosk uses — no internal-only RPCs.

Since 1.0

Three minor releases of operational + safety hardening on top of the 1.0 stable line. Each is a single feature, a single minor bump, a single CHANGELOG entry. No batch.

v1.1 — sign-in security

Token-keyed public endpoints (badge + photo URLs are unguessable 64-hex tokens, not sequential ids), PNG magic-byte validation on signature + photo upload, login rate limiting. From the v1.0 senior code review.

Shipped.

v1.2 — visitor bans

Admins and security users can ban visitors by record, by email, or by name + company substring. Banned attempts get a generic kiosk refusal, an audit row, and an email + SMS to all on-duty admin / security so reception intercepts at the door.

Shipped.

v1.3 — operational hardening

Notifications failure log so missing host emails are debuggable, configurable photo retention (1–365 days, default 30), admins-only mode for the /active wall view for sites with NDA-sensitive client work, and a single npm run preflight that runs everything CI runs.

Shipped.

v1.4 — security pass

Session cookie tightened to SameSite=Strict; AD configuration is validated at startup so an enabled-but- unauthenticated LDAP can’t silently fall back to an anonymous bind. Confirms the PNG magic-byte check covers signatures as well as photos. The rest of the v1.0 senior review, closed.

Shipped.

v1.5 — GDPR purge + backup docs

Per-visitor right-to-be-forgotten endpoint (DELETE /api/visitors/:id) that scrubs PII from every visit, invitation, and notification log entry while preserving the workshop’s audit shape. Plus a real backup + restore section in the README (what’s in data/, hot-backup with sqlite3 .backup, nightly cron, restore procedure).

Shipped.

What it isn't

Honesty is part of the design. visitas.world is not:

Trying it

git clone https://github.com/djsincla/visitas.git && cd visitas
cp .env.example .env
# set JWT_SECRET to something long and random
docker compose up -d --build
# open http://localhost:3000
# log in: admin / admin  (forced password change on first login)
# kiosk surface lives at http://localhost:3000/kiosk

SQLite database persists in ./data/. Configuration in ./config/ (mounted read-only). Edit config/visitor-form.json before first deploy to customize what the kiosk asks visitors.

Issues & support

Bug reports, feature requests, and questions all go to GitHub Issues. There's no separate ticket queue, no support email, no “contact our team” form — every conversation lives in the open where the workshop and any contributors can see it.

Open or browse issues →

The shape of the project