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
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.
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.
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| Table | Operation | Policy |
|---|---|---|
| All domain tables | SELECT / INSERT / UPDATE / DELETE | is_org_member(org_id) |
| organizations | SELECT | deleted_at IS NULL AND is_org_member(id) |
| organizations | UPDATE | is_org_admin(id) |
| audit_logs | SELECT (admin) | is_org_admin(org_id) |
| audit_logs | INSERT | is_org_member(org_id) |
| feature_flags | INSERT / UPDATE | is_org_admin(org_id) |
| users | SELECT | auth.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