From 69831b17ff85c7c2cdd7239292605487d54c1ebe Mon Sep 17 00:00:00 2001 From: martmull Date: Wed, 28 May 2025 13:02:27 +0200 Subject: [PATCH] Signed file follow up (#12347) --- .../activity-query-result-getter.handler.ts | 14 +++------ .../attachment-query-result-getter.handler.ts | 14 ++------- .../person-query-result-getter.handler.ts | 12 ++----- ...pace-member-query-result-getter.handler.ts | 12 ++----- .../file/services/file.service.ts | 17 ++++++++++ .../search/services/search.service.ts | 12 ++----- ...ted-workspace-member-transpiler.service.ts | 31 +++++++------------ .../engine/core-modules/user/user.resolver.ts | 20 +++--------- .../workspace-invitation.resolver.ts | 12 ++----- .../workspace/workspace.resolver.ts | 21 +++---------- .../metadata-modules/role/role.resolver.ts | 12 ++----- 11 files changed, 58 insertions(+), 119 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/activity-query-result-getter.handler.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/activity-query-result-getter.handler.ts index c15dc61c1..6c9c16455 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/activity-query-result-getter.handler.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/activity-query-result-getter.handler.ts @@ -1,12 +1,9 @@ -import { buildSignedPath } from 'twenty-shared/utils'; - import { QueryResultGetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.workspace-entity'; import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity'; -import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils'; // eslint-disable-next-line @typescript-eslint/no-explicit-any type RichTextBlock = Record; @@ -57,19 +54,16 @@ export class ActivityQueryResultGetterHandler imageUrl.searchParams.delete('token'); - const signedPayload = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(imageProps.url), - workspaceId: workspaceId, + const signedPath = this.fileService.signFileUrl({ + url: imageProps.url.toString(), + workspaceId, }); return { ...block, props: { ...imageProps, - url: buildSignedPath({ - path: imageUrl.toString(), - token: signedPayload, - }), + url: signedPath, }, }; }), diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/attachment-query-result-getter.handler.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/attachment-query-result-getter.handler.ts index 3632e4217..bf74a69b4 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/attachment-query-result-getter.handler.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/attachment-query-result-getter.handler.ts @@ -1,10 +1,7 @@ -import { buildSignedPath } from 'twenty-shared/utils'; - import { QueryResultGetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface'; import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity'; -import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils'; export class AttachmentQueryResultGetterHandler implements QueryResultGetterHandlerInterface @@ -19,14 +16,9 @@ export class AttachmentQueryResultGetterHandler return attachment; } - const signedPayload = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(attachment.fullPath), - workspaceId: workspaceId, - }); - - const signedPath = buildSignedPath({ - path: attachment.fullPath, - token: signedPayload, + const signedPath = this.fileService.signFileUrl({ + url: attachment.fullPath, + workspaceId, }); const fullPath = `${process.env.SERVER_URL}/files/${signedPath}`; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/person-query-result-getter.handler.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/person-query-result-getter.handler.ts index 4abafd086..004f7f904 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/person-query-result-getter.handler.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/person-query-result-getter.handler.ts @@ -1,10 +1,7 @@ -import { buildSignedPath } from 'twenty-shared/utils'; - import { QueryResultGetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface'; import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; -import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils'; export class PersonQueryResultGetterHandler implements QueryResultGetterHandlerInterface @@ -19,17 +16,14 @@ export class PersonQueryResultGetterHandler return person; } - const signedPayload = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(person.avatarUrl), + const signedPath = this.fileService.signFileUrl({ + url: person.avatarUrl, workspaceId, }); return { ...person, - avatarUrl: buildSignedPath({ - path: person.avatarUrl, - token: signedPayload, - }), + avatarUrl: signedPath, }; } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler.ts index 782888add..0068bbe1d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler.ts @@ -1,10 +1,7 @@ -import { buildSignedPath } from 'twenty-shared/utils'; - import { QueryResultGetterHandlerInterface } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/interfaces/query-result-getter-handler.interface'; import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; -import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils'; export class WorkspaceMemberQueryResultGetterHandler implements QueryResultGetterHandlerInterface @@ -19,17 +16,14 @@ export class WorkspaceMemberQueryResultGetterHandler return workspaceMember; } - const signedPayload = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(workspaceMember.avatarUrl), + const signedPath = this.fileService.signFileUrl({ + url: workspaceMember.avatarUrl, workspaceId, }); return { ...workspaceMember, - avatarUrl: buildSignedPath({ - path: workspaceMember.avatarUrl, - token: signedPayload, - }), + avatarUrl: signedPath, }; } } diff --git a/packages/twenty-server/src/engine/core-modules/file/services/file.service.ts b/packages/twenty-server/src/engine/core-modules/file/services/file.service.ts index 954909d6d..6597048dd 100644 --- a/packages/twenty-server/src/engine/core-modules/file/services/file.service.ts +++ b/packages/twenty-server/src/engine/core-modules/file/services/file.service.ts @@ -4,10 +4,13 @@ import { basename, dirname, extname } from 'path'; import { Stream } from 'stream'; import { v4 as uuidV4 } from 'uuid'; +import { buildSignedPath } from 'twenty-shared/utils'; +import { isNonEmptyString } from '@sniptt/guards'; import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; +import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils'; export type FilePayloadToEncode = { workspaceId: string; @@ -35,6 +38,20 @@ export class FileService { }); } + signFileUrl({ url, workspaceId }: { url: string; workspaceId: string }) { + if (!isNonEmptyString(url)) { + return url; + } + + return buildSignedPath({ + path: url, + token: this.encodeFileToken({ + filename: extractFilenameFromPath(url), + workspaceId, + }), + }); + } + encodeFileToken(payloadToEncode: FilePayloadToEncode) { const fileTokenExpiresIn = this.twentyConfigService.get( 'FILE_TOKEN_EXPIRES_IN', diff --git a/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts b/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts index e4e873fb7..e8e6ec11d 100644 --- a/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts +++ b/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { isNonEmptyString } from '@sniptt/guards'; import { FieldMetadataType } from 'twenty-shared/types'; -import { buildSignedPath, getLogoUrlFromDomainName } from 'twenty-shared/utils'; +import { getLogoUrlFromDomainName } from 'twenty-shared/utils'; import { Brackets, ObjectLiteral } from 'typeorm'; import chunk from 'lodash.chunk'; @@ -43,7 +43,6 @@ import { formatSearchTerms } from 'src/engine/core-modules/search/utils/format-s import { SearchArgs } from 'src/engine/core-modules/search/dtos/search-args'; import { SearchResultConnectionDTO } from 'src/engine/core-modules/search/dtos/search-result-connection.dto'; import { SearchResultEdgeDTO } from 'src/engine/core-modules/search/dtos/search-result-edge.dto'; -import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils'; import { SearchRecordDTO } from 'src/engine/core-modules/search/dtos/search-record.dto'; type LastRanks = { tsRankCD: number; tsRank: number }; @@ -366,15 +365,10 @@ export class SearchService { } private getImageUrlWithToken(avatarUrl: string, workspaceId: string): string { - const signedPayload = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(avatarUrl), + return this.fileService.signFileUrl({ + url: avatarUrl, workspaceId, }); - - return buildSignedPath({ - path: avatarUrl, - token: signedPayload, - }); } getImageIdentifierValue( diff --git a/packages/twenty-server/src/engine/core-modules/user/services/deleted-workspace-member-transpiler.service.ts b/packages/twenty-server/src/engine/core-modules/user/services/deleted-workspace-member-transpiler.service.ts index 71f7c1c96..d4d916db8 100644 --- a/packages/twenty-server/src/engine/core-modules/user/services/deleted-workspace-member-transpiler.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user/services/deleted-workspace-member-transpiler.service.ts @@ -1,11 +1,8 @@ import { Injectable } from '@nestjs/common'; -import { buildSignedPath } from 'twenty-shared/utils'; - import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { DeletedWorkspaceMember } from 'src/engine/core-modules/user/dtos/deleted-workspace-member.dto'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; -import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils'; @Injectable() export class DeletedWorkspaceMemberTranspiler { @@ -18,15 +15,10 @@ export class DeletedWorkspaceMemberTranspiler { workspaceMember: Pick; workspaceId: string; }): string { - const signedPayload = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(workspaceMember.avatarUrl), + return this.fileService.signFileUrl({ + url: workspaceMember.avatarUrl, workspaceId, }); - - return buildSignedPath({ - path: workspaceMember.avatarUrl, - token: signedPayload, - }); } toDeletedWorkspaceMemberDto( @@ -40,15 +32,16 @@ export class DeletedWorkspaceMemberTranspiler { userEmail, } = workspaceMember; - const avatarUrl = userWorkspaceId - ? this.generateSignedAvatarUrl({ - workspaceId: userWorkspaceId, - workspaceMember: { - avatarUrl: avatarUrlFromEntity, - id, - }, - }) - : null; + const avatarUrl = + userWorkspaceId && avatarUrlFromEntity + ? this.generateSignedAvatarUrl({ + workspaceId: userWorkspaceId, + workspaceMember: { + avatarUrl: avatarUrlFromEntity, + id, + }, + }) + : null; return { id, diff --git a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts index b28c282f6..2aedd49af 100644 --- a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts @@ -14,7 +14,6 @@ import crypto from 'crypto'; import { GraphQLJSONObject } from 'graphql-type-json'; import { FileUpload, GraphQLUpload } from 'graphql-upload'; import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants'; -import { buildSignedPath } from 'twenty-shared/utils'; import { WorkspaceActivationStatus } from 'twenty-shared/workspace'; import { In, Repository } from 'typeorm'; @@ -31,7 +30,6 @@ import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/service import { SignedFileDTO } from 'src/engine/core-modules/file/file-upload/dtos/signed-file.dto'; import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service'; import { FileService } from 'src/engine/core-modules/file/services/file.service'; -import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils'; import { OnboardingStatus } from 'src/engine/core-modules/onboarding/enums/onboarding-status.enum'; import { OnboardingService, @@ -220,15 +218,10 @@ export class UserResolver { ); if (workspaceMember && workspaceMember.avatarUrl) { - const avatarUrlToken = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(workspaceMember.avatarUrl), + workspaceMember.avatarUrl = this.fileService.signFileUrl({ + url: workspaceMember.avatarUrl, workspaceId: workspace.id, }); - - workspaceMember.avatarUrl = buildSignedPath({ - path: workspaceMember.avatarUrl, - token: avatarUrlToken, - }); } // TODO Refactor to be transpiled to WorkspaceMember instead @@ -272,15 +265,10 @@ export class UserResolver { for (const workspaceMemberEntity of workspaceMemberEntities) { if (workspaceMemberEntity.avatarUrl) { - const avatarUrlToken = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(workspaceMemberEntity.avatarUrl), + workspaceMemberEntity.avatarUrl = this.fileService.signFileUrl({ + url: workspaceMemberEntity.avatarUrl, workspaceId: workspace.id, }); - - workspaceMemberEntity.avatarUrl = buildSignedPath({ - path: workspaceMemberEntity.avatarUrl, - token: avatarUrlToken, - }); } // TODO Refactor to be transpiled to WorkspaceMember instead diff --git a/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts b/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts index 8cfdbfd31..2da041318 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.resolver.ts @@ -1,8 +1,6 @@ import { UseFilters, UseGuards } from '@nestjs/common'; import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'; -import { buildSignedPath } from 'twenty-shared/utils'; - import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { User } from 'src/engine/core-modules/user/user.entity'; import { SendInvitationsOutput } from 'src/engine/core-modules/workspace-invitation/dtos/send-invitations.output'; @@ -16,7 +14,6 @@ import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; -import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils'; import { SendInvitationsInput } from './dtos/send-invitations.input'; @@ -72,15 +69,10 @@ export class WorkspaceInvitationResolver { let workspaceLogoWithToken = ''; if (workspace.logo) { - const signedPayload = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(workspace.logo), + workspaceLogoWithToken = this.fileService.signFileUrl({ + url: workspace.logo, workspaceId: workspace.id, }); - - workspaceLogoWithToken = buildSignedPath({ - path: workspace.logo, - token: signedPayload, - }); } return await this.workspaceInvitationService.sendInvitations( diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts index 8cc081215..719479a7b 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.resolver.ts @@ -12,7 +12,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import assert from 'assert'; import { FileUpload, GraphQLUpload } from 'graphql-upload'; -import { buildSignedPath, isDefined } from 'twenty-shared/utils'; +import { isDefined } from 'twenty-shared/utils'; import { Repository } from 'typeorm'; import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface'; @@ -52,7 +52,6 @@ import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto'; import { RoleService } from 'src/engine/metadata-modules/role/role.service'; import { GraphqlValidationExceptionFilter } from 'src/filters/graphql-validation-exception.filter'; import { streamToBuffer } from 'src/utils/stream-to-buffer'; -import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils'; import { SignedFileDTO } from 'src/engine/core-modules/file/file-upload/dtos/signed-file.dto'; import { Workspace } from './workspace.entity'; @@ -229,15 +228,10 @@ export class WorkspaceResolver { async logo(@Parent() workspace: Workspace): Promise { if (workspace.logo) { try { - const signedPayload = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(workspace.logo), + return this.fileService.signFileUrl({ + url: workspace.logo, workspaceId: workspace.id, }); - - return buildSignedPath({ - path: workspace.logo, - token: signedPayload, - }); } catch (e) { return workspace.logo; } @@ -304,15 +298,10 @@ export class WorkspaceResolver { if (workspace.logo) { try { - const signedPayload = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(workspace.logo), + workspaceLogoWithToken = this.fileService.signFileUrl({ + url: workspace.logo, workspaceId: workspace.id, }); - - workspaceLogoWithToken = buildSignedPath({ - path: workspace.logo, - token: signedPayload, - }); } catch (e) { workspaceLogoWithToken = workspace.logo; } diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts index d2ce755ee..9b1a5997e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts @@ -8,12 +8,9 @@ import { Resolver, } from '@nestjs/graphql'; -import { buildSignedPath } from 'twenty-shared/utils'; - 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 { FileService } from 'src/engine/core-modules/file/services/file.service'; -import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -194,15 +191,10 @@ export class RoleResolver { await Promise.all( workspaceMembers.map(async (workspaceMember) => { if (workspaceMember && workspaceMember.avatarUrl) { - const avatarUrlToken = this.fileService.encodeFileToken({ - filename: extractFilenameFromPath(workspaceMember.avatarUrl), + workspaceMember.avatarUrl = this.fileService.signFileUrl({ + url: workspaceMember.avatarUrl, workspaceId: workspace.id, }); - - workspaceMember.avatarUrl = buildSignedPath({ - path: workspaceMember.avatarUrl, - token: avatarUrlToken, - }); } }), );