Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

Organization Authorization

FRAME uses Better Auth organization access control for the permission vocabulary and NestJS policies for API enforcement.

flowchart LR
    A[Better Auth session] --> B[Active organization]
    B --> C[OrganizationPermissionGuard]
    C --> D[OrganizationPolicyService]
    D --> E[Standard or dynamic role]
    E --> F[Controller and use case]
    F --> G[Organization-scoped repository query]
    G --> H[(PostgreSQL constraints)]

OrganizationPermissionGuard reads the permission declared by @RequireOrganizationPermission. OrganizationPolicyService verifies that the session user is a member of the active organization, checks the standard roles from @frame/auth/permissions, and then evaluates any dynamic organization roles stored by Better Auth.

Protected organization endpoints use both authorization primitives:

  • @RequireOrganizationPermission(ORGANIZATION_PERMISSIONS.<resource>.<action>) declares the required permission.
  • @ActiveOrganizationId() injects the active organization identifier from the authenticated session and rejects requests without one.

Controllers pass the organization identifier through the use case and repository port. They do not accept a tenant identifier from a request body as the authority for cats, owners, or similar product records.

Controller authorization and query scoping solve different problems. Repositories must include organizationId in every tenant-owned operation:

  • inserts assign the active organization identifier;
  • lists filter by the organization identifier;
  • reads, updates, and deletes filter by both record and organization identifiers;
  • relationships use tenant-safe database constraints where one tenant-owned record references another.

This prevents a valid member from reading or mutating another organization’s record by guessing its identifier.

An upload intent that mutates an organization-owned asset must perform the corresponding organization policy check before generating a key or presigned URL. Organization logos require organization:update, require an organization identifier, and store the organization identifier in the object-key prefix.

  1. Reuse a permission from ORGANIZATION_PERMISSIONS; add new vocabulary in packages/auth first when required.
  2. Apply @RequireOrganizationPermission to the controller method.
  3. Read the active organization with @ActiveOrganizationId().
  4. Carry organizationId through the use case and repository port.
  5. Add organization predicates to every repository query and tenant-safe constraints to related tables.
  6. Test permission behavior and verify that repository calls receive the organization identifier.

See ADR 0003 for the role model and architectural rationale.