Skip to content

refactor(policy): decentralize subject resolution to modules #3381

@PierreBrisorgueil

Description

@PierreBrisorgueil

Problem

Every new module must modify lib/middlewares/policy.js to register its CASL subjects:

  • Add entries to resolveSubject() (document-level checks)
  • Add entries to deriveSubjectType() (route-level checks)
  • Add route exclusions for the org fallback

This couples every module to a shared file, breaking module independence.

Proposed solution

Each module defines its own authorization middleware instead of relying on the generic policy.isAllowed:

// modules/{module}/middlewares/{module}.authorize.js
const authorize = (action, subject) => (req, res, next) => {
  if (req.ability.can(action, subject)) return next();
  return responses.error(res, 403, 'Forbidden')();
};

Routes use the module-local middleware:

.all(passport.authenticate('jwt'), organization.resolveOrganization)
.get(authorize('read', 'MySubject'), controller.list)

Steps

  1. Add a shared lib/helpers/authorize.js helper (DRY across modules)
  2. Migrate each module's routes from policy.isAllowed to authorize(action, subject)
  3. Remove resolveSubject() and deriveSubjectType() from policy.js
  4. Keep defineAbilityFor() and policy auto-discovery (unchanged)

Migration

⚠️ MIGRATION.md required — downstream projects using policy.isAllowed in custom routes must switch to the new authorize() helper. The old policy.isAllowed should be kept as deprecated for one release cycle.

Affected modules

All: tasks, billing, users, organizations, uploads, admin, audit + any downstream custom modules (scraps, historys, developers, etc.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions