Authorization and Roles Contract

Status: Canonical (Draft) Last Updated: 2026-02-06 Owner: Engineering


Purpose

This document defines the authorization (AuthZ) model and role system for CAIRL.

It answers one question only:

Given an authenticated user, what actions are they allowed to perform?

This document is authoritative and defines system-wide invariants. All features, specs, flows, and implementations MUST comply.


Scope

This contract applies to:

  • All application features
  • All Next.js server code (Route Handlers, Server Actions)
  • All Postgres tables protected by Supabase Row Level Security (RLS)
  • All admin and moderation functionality

No feature may override this contract.


Definitions

  • AuthN (Authentication): Verifying who the user is. Handled by NextAuth.
  • AuthZ (Authorization): Determining what the user can do. Defined here.
  • Role: A coarse-grained classification that affects authorization.
  • RLS: Row Level Security enforced at the Postgres layer via Supabase.
  • User-owned data: Data belonging to a specific user.
  • System-owned data: Data controlled by the platform, not an individual user.

Authentication Source of Truth

  • Authentication is handled by NextAuth.
  • A valid NextAuth session is required for all authenticated actions.
  • The application user identifier is a UUID stored in Postgres.

NextAuth is responsible only for identity. NextAuth does NOT determine authorization.


Authorization Enforcement Model

Authorization is enforced using a defense-in-depth approach:

  1. Postgres RLS (Primary Authority)

    • RLS policies MUST restrict access to user-owned rows.
    • RLS is the final enforcement layer and MUST NOT be bypassed.
  2. Server-Side Authorization Checks

    • Next.js Route Handlers and Server Actions MUST perform role checks.
    • These checks supplement RLS and provide clearer error handling.
  3. Client-Side Gating (Non-authoritative)

    • UI may hide or disable actions.
    • UI gating MUST NOT be relied upon for security.

Role Model

Defined Roles

The system currently defines the following roles:

  • user

    • Default role for all authenticated users
    • May only access their own data
    • Subject to subscription and feature gating
  • admin

    • Elevated role for internal operators
    • May access system-owned data
    • May perform moderation and enforcement actions

No other roles are currently supported.


Role Storage (Current State)

Roles are currently represented in the application database with known issues:

  • Role information exists on the user record.
  • There is a known duplicate / inconsistent admin role representation.
  • This inconsistency is acknowledged and permitted temporarily.

This document intentionally describes behavior, not ideal structure.


Role Resolution Rules

When determining a user’s role:

  1. The system MUST resolve the effective role server-side.
  2. If multiple role indicators exist:
    • admin takes precedence over user
  3. Absence of an admin indicator implies user role.

If role resolution is ambiguous, the system MUST default to the least-privileged role.


Admin Authorization Invariants

The following actions are ADMIN-ONLY and MUST be enforced server-side:

  • Viewing or modifying system allowlists
  • Approving or denying user requests requiring moderation
  • Accessing data across multiple users
  • Viewing enforcement, abuse, or suppression data
  • Performing irreversible system actions

UI gating alone is insufficient.


User Authorization Invariants

All users (including admins acting as users):

  • May only access user-owned rows via RLS
  • May not bypass quotas, limits, or safety controls
  • May not impersonate another user without explicit tooling

Admin privileges do not bypass safety invariants unless explicitly stated.


Supabase Row Level Security Requirements

The following rules are mandatory:

  • All tables containing user-owned data MUST have RLS enabled.
  • RLS policies MUST reference the authenticated user identifier.
  • No client-side Supabase key may bypass RLS.
  • Service role keys MUST be restricted to server-only contexts.

Any table without RLS MUST be explicitly documented and justified.


Server-Side Privileged Access

Use of elevated database access (e.g., service role keys) is permitted ONLY:

  • In server-side code
  • For clearly documented administrative operations
  • With explicit scope and justification

Privileged access MUST NOT be used for normal user flows.


Subscription and Feature Gating

Roles are orthogonal to subscription tiers.

  • Role determines who you are
  • Tier determines what features you can use

Admin role does NOT imply unlimited feature access unless specified.


Error Handling and Enforcement

Authorization failures MUST result in:

  • A clear server-side rejection
  • A consistent error code
  • No partial side effects

Authorization failures MUST be logged for auditing.


Migration and Cleanup Notes

The current role system is known to be imperfect.

Future improvements MAY include:

  • Normalizing roles into a separate table
  • Removing duplicate admin indicators
  • Introducing scoped admin permissions

Such changes MUST be introduced via:

  • A new or updated contract
  • A migration plan
  • Updated RLS policies

Non-Negotiable Rules

  • Authorization MUST be enforced server-side.
  • RLS MUST be the final enforcement layer.
  • Ambiguity defaults to least privilege.
  • UI checks are advisory only.
  • No feature may weaken these rules.

References

  • NextAuth documentation
  • Supabase Row Level Security documentation
  • docs/governance/doc-authority.new.md

End of Document