Skip to main content
CYPEX Documentation
Support
v2.0.0 Latest stable release View changelog ->

Conceptual overview

3 min read

This page describes the multi-tenant Organizations model introduced in CYPEX v2.0.0 and why PostgreSQL Row-Level Security (RLS) is part of it.

The single-tenant model (v1.x)

Before v2.0.0, a CYPEX instance was effectively single-tenant:

  • One database, one application schema, one set of users.
  • Multi-tenancy was emulated at the application layer by filtering rows on a client identifier.
  • PostgreSQL row-level security was not enabled on tenant-scoped tables.

This worked, but every enforcement decision lived in application code. A single missing WHERE clause, an ad-hoc reporting query, or a hand-written maintenance script could leak rows across tenants.

The multi-tenant model (v2.0.0)

From v2.0.0 on, a CYPEX instance is multi-tenant by construction:

  • An Organization is the unit of tenancy. Some tables carry an organization_id column directly; the rest derive their scope by joining to a table that does.
  • A user is mapped to one or more Organizations through cypex.t_role_organization.
  • Every tenant-scoped table has Row-Level Security enabled, and the policies resolve the active Organization from the caller’s JWT claims.
  • CYPEX passes the access token to PostgREST, which exposes its claims on the request.jwt.claims GUC. Helper functions such as cypex.current_organization_id() read that GUC inside the policies, so the database itself enforces visibility.

The simplest mental model:

Tip
One Organization == one tenant == one organization_id value, resolved from the caller’s JWT claims and enforced by PostgreSQL RLS.

Why RLS

RLS gives CYPEX defense in depth:

  • The application still filters on organization_id (defense layer 1).
  • PostgreSQL re-checks the policy predicate against the organization in request.jwt.claims for every row read or written (defense layer 2).
  • Even direct psql sessions and ad-hoc reports inherit the same restrictions, unless the role is privileged (see Roles and permissions).

For background on PostgreSQL Row Security Policies, see the official PostgreSQL documentation.

High-level upgrade flow

The upgrade from v1.9.3 to v2.0.0 follows this sequence:

  1. Backup — full logical backup (pg_dump) plus, ideally, a filesystem snapshot.
  2. Run migrations — apply the v2.0.0 migration scripts in order: Organizations schema, roles and helper functions, RLS policies, and the default password policy migration.
  3. Verify — run the verification queries listed in RLS impact on existing data.
  4. Cut traffic — restart CYPEX so the new role and JWT claims are picked up, then remove the maintenance banner.

The exact SQL each migration applies is documented in the additive changes and breaking changes pages.

Key concepts at a glance

ConceptWhere it livesPurpose
Organizationcypex.t_organizationTenant identifier
Module ↔ Organizationcypex.t_module_organizationMaps modules to their owning organization
Role ↔ Organizationcypex.t_role_organizationMaps roles to organizations they can access
Permission audit logcypex_log.t_permission_audit_logRecords permission decisions
Helper functionscypex.is_organization_admin(), etc.Read JWT context inside SQL
Claims GUCrequest.jwt.claimsCarries org_id and organization_ids into the session

Prerequisite reading