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 f4259404c82..f1f12b09339 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 726ae136dc1..bbea2e92035 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 ad30ae45720..ae4eb48342e 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 88773f89888..b8d266ab377 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 f5f76363967..db241baa4f5 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 0cbc56f2f4b..ca28d217a64 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 a23fecd759e..dcb53064271 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 79249e0035f..75aac10ada4 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 cd7e61812f0..b0c38ac4f75 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 d7fdce15a10..be3289162cf 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 19baa8ded29..20ecfcdd849 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 a9199fb9fbc..b82e5ea3b8c 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 c789c178fb6..b6d580644d2 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 141f0246022..92dda2807b1 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 10bcb187335..29d51490004 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 b7a077fd3fa..78d19e57f6b 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 a83ee148dd2..00000000000 --- 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 b1765695df9..af9bd8b2394 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 5cfa611d9c5..8f2463c47a1 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 0dfcf063bbf..37cd9dad8ef 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 0704e8b7bfb..12156bff7df 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 bca8e084308..9bbcef3b0e1 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 c8c7c551790..a9275783dde 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 ba2cdab51f6..b87c7784278 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 00000000000..c9f31d8c3cb --- /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 00000000000..18d3d689820 --- /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 00000000000..234c5c76a73 --- /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 00000000000..ee85f50e016 --- /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 00000000000..50181e75f04 --- /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 6f43513ff09..70d5f74a078 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 28a554ec490..51b9c0cbfac 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 cb2a19bb4ec..a2edaa74057 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 00000000000..3b224698910 --- /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 00000000000..6bee9becbef --- /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 00000000000..b1bb679d828 --- /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 00000000000..218a497d25b --- /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 00000000000..4d2ecb177ae --- /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 00000000000..59efd68fc44 --- /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 42cef41ebce..7f26db9343f 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 02160d92cb9..b3c27d4cc02 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 20800e5854b..6e421da62bb 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 b7045307b1e..03e70bca120 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 3d1bafb06b4..d7a2a56d9e2 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 6884216e9db..854ff0452e2 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 5847765bf6f..83b2daedb1e 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 f2117c1107f..1d928561429 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 f7bfd2098c7..731d029a38d 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 584bb396096..de811606a9d 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 cf71b445375..1ce5d393ab4 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 e75c4969e49..d0a74ae3bd7 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 40ec2d44061..732261d74cc 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 3f9bee83892..ff36091fb51 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 b85f985e0a0..6b31f15a412 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 1f48ec8d39d..dd7a533b394 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 95e9c137384..638903fa7f3 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 ce72656ebbc..c53af593e89 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 a416acbc4ee..df29db8038d 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 a5ed6abcc0b..6f296b3f0de 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 a862972630e..fd7e0a0ae21 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 9843f602e68..5f2593046b8 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 4578646a4c4..3ba4b2c8e15 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 f91457ed947..2b46ebe8d57 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 8c6c41bb897..557feb32654 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';