Architecture memo · internal

How every Waypoint service actually ties together

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.


The whole picture

GUEST’S DEVICE phone / laptop, guest SSID STAFF’S DEVICE laptop / phone, 802.1X SSID GUEST WIFI SSID controller: UniFi · Aruba · Ruckus · Omada · pfSense · MikroTik · FortiGate STAFF 802.1X SSID same controller, or a dedicated enterprise AP for the staff network WAYPOINT CORE Railway — Next.js + Postgres • Branded captive portal (per property) • Guest-capture API → vendor authorize • Staff WiFi captive-portal method → sidecar • WireGuard keypair gen + Test VPN button • PMS booking lookup (guest recognition) • Admin dashboard, billing, audit log • Org / property / team management never stores staff/legacy-guest credentials, or a WireGuard private key SAME EC2 BOX — SELF-HOSTED, NOT RAILWAY RADIATOR legacy guest — IFC8 • AuthBy FIDELIO   per property commercial license (lapsing 2026-09-10 — no pilot, not renewing) no open-source module covers this protocol — kept deliberately. FREERADIUS staff 802.1X — zero-license • One LDAP instance   per property PEAP outer, GTC inner (not MSCHAPv2) pool{start=0}: one AD outage can't crash every property's staff auth SIDECAR Staff WiFi + tunnel status wired in — live 5th portal sign-in method • POST /check — LDAP   bind-check (ldapts) • POST /wireguard-status —   shells `wg show`, backs   the dashboard’s Test VPN • called directly by   Waypoint Core, not via   FreeRADIUS/Radiator HOTEL’S ON-PREM OPERA legacy PMS, IFC8 interface link (not Opera Cloud) HOTEL’S ON-PREM AD one domain controller per property MODERN PMS APIS Mews · Cloudbeds · Opera Cloud (via OHIP, REST) GUEST VENDOR CLOUDS UniFi · Aruba ClearPass · Ruckus · Omada · Meraki STRIPE billing HTTP — branded captive portal RADIUS Access-Request (EAP-PEAP) alt: direct RADIUS request (bypasses Waypoint’s portal) recommended — proxy credential check WireGuard + IFC8 WireGuard tunnel booking lookup authorize guest billing
Solid lines are today's or the recommended paths; the dashed line is a real alternative worth weighing, not a default. The teal boxes and arrow mark real decisions this diagram reflects: proxy legacy-PMS guest auth through Waypoint, run staff 802.1X on FreeRADIUS rather than Radiator, and the Sidecar (now solid-bordered, no longer dashed) is wired into a real, live call from Waypoint Core over HTTPS — Staff WiFi credential checks and VPN status checks — reached directly rather than through FreeRADIUS or Radiator, even though all three share the same box. WireGuard tunnels remain the one piece of infrastructure all three self-hosted services share, though the property-side keypair for those tunnels is now generated by Waypoint Core itself rather than requested from the hotel.

Three separate auth mechanisms, not onethe core insight

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:

01

Standard guest sign-in

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.

owner of truth: Waypoint
02

Legacy-PMS guest sign-in

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.

owner of truth: the hotel's Opera PMS
03

Staff 802.1X sign-in

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.

owner of truth: the hotel's AD

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.


Weaning staff auth off Radiatordecided, sketch verified

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.


A fallback path for devices that can't do 802.1Xlive

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).


WireGuard config generation is now self-service toolive

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.


The one open design questionneeds a decision

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 sits outside all of thisseparate track

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 typeGuest auth pathStaff auth pathPMS integration style
Standard (any vendor)Waypoint portal → vendor authorize802.1X → FreeRADIUS → hotel AD (optional)none, or modern REST (Mews/Cloudbeds/Opera Cloud)
Legacy on-prem OperaWaypoint portal → Radiator → IFC8 (recommended)802.1X → FreeRADIUS → hotel AD (optional)Radiator’s AuthBy FIDELIO, not lib/pms.ts
Opera CloudWaypoint portal → vendor authorize802.1X → FreeRADIUS → hotel AD (optional)OHIP REST, via lib/pms.ts

What this changes about scalingrevisiting last week's take

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.