# User Connections (Server Storage)

> Save personal ClickHouse hosts to the server so they sync across devices when signed in with Clerk.

| | |
|---|---|
| **Feature id** | (none — gated by env flags) |
| **Default access** | Authenticated Clerk users only |
| **Requires auth** | Yes (Clerk) |
| **Storage** | D1 (Cloudflare) or PostgreSQL |

## What it does

When enabled, users can choose **Save to server** in the Add Host dialog. Credentials are encrypted at rest with `CHM_CONNECTIONS_ENCRYPTION_KEY` and never returned to the client after save.

Browser-only storage remains available when server storage is disabled or the user prefers a local connection.

## Enable server storage

1. Set auth to Clerk (`CHM_AUTH_PROVIDER=clerk`, `VITE_AUTH_PROVIDER=clerk`).
2. Configure a database backend:
   - **Cloudflare Workers**: `CONVERSATIONS_D1` binding (shared D1 database; migration `0002_user_connections.sql`).
   - **Node / K8s**: `DATABASE_URL` or `POSTGRES_URL`.
3. Set feature flags:
   ```bash
   VITE_FEATURE_USER_CONNECTIONS_DB=true
   CHM_FEATURE_USER_CONNECTIONS_DB=true
   ```
4. Set the encryption secret (32-byte key, base64-encoded):
   ```bash
   wrangler secret put CHM_CONNECTIONS_ENCRYPTION_KEY
   ```
5. Redeploy the dashboard.

The Add Host dialog shows an enabled **Save to server** toggle when all requirements are met. Otherwise it displays a note with a link to this page.

## Security notes

- **Per-user isolation:** Each connection row is keyed by the signed-in Clerk `userId`. List, read, update, delete, and chart/table proxy routes all resolve auth first and query with `WHERE user_id = ?`. Guessing another user's `connectionId` returns 404 — never their credentials.
- **Not org-shared:** Connections belong to the individual Clerk account, not the whole organization. Two teammates each see only their own server-stored hosts.
- Passwords are encrypted with AES-256-GCM before writing to D1/Postgres.
- API routes return connection metadata only; passwords are never echoed after create.
- Browser-stored hosts use short-lived session tokens so passwords are not sent on every chart/table request.
- The client cache for server connections is scoped per Clerk user, cleared on sign-out / account switch, and excluded from persisted localStorage so another login on the same browser cannot flash a prior user's host list.
- Shared `?host=-1000` URLs only work for the same signed-in user with that connection saved.

## Related

- [Browser Connections](/features/browser-connections)
- [Authentication](/authentication)
- [Environment variables](/reference/environment-variables)
