Licensing: Alpha/Beta Tier Access
Vidar the Patient — Architect
By the end of this chapter you’ll understand:
- How a license key travels from email to the OS keychain without ever being stored in plaintext on the server
- Which components enforce the gate in the UI and which commands enforce it in the Rust backend
- How the board grants and revokes alpha access without a user-facing admin panel
Licensing: Alpha/Beta Tier Access
Status: Delivered
CAS: CAS-1100
Delivered: 2026-04-29
PRs: #318, #319, #321
What’s new
Casaconomy now has a built-in licensing system that gates premium features by tier.
You can grant alpha access to a user by email, and their copy of the app activates
against a license key that lives in their OS keychain — no account, no password.
Premium features (provider sync, advanced rules, OCR, multi-device vault) are
hidden behind a PremiumGate component that shows an upgrade prompt to unlicensed
users and renders normally for activated ones.
How to use it
For the board (granting alpha access):
- A user sends their email to request alpha access via the in-app form or directly.
- The Worker issues a license key in the format
CASA-XXXX-XXXX-XXXX-XXXXand emails it to them via Resend. - The user opens Settings → License, types the key and a device name, and clicks Activate. The app contacts the licensing Worker, records the activation, and stores the token in their OS keychain.
- Premium features unlock immediately. The status badge on the Settings page shows “Active (Alpha)”.
For the board (revoking a license):
curl -X POST https://licenses.casaconomy.com/v1/licenses/revoke \ -H "Authorization: Bearer $ADMIN_API_KEY" \ -d '{"license_key": "CASA-..."}'This marks the license revoked, removes all device activations, and sends the user a revocation email.
First-run experience:
Unactivated users see a dismissible banner at the top of the app pointing them to
Settings → License. Attempting to use a gated feature from an unactivated device
shows an upgrade modal with a “Get Casaconomy Plus” CTA (currently links to
https://casaconomy.com/upgrade — placeholder until Phase 5 checkout is wired).
What changed under the hood
- Cloudflare Worker (
workers/licensing/) — TypeScript HTTP API on Cloudflare Workers with four endpoints: access-request, activate, validate, revoke. License keys are SHA-256 hashed before storage; the raw key is only ever sent once by email. - D1 schema (
workers/licensing/migrations/) — five tables:tiers,customers,licenses,activations,audit. Alpha tier seeded with unlimited devices and all features enabled. - Rust license service (
src-tauri/src/services/license_service.rs) — manages local state, calls the Worker on activate/validate/deactivate, derivesLicenseStatefrom the cached token, runs a 24-hour background revalidation loop. Tokens stored in OS keychain on release builds; in-memory on debug builds to avoid macOS “Always Allow” prompts during development. - Tauri commands (
src-tauri/src/commands/license_commands.rs) —get_license_state,is_premium_enabled,activate_license,deactivate_device,get_device_info. - Frontend —
LicenseActivationBanner,LicenseSettingsPage,PremiumGate,UpgradePromptModal,useLicenseStatehook.
Why we built it
The app was approaching a state where it could be shared with a small circle of alpha users, but had no way to distinguish them from anyone who downloaded it. Rather than bolt on auth later, we built a lightweight keychain-based licensing layer now — self-contained, no accounts required, easy to revoke — that can be extended with Stripe payments in Phase 5 without redesigning the activation UX.
Known limitations / follow-on work
- No payment processing yet. Alpha and beta tiers are free. Phase 5 will add Stripe (or Lemon Squeezy) checkout and map subscriptions to license issuance.
- Trial tier is reserved but not wired. The schema has a
trialtier; theLicenseState::Trialvariant exists in Rust. No flow issues a trial license today. - macOS keychain on Sequoia. Background-launched app builds may hit the WKWebView throttling issue tracked in CAS-698 — unrelated to licensing but affects the smoke walk that validates the activation flow end-to-end.
- Single
upgradeCTA link. All upgrade CTAs point to a placeholder URL. This will be replaced when the Phase 5 checkout page is live. - Admin API is a raw curl workflow. No UI for the board to issue or revoke licenses. A lightweight admin panel is a natural Phase 5 follow-on.
Recap
- A
CASA-XXXXlicense key is SHA-256-hashed before storage; the raw key is sent once by email and then the server never holds it in recoverable form. - The
PremiumGatecomponent and theis_premium_enabledTauri command are the two enforcement points — frontend and backend each check independently. - The Rust license service runs a 24-hour background revalidation loop; tokens are stored in the OS keychain on release builds to avoid macOS prompts during development.
What changed {#what-changed}
This feature shipped in CAS-1100. See: CHANGELOG → 2026-05-18