Introduce updateWorkspaceMemberSettings and clarify product (#19441)
## Summary Introduces a dedicated **metadata** mutation to update **standard (non-custom)** workspace member settings, moves profile-related UI to use it, and aligns **workspace member** record permissions with the rest of the CRM so users cannot escalate visibility via RLS by editing their own member record. ## Product behaviour ### Profile and appearance (standard fields) - Users can still update **their own** standard workspace member fields that the product exposes in **Settings / Profile** (e.g. name, locale, color scheme, avatar flow) via the new **`updateWorkspaceMemberSettings`** mutation. - The mutation returns a **boolean**; the app **merges** the updated fields into local state so the UI stays in sync without refetching the full workspace member record. - **Locale** changes also keep **`userWorkspace`** in sync when a locale is present in the payload (including from the workspace `updateOne` path when applicable). ### Custom fields on workspace members - The dedicated metadata mutation **rejects** any **custom** workspace member field (and unknown keys). Those updates must go through the normal **object** `updateOne` pipeline, which is subject to **object- and field-level** permissions like other records. But since we don't have object- and field-level permission configuration for system objects yet, this permission is derived from Workspace member settings permission. - **Workspace member** is no longer exempt from ORM permission validation for updates merely because it is a **system** object. Users who **do not** have workspace member access (e.g. no **Workspace members** settings permission and no equivalent broad settings access on the role) **cannot** use `updateOne` on `workspaceMember` to change **custom** (or other) fields on their own row—even though that row is used for RLS predicates. - This closes a path where someone could widen what they can see by writing to fields that drive row-level rules. ### Who can change another member - Updating **another** user’s workspace member still requires **Workspace members** (or equivalent) settings permission, consistent with admin tooling.
This commit is contained in:
+17
-11
@@ -5,11 +5,13 @@ import { useCallback, useState } from 'react';
|
||||
import { CalendarEventParticipantsResponseStatus } from '@/activities/calendar/components/CalendarEventParticipantsResponseStatus';
|
||||
import { type CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { formatFieldMetadataItemAsFieldDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsFieldDefinition';
|
||||
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
|
||||
import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { useIsRecordReadOnly } from '@/object-record/read-only/hooks/useIsRecordReadOnly';
|
||||
import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordFieldReadOnly';
|
||||
import { RecordFieldsScopeContextProvider } from '@/object-record/record-field-list/contexts/RecordFieldsScopeContext';
|
||||
import { useFieldListFieldMetadataItems } from '@/object-record/record-field-list/hooks/useFieldListFieldMetadataItems';
|
||||
import {
|
||||
@@ -19,9 +21,9 @@ import {
|
||||
} from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { useIsRecordReadOnly } from '@/object-record/read-only/hooks/useIsRecordReadOnly';
|
||||
import { isRecordFieldReadOnly } from '@/object-record/read-only/utils/isRecordFieldReadOnly';
|
||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId';
|
||||
import { CoreObjectNameSingular } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
AvatarOrIcon,
|
||||
@@ -32,7 +34,6 @@ import {
|
||||
} from 'twenty-ui/components';
|
||||
import { IconCalendarEvent } from 'twenty-ui/display';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
||||
|
||||
type CalendarEventDetailsProps = {
|
||||
@@ -70,7 +71,6 @@ const StyledTitle = styled.h2<{ canceled?: boolean }>`
|
||||
font-weight: ${themeCssVariables.font.weight.semiBold};
|
||||
margin: ${themeCssVariables.spacing[0]} ${themeCssVariables.spacing[0]}
|
||||
${themeCssVariables.spacing[2]};
|
||||
|
||||
text-decoration: ${({ canceled }) => (canceled ? 'line-through' : 'none')};
|
||||
`;
|
||||
|
||||
@@ -149,12 +149,21 @@ export const CalendarEventDetails = ({
|
||||
const objectPermissions = useObjectPermissionsForObject(
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const isRecordReadOnly = useIsRecordReadOnly({
|
||||
recordId: calendarEvent.id,
|
||||
objectMetadataId: objectMetadataItem.id,
|
||||
});
|
||||
|
||||
const renderField = (fieldMetadataItem: FieldMetadataItem) => {
|
||||
const fieldDefinition = formatFieldMetadataItemAsFieldDefinition({
|
||||
field: fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 72,
|
||||
});
|
||||
|
||||
const isReadOnly = isRecordFieldReadOnly({
|
||||
isRecordReadOnly,
|
||||
isSystemObject: objectMetadataItem.isSystem,
|
||||
@@ -164,6 +173,8 @@ export const CalendarEventDetails = ({
|
||||
isUIReadOnly: fieldMetadataItem.isUIReadOnly ?? false,
|
||||
isCustom: fieldMetadataItem.isCustom ?? false,
|
||||
},
|
||||
fieldDefinition,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -173,12 +184,7 @@ export const CalendarEventDetails = ({
|
||||
value={{
|
||||
recordId: calendarEvent.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition: formatFieldMetadataItemAsFieldDefinition({
|
||||
field: fieldMetadataItem,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 72,
|
||||
}),
|
||||
fieldDefinition,
|
||||
useUpdateRecord: useUpdateOneCalendarEventRecordMutation,
|
||||
maxWidth: 300,
|
||||
isRecordFieldReadOnly: isReadOnly,
|
||||
|
||||
Reference in New Issue
Block a user