Organization Authorization
FRAME uses Better Auth organization access control for the permission vocabulary and NestJS policies for API enforcement.
Enforcement flow
Section titled “Enforcement flow”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.
Controller contract
Section titled “Controller contract”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.
Persistence boundary
Section titled “Persistence boundary”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.
Upload intents
Section titled “Upload intents”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.
Adding a protected endpoint
Section titled “Adding a protected endpoint”- Reuse a permission from
ORGANIZATION_PERMISSIONS; add new vocabulary inpackages/authfirst when required. - Apply
@RequireOrganizationPermissionto the controller method. - Read the active organization with
@ActiveOrganizationId(). - Carry
organizationIdthrough the use case and repository port. - Add organization predicates to every repository query and tenant-safe constraints to related tables.
- Test permission behavior and verify that repository calls receive the organization identifier.
See ADR 0003 for the role model and architectural rationale.