How can we help?

Sharing

Share dashboards publicly with optional password protection

Dashboards can be shared publicly via unique token-based URLs. Shared dashboards support optional password protection, rate limiting, and read-only viewing with live query execution.

Public share URL format: /share/:token

Sharing Flow

Enabling Sharing

  1. Click the visibility dropdown in the dashboard toolbar (Private/Public toggle)
  2. A unique token is generated and stored
  3. The share URL becomes active immediately

Disabling Sharing

  1. Select "Private" from the visibility dropdown
  2. The share link is deleted
  3. Access is immediately revoked. Existing public URLs stop working.

Share Status

The shareToken and password status are tracked per-tab. The visibility dropdown in the toolbar reflects the current state (lock icon for private, globe icon for public).

Password Protection

Setting a Password

  • When enabling sharing, an optional password can be provided
  • Passwords are hashed with bcryptjs using 10 salt rounds
  • A null hash means no password is required

Updating or Removing a Password

  • You can update or remove the password on an existing share at any time
  • Pass null to remove password protection

Password Verification Flow (Public Access)

  1. Public user visits /share/:token
  2. If password-protected and no password provided: a password gate UI is shown
  3. User submits password
  4. On mismatch: "Incorrect password" error (HTTP 401)
  5. On success: dashboard data is returned and rendered

The password is also required for query execution. The frontend stores it in a ref and sends it with each request.

Rate Limiting

Two rate limiters protect public sharing endpoints. Both use an in-memory store with automatic cleanup every 60 seconds.

Content Fetch (sharing.getPublic)

  • Limit: 30 requests per 60 seconds
  • Key: Client IP (via x-forwarded-for or x-real-ip header)
  • Returns HTTP 429 with Retry-After header when exceeded

Query Execution (sharing.executePublicQueries)

  • Limit: 10 requests per 60 seconds
  • Key: {IP}:{token} (per-IP, per-dashboard)
  • Falls back to IP-only if token extraction fails
  • Returns HTTP 429 with Retry-After header when exceeded

When rate limited, the frontend shows a dismissible banner: "Too many requests. Please wait a moment and try again."

Public Share Page

States

StateUI
LoadingCentered spinner
ErrorError message with DB Pro branding
Password requiredPassword form
Dashboard loadedFull-screen dashboard in read-only mode
Query errorDismissible banner above the dashboard

Load Sequence

  1. Extract token from URL params
  2. Fetch dashboard data using the token
  3. Handle response:
    • Token not found: "This shared link is no longer available"
    • Password required: show password gate
    • Dashboard data returned: deserialize and render
  4. Automatically execute widget queries to fetch data
  5. Dashboard renders in read-only mode

Read-Only Mode

Shared dashboards render with readOnly enabled:

  • Drag-and-drop is disabled
  • Resize handles are hidden
  • Context menus are hidden (no Edit/Duplicate/Remove)
  • Manual refresh via the toolbar button is allowed (subject to rate limiting)
  • Auto-refresh intervals are supported

Branding

A "Powered by DB Pro" badge is fixed to the bottom-right corner with a link to dbpro.app.

API Endpoints

Protected (Authenticated)

EndpointInputOutput
sharing.enable{ entityType, entityId, password? }{ token }
sharing.disable{ entityType, entityId }{ success: true }
sharing.updatePassword{ entityType, entityId, password | null }{ success: true }
sharing.getStatus{ entityType, entityId }{ token, hasPassword } | null

Public (Unauthenticated, Rate Limited)

EndpointInputOutput
sharing.getPublic{ token, password? }{ entityType, name, data } | { requiresPassword } | null
sharing.executePublicQueries{ token, password? }Record<number, { rows, fields }>

Query Execution for Public Shares

When a public user views a shared dashboard:

  1. The server fetches the dashboard and its associated connection
  2. Connection credentials are decrypted (AES-256-GCM)
  3. Widget queries are extracted from the serialized dashboard data
  4. Queries execute in parallel via Promise.allSettled
  5. Results are mapped back to widget indices and returned

Public users can only execute the pre-configured widget queries. They cannot run arbitrary SQL.

Database Schema

shared_links Table

ColumnTypeDescription
idTEXT (PK)CUID2 auto-generated
tokenTEXT (unique)Public share token (CUID2)
entity_typeTEXT"dashboard" or "saved_query"
entity_idTEXTReference to the shared entity
password_hashTEXT (nullable)bcrypt hash, null = no password
created_byTEXT (FK)References users.id
created_atTEXTISO 8601 timestamp

Error Messages

ScenarioBackend CodeUser-Facing Message
Token not foundNOT_FOUND"This shared link is no longer available"
Wrong passwordUNAUTHORIZED"Incorrect password"
No password provided (required)UNAUTHORIZED"Password required"
Rate limit exceeded429"Too many requests. Please wait a moment and try again."
Unsupported databaseBAD_REQUEST"Database type is not supported"
Dashboard not foundNOT_FOUND"Dashboard not found"
Connection not foundNOT_FOUND"Connection not found"
Generic failure--"Failed to load shared content"

Security

  • Token generation: CUID2 (cryptographically secure random IDs)
  • Password hashing: bcryptjs with 10 salt rounds
  • Connection encryption: AES-256-GCM for stored credentials
  • Rate limiting: Dual-layer (IP-based for content fetch, IP+token for query execution)
  • Query scope: Public users can only execute pre-configured widget queries, not arbitrary SQL
  • Immediate revocation: Disabling sharing deletes the token row, instantly invalidating all public access