Skip to main content

Customer Portal — Data Model

Status: populated Owner: Product Engineering (Frontend) Last updated: 2026-04-18

1. No Database

The customer-portal is a stateless frontend service. It owns no database schema, no PostgreSQL tables, and no Redis keys. All persistent data is owned by upstream backend services consumed via Kong.

The portal uses Next.js iron-session (or equivalent encrypted cookie library) to store session data server-side without a session store.

  • Type: Encrypted, serialized JSON
  • Flags: HttpOnly; Secure; SameSite=Strict; Path=/
  • TTL: Matches JWT exp (1 hour); renewed on refresh
  • Contents:
{
"userId": "usr_01H...",
"accountId": "acc_01H...",
"email": "customer@example.com",
"roles": ["customer"],
"accessToken": "<platform-jwt>",
"expiresAt": "2026-04-18T15:00:00Z"
}
  • Type: Encrypted, serialized JSON
  • Flags: HttpOnly; Secure; SameSite=Strict; Path=/api/auth/refresh
  • TTL: 7 days
  • Contents:
{
"refreshToken": "<opaque-refresh-token>"
}

The Path restriction ensures the refresh token cookie is only sent to the refresh endpoint, not all API routes.

3. Session Storage

KeyTypeContentCleared
cust_msg_filtersJSON stringLast-used message filter state { from, to, status, startDate, endDate }On tab close

4. In-Memory React State

No persistent client state. All server data is fetched fresh on navigation. Ephemeral component state:

ComponentStateDescription
ApiKeyCreateModalrawKey: string | nullRaw key string held only during "key created" display; nulled on close
WebhookCreateModalsigningSecret: string | nullSigning secret held only during "webhook created" display
TestSmsFormformStatereact-hook-form field values
MessageFiltersURL search paramsSerialised to URL; not in component state

5. No localStorage for Sensitive Data

The portal explicitly avoids writing tokens or credentials to localStorage or sessionStorage. Firebase Auth's IndexedDB-based persistence is set to browserSessionPersistence to prevent cross-tab token leakage.

6. Cache Headers

Next.js server component responses:

  • /dashboard: Cache-Control: private, no-store — always fresh
  • /messages: Cache-Control: private, no-store
  • Static assets (/_next/static): Cache-Control: public, max-age=31536000, immutable