Signed file follow up (#12347)

This commit is contained in:
martmull
2025-05-28 13:02:27 +02:00
committed by GitHub
parent d4fac6793a
commit 69831b17ff
11 changed files with 58 additions and 119 deletions

View File

@ -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<string, any>;
@ -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,
},
};
}),

View File

@ -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}`;

View File

@ -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,
};
}
}

View File

@ -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,
};
}
}

View File

@ -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',

View File

@ -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(

View File

@ -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<WorkspaceMemberWorkspaceEntity, 'avatarUrl' | 'id'>;
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,

View File

@ -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

View File

@ -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(

View File

@ -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<string> {
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;
}

View File

@ -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,
});
}
}),
);