Architecture

Security

OpsDash is built with a three-layer security architecture: Row-Level Security at the database, server-side auth and role checks at the application layer, and audit logging for every mutation.

Three-layer model

1

Database — Supabase RLS

Every query is filtered by org_id at the PostgreSQL level. Even if application code had a bug, the database would not return another org's data.

2

Application — Server Actions

Every server action calls withOrg() first. It verifies authentication, resolves the org by slug, and checks org membership. If any check fails, the action returns an error immediately.

3

Role checks

Sensitive actions additionally check the user's org role. requireNonViewer() blocks viewer mutations. withOrgAdmin() blocks non-admin roles. requireFeatureAccess() checks feature flag + role allowlist.

Row-Level Security

All data tables have RLS policies that enforce org_id scoping.

-- Used in all RLS policies
is_org_member(org_id uuid) → boolean
is_org_admin(org_id uuid) → boolean
is_same_org_member(user_id uuid) → boolean
TableOperationPolicy
All domain tablesSELECT / INSERT / UPDATE / DELETEis_org_member(org_id)
organizationsSELECTdeleted_at IS NULL AND is_org_member(id)
organizationsUPDATEis_org_admin(id)
audit_logsSELECT (admin)is_org_admin(org_id)
audit_logsINSERTis_org_member(org_id)
feature_flagsINSERT / UPDATEis_org_admin(org_id)
usersSELECTauth.uid() = id OR is_same_org_member(id)

Authentication

OpsDash uses Supabase Auth. Sessions are stored in HTTP-only cookies — no tokens in localStorage.

  • Email + password (always available)
  • Google OAuth (configurable)
  • GitHub OAuth (configurable)
  • TOTP 2FA (any authenticator app — Enterprise)

Audit logging

Every mutation calls createAuditLog() after a successful DB write. Audit logs are written to the audit_logs table and are visible to org admins in Settings → Admin → Audit Log.

await createAuditLog(supabase, org.id, user.id, 'contact.created', {
  contact_id: contact.id,
  name: contact.full_name,
});

Audit logs are an Enterprise feature (platform:audit-logs). On Free and Pro, logs are still written but not surfaced in the UI.

API key security

  • Keys are generated as opsd_<random 40 chars>
  • Only the SHA-256 hash is stored — the plain key is shown once at creation
  • Keys have configurable permissions (lead:create, lead:read, form:submit)
  • All API requests are logged per-key in api_key_usage
  • Rate limited to the org's plan API quota

Cron endpoint security

All cron endpoints require a Bearer token matching CRON_SECRET. This must be a random 32-byte hex string — never leave it unset in production.

# Generate a secure cron secret
openssl rand -hex 32