directus / directus

The flexible backend for all your projects 🐰 Turn your DB into a headless CMS, admin panels, or apps with a custom UI, instant APIs, auth & more.
https://directus.io
Other
28.83k stars 4.02k forks source link

Row-Level Policy Fails with Double M2O Relationship: ‘Invalid filter key’” #24388

Closed igmarketing closed 4 days ago

igmarketing commented 2 weeks ago

Describe the Bug

I’m encountering an “Invalid filter key” error when applying row-level security (RLS) policies that reference nested Many-to-One (M2O) relationships. Specifically, I want to filter n8n_workflows by its department_id (which references n8n_departments), then further by that department’s company_id (which references n8n_companies).

My RLS Policy Snippet

Below is an example excerpt from my policies JSON, showing how I’m trying to filter on department_id.company_id:

{
  "policy": "53562580-8205-48a6-afd3-bec8a5fb3422",
  "collection": "n8n_workflows",
  "action": "read",
  "fields": ["*"],
  "permissions": {
    "department_id.company_id": {
      "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
    }
  }
}

When my Company User tries to read data, Directus returns:

Invalid query. Invalid filter key "department_id.company_id" on "n8n_workflows".

However, as Superadmin, I can query n8n_workflows?fields=*,department_id.* and see department_id expand to the related department object.

Collections & Relationships

I have verified that in the Directus “Data Model” UI, both are set up as Many-to-One fields.

What I Expected

I expected the row-level policy to allow me to nest department_id.company_id so that a Company User can only see workflows where the department’s company_id matches their assigned company. Essentially, we want this chain of references:

n8n_workflows.department_id.company_id == $CURRENT_USER's allowed company

What Happened Instead

Directus reports “invalid filter key,” implying it doesn’t recognize the nested property. This works fine for superadmin (who likely bypasses constraints) but breaks for the company user role.

I’ve tried variations like:

"department.company_id": {
  "_in": "..."
}

and

"department_id.company.id": {
  "_in": "..."
}

but all produce the same error.

To Reproduce

  1. Create three collections in Directus:

    • n8n_workflows (with a department_id M2O referencing n8n_departments)
    • n8n_departments (with a company_id M2O referencing n8n_companies)
    • n8n_companies
  2. Set a row-level policy for a non-admin role (e.g. “Company User”) that attempts:

    {
     "collection": "n8n_workflows",
     "action": "read",
     "fields": ["*"],
     "permissions": {
       "department_id.company_id": {
         "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
       }
     }
    }
  3. Log in as that Company User and perform a GET /items/n8n_workflows.

  4. Observe the error in the response:

    Invalid query. Invalid filter key "department_id.company_id" on "n8n_workflows".

Additional Steps or Logs


Screenshots / Exports

Below are some relevant screenshots: Comany access policy Get Items Admin Get Items User

  1. Screenshot of department_id relationship in “Data Model” (Many-to-One to n8n_departments):
    n8n_workflows relationships

  2. Screenshot of company_id in n8n_departments (Many-to-One to n8n_companies):
    n8n_departments relationships

  3. Policy JSON

    [
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_companies",
    "action": "read",
    "fields": ["*"],
    "permissions": {
      "id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_companies",
    "action": "update",
    "fields": ["*"],
    "permissions": {
      "id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_departments",
    "action": "create",
    "fields": ["*"],
    "permissions": {
      "company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_departments",
    "action": "read",
    "fields": ["*"],
    "permissions": {
      "company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_departments",
    "action": "update",
    "fields": ["*"],
    "permissions": {
      "company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_departments",
    "action": "delete",
    "fields": ["*"],
    "permissions": {
      "company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_user_companies",
    "action": "read",
    "fields": ["*"],
    "permissions": {
      "company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_user_departments",
    "action": "read",
    "fields": ["*"],
    "permissions": {
      "department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_users",
    "action": "read",
    "fields": ["*"],
    "permissions": {
      "n8n_user_companies.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_users",
    "action": "update",
    "fields": ["*"],
    "permissions": {
      "n8n_user_companies.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflow_executions",
    "action": "create",
    "fields": ["*"],
    "permissions": {
      "workflow_id.department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflow_executions",
    "action": "read",
    "fields": ["*"],
    "permissions": {
      "workflow_id.department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflow_executions",
    "action": "update",
    "fields": ["*"],
    "permissions": {
      "workflow_id.department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflow_executions",
    "action": "delete",
    "fields": ["*"],
    "permissions": {
      "workflow_id.department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflow_fields",
    "action": "create",
    "fields": ["*"],
    "permissions": {
      "workflow_id.department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflow_fields",
    "action": "read",
    "fields": ["*"],
    "permissions": {
      "workflow_id.department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflow_fields",
    "action": "update",
    "fields": ["*"],
    "permissions": {
      "workflow_id.department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflow_fields",
    "action": "delete",
    "fields": ["*"],
    "permissions": {
      "workflow_id.department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflows",
    "action": "create",
    "fields": ["*"],
    "permissions": {
      "department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflows",
    "action": "read",
    "fields": ["*"],
    "permissions": {
      "department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflows",
    "action": "update",
    "fields": ["*"],
    "permissions": {
      "department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "n8n_workflows",
    "action": "delete",
    "fields": ["*"],
    "permissions": {
      "department_id.company_id": {
        "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "directus_users",
    "action": "read",
    "fields": ["*"],
    "permissions": {
      "id": {
        "_eq": "$CURRENT_USER.id"
      }
    }
    },
    {
    "policy": "53562580-8205-48a6-afd3-bec8a5fb3420",
    "collection": "directus_users",
    "action": "update",
    "fields": ["email","password","first_name","last_name"],
    "permissions": {
      "id": {
        "_eq": "$CURRENT_USER.id"
      }
    }
    }
    ]
  4. Database exports:


Summary

In short, I’m unable to chain two M2O relationships (department_id.company_id) in a single row-level policy. I need assistance or a workaround to ensure my Company Users can only see n8n_workflows belonging to their assigned company.

  1. Is multi-level M2O chaining supported in Directus 11 row-level policies, or is this a known limitation/bug?

Thank you for any guidance or suggestions!

Directus Version

11.3.5

Hosting Strategy

Self-Hosted (Custom)

Database

PostgreSQL 17.2

linear[bot] commented 2 weeks ago

GEN-1125 Row-Level Policy Fails with Double M2O Relationship: ‘Invalid filter key’”

rijkvanzanten commented 1 week ago

The filter keys don't support dot-notation for nested fields (eg "department_id.company_id"). Can you try it with:

{
  "department_id": {
    "company_id": {
      "_in": "$CURRENT_USER.n8n_user_id.n8n_user_companies.company_id"
    }
  }
}

instead?

github-actions[bot] commented 4 days ago

Heya! There isn't enough information available to keep investigating this issue at present time, so it'll be closed until more information becomes available. Thanks!