How it's built
How I built it
A Cloudflare Worker that rewrites Cloudpath DPSK passphrases at enrollment—and the failure design that keeps it out of the path to network access.
The failure design
The core constraint was that this service must never block a user from connecting. RADIUS auth and DPSK verification happen inside Cloudpath and the network gear—the service is only a passphrase editor. I wanted word phrases to appear on the enrollment page, which Cloudpath serves, but that required a browser-side fetch. So failure is possible at two points: before the change, and on the display side.
The before-change case is clean. If the notification call fails for any reason—service down, Cloudpath unreachable, bad credentials, enrollment not found—the DPSK passphrase is never changed. The snippet polls for up to 15 seconds, seven attempts 2 seconds apart, then writes Cloudpath's own phrase. The user gets a random passphrase instead of a word one, but they connect.
A few other choices: I retry only on 409—phrase collision or a device joining mid-flight—never on auth errors or Cloudpath 5xx responses. Hammering a sick Cloudpath is worse than one missed word phrase. Before any PUT, I check deviceCount. Cloudpath enforces this too, but I wanted an explicit skip with no retry. And I never log the shared secret, passphrases, enrollment IDs, or DPSK GUIDs to console; the customer-visible activity log records the last two for the tenant's own debugging.
Credentials, tenancy, and Cloudpath calls
Each tenant gets a UUID tenant ID—public, embedded in the snippet—and a separate UUID shared secret, shown in the dashboard and regeneratable with one click. The secret authenticates the webhook. The tenant ID scopes DPSK lookups, paired with the exact old passphrase and a 5-minute TTL.
API credentials are encrypted with AES-256-GCM before storage. Each field—username and password separately—gets its own random 12-byte IV and a 16-byte salt. The key comes from HKDF-SHA256 over a Worker secret, with the tenant ID as info and the per-field salt, so two identical passwords don't produce identical ciphertext. Passphrase selection uses crypto.getRandomValues with modulo arithmetic; the modulo bias is negligible—a relative difference of about 3×10⁻⁷—and I didn't add rejection sampling.
Cloudpath JWTs are stateless: one minted per webhook call, never cached. The cloud-hosted platform caps token requests at 10 per minute per Cloudpath account, so that's the throughput ceiling at high enrollment volume—roughly 10 word-phrase changes per minute. Over that limit, enrollments fall back to Cloudpath's random phrase; they succeed, just without word format. The Cloudpath PUT requires the full resource object, not a patch, so I GET it, replace only the passphrase field, and PUT it back.