Skip to content

refactor: casl factory - jobs#2805

Open
HayenNico wants to merge 10 commits into
masterfrom
refactor-casl-factory-jobs
Open

refactor: casl factory - jobs#2805
HayenNico wants to merge 10 commits into
masterfrom
refactor-casl-factory-jobs

Conversation

@HayenNico

@HayenNico HayenNico commented Jun 26, 2026

Copy link
Copy Markdown
Member

Description

Subsection of PR #2748 for jobss. PR depends on #2759 to be merged first.

This unifies the jobsEndpointAccess and jobsAccess functions in CaslAbilityFactory, removes helper functions and instance-level Action elements and adjusts the affected controller to accommodate the change. The job-specific code is extracted into a separate module.
Functions in CaslAbilityFactory for the handling of jobs MongoDB queries were moved to the jobs.controller.utils.ts module.

Changes:

  • Replace CaslAbilityFactory.jobsInstanceAccess and CaslAbilityFactory.jobsAccess with one function CaslAbilityFactory.jobAccess
  • Code for CaslAbilityFactory.jobAccess is factored out into new module JobAbility
  • Remove helper function CaslAbilityFactory.jobsInstanceAccessCan
  • Remove all instance-level job Action elements, rename endpoint-level actions
  • Move mongo query handlers CaslAbilityFactory.jobsMongoQueryReadAccess + helper functions to jobs.controller.utils.ts under new name readAccessFilter
  • Adjust endpoint and instance auth logic in job utils controller

Tests included

  • Included for each change/fix?
  • Passing?

Documentation

  • swagger documentation updated (required for API changes)
  • official documentation updated

Summary by Sourcery

Refactor job authorization to centralize CASL job abilities in a dedicated JobAbility service and simplify job access checks across the CASL factory and jobs controller.

Enhancements:

  • Replace separate jobs endpoint/instance ability builders with a single jobAccess entry that delegates to a new JobAbility class.
  • Move job-specific MongoDB read access logic from the CASL factory into JobsControllerUtils as a reusable readAccessFilter helper.
  • Simplify job authorization in jobs controller utilities to rely on unified JobCreate, JobRead, and JobUpdate checks instead of multiple granular job actions.
  • Register JobAbility in the CASL module and streamline the Action enum by removing instance-level job actions while keeping endpoint-level job actions.

@HayenNico
HayenNico requested a review from a team as a code owner June 26, 2026 08:31

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • In JobAbility.buildAbility, the unauthenticated rules are mixed in with the authenticated path (the per-job config loop runs before the !user check); consider returning early for !user before the authenticated logic to make the control flow clearer and reduce the chance of future regressions when extending the method.
  • In JobsControllerUtils, the isAlwaysFalseQuery/isEmptyObject helpers and the generic branching in readAccessFilter make sense for multiple query variants but are currently only used with a single accessibleBy(..., Action.JobRead) call; consider simplifying or extracting the helpers to a shared utility so the intent of the filtering logic stays obvious as the job abilities evolve.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `JobAbility.buildAbility`, the unauthenticated rules are mixed in with the authenticated path (the per-job config loop runs before the `!user` check); consider returning early for `!user` before the authenticated logic to make the control flow clearer and reduce the chance of future regressions when extending the method.
- In `JobsControllerUtils`, the `isAlwaysFalseQuery`/`isEmptyObject` helpers and the generic branching in `readAccessFilter` make sense for multiple query variants but are currently only used with a single `accessibleBy(..., Action.JobRead)` call; consider simplifying or extracting the helpers to a shared utility so the intent of the filtering logic stays obvious as the job abilities evolve.

## Individual Comments

### Comment 1
<location path="src/casl/abilities/jobs.ability.ts" line_range="74-81" />
<code_context>
+      const ifOwnerUser = { ownerUser: user.username };
+      const ifOwnerGroup = { ownerGroup: { $in: user.currentGroups } };
+
+      const createAuthorizationValues = [
+        ...Object.values(CreateJobAuth).filter(
+          (v) => String(v) !== "#jobAdmin",
+        ),
+        ...user.currentGroups.map((g) => "@" + g),
+        user.username,
+      ];
+      const updateAuthorizationValues = [
+        ...Object.values(UpdateJobAuth).filter(
+          (v) => String(v) !== "#jobAdmin",
</code_context>
<issue_to_address>
**🚨 issue (security):** Create/Update authorization lists now include dataset/job-scoped auth values, which may broaden permissions compared to the previous logic.

The prior `jobsInstanceAccessCan` logic distinguished dataset/job-scoped auth values (e.g. `#dataset*`, `#job*`) from generic instance-level permissions. Here, `createAuthorizationValues` / `updateAuthorizationValues` include all `CreateJobAuth` / `UpdateJobAuth` enums (minus `#jobAdmin`), so any matching value in `jobConfig.create.auth` / `.update.auth` now grants `JobCreate` / `JobUpdate`. This broadens permissions vs. the old behavior. If that’s not intended, you’ll need to reintroduce a filter that excludes dataset/job-scoped entries or otherwise preserve that distinction.
</issue_to_address>

### Comment 2
<location path="src/jobs/jobs.controller.utils.ts" line_range="276" />
<code_context>
-    });
-  }
-
-  isAlwaysFalseQuery(q: Record<string, unknown>): boolean {
-    const expr = q.$expr as { $eq?: unknown } | undefined;
-    const eq = expr?.$eq;
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying the helper functions and `readAccessFilter` to use clearer typing and a single-query flow while preserving behavior.

You can reduce complexity in this change without losing any functionality by simplifying the helpers and `readAccessFilter`.

### 1. Simplify `isEmptyObject` and improve typing

You don’t need the cast, and you can make the intent clearer with a type predicate:

```ts
isEmptyObject(q: unknown): q is Record<string, unknown> {
  return !!q && typeof q === "object" && Object.keys(q).length === 0;
}
```

This avoids the misleading `as boolean` and gives you a useful type guard.

### 2. Loosen `isAlwaysFalseQuery` input type and simplify checks

`isAlwaysFalseQuery` doesn’t need the input to be a `Record<string, unknown>`, and you can keep the CASL-specific check while making the implementation more defensive:

```ts
isAlwaysFalseQuery(q: unknown): boolean {
  if (!q || typeof q !== "object") return false;

  const expr = (q as { $expr?: { $eq?: unknown } }).$expr;
  const eq = expr?.$eq;

  return Array.isArray(eq) && eq.length === 2 && eq.includes(0) && eq.includes(1);
}
```

This keeps the same semantics but avoids assuming `$expr` exists on the type.

### 3. Simplify `readAccessFilter` to a single-query flow

Right now you always build an array with a single query and then do `filter` / `some` / `length` checks as if you had multiple sources. Since only one query is ever produced, you can simplify to a single-object flow with identical behavior:

```ts
readAccessFilter(user: JWTUser) {
  const abilities = this.caslAbilityFactory.jobAccess(user);
  const q = accessibleBy(abilities, Action.JobRead).ofType(JobClass);

  // No access at all: CASL's always-false expression
  if (this.isAlwaysFalseQuery(q)) {
    return { $expr: { $eq: [0, 1] } };
  }

  // Unrestricted access: empty filter
  if (this.isEmptyObject(q)) {
    return {};
  }

  // Restricted access: use CASL-generated filter as-is
  return q;
}
```

This preserves the current behavior for the existing use case (single `accessibleBy` call), removes unnecessary branching and `$or` composition, and keeps the CASL-specific normalization in one place. If you later introduce multiple query sources, you can reintroduce combining logic at that time.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/casl/abilities/jobs.ability.ts
Comment thread src/jobs/jobs.controller.utils.ts Outdated
Base automatically changed from refactor-casl-factory-cleanup to master June 26, 2026 09:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant