11744 emails broken image in emails (#12265)
- refactor file tokens - update file token management - generate one token per file per workspaceId - move token from query params to url path
This commit is contained in:
+10
-4
@@ -1,8 +1,11 @@
|
||||
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 {
|
||||
@@ -15,12 +18,15 @@ export class DeletedWorkspaceMemberTranspiler {
|
||||
workspaceMember: Pick<WorkspaceMemberWorkspaceEntity, 'avatarUrl' | 'id'>;
|
||||
workspaceId: string;
|
||||
}): string {
|
||||
const avatarUrlToken = this.fileService.encodeFileToken({
|
||||
workspaceMemberId: workspaceMember.id,
|
||||
workspaceId: workspaceId,
|
||||
const signedPayload = this.fileService.encodeFileToken({
|
||||
filename: extractFilenameFromPath(workspaceMember.avatarUrl),
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return `${workspaceMember.avatarUrl}?token=${avatarUrlToken}`;
|
||||
return buildSignedPath({
|
||||
path: workspaceMember.avatarUrl,
|
||||
token: signedPayload,
|
||||
});
|
||||
}
|
||||
|
||||
toDeletedWorkspaceMemberDto(
|
||||
|
||||
@@ -14,6 +14,7 @@ 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';
|
||||
|
||||
@@ -52,6 +53,8 @@ import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { SignedFileDTO } from 'src/engine/core-modules/file/file-upload/dtos/signed-file.dto';
|
||||
import { extractFilenameFromPath } from 'src/engine/core-modules/file/utils/extract-file-id-from-path.utils';
|
||||
|
||||
const getHMACKey = (email?: string, key?: string | null) => {
|
||||
if (!email || !key) return null;
|
||||
@@ -186,11 +189,14 @@ export class UserResolver {
|
||||
|
||||
if (workspaceMember && workspaceMember.avatarUrl) {
|
||||
const avatarUrlToken = this.fileService.encodeFileToken({
|
||||
workspaceMemberId: workspaceMember.id,
|
||||
filename: extractFilenameFromPath(workspaceMember.avatarUrl),
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
workspaceMember.avatarUrl = `${workspaceMember.avatarUrl}?token=${avatarUrlToken}`;
|
||||
workspaceMember.avatarUrl = buildSignedPath({
|
||||
path: workspaceMember.avatarUrl,
|
||||
token: avatarUrlToken,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO Refactor to be transpiled to WorkspaceMember instead
|
||||
@@ -235,11 +241,14 @@ export class UserResolver {
|
||||
for (const workspaceMemberEntity of workspaceMemberEntities) {
|
||||
if (workspaceMemberEntity.avatarUrl) {
|
||||
const avatarUrlToken = this.fileService.encodeFileToken({
|
||||
workspaceMemberId: workspaceMemberEntity.id,
|
||||
filename: extractFilenameFromPath(workspaceMemberEntity.avatarUrl),
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
workspaceMemberEntity.avatarUrl = `${workspaceMemberEntity.avatarUrl}?token=${avatarUrlToken}`;
|
||||
workspaceMemberEntity.avatarUrl = buildSignedPath({
|
||||
path: workspaceMemberEntity.avatarUrl,
|
||||
token: avatarUrlToken,
|
||||
});
|
||||
}
|
||||
|
||||
// TODO Refactor to be transpiled to WorkspaceMember instead
|
||||
@@ -314,13 +323,13 @@ export class UserResolver {
|
||||
return getHMACKey(parent.email, key);
|
||||
}
|
||||
|
||||
@Mutation(() => String)
|
||||
@Mutation(() => SignedFileDTO)
|
||||
async uploadProfilePicture(
|
||||
@AuthUser() { id }: User,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, filename, mimetype }: FileUpload,
|
||||
): Promise<string> {
|
||||
): Promise<SignedFileDTO> {
|
||||
if (!id) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
@@ -329,7 +338,7 @@ export class UserResolver {
|
||||
const buffer = await streamToBuffer(stream);
|
||||
const fileFolder = FileFolder.ProfilePicture;
|
||||
|
||||
const { paths } = await this.fileUploadService.uploadImage({
|
||||
const { files } = await this.fileUploadService.uploadImage({
|
||||
file: buffer,
|
||||
filename,
|
||||
mimeType: mimetype,
|
||||
@@ -337,11 +346,11 @@ export class UserResolver {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const fileToken = this.fileService.encodeFileToken({
|
||||
workspaceId: workspaceId,
|
||||
});
|
||||
if (!files.length) {
|
||||
throw new Error('Failed to upload profile picture');
|
||||
}
|
||||
|
||||
return `${paths[0]}?token=${fileToken}`;
|
||||
return files[0];
|
||||
}
|
||||
|
||||
@Mutation(() => User)
|
||||
|
||||
Reference in New Issue
Block a user