From fc908e9d87dbb5df1a552ab7fc98c2e3f34a6a23 Mon Sep 17 00:00:00 2001 From: Weiko Date: Wed, 28 Jan 2026 18:50:42 +0100 Subject: [PATCH] Refactor WorkspaceAuthContext to use discriminated union types (#17491) ## Context The previous WorkspaceAuthContext was a single interface with many optional fields, making it unclear which fields are available in different authentication scenarios. This made the code harder to reason about and required runtime checks scattered throughout the codebase. ## Changes - Introduced a discriminated union type for WorkspaceAuthContext with four specific variants: -> UserWorkspaceAuthContext - for authenticated users -> ApiKeyWorkspaceAuthContext - for API key authentication -> ApplicationWorkspaceAuthContext - for application-based auth -> SystemWorkspaceAuthContext - for system/internal operations - Added type guard functions (isUserAuthContext, isApiKeyAuthContext, etc.) for safe type narrowing - Added builder utilities (buildUserAuthContext, buildApiKeyAuthContext, etc.) to construct each context variant with proper type safety - Refactored WorkspaceAuthContextMiddleware to use the new builders instead of constructing a loosely-typed object - Moved the type definition from twenty-orm/interfaces/ to core-modules/auth/types/ for better organization - Updated all consumers across query runners, tool providers, and modules to use the new type location ## Notes - I had to query User and WorkspaceMember in some parts of tool module that were expecting userWorkspaceId but not the rest of UserWorkspaceAuthContext (that should be required with the new proper type otherwise it would break a lot of logic and mostly permissions with the newly added RLS -> This is what we expect from UserWorkspaceAuthContext and how it's done in the "normal" path in HTTP middleware) - WorkspaceMember is in the cache already but ideally we should move User (And Workspace?) in the cache as well to avoid querying the DB after each request (this is also valid for HTTP middleware when we hydrate the Request object btw) --- .../common-base-query-runner.service.ts | 16 ++-- ...common-create-many-query-runner.service.ts | 3 +- .../common-create-one-query-runner.service.ts | 3 +- ...common-delete-many-query-runner.service.ts | 3 +- .../common-delete-one-query-runner.service.ts | 3 +- ...ommon-destroy-many-query-runner.service.ts | 3 +- ...common-destroy-one-query-runner.service.ts | 3 +- ...on-find-duplicates-query-runner.service.ts | 3 +- .../common-find-many-query-runner.service.ts | 2 +- .../common-find-one-query-runner.service.ts | 2 +- .../common-group-by-query-runner.service.ts | 2 +- .../common-merge-many-query-runner.service.ts | 3 +- ...ommon-restore-many-query-runner.service.ts | 3 +- ...common-restore-one-query-runner.service.ts | 3 +- ...common-update-many-query-runner.service.ts | 3 +- .../common-update-one-query-runner.service.ts | 3 +- .../workspace-auth-context.interface.ts | 27 ------- ...mmon-extended-query-runner-context.type.ts | 3 +- .../src/engine/api/mcp/mcp.module.ts | 6 ++ .../__tests__/mcp-protocol.service.spec.ts | 53 +++++++++++++ .../api/mcp/services/mcp-protocol.service.ts | 75 +++++++++++++++---- .../core/handlers/rest-api-base.handler.ts | 30 +++----- .../api/rest/types/authenticated-request.ts | 2 +- .../actor-from-auth-context.service.ts | 3 +- .../guards/is-api-key-auth-context.guard.ts | 10 +++ .../is-application-auth-context.guard.ts | 10 +++ ...ding-activation-user-auth-context.guard.ts | 10 +++ .../guards/is-system-auth-context.guard.ts | 10 +++ .../auth/guards/is-user-auth-context.guard.ts | 10 +++ .../is-workspace-auth-context.guard.ts} | 3 +- .../workspace-auth-context.middleware.ts | 65 +++++++++++++--- .../storage/workspace-auth-context.storage.ts | 2 +- .../auth/types/workspace-auth-context.type.ts | 51 +++++++++++++ .../utils/build-api-key-auth-context.util.ts | 19 +++++ .../build-application-auth-context.util.ts | 19 +++++ ...nding-activation-user-auth-context.util.ts | 21 ++++++ .../utils/build-system-auth-context.util.ts | 17 +++++ .../utils/build-user-auth-context.util.ts | 25 +++++++ .../common-api-context-builder.service.ts | 15 ++-- .../record-crud-execution-context.type.ts | 3 +- .../types/tool-generator.types.ts | 3 +- .../interfaces/tool-provider.interface.ts | 3 +- .../providers/database-tool.provider.ts | 64 ++++++++++++---- .../resolvers/tool-index.resolver.ts | 4 + .../tool-provider/tool-provider.module.ts | 3 + .../services/agent-async-executor.service.ts | 14 +++- .../navigation-menu-item.resolver.ts | 3 +- .../navigation-menu-item.service.ts | 15 ++-- .../workspace-entity-manager.spec.ts | 2 +- .../workspace-entity-manager.ts | 2 +- .../global-workspace-datasource.ts | 2 +- .../global-workspace-orm.manager.ts | 3 +- .../storage/orm-workspace-context.storage.ts | 3 +- .../utils/build-system-auth-context.util.ts | 16 ++-- ...ected-account-delete-one.pre-query.hook.ts | 2 +- .../services/dashboard-duplication.service.ts | 3 +- .../note-post-query-hook.service.ts | 3 +- .../task-post-query-hook.service.ts | 3 +- .../workflow-create-many.post-query.hook.ts | 2 +- .../workflow-create-one.post-query.hook.ts | 2 +- .../workflow-execution-context.service.ts | 26 +++---- .../types/workflow-execution-context.type.ts | 3 +- ...space-member-delete-one.post-query.hook.ts | 2 +- 63 files changed, 544 insertions(+), 186 deletions(-) delete mode 100644 packages/twenty-server/src/engine/api/common/interfaces/workspace-auth-context.interface.ts create mode 100644 packages/twenty-server/src/engine/core-modules/auth/guards/is-api-key-auth-context.guard.ts create mode 100644 packages/twenty-server/src/engine/core-modules/auth/guards/is-application-auth-context.guard.ts create mode 100644 packages/twenty-server/src/engine/core-modules/auth/guards/is-pending-activation-user-auth-context.guard.ts create mode 100644 packages/twenty-server/src/engine/core-modules/auth/guards/is-system-auth-context.guard.ts create mode 100644 packages/twenty-server/src/engine/core-modules/auth/guards/is-user-auth-context.guard.ts rename packages/twenty-server/src/engine/{api/common/utils/is-workspace-auth-context.util.ts => core-modules/auth/guards/is-workspace-auth-context.guard.ts} (78%) create mode 100644 packages/twenty-server/src/engine/core-modules/auth/types/workspace-auth-context.type.ts create mode 100644 packages/twenty-server/src/engine/core-modules/auth/utils/build-api-key-auth-context.util.ts create mode 100644 packages/twenty-server/src/engine/core-modules/auth/utils/build-application-auth-context.util.ts create mode 100644 packages/twenty-server/src/engine/core-modules/auth/utils/build-pending-activation-user-auth-context.util.ts create mode 100644 packages/twenty-server/src/engine/core-modules/auth/utils/build-system-auth-context.util.ts create mode 100644 packages/twenty-server/src/engine/core-modules/auth/utils/build-user-auth-context.util.ts diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts index f4259404c8..f1f12b0933 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-base-query-runner.service.ts @@ -3,7 +3,6 @@ import { Inject, Injectable } from '@nestjs/common'; import { type PermissionFlagType } from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; import { QueryResultFieldValue } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-field-value'; import { DataArgProcessor } from 'src/engine/api/common/common-args-processors/data-arg-processor/data-arg.processor'; @@ -24,14 +23,17 @@ import { } from 'src/engine/api/common/types/common-query-args.type'; import { CommonQueryResult } from 'src/engine/api/common/types/common-query-result.type'; import { CommonSelectedFieldsResult } from 'src/engine/api/common/types/common-selected-fields-result.type'; -import { isWorkspaceAuthContext } from 'src/engine/api/common/utils/is-workspace-auth-context.util'; import { OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS } from 'src/engine/api/graphql/graphql-query-runner/constants/objects-with-settings-permissions-requirements'; import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper'; import { WorkspacePreQueryHookPayload } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service'; import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service'; +import { isApiKeyAuthContext } from 'src/engine/core-modules/auth/guards/is-api-key-auth-context.guard'; +import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard'; +import { isWorkspaceAuthContext } from 'src/engine/core-modules/auth/guards/is-workspace-auth-context.guard'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service'; import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type'; @@ -287,10 +289,14 @@ export abstract class CommonBaseQueryRunnerService< const userHasPermission = await this.permissionsService.userHasWorkspaceSettingPermission({ - userWorkspaceId: authContext.userWorkspaceId, + userWorkspaceId: isUserAuthContext(authContext) + ? authContext.userWorkspaceId + : undefined, setting: permissionRequired, workspaceId: workspace.id, - apiKeyId: authContext.apiKey?.id, + apiKeyId: isApiKeyAuthContext(authContext) + ? authContext.apiKey.id + : undefined, }); if (!userHasPermission) { @@ -363,7 +369,7 @@ export abstract class CommonBaseQueryRunnerService< private async throttleQueryExecution(authContext: WorkspaceAuthContext) { try { - if (!isDefined(authContext.apiKey)) return; + if (!isApiKeyAuthContext(authContext)) return; const workspaceId = authContext.workspace.id; diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service.ts index 726ae136dc..bbea2e9203 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service.ts @@ -6,8 +6,7 @@ import { ObjectRecord } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FindOptionsRelations, In, InsertResult, ObjectLiteral } from 'typeorm'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { PartialObjectRecordWithId } from 'src/engine/api/common/common-query-runners/common-create-many-query-runner/types/partial-object-record-with-id.type'; import { buildWhereConditions } from 'src/engine/api/common/common-query-runners/common-create-many-query-runner/utils/build-where-conditions.util'; diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-create-one-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-create-one-query-runner.service.ts index ad30ae4572..ae4eb48342 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-create-one-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-create-one-query-runner.service.ts @@ -2,8 +2,7 @@ import { Injectable } from '@nestjs/common'; import { type ObjectRecord } from 'twenty-shared/types'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonCreateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service'; import { CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type'; diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-delete-many-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-delete-many-query-runner.service.ts index 88773f8988..b8d266ab37 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-delete-many-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-delete-many-query-runner.service.ts @@ -5,8 +5,7 @@ import { ObjectRecord } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FindOptionsRelations, ObjectLiteral } from 'typeorm'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonQueryRunnerException, diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-delete-one-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-delete-one-query-runner.service.ts index f5f7636396..db241baa4f 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-delete-one-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-delete-one-query-runner.service.ts @@ -4,8 +4,7 @@ import { msg } from '@lingui/core/macro'; import { type ObjectRecord } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonDeleteManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-many-query-runner.service'; import { diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-destroy-many-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-destroy-many-query-runner.service.ts index 0cbc56f2f4..ca28d217a6 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-destroy-many-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-destroy-many-query-runner.service.ts @@ -5,8 +5,7 @@ import { ObjectRecord } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FindOptionsRelations, ObjectLiteral } from 'typeorm'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonQueryRunnerException, diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service.ts index a23fecd759..dcb5306427 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service.ts @@ -4,8 +4,7 @@ import { msg } from '@lingui/core/macro'; import { type ObjectRecord } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonDestroyManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-many-query-runner.service'; import { diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-duplicates-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-duplicates-query-runner.service.ts index 79249e0035..75aac10ada 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-duplicates-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-duplicates-query-runner.service.ts @@ -9,8 +9,7 @@ import { ObjectRecord, OrderByDirection } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FindOptionsRelations, In, ObjectLiteral } from 'typeorm'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonQueryRunnerException, diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-many-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-many-query-runner.service.ts index cd7e61812f..b0c38ac4f7 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-many-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-many-query-runner.service.ts @@ -8,12 +8,12 @@ import { import { ObjectRecord, OrderByDirection } from 'twenty-shared/types'; import { FindOptionsRelations, ObjectLiteral } from 'typeorm'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; import { ObjectRecordFilter, ObjectRecordOrderBy, } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonQueryRunnerException, diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-one-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-one-query-runner.service.ts index d7fdce15a1..be3289162c 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-one-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-find-one-query-runner.service.ts @@ -6,9 +6,9 @@ import { ObjectRecord } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FindOptionsRelations, ObjectLiteral } from 'typeorm'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonQueryRunnerException, diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-group-by-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-group-by-query-runner.service.ts index 19baa8ded2..20ecfcdd84 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-group-by-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-group-by-query-runner.service.ts @@ -15,9 +15,9 @@ import { } from 'twenty-shared/utils'; import { ObjectLiteral } from 'typeorm'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonQueryRunnerException, diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts index a9199fb9fb..b82e5ea3b8 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-merge-many-query-runner.service.ts @@ -15,8 +15,7 @@ import { isDefined } from 'twenty-shared/utils'; import { FindOptionsRelations, In, ObjectLiteral } from 'typeorm'; import { v4 as uuidv4 } from 'uuid'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonQueryRunnerException, diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-restore-many-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-restore-many-query-runner.service.ts index c789c178fb..b6d580644d 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-restore-many-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-restore-many-query-runner.service.ts @@ -5,8 +5,7 @@ import { ObjectRecord } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { FindOptionsRelations, ObjectLiteral } from 'typeorm'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonQueryRunnerException, diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-restore-one-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-restore-one-query-runner.service.ts index 141f024602..92dda2807b 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-restore-one-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-restore-one-query-runner.service.ts @@ -4,8 +4,7 @@ import { msg } from '@lingui/core/macro'; import { type ObjectRecord } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonRestoreManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-restore-many-query-runner.service'; import { diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-update-many-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-update-many-query-runner.service.ts index 10bcb18733..29d5149000 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-update-many-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-update-many-query-runner.service.ts @@ -5,8 +5,7 @@ import { QUERY_MAX_RECORDS_FROM_RELATION } from 'twenty-shared/constants'; import { ObjectRecord } from 'twenty-shared/types'; import { FindOptionsRelations, ObjectLiteral } from 'typeorm'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonQueryRunnerException, diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/common-update-one-query-runner.service.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/common-update-one-query-runner.service.ts index b7a077fd3f..78d19e57f6 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/common-update-one-query-runner.service.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/common-update-one-query-runner.service.ts @@ -3,8 +3,7 @@ import { Injectable } from '@nestjs/common'; import { msg } from '@lingui/core/macro'; import { type ObjectRecord } from 'twenty-shared/types'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { CommonBaseQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-base-query-runner.service'; import { CommonUpdateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-update-many-query-runner.service'; import { diff --git a/packages/twenty-server/src/engine/api/common/interfaces/workspace-auth-context.interface.ts b/packages/twenty-server/src/engine/api/common/interfaces/workspace-auth-context.interface.ts deleted file mode 100644 index a83ee148dd..0000000000 --- a/packages/twenty-server/src/engine/api/common/interfaces/workspace-auth-context.interface.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; - -interface AuthContextWithDefinedWorkspaceProperties { - user: AuthContext['user']; - application: AuthContext['application']; - workspace: NonNullable; - workspaceMetadataVersion?: string; - workspaceMemberId: AuthContext['workspaceMemberId']; - workspaceMember: AuthContext['workspaceMember']; - userWorkspaceId: AuthContext['userWorkspaceId']; - apiKey: AuthContext['apiKey']; -} - -interface ApiKeyAuthContext extends Request { - apiKey: NonNullable; -} - -interface UserWorkspaceAuthContext extends Request { - userWorkspaceId: NonNullable; -} - -interface ApplicationAuthContext extends Request { - application: NonNullable; -} - -export type WorkspaceAuthContext = AuthContextWithDefinedWorkspaceProperties & - (ApiKeyAuthContext | UserWorkspaceAuthContext | ApplicationAuthContext); diff --git a/packages/twenty-server/src/engine/api/common/types/common-extended-query-runner-context.type.ts b/packages/twenty-server/src/engine/api/common/types/common-extended-query-runner-context.type.ts index b1765695df..af9bd8b239 100644 --- a/packages/twenty-server/src/engine/api/common/types/common-extended-query-runner-context.type.ts +++ b/packages/twenty-server/src/engine/api/common/types/common-extended-query-runner-context.type.ts @@ -1,7 +1,6 @@ import { type ObjectLiteral } from 'typeorm'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { type CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type'; import { type GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser'; import { type GlobalWorkspaceDataSource } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource'; diff --git a/packages/twenty-server/src/engine/api/mcp/mcp.module.ts b/packages/twenty-server/src/engine/api/mcp/mcp.module.ts index 5cfa611d9c..8f2463c47a 100644 --- a/packages/twenty-server/src/engine/api/mcp/mcp.module.ts +++ b/packages/twenty-server/src/engine/api/mcp/mcp.module.ts @@ -1,4 +1,5 @@ import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { McpCoreController } from 'src/engine/api/mcp/controllers/mcp-core.controller'; import { McpMetadataController } from 'src/engine/api/mcp/controllers/mcp-metadata.controller'; @@ -10,8 +11,11 @@ import { TokenModule } from 'src/engine/core-modules/auth/token/token.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module'; import { ToolProviderModule } from 'src/engine/core-modules/tool-provider/tool-provider.module'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; +import { UserEntity } from 'src/engine/core-modules/user/user.entity'; import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; @Module({ imports: [ @@ -22,6 +26,8 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/ MetricsModule, UserRoleModule, ToolProviderModule, + TypeOrmModule.forFeature([UserEntity, UserWorkspaceEntity]), + WorkspaceCacheModule, ], controllers: [McpCoreController, McpMetadataController], exports: [McpProtocolService], diff --git a/packages/twenty-server/src/engine/api/mcp/services/__tests__/mcp-protocol.service.spec.ts b/packages/twenty-server/src/engine/api/mcp/services/__tests__/mcp-protocol.service.spec.ts index 0dfcf063bb..37cd9dad8e 100644 --- a/packages/twenty-server/src/engine/api/mcp/services/__tests__/mcp-protocol.service.spec.ts +++ b/packages/twenty-server/src/engine/api/mcp/services/__tests__/mcp-protocol.service.spec.ts @@ -1,5 +1,6 @@ import { HttpException, HttpStatus } from '@nestjs/common'; import { Test, type TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; import { jsonSchema } from 'ai'; @@ -12,8 +13,10 @@ import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api- import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { ToolRegistryService } from 'src/engine/core-modules/tool-provider/services/tool-registry.service'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; describe('McpProtocolService', () => { let service: McpProtocolService; @@ -22,15 +25,32 @@ describe('McpProtocolService', () => { let userRoleService: jest.Mocked; let mcpToolExecutorService: jest.Mocked; let apiKeyRoleService: jest.Mocked; + let userWorkspaceRepository: jest.Mocked<{ findOne: jest.Mock }>; + let workspaceCacheService: jest.Mocked<{ getOrRecompute: jest.Mock }>; const mockWorkspace = { id: 'workspace-1' } as WorkspaceEntity; const mockUserWorkspaceId = 'user-workspace-1'; + const mockUserId = 'user-1'; + const mockWorkspaceMemberId = 'workspace-member-1'; const mockRoleId = 'role-1'; const mockAdminRoleId = 'admin-role-1'; const mockApiKey = { id: 'api-key-1', workspaceId: mockWorkspace.id, } as ApiKeyEntity; + const mockUser = { id: mockUserId }; + const mockUserWorkspace = { + id: mockUserWorkspaceId, + user: mockUser, + }; + const mockWorkspaceMember = { + id: mockWorkspaceMemberId, + userId: mockUserId, + }; + const mockFlatWorkspaceMemberMaps = { + idByUserId: { [mockUserId]: mockWorkspaceMemberId }, + byId: { [mockWorkspaceMemberId]: mockWorkspaceMember }, + }; beforeEach(async () => { const mockFeatureFlagService = { @@ -54,6 +74,15 @@ describe('McpProtocolService', () => { getRoleIdForApiKeyId: jest.fn().mockResolvedValue(mockAdminRoleId), }; + const mockWorkspaceCacheService = { + getOrRecompute: jest.fn(), + invalidateAndRecompute: jest.fn(), + }; + + const mockUserWorkspaceRepository = { + findOne: jest.fn(), + }; + const module: TestingModule = await Test.createTestingModule({ providers: [ McpProtocolService, @@ -77,6 +106,14 @@ describe('McpProtocolService', () => { provide: ApiKeyRoleService, useValue: mockApiKeyRoleService, }, + { + provide: WorkspaceCacheService, + useValue: mockWorkspaceCacheService, + }, + { + provide: getRepositoryToken(UserWorkspaceEntity), + useValue: mockUserWorkspaceRepository, + }, ], }).compile(); @@ -86,6 +123,10 @@ describe('McpProtocolService', () => { userRoleService = module.get(UserRoleService); mcpToolExecutorService = module.get(McpToolExecutorService); apiKeyRoleService = module.get(ApiKeyRoleService); + userWorkspaceRepository = module.get( + getRepositoryToken(UserWorkspaceEntity), + ); + workspaceCacheService = module.get(WorkspaceCacheService); }); it('should be defined', () => { @@ -216,6 +257,10 @@ describe('McpProtocolService', () => { it('should handle tools/call method with userWorkspaceId', async () => { featureFlagService.isFeatureEnabled.mockResolvedValue(true); userRoleService.getRoleIdForUserWorkspace.mockResolvedValue(mockRoleId); + userWorkspaceRepository.findOne.mockResolvedValue(mockUserWorkspace); + workspaceCacheService.getOrRecompute.mockResolvedValue({ + flatWorkspaceMemberMaps: mockFlatWorkspaceMemberMaps, + }); const mockTool = { description: 'Test tool', @@ -322,6 +367,10 @@ describe('McpProtocolService', () => { it('should handle tools listing', async () => { featureFlagService.isFeatureEnabled.mockResolvedValue(true); userRoleService.getRoleIdForUserWorkspace.mockResolvedValue(mockRoleId); + userWorkspaceRepository.findOne.mockResolvedValue(mockUserWorkspace); + workspaceCacheService.getOrRecompute.mockResolvedValue({ + flatWorkspaceMemberMaps: mockFlatWorkspaceMemberMaps, + }); const mockToolsMap = { testTool: { @@ -398,6 +447,10 @@ describe('McpProtocolService', () => { it('should handle error when tool is not found', async () => { featureFlagService.isFeatureEnabled.mockResolvedValue(true); userRoleService.getRoleIdForUserWorkspace.mockResolvedValue(mockRoleId); + userWorkspaceRepository.findOne.mockResolvedValue(mockUserWorkspace); + workspaceCacheService.getOrRecompute.mockResolvedValue({ + flatWorkspaceMemberMaps: mockFlatWorkspaceMemberMaps, + }); toolRegistryService.getToolsByCategories.mockResolvedValue({}); mcpToolExecutorService.handleToolCall.mockRejectedValue( diff --git a/packages/twenty-server/src/engine/api/mcp/services/mcp-protocol.service.ts b/packages/twenty-server/src/engine/api/mcp/services/mcp-protocol.service.ts index 0704e8b7bf..12156bff7d 100644 --- a/packages/twenty-server/src/engine/api/mcp/services/mcp-protocol.service.ts +++ b/packages/twenty-server/src/engine/api/mcp/services/mcp-protocol.service.ts @@ -1,21 +1,27 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; - -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; +import { Repository } from 'typeorm'; import { type JsonRpc } from 'src/engine/api/mcp/dtos/json-rpc'; import { McpToolExecutorService } from 'src/engine/api/mcp/services/mcp-tool-executor.service'; import { wrapJsonRpcResponse } from 'src/engine/api/mcp/utils/wrap-jsonrpc-response.util'; import { type ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity'; import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; +import { buildApiKeyAuthContext } from 'src/engine/core-modules/auth/utils/build-api-key-auth-context.util'; +import { buildSystemAuthContext } from 'src/engine/core-modules/auth/utils/build-system-auth-context.util'; +import { buildUserAuthContext } from 'src/engine/core-modules/auth/utils/build-user-auth-context.util'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { ToolCategory } from 'src/engine/core-modules/tool-provider/enums/tool-category.enum'; import { ToolRegistryService } from 'src/engine/core-modules/tool-provider/services/tool-registry.service'; import { ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum'; +import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; @Injectable() export class McpProtocolService { @@ -25,6 +31,9 @@ export class McpProtocolService { private readonly userRoleService: UserRoleService, private readonly mcpToolExecutorService: McpToolExecutorService, private readonly apiKeyRoleService: ApiKeyRoleService, + @InjectRepository(UserWorkspaceEntity) + private readonly userWorkspaceRepository: Repository, + private readonly workspaceCacheService: WorkspaceCacheService, ) {} async checkAiEnabled(workspaceId: string): Promise { @@ -87,19 +96,59 @@ export class McpProtocolService { return roleId; } - private buildAuthContext( + private async buildAuthContext( workspace: WorkspaceEntity, userWorkspaceId?: string, apiKey?: ApiKeyEntity, - ): WorkspaceAuthContext { - return { - user: null, - apiKey: apiKey ?? null, - application: null, - workspace, - workspaceMemberId: undefined, - userWorkspaceId: userWorkspaceId ?? undefined, - } as WorkspaceAuthContext; + ): Promise { + if (isDefined(apiKey)) { + return buildApiKeyAuthContext({ workspace, apiKey }); + } + + if (isDefined(userWorkspaceId)) { + const userWorkspace = await this.userWorkspaceRepository.findOne({ + where: { + id: userWorkspaceId, + }, + relations: { + user: true, + }, + }); + + const user = userWorkspace?.user; + + if (!isDefined(user)) { + throw new HttpException('User not found', HttpStatus.FORBIDDEN); + } + + const { flatWorkspaceMemberMaps } = + await this.workspaceCacheService.getOrRecompute(workspace.id, [ + 'flatWorkspaceMemberMaps', + ]); + + const workspaceMemberId = flatWorkspaceMemberMaps.idByUserId[user.id]; + + const workspaceMember = isDefined(workspaceMemberId) + ? flatWorkspaceMemberMaps.byId[workspaceMemberId] + : undefined; + + if (!isDefined(workspaceMemberId) || !isDefined(workspaceMember)) { + throw new HttpException( + 'Workspace member not found', + HttpStatus.FORBIDDEN, + ); + } + + return buildUserAuthContext({ + workspace, + userWorkspaceId, + user, + workspaceMemberId, + workspaceMember, + }); + } + + return buildSystemAuthContext({ workspace }); } async handleMCPCoreQuery( @@ -137,7 +186,7 @@ export class McpProtocolService { apiKey, ); - const authContext = this.buildAuthContext( + const authContext = await this.buildAuthContext( workspace, userWorkspaceId, apiKey, diff --git a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts index bca8e08430..9bbcef3b0e 100644 --- a/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts +++ b/packages/twenty-server/src/engine/api/rest/core/handlers/rest-api-base.handler.ts @@ -7,8 +7,6 @@ import { isDefined, } from 'twenty-shared/utils'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - import { CommonGroupByOutputItem } from 'src/engine/api/common/types/common-group-by-output-item.type'; import { CommonSelectedFields } from 'src/engine/api/common/types/common-selected-fields-result.type'; import { RestToCommonSelectedFieldsHandler } from 'src/engine/api/rest/core/rest-to-common-args-handlers/selected-fields-handler'; @@ -17,7 +15,11 @@ import { Depth } from 'src/engine/api/rest/input-request-parsers/types/depth.typ import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request'; import { ActorFromAuthContextService } from 'src/engine/core-modules/actor/services/actor-from-auth-context.service'; import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service'; +import { isApiKeyAuthContext } from 'src/engine/core-modules/auth/guards/is-api-key-auth-context.guard'; +import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard'; +import { getWorkspaceAuthContext } from 'src/engine/core-modules/auth/storage/workspace-auth-context.storage'; import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service'; +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; @@ -79,28 +81,15 @@ export abstract class RestApiBaseHandler { FormatResult | { data: FormatResult[] } | CommonGroupByOutputItem[] >; - public getAuthContextFromRequest( - request: AuthenticatedRequest, - ): WorkspaceAuthContext { - return request; - } - private getObjectsPermissions = async (authContext: WorkspaceAuthContext) => { let roleId: string; - if (isDefined(authContext.apiKey)) { + if (isApiKeyAuthContext(authContext)) { roleId = await this.apiKeyRoleService.getRoleIdForApiKeyId( authContext.apiKey.id, authContext.workspace.id, ); - } else { - if (!isDefined(authContext.userWorkspaceId)) { - throw new PermissionsException( - 'No user workspace ID found in authentication context', - PermissionsExceptionCode.NO_AUTHENTICATION_CONTEXT, - ); - } - + } else if (isUserAuthContext(authContext)) { const userWorkspaceRoleId = await this.userRoleService.getRoleIdForUserWorkspace({ userWorkspaceId: authContext.userWorkspaceId, @@ -115,6 +104,11 @@ export abstract class RestApiBaseHandler { } roleId = userWorkspaceRoleId; + } else { + throw new PermissionsException( + 'Authentication context is invalid', + PermissionsExceptionCode.NO_AUTHENTICATION_CONTEXT, + ); } const { rolesPermissions } = @@ -161,7 +155,7 @@ export abstract class RestApiBaseHandler { objectIdByNameSingular, } = await this.getObjectMetadata(request, parsedObject); - const authContext = this.getAuthContextFromRequest(request); + const authContext = getWorkspaceAuthContext(); return { authContext, diff --git a/packages/twenty-server/src/engine/api/rest/types/authenticated-request.ts b/packages/twenty-server/src/engine/api/rest/types/authenticated-request.ts index c8c7c55179..a9275783dd 100644 --- a/packages/twenty-server/src/engine/api/rest/types/authenticated-request.ts +++ b/packages/twenty-server/src/engine/api/rest/types/authenticated-request.ts @@ -1,5 +1,5 @@ import { type Request } from 'express'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; export type AuthenticatedRequest = Request & WorkspaceAuthContext; diff --git a/packages/twenty-server/src/engine/core-modules/actor/services/actor-from-auth-context.service.ts b/packages/twenty-server/src/engine/core-modules/actor/services/actor-from-auth-context.service.ts index ba2cdab51f..b87c778427 100644 --- a/packages/twenty-server/src/engine/core-modules/actor/services/actor-from-auth-context.service.ts +++ b/packages/twenty-server/src/engine/core-modules/actor/services/actor-from-auth-context.service.ts @@ -3,8 +3,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { type ActorMetadata } from 'twenty-shared/types'; import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { buildCreatedByFromApiKey } from 'src/engine/core-modules/actor/utils/build-created-by-from-api-key.util'; import { buildCreatedByFromApplication } from 'src/engine/core-modules/actor/utils/build-created-by-from-application.util'; import { buildCreatedByFromFullNameMetadata } from 'src/engine/core-modules/actor/utils/build-created-by-from-full-name-metadata.util'; diff --git a/packages/twenty-server/src/engine/core-modules/auth/guards/is-api-key-auth-context.guard.ts b/packages/twenty-server/src/engine/core-modules/auth/guards/is-api-key-auth-context.guard.ts new file mode 100644 index 0000000000..c9f31d8c3c --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/guards/is-api-key-auth-context.guard.ts @@ -0,0 +1,10 @@ +import { + type ApiKeyWorkspaceAuthContext, + type WorkspaceAuthContext, +} from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; + +export const isApiKeyAuthContext = ( + context: WorkspaceAuthContext, +): context is ApiKeyWorkspaceAuthContext => { + return context.type === 'apiKey'; +}; diff --git a/packages/twenty-server/src/engine/core-modules/auth/guards/is-application-auth-context.guard.ts b/packages/twenty-server/src/engine/core-modules/auth/guards/is-application-auth-context.guard.ts new file mode 100644 index 0000000000..18d3d68982 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/guards/is-application-auth-context.guard.ts @@ -0,0 +1,10 @@ +import { + type ApplicationWorkspaceAuthContext, + type WorkspaceAuthContext, +} from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; + +export const isApplicationAuthContext = ( + context: WorkspaceAuthContext, +): context is ApplicationWorkspaceAuthContext => { + return context.type === 'application'; +}; diff --git a/packages/twenty-server/src/engine/core-modules/auth/guards/is-pending-activation-user-auth-context.guard.ts b/packages/twenty-server/src/engine/core-modules/auth/guards/is-pending-activation-user-auth-context.guard.ts new file mode 100644 index 0000000000..234c5c76a7 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/guards/is-pending-activation-user-auth-context.guard.ts @@ -0,0 +1,10 @@ +import { + type PendingActivationUserWorkspaceAuthContext, + type WorkspaceAuthContext, +} from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; + +export const isPendingActivationUserAuthContext = ( + context: WorkspaceAuthContext, +): context is PendingActivationUserWorkspaceAuthContext => { + return context.type === 'pendingActivationUser'; +}; diff --git a/packages/twenty-server/src/engine/core-modules/auth/guards/is-system-auth-context.guard.ts b/packages/twenty-server/src/engine/core-modules/auth/guards/is-system-auth-context.guard.ts new file mode 100644 index 0000000000..ee85f50e01 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/guards/is-system-auth-context.guard.ts @@ -0,0 +1,10 @@ +import { + type SystemWorkspaceAuthContext, + type WorkspaceAuthContext, +} from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; + +export const isSystemAuthContext = ( + context: WorkspaceAuthContext, +): context is SystemWorkspaceAuthContext => { + return context.type === 'system'; +}; diff --git a/packages/twenty-server/src/engine/core-modules/auth/guards/is-user-auth-context.guard.ts b/packages/twenty-server/src/engine/core-modules/auth/guards/is-user-auth-context.guard.ts new file mode 100644 index 0000000000..50181e75f0 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/guards/is-user-auth-context.guard.ts @@ -0,0 +1,10 @@ +import { + type UserWorkspaceAuthContext, + type WorkspaceAuthContext, +} from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; + +export const isUserAuthContext = ( + context: WorkspaceAuthContext, +): context is UserWorkspaceAuthContext => { + return context.type === 'user'; +}; diff --git a/packages/twenty-server/src/engine/api/common/utils/is-workspace-auth-context.util.ts b/packages/twenty-server/src/engine/core-modules/auth/guards/is-workspace-auth-context.guard.ts similarity index 78% rename from packages/twenty-server/src/engine/api/common/utils/is-workspace-auth-context.util.ts rename to packages/twenty-server/src/engine/core-modules/auth/guards/is-workspace-auth-context.guard.ts index 6f43513ff0..70d5f74a07 100644 --- a/packages/twenty-server/src/engine/api/common/utils/is-workspace-auth-context.util.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/guards/is-workspace-auth-context.guard.ts @@ -1,8 +1,7 @@ import { isDefined } from 'twenty-shared/utils'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; export const isWorkspaceAuthContext = ( context: AuthContext, diff --git a/packages/twenty-server/src/engine/core-modules/auth/middlewares/workspace-auth-context.middleware.ts b/packages/twenty-server/src/engine/core-modules/auth/middlewares/workspace-auth-context.middleware.ts index 28a554ec49..51b9c0cbfa 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/middlewares/workspace-auth-context.middleware.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/middlewares/workspace-auth-context.middleware.ts @@ -3,9 +3,16 @@ import { Injectable, type NestMiddleware } from '@nestjs/common'; import { type NextFunction, type Request, type Response } from 'express'; import { isDefined } from 'twenty-shared/utils'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { + AuthException, + AuthExceptionCode, +} from 'src/engine/core-modules/auth/auth.exception'; import { withWorkspaceAuthContext } from 'src/engine/core-modules/auth/storage/workspace-auth-context.storage'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; +import { buildApiKeyAuthContext } from 'src/engine/core-modules/auth/utils/build-api-key-auth-context.util'; +import { buildApplicationAuthContext } from 'src/engine/core-modules/auth/utils/build-application-auth-context.util'; +import { buildPendingActivationUserAuthContext } from 'src/engine/core-modules/auth/utils/build-pending-activation-user-auth-context.util'; +import { buildUserAuthContext } from 'src/engine/core-modules/auth/utils/build-user-auth-context.util'; @Injectable() export class WorkspaceAuthContextMiddleware implements NestMiddleware { @@ -16,18 +23,54 @@ export class WorkspaceAuthContextMiddleware implements NestMiddleware { return; } - const authContext: WorkspaceAuthContext = { - user: req.user, - workspace: req.workspace, - workspaceMemberId: req.workspaceMemberId, - workspaceMember: req.workspaceMember, - userWorkspaceId: req.userWorkspaceId, - apiKey: req.apiKey, - application: req.application, - } as WorkspaceAuthContext; + const authContext = this.buildAuthContext(req); withWorkspaceAuthContext(authContext, () => { next(); }); } + + private buildAuthContext(req: Request): WorkspaceAuthContext { + if (isDefined(req.apiKey)) { + return buildApiKeyAuthContext({ + workspace: req.workspace!, + apiKey: req.apiKey, + }); + } + + if (isDefined(req.application)) { + return buildApplicationAuthContext({ + workspace: req.workspace!, + application: req.application, + }); + } + + if ( + isDefined(req.userWorkspaceId) && + isDefined(req.workspaceMemberId) && + isDefined(req.workspaceMember) && + isDefined(req.user) + ) { + return buildUserAuthContext({ + workspace: req.workspace!, + userWorkspaceId: req.userWorkspaceId, + user: req.user, + workspaceMemberId: req.workspaceMemberId, + workspaceMember: req.workspaceMember, + }); + } + + if (isDefined(req.userWorkspaceId) && isDefined(req.user)) { + return buildPendingActivationUserAuthContext({ + workspace: req.workspace!, + userWorkspaceId: req.userWorkspaceId, + user: req.user, + }); + } + + throw new AuthException( + 'No authentication context found', + AuthExceptionCode.UNAUTHENTICATED, + ); + } } diff --git a/packages/twenty-server/src/engine/core-modules/auth/storage/workspace-auth-context.storage.ts b/packages/twenty-server/src/engine/core-modules/auth/storage/workspace-auth-context.storage.ts index cb2a19bb4e..a2edaa7405 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/storage/workspace-auth-context.storage.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/storage/workspace-auth-context.storage.ts @@ -1,6 +1,6 @@ import { AsyncLocalStorage } from 'async_hooks'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; export const workspaceAuthContextStorage = new AsyncLocalStorage(); diff --git a/packages/twenty-server/src/engine/core-modules/auth/types/workspace-auth-context.type.ts b/packages/twenty-server/src/engine/core-modules/auth/types/workspace-auth-context.type.ts new file mode 100644 index 0000000000..3b22469891 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/types/workspace-auth-context.type.ts @@ -0,0 +1,51 @@ +import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; + +export type WorkspaceAuthContextType = + | 'system' + | 'user' + | 'apiKey' + | 'application' + | 'pendingActivationUser'; + +interface BaseWorkspaceAuthContext { + type: WorkspaceAuthContextType; + workspace: NonNullable; + workspaceMetadataVersion?: string; +} + +export interface ApiKeyWorkspaceAuthContext extends BaseWorkspaceAuthContext { + type: 'apiKey'; + apiKey: NonNullable; +} + +export interface UserWorkspaceAuthContext extends BaseWorkspaceAuthContext { + type: 'user'; + userWorkspaceId: NonNullable; + user: NonNullable; + workspaceMemberId: NonNullable; + workspaceMember: NonNullable; +} + +export interface ApplicationWorkspaceAuthContext + extends BaseWorkspaceAuthContext { + type: 'application'; + application: NonNullable; +} + +export interface SystemWorkspaceAuthContext extends BaseWorkspaceAuthContext { + type: 'system'; +} + +export interface PendingActivationUserWorkspaceAuthContext + extends BaseWorkspaceAuthContext { + type: 'pendingActivationUser'; + userWorkspaceId: NonNullable; + user: NonNullable; +} + +export type WorkspaceAuthContext = + | ApiKeyWorkspaceAuthContext + | UserWorkspaceAuthContext + | ApplicationWorkspaceAuthContext + | SystemWorkspaceAuthContext + | PendingActivationUserWorkspaceAuthContext; diff --git a/packages/twenty-server/src/engine/core-modules/auth/utils/build-api-key-auth-context.util.ts b/packages/twenty-server/src/engine/core-modules/auth/utils/build-api-key-auth-context.util.ts new file mode 100644 index 0000000000..6bee9becbe --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/utils/build-api-key-auth-context.util.ts @@ -0,0 +1,19 @@ +import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { type ApiKeyWorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; + +type ApiKeyAuthContextInput = { + workspace: NonNullable; + apiKey: NonNullable; + workspaceMetadataVersion?: string; +}; + +export const buildApiKeyAuthContext = ( + input: ApiKeyAuthContextInput, +): ApiKeyWorkspaceAuthContext => { + return { + type: 'apiKey', + workspace: input.workspace, + apiKey: input.apiKey, + workspaceMetadataVersion: input.workspaceMetadataVersion, + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/auth/utils/build-application-auth-context.util.ts b/packages/twenty-server/src/engine/core-modules/auth/utils/build-application-auth-context.util.ts new file mode 100644 index 0000000000..b1bb679d82 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/utils/build-application-auth-context.util.ts @@ -0,0 +1,19 @@ +import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { type ApplicationWorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; + +type ApplicationAuthContextInput = { + workspace: NonNullable; + application: NonNullable; + workspaceMetadataVersion?: string; +}; + +export const buildApplicationAuthContext = ( + input: ApplicationAuthContextInput, +): ApplicationWorkspaceAuthContext => { + return { + type: 'application', + workspace: input.workspace, + application: input.application, + workspaceMetadataVersion: input.workspaceMetadataVersion, + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/auth/utils/build-pending-activation-user-auth-context.util.ts b/packages/twenty-server/src/engine/core-modules/auth/utils/build-pending-activation-user-auth-context.util.ts new file mode 100644 index 0000000000..218a497d25 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/utils/build-pending-activation-user-auth-context.util.ts @@ -0,0 +1,21 @@ +import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { type PendingActivationUserWorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; + +type PendingActivationUserAuthContextInput = { + workspace: NonNullable; + userWorkspaceId: NonNullable; + user: NonNullable; + workspaceMetadataVersion?: string; +}; + +export const buildPendingActivationUserAuthContext = ( + input: PendingActivationUserAuthContextInput, +): PendingActivationUserWorkspaceAuthContext => { + return { + type: 'pendingActivationUser', + workspace: input.workspace, + userWorkspaceId: input.userWorkspaceId, + user: input.user, + workspaceMetadataVersion: input.workspaceMetadataVersion, + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/auth/utils/build-system-auth-context.util.ts b/packages/twenty-server/src/engine/core-modules/auth/utils/build-system-auth-context.util.ts new file mode 100644 index 0000000000..4d2ecb177a --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/utils/build-system-auth-context.util.ts @@ -0,0 +1,17 @@ +import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { type SystemWorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; + +type SystemAuthContextInput = { + workspace: NonNullable; + workspaceMetadataVersion?: string; +}; + +export const buildSystemAuthContext = ( + input: SystemAuthContextInput, +): SystemWorkspaceAuthContext => { + return { + type: 'system', + workspace: input.workspace, + workspaceMetadataVersion: input.workspaceMetadataVersion, + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/auth/utils/build-user-auth-context.util.ts b/packages/twenty-server/src/engine/core-modules/auth/utils/build-user-auth-context.util.ts new file mode 100644 index 0000000000..59efd68fc4 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/utils/build-user-auth-context.util.ts @@ -0,0 +1,25 @@ +import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { type UserWorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; + +type UserAuthContextInput = { + workspace: NonNullable; + userWorkspaceId: NonNullable; + user: NonNullable; + workspaceMemberId: NonNullable; + workspaceMember: NonNullable; + workspaceMetadataVersion?: string; +}; + +export const buildUserAuthContext = ( + input: UserAuthContextInput, +): UserWorkspaceAuthContext => { + return { + type: 'user', + workspace: input.workspace, + userWorkspaceId: input.userWorkspaceId, + user: input.user, + workspaceMemberId: input.workspaceMemberId, + workspaceMember: input.workspaceMember, + workspaceMetadataVersion: input.workspaceMetadataVersion, + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/services/common-api-context-builder.service.ts b/packages/twenty-server/src/engine/core-modules/record-crud/services/common-api-context-builder.service.ts index 42cef41ebc..7f26db9343 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/services/common-api-context-builder.service.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/services/common-api-context-builder.service.ts @@ -3,12 +3,14 @@ import { Injectable } from '@nestjs/common'; import { type ObjectsPermissions } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - import { type CommonBaseQueryRunnerContext } from 'src/engine/api/common/types/common-base-query-runner-context.type'; import { type CommonSelectedFields } from 'src/engine/api/common/types/common-selected-fields-result.type'; import { getAllSelectableFields } from 'src/engine/api/rest/core/rest-to-common-args-handlers/utils/get-all-selectable-fields.util'; import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service'; +import { isApiKeyAuthContext } from 'src/engine/core-modules/auth/guards/is-api-key-auth-context.guard'; +import { isApplicationAuthContext } from 'src/engine/core-modules/auth/guards/is-application-auth-context.guard'; +import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { RecordCrudException, RecordCrudExceptionCode, @@ -117,14 +119,17 @@ export class CommonApiContextBuilderService { const workspaceId = authContext.workspace.id; let roleId: string; - if (isDefined(authContext.apiKey)) { + if (isApiKeyAuthContext(authContext)) { roleId = await this.apiKeyRoleService.getRoleIdForApiKeyId( authContext.apiKey.id, workspaceId, ); - } else if (isDefined(authContext.application?.defaultLogicFunctionRoleId)) { + } else if ( + isApplicationAuthContext(authContext) && + isDefined(authContext.application.defaultLogicFunctionRoleId) + ) { roleId = authContext.application.defaultLogicFunctionRoleId; - } else if (isDefined(authContext.userWorkspaceId)) { + } else if (isUserAuthContext(authContext)) { const userWorkspaceRoleId = await this.userRoleService.getRoleIdForUserWorkspace({ userWorkspaceId: authContext.userWorkspaceId, diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/types/record-crud-execution-context.type.ts b/packages/twenty-server/src/engine/core-modules/record-crud/types/record-crud-execution-context.type.ts index 02160d92cb..b3c27d4cc0 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/types/record-crud-execution-context.type.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/types/record-crud-execution-context.type.ts @@ -1,5 +1,4 @@ -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; export type RecordCrudExecutionContext = { diff --git a/packages/twenty-server/src/engine/core-modules/tool-generator/types/tool-generator.types.ts b/packages/twenty-server/src/engine/core-modules/tool-generator/types/tool-generator.types.ts index 20800e5854..6e421da62b 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-generator/types/tool-generator.types.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-generator/types/tool-generator.types.ts @@ -4,8 +4,7 @@ import { type RestrictedFieldsPermissions, } from 'twenty-shared/types'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { type ObjectMetadataForToolSchema } from 'src/engine/core-modules/record-crud/types/object-metadata-for-tool-schema.type'; import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/interfaces/tool-provider.interface.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/interfaces/tool-provider.interface.ts index b7045307b1..03e70bca12 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/interfaces/tool-provider.interface.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/interfaces/tool-provider.interface.ts @@ -2,8 +2,7 @@ import { type ToolSet } from 'ai'; import { type CodeExecutionData } from 'twenty-shared/ai'; import { type ActorMetadata } from 'twenty-shared/types'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { type ToolCategory } from 'src/engine/core-modules/tool-provider/enums/tool-category.enum'; import { type ToolType } from 'src/engine/core-modules/tool/enums/tool-type.enum'; import { type FlatAgentWithRoleId } from 'src/engine/metadata-modules/flat-agent/types/flat-agent.type'; diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts index 3d1bafb06b..d7a2a56d9e 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/providers/database-tool.provider.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { type ToolSet } from 'ai'; import { @@ -6,20 +7,28 @@ import { type ObjectsPermissionsByRoleId, } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; import { type ToolProvider, type ToolProviderContext, } from 'src/engine/core-modules/tool-provider/interfaces/tool-provider.interface'; import { getFlatFieldsFromFlatObjectMetadata } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-flat-fields-for-flat-object-metadata.util'; +import { + AuthException, + AuthExceptionCode, +} from 'src/engine/core-modules/auth/auth.exception'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; +import { buildUserAuthContext } from 'src/engine/core-modules/auth/utils/build-user-auth-context.util'; import { CreateRecordService } from 'src/engine/core-modules/record-crud/services/create-record.service'; import { DeleteRecordService } from 'src/engine/core-modules/record-crud/services/delete-record.service'; import { FindRecordsService } from 'src/engine/core-modules/record-crud/services/find-records.service'; import { UpdateRecordService } from 'src/engine/core-modules/record-crud/services/update-record.service'; import { createDirectRecordToolsFactory } from 'src/engine/core-modules/record-crud/tool-factory/direct-record-tools.factory'; import { ToolCategory } from 'src/engine/core-modules/tool-provider/enums/tool-category.enum'; +import { UserEntity } from 'src/engine/core-modules/user/user.entity'; +import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { isWorkflowRelatedObject } from 'src/engine/metadata-modules/ai/ai-agent/utils/is-workflow-related-object.util'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { computePermissionIntersection } from 'src/engine/twenty-orm/utils/compute-permission-intersection.util'; @@ -36,6 +45,8 @@ export class DatabaseToolProvider implements ToolProvider { private readonly updateRecordService: UpdateRecordService, private readonly deleteRecordService: DeleteRecordService, private readonly findRecordsService: FindRecordsService, + @InjectRepository(UserEntity) + private readonly userRepository: Repository, ) {} async isAvailable(_context: ToolProviderContext): Promise { @@ -46,22 +57,49 @@ export class DatabaseToolProvider implements ToolProvider { async generateTools(context: ToolProviderContext): Promise { const tools: ToolSet = {}; - // Build authContext from available context info - // userWorkspaceId is required for user-based tool generation - if (!context.userWorkspaceId) { + // Both userId and userWorkspaceId are required for user-based tool generation + if (!isDefined(context.userId) || !isDefined(context.userWorkspaceId)) { return tools; } - const authContext: WorkspaceAuthContext = { - user: context.userId ? { id: context.userId } : null, - workspace: { - id: context.workspaceId, - } as WorkspaceAuthContext['workspace'], - workspaceMemberId: undefined, + const user = await this.userRepository.findOne({ + where: { + id: context.userId, + }, + }); + + if (!isDefined(user)) { + throw new AuthException( + 'User not found', + AuthExceptionCode.UNAUTHENTICATED, + ); + } + + const { flatWorkspaceMemberMaps } = + await this.workspaceCacheService.getOrRecompute(context.workspaceId, [ + 'flatWorkspaceMemberMaps', + ]); + + const workspaceMemberId = flatWorkspaceMemberMaps.idByUserId[user.id]; + + const workspaceMember = isDefined(workspaceMemberId) + ? flatWorkspaceMemberMaps.byId[workspaceMemberId] + : undefined; + + if (!isDefined(workspaceMemberId) || !isDefined(workspaceMember)) { + throw new AuthException( + 'Workspace member not found', + AuthExceptionCode.UNAUTHENTICATED, + ); + } + + const authContext: WorkspaceAuthContext = buildUserAuthContext({ + workspace: { id: context.workspaceId } as WorkspaceEntity, userWorkspaceId: context.userWorkspaceId, - apiKey: null, - application: null, - } as WorkspaceAuthContext; + user, + workspaceMemberId, + workspaceMember, + }); const { rolesPermissions } = await this.workspaceCacheService.getOrRecompute(context.workspaceId, [ diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/resolvers/tool-index.resolver.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/resolvers/tool-index.resolver.ts index 6884216e9d..854ff0452e 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/resolvers/tool-index.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/resolvers/tool-index.resolver.ts @@ -4,8 +4,10 @@ import { Field, ObjectType, Query, Resolver } from '@nestjs/graphql'; import graphqlTypeJson from 'graphql-type-json'; import { ToolRegistryService } from 'src/engine/core-modules/tool-provider/services/tool-registry.service'; +import { UserEntity } from 'src/engine/core-modules/user/user.entity'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator'; +import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; @@ -40,6 +42,7 @@ export class ToolIndexResolver { @Query(() => [ToolIndexEntryDTO]) @UseGuards(NoPermissionGuard) async getToolIndex( + @AuthUser({ allowUndefined: true }) user: UserEntity | undefined, @AuthWorkspace() workspace: WorkspaceEntity, @AuthUserWorkspaceId() userWorkspaceId: string, ): Promise { @@ -53,6 +56,7 @@ export class ToolIndexResolver { } return this.toolRegistryService.buildToolIndex(workspace.id, roleId, { + userId: user?.id, userWorkspaceId, }); } diff --git a/packages/twenty-server/src/engine/core-modules/tool-provider/tool-provider.module.ts b/packages/twenty-server/src/engine/core-modules/tool-provider/tool-provider.module.ts index 5847765bf6..83b2daedb1 100644 --- a/packages/twenty-server/src/engine/core-modules/tool-provider/tool-provider.module.ts +++ b/packages/twenty-server/src/engine/core-modules/tool-provider/tool-provider.module.ts @@ -1,4 +1,5 @@ import { forwardRef, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { RecordCrudModule } from 'src/engine/core-modules/record-crud/record-crud.module'; import { ToolGeneratorModule } from 'src/engine/core-modules/tool-generator/tool-generator.module'; @@ -12,6 +13,7 @@ import { LogicFunctionToolProvider } from 'src/engine/core-modules/tool-provider import { ViewToolProvider } from 'src/engine/core-modules/tool-provider/providers/view-tool.provider'; import { WorkflowToolProvider } from 'src/engine/core-modules/tool-provider/providers/workflow-tool.provider'; import { ToolModule } from 'src/engine/core-modules/tool/tool.module'; +import { UserEntity } from 'src/engine/core-modules/user/user.entity'; import { AiAgentExecutionModule } from 'src/engine/metadata-modules/ai/ai-agent-execution/ai-agent-execution.module'; import { AiModelsModule } from 'src/engine/metadata-modules/ai/ai-models/ai-models.module'; import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; @@ -46,6 +48,7 @@ import { ToolRegistryService } from './services/tool-registry.service'; WorkspaceManyOrAllFlatEntityMapsCacheModule, LogicFunctionModule, UserRoleModule, + TypeOrmModule.forFeature([UserEntity]), ], providers: [ ToolIndexResolver, diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts index f2117c1107..1d92856142 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent-execution/services/agent-async-executor.service.ts @@ -9,11 +9,13 @@ import { type ToolSet, } from 'ai'; import { type ActorMetadata } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; import { type Repository } from 'typeorm'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; import { type ToolProviderContext } from 'src/engine/core-modules/tool-provider/interfaces/tool-provider.interface'; +import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { ToolCategory } from 'src/engine/core-modules/tool-provider/enums/tool-category.enum'; import { ToolRegistryService } from 'src/engine/core-modules/tool-provider/services/tool-registry.service'; import { type AgentExecutionResult } from 'src/engine/metadata-modules/ai/ai-agent-execution/types/agent-execution-result.type'; @@ -130,8 +132,14 @@ export class AgentAsyncExecutorService { authContext, actorContext, agent: agent as unknown as ToolProviderContext['agent'], - userId: authContext?.user?.id, - userWorkspaceId: authContext?.userWorkspaceId, + userId: + isDefined(authContext) && isUserAuthContext(authContext) + ? authContext.user.id + : undefined, + userWorkspaceId: + isDefined(authContext) && isUserAuthContext(authContext) + ? authContext.userWorkspaceId + : undefined, }, { categories: [ diff --git a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.resolver.ts index f7bfd2098c..731d029a38 100644 --- a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.resolver.ts @@ -11,8 +11,7 @@ import { import { isDefined } from 'twenty-shared/utils'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity'; import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; diff --git a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts index 584bb39609..de811606a9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/navigation-menu-item/navigation-menu-item.service.ts @@ -2,10 +2,12 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'twenty-shared/utils'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service'; import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { isApiKeyAuthContext } from 'src/engine/core-modules/auth/guards/is-api-key-auth-context.guard'; +import { isApplicationAuthContext } from 'src/engine/core-modules/auth/guards/is-application-auth-context.guard'; +import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { getRecordDisplayName } from 'src/engine/core-modules/record-crud/utils/get-record-display-name.util'; import { getRecordImageIdentifier } from 'src/engine/core-modules/record-crud/utils/get-record-image-identifier.util'; @@ -356,18 +358,21 @@ export class NavigationMenuItemService { authContext: WorkspaceAuthContext, workspaceId: string, ): Promise { - if (isDefined(authContext.apiKey)) { + if (isApiKeyAuthContext(authContext)) { return this.apiKeyRoleService.getRoleIdForApiKeyId( authContext.apiKey.id, workspaceId, ); } - if (isDefined(authContext.application?.defaultLogicFunctionRoleId)) { + if ( + isApplicationAuthContext(authContext) && + isDefined(authContext.application.defaultLogicFunctionRoleId) + ) { return authContext.application.defaultLogicFunctionRoleId; } - if (isDefined(authContext.userWorkspaceId)) { + if (isUserAuthContext(authContext)) { try { return await this.userRoleService.getRoleIdForUserWorkspace({ userWorkspaceId: authContext.userWorkspaceId, diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts index cf71b44537..1ce5d393ab 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts @@ -6,9 +6,9 @@ import { EntityManager } from 'typeorm'; import { EntityPersistExecutor } from 'typeorm/persistence/EntityPersistExecutor'; import { PlainObjectToDatabaseEntityTransformer } from 'typeorm/query-builder/transformer/PlainObjectToDatabaseEntityTransformer'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts index e75c4969e4..d0a74ae3bd 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts @@ -33,10 +33,10 @@ import { PlainObjectToDatabaseEntityTransformer } from 'typeorm/query-builder/tr import { type UpsertOptions } from 'typeorm/repository/UpsertOptions'; import { InstanceChecker } from 'typeorm/util/InstanceChecker'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { InternalServerError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; diff --git a/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.ts b/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.ts index 40ec2d4406..732261d74c 100644 --- a/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.ts +++ b/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.ts @@ -13,9 +13,9 @@ import { import { EntityManagerFactory } from 'typeorm/entity-manager/EntityManagerFactory'; import { EntityMetadataNotFoundError } from 'typeorm/error/EntityMetadataNotFoundError'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { PermissionsException, PermissionsExceptionCode, diff --git a/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager.ts b/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager.ts index 3f9bee8389..ff36091fb5 100644 --- a/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager.ts +++ b/packages/twenty-server/src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager.ts @@ -2,8 +2,7 @@ import { Injectable, type Type } from '@nestjs/common'; import { type ObjectLiteral } from 'typeorm'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { getWorkspaceAuthContext } from 'src/engine/core-modules/auth/storage/workspace-auth-context.storage'; import { buildObjectIdByNameMaps } from 'src/engine/metadata-modules/flat-object-metadata/utils/build-object-id-by-name-maps.util'; import { GlobalWorkspaceDataSource } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource'; diff --git a/packages/twenty-server/src/engine/twenty-orm/storage/orm-workspace-context.storage.ts b/packages/twenty-server/src/engine/twenty-orm/storage/orm-workspace-context.storage.ts index b85f985e0a..6b31f15a41 100644 --- a/packages/twenty-server/src/engine/twenty-orm/storage/orm-workspace-context.storage.ts +++ b/packages/twenty-server/src/engine/twenty-orm/storage/orm-workspace-context.storage.ts @@ -3,8 +3,7 @@ import { AsyncLocalStorage } from 'async_hooks'; import { type ObjectsPermissionsByRoleId } from 'twenty-shared/types'; import { type EntityMetadata } from 'typeorm'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { type FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/build-system-auth-context.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/build-system-auth-context.util.ts index 1f48ec8d39..dd7a533b39 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/build-system-auth-context.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/build-system-auth-context.util.ts @@ -1,15 +1,13 @@ -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; +import { type SystemWorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; +import { buildSystemAuthContext as buildSystemAuthContextUtil } from 'src/engine/core-modules/auth/utils/build-system-auth-context.util'; +import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; // Builds a minimal WorkspaceAuthContext for system operations (jobs, commands, crons) // that don't have a user context but need to operate on a workspace export const buildSystemAuthContext = ( workspaceId: string, -): WorkspaceAuthContext => { - return { - user: null, - workspace: { id: workspaceId } as WorkspaceAuthContext['workspace'], - workspaceMemberId: undefined, - userWorkspaceId: undefined, - apiKey: null, - } as WorkspaceAuthContext; +): SystemWorkspaceAuthContext => { + return buildSystemAuthContextUtil({ + workspace: { id: workspaceId } as WorkspaceEntity, + }); }; diff --git a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts b/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts index 95e9c13738..638903fa7f 100644 --- a/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts +++ b/packages/twenty-server/src/modules/connected-account/query-hooks/connected-account-delete-one.pre-query.hook.ts @@ -3,8 +3,8 @@ import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata'; import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { type DeleteOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; diff --git a/packages/twenty-server/src/modules/dashboard/services/dashboard-duplication.service.ts b/packages/twenty-server/src/modules/dashboard/services/dashboard-duplication.service.ts index ce72656ebb..c53af593e8 100644 --- a/packages/twenty-server/src/modules/dashboard/services/dashboard-duplication.service.ts +++ b/packages/twenty-server/src/modules/dashboard/services/dashboard-duplication.service.ts @@ -6,8 +6,7 @@ import { isDefined, } from 'twenty-shared/utils'; -import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { ActorFromAuthContextService } from 'src/engine/core-modules/actor/services/actor-from-auth-context.service'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; diff --git a/packages/twenty-server/src/modules/note/query-hooks/note-post-query-hook.service.ts b/packages/twenty-server/src/modules/note/query-hooks/note-post-query-hook.service.ts index a416acbc4e..df29db8038 100644 --- a/packages/twenty-server/src/modules/note/query-hooks/note-post-query-hook.service.ts +++ b/packages/twenty-server/src/modules/note/query-hooks/note-post-query-hook.service.ts @@ -3,8 +3,7 @@ import { Injectable } from '@nestjs/common'; import { assertIsDefinedOrThrow } from 'twenty-shared/utils'; import { In } from 'typeorm'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; diff --git a/packages/twenty-server/src/modules/task/query-hooks/task-post-query-hook.service.ts b/packages/twenty-server/src/modules/task/query-hooks/task-post-query-hook.service.ts index a5ed6abcc0..6f296b3f0d 100644 --- a/packages/twenty-server/src/modules/task/query-hooks/task-post-query-hook.service.ts +++ b/packages/twenty-server/src/modules/task/query-hooks/task-post-query-hook.service.ts @@ -3,8 +3,7 @@ import { Injectable } from '@nestjs/common'; import { assertIsDefinedOrThrow } from 'twenty-shared/utils'; import { In } from 'typeorm'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts index a862972630..fd7e0a0ae2 100644 --- a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-many.post-query.hook.ts @@ -1,8 +1,8 @@ import { assertIsDefinedOrThrow } from 'twenty-shared/utils'; import { type WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; diff --git a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts index 9843f602e6..5f2593046b 100644 --- a/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts +++ b/packages/twenty-server/src/modules/workflow/common/query-hooks/workflow-create-one.post-query.hook.ts @@ -1,8 +1,8 @@ import { assertIsDefinedOrThrow } from 'twenty-shared/utils'; import { type WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/services/workflow-execution-context.service.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/services/workflow-execution-context.service.ts index 4578646a4c..3ba4b2c8e1 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/services/workflow-execution-context.service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/services/workflow-execution-context.service.ts @@ -3,8 +3,9 @@ import { Injectable } from '@nestjs/common'; import { FieldActorSource } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; +import { buildApplicationAuthContext } from 'src/engine/core-modules/auth/utils/build-application-auth-context.util'; +import { buildUserAuthContext } from 'src/engine/core-modules/auth/utils/build-user-auth-context.util'; import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; import { ADMIN_ROLE } from 'src/engine/metadata-modules/role/constants/admin-role'; @@ -70,14 +71,13 @@ export class WorkflowExecutionContextService { workspaceId, }); - const authContext = { - user: userWorkspace.user, - apiKey: null, - application: null, + const authContext: WorkspaceAuthContext = buildUserAuthContext({ workspace: userWorkspace.workspace, - workspaceMemberId: workspaceMember.id, userWorkspaceId: userWorkspace.id, - } as WorkspaceAuthContext; + user: userWorkspace.user, + workspaceMemberId: workspaceMember.id, + workspaceMember, + }); return { isActingOnBehalfOfUser: true, @@ -114,17 +114,13 @@ export class WorkflowExecutionContextService { ? { unionOf: [roleId] } : { shouldBypassPermissionChecks: true as const }; - const authContext = { - user: null, - apiKey: null, + const authContext: WorkspaceAuthContext = buildApplicationAuthContext({ + workspace, application: { ...application, defaultLogicFunctionRoleId: roleId, }, - workspace, - workspaceMemberId: undefined, - userWorkspaceId: undefined, - } as WorkspaceAuthContext; + }); return { isActingOnBehalfOfUser: false, diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-execution-context.type.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-execution-context.type.ts index f91457ed94..2b46ebe8d5 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-execution-context.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/types/workflow-execution-context.type.ts @@ -1,7 +1,6 @@ import { type ActorMetadata } from 'twenty-shared/types'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; - +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; export type WorkflowExecutionContext = { diff --git a/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-one.post-query.hook.ts b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-one.post-query.hook.ts index 8c6c41bb89..557feb3265 100644 --- a/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-one.post-query.hook.ts +++ b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-delete-one.post-query.hook.ts @@ -4,8 +4,8 @@ import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils'; import { Repository } from 'typeorm'; import { type WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; -import { type WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';