Skip to content

Browser storage minimization and server authority

This page implements the browser storage minimization program: keep only intentional browser storage for UI and non-authoritative tab helpers; move business, security, and audit state to server authority (prego-control-plane D1, Auth session or JWT, Frappe ERP).

Related: Repo responsibility matrix, How to add an API, Tenant and trust boundaries.


1. Goals and principles

  • Single source of truth: Tenant lifecycle, onboarding, billing, provisioning, and permissions must not rely on the browser as the final authority.
  • Browser storage roles (allowed): (1) pure presentation preferences, (2) short-lived opaque tokens or correlation IDs that the server issued or validates, (3) performance or offline cache that is not the business ledger.
  • Repo boundaries: Orchestration and CP D1 live in prego-control-plane; ERP domain in prego_saas; gateway in prego-zuplo; Next apps in Prego monorepo.
flowchart LR
  subgraph browser [Browser_allowed]
    UI[UI_prefs_theme_sidebar]
    Tab[Tab_ephemeral_opaque_token]
    Cache[Cache_PWA_optional]
  end
  subgraph server [Server_authority]
    CP[(CP_D1)]
    ERP[(Frappe)]
    Auth[Auth_session_JWT_KV]
  end
  UI --> browser
  Tab -->|HTTPS_API| server
  CP --> Auth
  ERP --> CP

2. Inventory — whitelist and blacklist by surface

2.1 Whitelist (intentional browser storage after the program)

CategoryExamplesNotes
Pure UITheme, sidebar open state, marketing banner dismissed, location prompt shownLow risk; optional sync to profile later.
Consent UXPREGO_CONSENT cookie, prego_cookie_consent_v2 local mirrorImmediate UI; optional server audit log in a separate phase.
Non-authoritative tab helpersOpaque continuation token string, OTP-step email (until server confirms)Server owns verification and row state.
PWAWorkbox Cache API, IndexedDB for cache expirationNot business data.

2.2 Blacklist (must not be sole authority for routing or security)

PatternRiskTarget authority
localStorage onboarding completion flags without server checkUser can tamperCP D1 onboarding step plus API guard.
sessionStorage tenant id, plan tier, provision job as only sourceSpoofing, desynctenants_master, subscriptions, provision_jobs in CP (or existing tables).
Long-lived Bearer in sessionStorage for operator toolsXSS exfiltrationShort-lived session or HttpOnly cookie pattern.
sessionStorage employee id or email as permissionClient forgeryJWT or server session claims only.

2.3 Per-app inventory (current keys — migration targets)

www (apps/www)

StorageKeys (representative)Target
localStorageprego_locale, prego_countryCookie plus Accept-Language; optional profile in CP when logged in.
localStorageprego_cookie_consent_v2Keep for UX; optional audit append-only log.
sessionStorageprego_manual_approval_ct, prego_manual_approval_trial_email, prego_manual_approval_draft_recovery_usedMinimize: continuation maps to CP continuation_secret; email and draft live in D1 manual_approval_requests.

admin-web (apps/admin-web)

StorageKeys (representative)Target
localStorageprego_tenant_id, prego_onboarding_company_done, prego_onboarding_launch_done, prego_signed_inCP D1 user or tenant onboarding state; dashboard guard from API.
sessionStorageCheckout and provision hints (SS_CHECKOUT_*, SS_PROVISION_*, company draft JSON)CP rows: onboarding draft, funnel trace with TTL, provision correlation.
sessionStorageprego_auth_pending_email, prego_onboarding_verified_emailShort-lived only; canonical email from /auth/me and CP user.

client-web (apps/client-web)

StorageKeys (representative)Target
CookiesPREGO_SESSION, PREGO_JWT, PREGO_DEVICE_ID, PREGO_EID, etc.Remain server-validated; reduce duplicate state in sessionStorage.
sessionStorageprego_auth_email, prego_auth_employee_id, prego_provision_trace_id, demo flagsPrefer JWT claims and headers; trace from server job or URL once.
localStoragemy-ai-*, last_checkin_data, localeProduction: server chat and ERP attendance; local only for dev mock or offline queue (documented).

prego-control-plane (admin SPA and legacy HTML)

StorageKeysTarget
sessionStoragecp_internal_keyReplace with short-lived operator session (see §8).

3. CP D1 — onboarding, draft, and provision correlation (draft model)

Owner: prego-control-plane.

3.1 Proposed entities (draft — align with existing tenants_master, provision_jobs, Stripe webhooks)

ConceptSuggested shapePurpose
Onboarding statePer user_id + tenant_id (or pending tenant): current_step enum (company, review, launch, complete), company_draft_json, updated_atReplace ad hoc localStorage and SS_ONBOARDING_COMPANY_DRAFT.
Checkout funnelRow or columns: stripe_checkout_session_id, plan_tier, region, trace_id, expires_atReplace SS_CHECKOUT_* as source of truth; client passes id or short token only.
Provision correlationExisting provision_jobs + trace_id column or joinAlign with prego_provision_trace_id and admin session hints.

3.2 Entity relationship (CP D1 — planning reference)

Server authority for onboarding and billing centers on prego-control-plane D1. The following diagram is a logical view for migrations and internal APIs; table names may map to existing or new migrations.

erDiagram
  User ||--o{ UserTenant : has
  Tenant ||--o{ UserTenant : has
  Tenant ||--o| OnboardingState : has
  Tenant ||--o{ ProvisionJob : has
  Tenant ||--o| Subscription : has
  User {
    string id PK
    string email
  }
  Tenant {
    string id PK
    string status
  }
  UserTenant {
    string user_id FK
    string tenant_id FK
    string role
  }
  OnboardingState {
    string tenant_id FK
    string user_id FK
    string current_step
    string company_draft_json
    string updated_at
  }
  ProvisionJob {
    string id PK
    string tenant_id FK
    string trace_id
    string status
  }
  Subscription {
    string id PK
    string tenant_id FK
    string stripe_customer_id
  }

3.3 Draft internal APIs (Bearer INTERNAL_API_KEY or authenticated admin user per existing patterns)

MethodRoute (illustrative)Body or queryNotes
GET/internal/onboarding-stateuser_id or sessionReturns step, draft pointer, dashboard_unlocked.
PATCH/internal/onboarding-statePartial draft, step advanceValidates transitions server-side.
POST/internal/funnel-tracetrace_id, TTL, metadataOptional; for funnel analytics instead of only client storage.

Ordering: Add or extend OpenAPI in prego-zuplo only if exposed through the gateway; pure internal CP routes follow CP README.md and How to add an API for anything browser-callable.

Security (planning): Operator and internal flows — JWT lifetimes, Bearer scope for /internal/*, and rotation for long-lived keys are documented in §8 and Tenant and trust boundaries.


4. admin-web — routing guard and API contract

Problem today: getOnboardingDashboardProgress() and related helpers read localStorage and sessionStorage to decide dashboard unlock and resume paths.

Target contract:

  1. Single read on shell load: GET onboarding state from CP (via Zuplo or admin BFF) using the signed-in session. Response includes current_step, dashboard_unlocked, tenant_id, provision_job_id (if any).
  2. Guards: AdminShellRouteGuard and useRequireDashboardUnlocked use only API response (plus loading and error states). Remove reliance on prego_onboarding_*_done and prego_tenant_id in localStorage for authorization decisions.
  3. Optimistic UI: Optional client cache with stale-while-revalidate; on mismatch, server wins.
  4. Files to align in code (future implementation): apps/admin-web/lib/onboarding-dashboard-progress.ts, apps/admin-web/lib/onboarding-wizard-provision.ts, apps/admin-web/app/page.tsx, layout guards.

5. www — manual approval continuation

Server truth: CP D1 manual_approval_requests already includes continuation_secret, draft_json, OTP hashes, and related columns (migrations 0027, 0028).

Target behavior:

  • Continuation: Browser holds at most the opaque token string required to call POST /api/trial/manual-approval/* (via Zuplo). Prefer passing token in URL query for email links; minimize redundant copies in sessionStorage.
  • Recovery email: Loaded from server draft, not a second source of truth in prego_manual_approval_trial_email except as a UX cache cleared on success.
  • Draft recovery flag: Replace with server-side idempotency or draft version checks where possible.

Files (future implementation): apps/www/lib/trial-manual-approval-session.ts, apps/www/components/trial/ManualApprovalVerifyForm.tsx, apps/www/components/trial/TrialEmailGate.tsx.


6. client-web — phased removal of sessionStorage auth helpers

Principle: JWT and server-set cookies are authoritative. sessionStorage email and employee id are compatibility helpers for passcode and cross-step flows.

Phases:

  1. Audit: List all readers of prego_auth_email, prego_auth_employee_id, AUTH_PENDING_EMAIL, AUTH_SLUG, etc., in lib/api-client.ts, components/signin-form.tsx, passcode routes.
  2. Prefer /auth/me: After any OTP or passcode success, refresh user state from API; use React state or context instead of durable sessionStorage where possible.
  3. Passcode routes: Ensure PREGO_EID and pending cookies are set by server responses; reduce duplicate sessionStorage reads.
  4. Demo mode: Gate with NEXT_PUBLIC_DEMO_MODE and server-side demo headers; avoid persisting PREGO_DEMO_MODE in sessionStorage for production decisions.
  5. Provision trace: Keep header forwarding only; correlate with CP job id server-side.

7. ERP — attendance and Assistant UI (Frappe vs local cache)

DataAuthorityBrowser role
Attendance check-in / outFrappe DocTypes (business ledger)Optional offline queue only; sync with conflict policy.
Assistant UI chat history and knowledge indexprego_ai and Frappe adapters per productProduction uses server stores; my-ai-* localStorage is mock or dev-only behind flags.
Recent searches (UI convenience)None requiredMay remain local if not used for billing or compliance.

Policy: Document in app README that production Assistant UI persistence is server-side; local mock keys are not supported for compliance-heavy tenants unless explicitly allowed.


8. Operator auth — replacing cp_internal_key in sessionStorage

Current: Operator pastes internal API key; stored in sessionStorage as cp_internal_key (React store and legacy HTML).

Risks: XSS, shoulder surfing, no rotation, long-lived secret in browsing context.

Target directions (choose in security review):

  1. Short-lived session token: CP issues a token after password or SSO; HttpOnly cookie or memory-only script with refresh.
  2. OAuth or Cloudflare Access in front of operator routes; CP validates identity.
  3. Separate operator API keys with rotation, scoped permissions, audit log of usage.

Minimum near-term: Document that keys must be rotated if exposed; prefer not storing raw key in sessionStorage for new operator UIs.


Trigger: Legal or product requires provable consent history.

Design sketch:

  • On consent save, POST anonymized payload to CP: schema_version, timestamp, choices_hash, optional user_id hash.
  • Append-only table or log sink; no PII in clear text unless required.

Dependency: Product and legal sign-off before implementation.


10. Implementation order (engineering)

  1. Freeze inventory (§2) and add lint or docs checks if desired.
  2. Implement CP D1 migrations and internal GET or PATCH for onboarding state (§3).
  3. Switch admin-web guards to API-first (§4); delete or demote local flags.
  4. Tighten www manual approval storage (§5).
  5. client-web auth storage phases (§6).
  6. ERP and Assistant UI policy in tenant docs (§7).
  7. Operator auth hardening (§8).
  8. Consent audit (§9) if approved.

11. Risks and mitigations

  • Latency: More API calls on load — use parallel fetch and short TTL cache with revalidation.
  • Migration: Existing users with old localStorage — first load overwrites with server state; one-time banner if needed.
  • Cross-repo: Any new public route: OpenAPI in prego-zuplo first per How to add an API.

12. Deliverables checklist

DeliverableLocation
Whitelist and blacklist inventory§2
CP entity and API draft§3 (includes ERD §3.2)
admin-web guard contract§4
www manual approval direction§5
client-web phased plan§6
ERP and Assistant UI boundary§7
Operator auth review§8
Consent audit optional phase§9

This document is the planning source of truth for browser storage minimization; implementation tickets should reference section numbers.

Help