Licensing: Alpha/Beta Tier Access
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.