From 923c3beead1f7d4dfd70601e574d03b28e1f110a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Wed, 20 May 2026 21:16:11 +0200 Subject: [PATCH] fix(auth): clarify error when joining a non-active workspace (#20769) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary When an existing user accepts an invite into a workspace whose `activationStatus` is not `ACTIVE` (e.g. `SUSPENDED`, `INACTIVE`, `PENDING_CREATION`), the throw in `throwIfWorkspaceIsNotReadyForSignInUp` returns: > User is not part of the workspace The message describes the symptom (they aren't a member yet) instead of the cause (the workspace can't accept new members), which makes invitees assume their invite is broken when the real issue is the target workspace's state. The sibling branch a few lines above — for brand-new users hitting the same non-ACTIVE workspace — already returns `"Workspace is not ready to welcome new members"`. This PR reuses the same message in the existing-user branch so both paths give a consistent, accurate explanation. Single file, two string literals. ## Test plan - [ ] Sign in via Google with an existing Twenty account, accepting an invite to a `SUSPENDED` workspace → confirm the new message is shown instead of "User is not part of the workspace". - [ ] Confirm the happy path (sign-in to an `ACTIVE` workspace via invite) is unchanged — early-return on `ACTIVE` is untouched. --------- Co-authored-by: Charles Bochet --- .../auth/services/sign-in-up.service.ts | 4 +- ...-active-workspace.integration-spec.ts.snap | 25 ++++++++ ...n-non-active-workspace.integration-spec.ts | 60 +++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/failing-sign-up-in-non-active-workspace.integration-spec.ts.snap create mode 100644 packages/twenty-server/test/integration/graphql/suites/auth/sign-up/failing-sign-up-in-non-active-workspace.integration-spec.ts diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts index d1bccf47805..8a6732e3d9f 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/sign-in-up.service.ts @@ -249,10 +249,10 @@ export class SignInUpService { if (!userWorkspaceExists) { throw new AuthException( - 'User is not part of the workspace', + 'Workspace is not ready to welcome new members', AuthExceptionCode.FORBIDDEN_EXCEPTION, { - userFriendlyMessage: msg`User is not part of the workspace`, + userFriendlyMessage: msg`Workspace is not ready to welcome new members`, }, ); } diff --git a/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/failing-sign-up-in-non-active-workspace.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/failing-sign-up-in-non-active-workspace.integration-spec.ts.snap new file mode 100644 index 00000000000..7f6a624dac1 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/failing-sign-up-in-non-active-workspace.integration-spec.ts.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`signUpInWorkspace into a non-active workspace (integration) rejects a brand-new user joining a suspended workspace with the workspace-readiness message 1`] = ` +{ + "extensions": { + "code": "FORBIDDEN", + "subCode": "FORBIDDEN_EXCEPTION", + "userFriendlyMessage": "Workspace is not ready to welcome new members", + }, + "message": "Workspace is not ready to welcome new members", + "name": "ForbiddenError", +} +`; + +exports[`signUpInWorkspace into a non-active workspace (integration) rejects an existing user joining a suspended workspace with the workspace-readiness message 1`] = ` +{ + "extensions": { + "code": "FORBIDDEN", + "subCode": "FORBIDDEN_EXCEPTION", + "userFriendlyMessage": "Workspace is not ready to welcome new members", + }, + "message": "Workspace is not ready to welcome new members", + "name": "ForbiddenError", +} +`; diff --git a/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/failing-sign-up-in-non-active-workspace.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/failing-sign-up-in-non-active-workspace.integration-spec.ts new file mode 100644 index 00000000000..614fc9103f9 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/failing-sign-up-in-non-active-workspace.integration-spec.ts @@ -0,0 +1,60 @@ +import request from 'supertest'; +import { expectOneNotInternalServerErrorSnapshot } from 'test/integration/graphql/utils/expect-one-not-internal-server-error-snapshot.util'; +import { signUpOperationFactory } from 'test/integration/graphql/utils/sign-up-operation-factory.util'; +import { signUp } from 'test/integration/graphql/utils/sign-up.util'; +import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; + +import { SEED_APPLE_WORKSPACE_ID } from 'src/engine/workspace-manager/dev-seeder/core/constants/seeder-workspaces.constant'; + +const client = request(`http://localhost:${APP_PORT}`); + +describe('signUpInWorkspace into a non-active workspace (integration)', () => { + const existingUserEmail = `existing-non-active-${Date.now()}@example.com`; + const password = 'Test123!@#'; + + beforeAll(async () => { + await signUp({ + input: { email: existingUserEmail, password }, + expectToFail: false, + }); + + await testDataSource.query( + 'UPDATE core.workspace SET "activationStatus" = $1 WHERE id = $2', + [WorkspaceActivationStatus.SUSPENDED, SEED_APPLE_WORKSPACE_ID], + ); + }); + + afterAll(async () => { + await testDataSource.query( + 'UPDATE core.workspace SET "activationStatus" = $1 WHERE id = $2', + [WorkspaceActivationStatus.ACTIVE, SEED_APPLE_WORKSPACE_ID], + ); + + await testDataSource.query( + 'DELETE FROM core."user" WHERE email = $1', + [existingUserEmail], + ); + }); + + it('rejects an existing user joining a suspended workspace with the workspace-readiness message', async () => { + const response = await client.post('/metadata').send( + signUpOperationFactory({ + email: existingUserEmail, + password, + }), + ); + + expectOneNotInternalServerErrorSnapshot({ errors: response.body.errors }); + }); + + it('rejects a brand-new user joining a suspended workspace with the workspace-readiness message', async () => { + const response = await client.post('/metadata').send( + signUpOperationFactory({ + email: `new-non-active-${Date.now()}@example.com`, + password, + }), + ); + + expectOneNotInternalServerErrorSnapshot({ errors: response.body.errors }); + }); +});