Architecture memo · internal
A single map of guest sign-in, staff sign-in, PMS integrations, and the network infrastructure underneath — updated now that staff 802.1X is moving to FreeRADIUS (zero-license, verified live), leaving Radiator scoped to only the legacy Opera/IFC8 guest-auth role, and now that the staff-directory sidecar is actually wired into a live request flow (Staff WiFi, a fifth portal sign-in method) with a self-service WireGuard config generator behind it. Opera Cloud remains a third, unrelated modern path.
Before this round of changes it was easy to think of “network auth” as one thing. It's actually three, each with a different owner of truth, and only one of them touches Waypoint's own database:
Guest fills out Waypoint's branded portal. Waypoint's own guest-capture API records the sign-in and calls the property's vendor controller directly to grant access.
A guest at an on-prem-Opera hotel is authenticated against their real reservation via IFC8, at the RADIUS layer, through Radiator — not through Waypoint's own guest table.
A staff member's laptop authenticates against the hotel's own Active Directory over LDAP, reached by FreeRADIUS through a WireGuard tunnel. Waypoint never sees the credential, and neither does any commercial RADIUS vendor.
Mechanisms 2 and 3 run on two different services now (Radiator for legacy PMS, FreeRADIUS for staff), but they're colocated on the same self-hosted EC2 box because they solve the identical underlying problem: reaching into one specific hotel's private LAN from a box that isn't on that LAN. The WireGuard tunnel infrastructure isn't tied to either service specifically — it's general-purpose “let this box reach one property's private system” plumbing, shared by both.
Radiator is commercial, licensed software, and the staff-AD role never actually needed anything Radiator-specific — it's a textbook EAP-PEAP-against-LDAP setup, which FreeRADIUS (the open-source, zero-license standard RADIUS server) handles natively. Radiator earns its keep only for the legacy Opera/IFC8 bridge, where no open-source module exists for that niche protocol.
This wasn't just sketched — it was installed and run for real on the same EC2 box, catching a genuine bug in the process: FreeRADIUS's LDAP module eagerly opens its connection pool at server startup, and with one property's AD unreachable, the entire process crashed on boot — not a soft per-request failure. Left as-is, one hotel's AD having a bad night would have taken down staff WiFi at every property. Fixed with pool { start = 0 }, documented in FreeRADIUS's own bundled config comments, and reverified clean. Full detail in freeradius/staff-8021x-setup.md.
Still open: the actual EAP-PEAP/GTC handshake through a real AD account needs eapol_test, which isn't packaged for Ubuntu and has to be compiled from source — the same gap the Radiator side never closed either. That's pilot-property work, not a config question.
802.1X's real win is per-device WiFi encryption, but it needs a supplicant profile on every device — Windows staff laptops in particular don't do EAP-GTC out of the box (see the FreeRADIUS section above). The complement is a captive-portal fallback: same shape as guest sign-in, staff submit their AD username/password to a branded form, and something does a live LDAP bind check before authorizing the network grant.
That "something" is the staff directory sidecar — a small service colocated with FreeRADIUS/Radiator on the EC2 box, since Waypoint's own backend runs on Railway and has no path into any property's WireGuard-tunneled AD. Built and verified for real against a local test directory (correct/wrong password, unknown user, unknown property, bad shared secret all behave correctly), using ldapts rather than the more commonly-referenced ldapjs — the latter was decommissioned by its own maintainer in 2024, whose parting recommendation was to not use Node.js for LDAP at all rather than pointing at a successor package.
The captive-portal UI decision this was waiting on has since been made and shipped: Staff WiFi, a fifth sign-in method on a property's portal (Property.staffPortalEnabled, off by default). A username/password submitted there goes to a real Waypoint API route, which calls the sidecar's /check endpoint over HTTPS with a shared secret, then drives the exact same vendor network-authorize dispatch guest sign-in already uses on success — no second, parallel "how do we talk to the controller" code path. A successful staff sign-in never creates a Guest row.
Verified end to end in the browser against a property with the method turned on: the portal chip renders, the form switches to username/password with no consent checkboxes, and — with no sidecar actually deployed anywhere — submitting returns a clean "Staff WiFi isn't configured for this deployment" rejection rather than a crash. Still not done: anything against a real property's AD, or a real deployed sidecar reachable over the internet (the reverse proxy that would TLS-terminate it for Railway to reach is still just a documented step, not a running thing).
The sidecar's own tunnel — and legacy Opera/IFC8 auth, if a property ever needs that instead — still needs a real per-property WireGuard peer, and that used to mean asking a hotel's IT team to run wg genkey themselves and email back a public key. The Connections tab's new "WireGuard tunnel" card removes that step: it generates the property's entire keypair server-side (Node's built-in X25519 support, the identical RFC 7748 algorithm and clamping wg genkey itself uses — not a custom scheme) and hands back a ready-to-import .conf file, shown and downloadable exactly once. The private key is never written to Waypoint's database; only the derived public key and the ops-assigned tunnel address persist.
A Test VPN button checks whether the resulting tunnel is actually alive — proxied through the sidecar's new /wireguard-status endpoint (the same direct Waypoint Core → Sidecar call the Staff WiFi credential check uses), which shells wg show <iface> latest-handshakes on the EC2 box and reports whether that property's peer has handshaked recently.
A deliberate, disclosed tradeoff: generating the private key server-side means Waypoint briefly holds it in memory during that one request — standard practice for a hosted site-to-site VPN, and a real reduction in onboarding friction, but a property that specifically wants the key to never leave their hands can still use the fully manual path (documented in radiator/wireguard-setup.md) instead. Either way, actually adding the resulting public key as a live peer on the EC2 box's wg0.conf stays a manual ops step — nothing in Waypoint's app can reach that box to do it automatically.
For legacy-PMS guest sign-in, there are two real shapes, and they trade off differently:
Recommended — proxy through Waypoint
The guest still lands on Waypoint's branded captive portal and enters room number + surname, same as the existing Room sign-in method. Instead of checking that against Waypoint's own PmsBooking table, the guest-capture API forwards the check to Radiator, which validates it live against Opera over IFC8, then authorizes the vendor controller exactly like any other sign-in. Branding, consent capture, and the audit log all stay intact — the only thing that changes is where the "is this real" answer comes from.
Alternative — bypass Waypoint entirely
The hotel's WiFi controller talks straight RADIUS to Radiator (this is literally how the old Symmetry-IT / Fidelio pfSense config worked). Simpler to stand up per property since there's one fewer hop, but Waypoint loses branding, consent capture, and guest analytics for every sign-in at that property — the dashboard would show these properties as almost entirely inactive, which will look like a bug to anyone glancing at usage metrics.
The proxy approach costs one new internal API between Waypoint and Radiator; the bypass costs a permanent blind spot in every legacy property's guest data. Worth deciding before the first legacy pilot property, since the two shapes aren't a small refactor apart from each other.
Opera Cloud doesn't expose IFC8 at all — it's Oracle's SaaS product, integrated through OHIP (Oracle Hospitality Integration Platform), a REST/OAuth2 API. It has nothing to do with Radiator, WireGuard, or RADIUS. It slots into the existing lib/pms.ts dispatcher exactly the way Mews and Cloudbeds already do — a real, hardened OHIP client is the actual gap today (the current OPERA provider is disclosed as best-effort/unverified), not new architecture.
| Property type | Guest auth path | Staff auth path | PMS integration style |
|---|---|---|---|
| Standard (any vendor) | Waypoint portal → vendor authorize | 802.1X → FreeRADIUS → hotel AD (optional) | none, or modern REST (Mews/Cloudbeds/Opera Cloud) |
| Legacy on-prem Opera | Waypoint portal → Radiator → IFC8 (recommended) | 802.1X → FreeRADIUS → hotel AD (optional) | Radiator’s AuthBy FIDELIO, not lib/pms.ts |
| Opera Cloud | Waypoint portal → vendor authorize | 802.1X → FreeRADIUS → hotel AD (optional) | OHIP REST, via lib/pms.ts |
Splitting staff auth onto FreeRADIUS actually narrows Radiator's blast radius, not just its licensing footprint: Radiator now carries only the legacy-Opera guest path, so an outage there affects real hotel guests at legacy properties — still worth a second instance once the first legacy pilot is live, for exactly the reason discussed last time. FreeRADIUS carrying staff-only traffic is lower-stakes on its own (light load, staff can fall back to a hotspot briefly), except for the failure mode just found: without pool { start = 0 }, one property's AD blip could crash staff auth everywhere at once, which is a correctness fix, not a scaling one — already applied. Both services still live on one physical box today; that box itself becoming a single point of failure for two independent auth mechanisms is the next thing worth revisiting once either carries real property volume.