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
@@ -3666,6 +3666,7 @@ type Mutation {
verifyTwoFactorAuthenticationMethodForAuthenticatedUser(otp: String!): VerifyTwoFactorAuthenticationMethod!
deleteUser: User!
deleteUserFromWorkspace(workspaceMemberIdToDelete: String!): UserWorkspace!
updateWorkspaceMemberSettings(input: UpdateWorkspaceMemberSettingsInput!): Boolean!
updateUserEmail(newEmail: String!, verifyEmailRedirectPath: String): Boolean!
resendEmailVerificationToken(email: String!, origin: String!): ResendEmailVerificationToken!
activateWorkspace(data: ActivateWorkspaceInput!): Workspace!
@@ -4600,6 +4601,11 @@ input UpdateApplicationRegistrationVariablePayload {
description: String
}
input UpdateWorkspaceMemberSettingsInput {
workspaceMemberId: UUID!
update: JSON!
}
input ActivateWorkspaceInput {
displayName: String
}
@@ -3121,6 +3121,7 @@ export interface Mutation {
verifyTwoFactorAuthenticationMethodForAuthenticatedUser: VerifyTwoFactorAuthenticationMethod
deleteUser: User
deleteUserFromWorkspace: UserWorkspace
updateWorkspaceMemberSettings: Scalars['Boolean']
updateUserEmail: Scalars['Boolean']
resendEmailVerificationToken: ResendEmailVerificationToken
activateWorkspace: Workspace
@@ -6508,6 +6509,7 @@ export interface MutationGenqlSelection{
verifyTwoFactorAuthenticationMethodForAuthenticatedUser?: (VerifyTwoFactorAuthenticationMethodGenqlSelection & { __args: {otp: Scalars['String']} })
deleteUser?: UserGenqlSelection
deleteUserFromWorkspace?: (UserWorkspaceGenqlSelection & { __args: {workspaceMemberIdToDelete: Scalars['String']} })
updateWorkspaceMemberSettings?: { __args: {input: UpdateWorkspaceMemberSettingsInput} }
updateUserEmail?: { __args: {newEmail: Scalars['String'], verifyEmailRedirectPath?: (Scalars['String'] | null)} }
resendEmailVerificationToken?: (ResendEmailVerificationTokenGenqlSelection & { __args: {email: Scalars['String'], origin: Scalars['String']} })
activateWorkspace?: (WorkspaceGenqlSelection & { __args: {data: ActivateWorkspaceInput} })
@@ -6859,6 +6861,8 @@ export interface UpdateApplicationRegistrationVariableInput {id: Scalars['String
export interface UpdateApplicationRegistrationVariablePayload {value?: (Scalars['String'] | null),description?: (Scalars['String'] | null)}
export interface UpdateWorkspaceMemberSettingsInput {workspaceMemberId: Scalars['UUID'],update: Scalars['JSON']}
export interface ActivateWorkspaceInput {displayName?: (Scalars['String'] | null)}
export interface UpdateWorkspaceInput {subdomain?: (Scalars['String'] | null),customDomain?: (Scalars['String'] | null),displayName?: (Scalars['String'] | null),logo?: (Scalars['String'] | null),inviteHash?: (Scalars['String'] | null),isPublicInviteLinkEnabled?: (Scalars['Boolean'] | null),allowImpersonation?: (Scalars['Boolean'] | null),isGoogleAuthEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthEnabled?: (Scalars['Boolean'] | null),isPasswordAuthEnabled?: (Scalars['Boolean'] | null),isGoogleAuthBypassEnabled?: (Scalars['Boolean'] | null),isMicrosoftAuthBypassEnabled?: (Scalars['Boolean'] | null),isPasswordAuthBypassEnabled?: (Scalars['Boolean'] | null),defaultRoleId?: (Scalars['UUID'] | null),isTwoFactorAuthenticationEnforced?: (Scalars['Boolean'] | null),trashRetentionDays?: (Scalars['Float'] | null),eventLogRetentionDays?: (Scalars['Float'] | null),fastModel?: (Scalars['String'] | null),smartModel?: (Scalars['String'] | null),aiAdditionalInstructions?: (Scalars['String'] | null),editableProfileFields?: (Scalars['String'][] | null),enabledAiModelIds?: (Scalars['String'][] | null),useRecommendedModels?: (Scalars['Boolean'] | null)}
@@ -89,9 +89,9 @@ export default {
380,
387,
418,
499,
504,
505
500,
505,
506
],
"types": {
"BillingProductDTO": {
@@ -9068,6 +9068,15 @@ export default {
]
}
],
"updateWorkspaceMemberSettings": [
6,
{
"input": [
488,
"UpdateWorkspaceMemberSettingsInput!"
]
}
],
"updateUserEmail": [
6,
{
@@ -9097,7 +9106,7 @@ export default {
66,
{
"data": [
488,
489,
"ActivateWorkspaceInput!"
]
}
@@ -9106,7 +9115,7 @@ export default {
66,
{
"data": [
489,
490,
"UpdateWorkspaceInput!"
]
}
@@ -9121,7 +9130,7 @@ export default {
232,
{
"input": [
490,
491,
"SetupOIDCSsoInput!"
]
}
@@ -9130,7 +9139,7 @@ export default {
232,
{
"input": [
491,
492,
"SetupSAMLSsoInput!"
]
}
@@ -9139,7 +9148,7 @@ export default {
228,
{
"input": [
492,
493,
"DeleteSsoInput!"
]
}
@@ -9148,7 +9157,7 @@ export default {
229,
{
"input": [
493,
494,
"EditSsoInput!"
]
}
@@ -9170,7 +9179,7 @@ export default {
323,
{
"input": [
494,
495,
"SendEmailInput!"
]
}
@@ -9196,7 +9205,7 @@ export default {
"String!"
],
"connectionParameters": [
496,
497,
"EmailAccountConnectionParameters!"
],
"id": [
@@ -9208,7 +9217,7 @@ export default {
157,
{
"input": [
498,
499,
"UpdateLabPublicFeatureFlagInput!"
]
}
@@ -9286,7 +9295,7 @@ export default {
6,
{
"role": [
499,
500,
"AiModelRole!"
],
"modelId": [
@@ -9491,7 +9500,7 @@ export default {
68,
{
"input": [
500,
501,
"CreateOneAppTokenInput!"
]
}
@@ -9527,7 +9536,7 @@ export default {
6,
{
"workspaceMigration": [
502,
503,
"WorkspaceMigrationInput!"
]
}
@@ -9601,7 +9610,7 @@ export default {
"String!"
],
"fileFolder": [
505,
506,
"FileFolder!"
],
"filePath": [
@@ -11640,6 +11649,17 @@ export default {
1
]
},
"UpdateWorkspaceMemberSettingsInput": {
"workspaceMemberId": [
3
],
"update": [
15
],
"__typename": [
1
]
},
"ActivateWorkspaceInput": {
"displayName": [
1
@@ -11804,7 +11824,7 @@ export default {
1
],
"files": [
495
496
],
"__typename": [
1
@@ -11823,13 +11843,13 @@ export default {
},
"EmailAccountConnectionParameters": {
"IMAP": [
497
498
],
"SMTP": [
497
498
],
"CALDAV": [
497
498
],
"__typename": [
1
@@ -11869,7 +11889,7 @@ export default {
"AiModelRole": {},
"CreateOneAppTokenInput": {
"appToken": [
501
502
],
"__typename": [
1
@@ -11885,7 +11905,7 @@ export default {
},
"WorkspaceMigrationInput": {
"actions": [
503
504
],
"__typename": [
1
@@ -11893,7 +11913,7 @@ export default {
},
"WorkspaceMigrationDeleteActionInput": {
"type": [
504
505
],
"metadataName": [
355
@@ -11921,7 +11941,7 @@ export default {
260,
{
"input": [
507,
508,
"LogicFunctionLogsInput!"
]
}