fix(rls): prefill RLS predicate fields when creating related records (#22620)

## What / Why

Creating a record from a relation section (e.g. adding a child record
from its
parent's record page) fails with **"Record does not satisfy security
constraints"** for any role restricted by row-level permissions.

Root cause: `useAddNewRecordAndOpenSidePanel` builds the create payload
with only
the label field and the parent FK. Fields required by the role's RLS
predicates
(e.g. `owner = current workspace member`) are missing, so the server
rejects the
insert in `validateRLSPredicatesForRecords` with
`RLS_VALIDATION_FAILED`.

`useCreateNewIndexRecord` (the record table "+ New" path) already
handles this via
`buildRecordInputFromRLSPredicates()`. The relation-section creation
path was
simply never updated — same bug class, different entry point.

## How

Spread `buildRecordInputFromRLSPredicates()` into the create payload in
`useAddNewRecordAndOpenSidePanel`, mirroring `useCreateNewIndexRecord`.
The record
is then created with the RLS-required fields prefilled (e.g. owner =
current
member), so it passes server-side validation.

No behavior change for roles without RLS predicates:
`buildRecordInputFromRLSPredicates()`
returns an empty object when there are none.

## Test plan

Requires row-level permissions (Enterprise) enabled.

1. Create a role with an RLS predicate `owner IS current workspace
member`.
2. Assign it to a non-admin user; create a parent record owned by that
user.
3. As that user, open the parent record and add a child record from a
relation
   section (the "+" on a one-to-many / many-to-one relation field).
4. **Before:** "Record does not satisfy security constraints".
**After:** the child record is created, with owner prefilled to the
current member.

Also verified via REST against a self-hosted instance: inserting the
child record
without the owner field is rejected (HTTP 400, RLS_VALIDATION_FAILED);
inserting it
with `ownerId = current member` succeeds (HTTP 201).

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22620?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Aressand
2026-07-08 13:03:45 +02:00
committed by GitHub
parent b733a79821
commit 14e4b5bd31
@@ -5,6 +5,7 @@ import { useOpenRecordInSidePanel } from '@/side-panel/hooks/useOpenRecordInSide
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
import { useBuildRecordInputFromRLSPredicates } from '@/object-record/hooks/useBuildRecordInputFromRLSPredicates';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
import { canCreateRecordsForObjectMetadataItem } from '@/object-record/utils/canCreateRecordsForObjectMetadataItem';
@@ -49,6 +50,11 @@ export const useAddNewRecordAndOpenSidePanel = ({
const { updateOneRecord } = useUpdateOneRecord();
const { buildRecordInputFromRLSPredicates } =
useBuildRecordInputFromRLSPredicates({
objectMetadataItem: relationObjectMetadataItem,
});
const { openRecordInSidePanel } = useOpenRecordInSidePanel();
const apolloCoreClient = useApolloCoreClient();
@@ -75,11 +81,14 @@ export const useAddNewRecordAndOpenSidePanel = ({
createNewRecordAndOpenSidePanel: async (searchInput?: string) => {
const newRecordId = v4();
const createRecordPayload = buildRecordLabelPayload({
id: newRecordId,
searchInput,
objectMetadataItem: relationObjectMetadataItem,
});
const createRecordPayload = {
...buildRecordInputFromRLSPredicates(),
...buildRecordLabelPayload({
id: newRecordId,
searchInput,
objectMetadataItem: relationObjectMetadataItem,
}),
};
if (relationFieldMetadataItemRelationType === RelationType.MANY_TO_ONE) {
const gqlField =