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:
Marie
2026-04-14 18:29:00 +02:00
committed by GitHub
parent 42f452311b
commit bc28e1557c
58 changed files with 1986 additions and 478 deletions
@@ -7,11 +7,11 @@ import {
import { useColorScheme } from '@/ui/theme/hooks/useColorScheme';
import { resetJotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
const updateOneRecordMock = jest.fn();
const mockUpdateWorkspaceMemberSettings = jest.fn();
jest.mock('@/object-record/hooks/useUpdateOneRecord', () => ({
useUpdateOneRecord: () => ({
updateOneRecord: updateOneRecordMock,
jest.mock('@/settings/profile/hooks/useUpdateWorkspaceMemberSettings', () => ({
useUpdateWorkspaceMemberSettings: () => ({
updateWorkspaceMemberSettings: mockUpdateWorkspaceMemberSettings,
}),
}));
@@ -1,8 +1,7 @@
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useUpdateWorkspaceMemberSettings } from '@/settings/profile/hooks/useUpdateWorkspaceMemberSettings';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useCallback } from 'react';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { persistedColorSchemeState } from '@/ui/theme/states/persistedColorSchemeState';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { type ColorScheme } from '@/workspace-member/types/WorkspaceMember';
@@ -18,7 +17,7 @@ export const useColorScheme = () => {
currentWorkspaceMemberState,
);
const { updateOneRecord } = useUpdateOneRecord();
const { updateWorkspaceMemberSettings } = useUpdateWorkspaceMemberSettings();
const setPersistedColorScheme = useSetAtomState(persistedColorSchemeState);
const colorScheme = currentWorkspaceMember?.colorScheme ?? 'System';
@@ -38,10 +37,9 @@ export const useColorScheme = () => {
colorScheme: value,
};
});
await updateOneRecord({
objectNameSingular: CoreObjectNameSingular.WorkspaceMember,
idToUpdate: currentWorkspaceMember?.id,
updateOneRecordInput: {
await updateWorkspaceMemberSettings({
workspaceMemberId: currentWorkspaceMember.id,
update: {
colorScheme: value,
},
});
@@ -50,7 +48,7 @@ export const useColorScheme = () => {
currentWorkspaceMember,
setCurrentWorkspaceMember,
setPersistedColorScheme,
updateOneRecord,
updateWorkspaceMemberSettings,
],
);