From c543716381f314977c4799038cb385bec4b5ea72 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Sun, 4 Aug 2024 15:08:25 +0200 Subject: [PATCH] Various fixes: profilePicture / logo upload, imageIdentifiers (#6530) In this PR: - refactoring auth module to extract a jwt module that can be re-used from other part of the app (avoiding circular dependencies file module => auth => file (file and auth both need jwt actually) - activating imageIdentfier on person on workspace creation (this will put back the images on people) - fixing picture upload (we were missing some fileToken) --- .../components/ProfilePictureUploader.tsx | 7 +- .../components/NavigationDrawerHeader.tsx | 5 +- .../engine/core-modules/auth/auth.module.ts | 19 +---- .../auth/services/token.service.spec.ts | 12 ++-- .../auth/services/token.service.ts | 69 +++++++++---------- .../engine/core-modules/file/file.module.ts | 6 +- .../file/guards/file-path-guard.ts | 8 +-- .../file/services/file.service.ts | 6 +- .../src/engine/core-modules/jwt/jwt.module.ts | 27 ++++++++ .../jwt/services/jwt-wrapper.service.ts | 21 ++++++ .../engine/core-modules/user/user.resolver.ts | 20 ++---- .../workspace/workspace.module.ts | 3 +- .../workspace/workspace.resolver.ts | 21 +++++- ...ync-object-metadata-identifiers.service.ts | 9 ++- .../person.workspace-entity.ts | 1 + 15 files changed, 143 insertions(+), 91 deletions(-) create mode 100644 packages/twenty-server/src/engine/core-modules/jwt/jwt.module.ts create mode 100644 packages/twenty-server/src/engine/core-modules/jwt/services/jwt-wrapper.service.ts diff --git a/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx b/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx index cef17405c..dcfcfaf5e 100644 --- a/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx +++ b/packages/twenty-front/src/modules/settings/profile/components/ProfilePictureUploader.tsx @@ -51,7 +51,7 @@ export const ProfilePictureUploader = () => { setUploadController(null); setErrorMessage(null); - const avatarUrl = result?.data?.uploadProfilePicture; + const avatarUrl = result?.data?.uploadProfilePicture.split('?')[0]; if (!avatarUrl) { throw new Error('Avatar URL not found'); @@ -64,7 +64,10 @@ export const ProfilePictureUploader = () => { }, }); - setCurrentWorkspaceMember({ ...currentWorkspaceMember, avatarUrl }); + setCurrentWorkspaceMember({ + ...currentWorkspaceMember, + avatarUrl: result?.data?.uploadProfilePicture, + }); return result; } catch (error) { diff --git a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx index ddea26d1a..d6bbf8100 100644 --- a/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/navigation-drawer/components/NavigationDrawerHeader.tsx @@ -7,6 +7,7 @@ import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/consta import { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; +import { isNonEmptyString } from '@sniptt/guards'; import { NavigationDrawerCollapseButton } from './NavigationDrawerCollapseButton'; const StyledContainer = styled.div<{ isMultiWorkspace: boolean }>` @@ -65,7 +66,9 @@ export const NavigationDrawerHeader = ({ ) : ( <> - + {name} )} diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts index 2a090c79e..064cb7aad 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.module.ts @@ -1,7 +1,6 @@ /* eslint-disable no-restricted-imports */ import { HttpModule } from '@nestjs/axios'; import { forwardRef, Module } from '@nestjs/common'; -import { JwtModule } from '@nestjs/jwt'; import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; @@ -16,13 +15,13 @@ import { SignInUpService } from 'src/engine/core-modules/auth/services/sign-in-u import { TokenService } from 'src/engine/core-modules/auth/services/token.service'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module'; +import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module'; import { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module'; import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; import { User } from 'src/engine/core-modules/user/user.entity'; import { UserModule } from 'src/engine/core-modules/user/user.module'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; -import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module'; import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module'; @@ -32,27 +31,15 @@ import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/stan import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity'; -import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars.module'; import { AuthResolver } from './auth.resolver'; import { AuthService } from './services/auth.service'; import { JwtAuthStrategy } from './strategies/jwt.auth.strategy'; -const jwtModule = JwtModule.registerAsync({ - useFactory: async (environmentService: EnvironmentService) => { - return { - secret: environmentService.get('ACCESS_TOKEN_SECRET'), - signOptions: { - expiresIn: environmentService.get('ACCESS_TOKEN_EXPIRES_IN'), - }, - }; - }, - inject: [EnvironmentService], -}); @Module({ imports: [ - jwtModule, + JwtModule, FileUploadModule, DataSourceModule, forwardRef(() => UserModule), @@ -89,6 +76,6 @@ const jwtModule = JwtModule.registerAsync({ GoogleAPIsService, AppTokenService, ], - exports: [jwtModule, TokenService], + exports: [TokenService], }) export class AuthModule {} diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/token.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/services/token.service.spec.ts index 1e5d63996..28122edc0 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/token.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/token.service.spec.ts @@ -1,25 +1,25 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { JwtService } from '@nestjs/jwt'; -import { getRepositoryToken } from '@nestjs/typeorm'; import { BadRequestException, InternalServerErrorException, NotFoundException, } from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; import crypto from 'crypto'; import { IsNull, MoreThan, Repository } from 'typeorm'; -import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { AppToken, AppTokenType, } from 'src/engine/core-modules/app-token/app-token.entity'; -import { User } from 'src/engine/core-modules/user/user.entity'; import { JwtAuthStrategy } from 'src/engine/core-modules/auth/strategies/jwt.auth.strategy'; -import { EmailService } from 'src/engine/integrations/email/email.service'; +import { User } from 'src/engine/core-modules/user/user.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { EmailService } from 'src/engine/integrations/email/email.service'; +import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { TokenService } from './token.service'; diff --git a/packages/twenty-server/src/engine/core-modules/auth/services/token.service.ts b/packages/twenty-server/src/engine/core-modules/auth/services/token.service.ts index 0216f9686..88cb7e088 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/services/token.service.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/services/token.service.ts @@ -7,50 +7,50 @@ import { UnauthorizedException, UnprocessableEntityException, } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; import { InjectRepository } from '@nestjs/typeorm'; import crypto from 'crypto'; -import { addMilliseconds, differenceInMilliseconds } from 'date-fns'; -import ms from 'ms'; -import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken'; -import { IsNull, MoreThan, Repository } from 'typeorm'; -import { Request } from 'express'; -import { ExtractJwt } from 'passport-jwt'; import { render } from '@react-email/render'; +import { addMilliseconds, differenceInMilliseconds } from 'date-fns'; +import { Request } from 'express'; +import { JsonWebTokenError, TokenExpiredError } from 'jsonwebtoken'; +import ms from 'ms'; +import { ExtractJwt } from 'passport-jwt'; import { PasswordResetLinkEmail } from 'twenty-emails'; +import { IsNull, MoreThan, Repository } from 'typeorm'; import { - JwtAuthStrategy, - JwtPayload, -} from 'src/engine/core-modules/auth/strategies/jwt.auth.strategy'; -import { assert } from 'src/utils/assert'; + AppToken, + AppTokenType, +} from 'src/engine/core-modules/app-token/app-token.entity'; +import { EmailPasswordResetLink } from 'src/engine/core-modules/auth/dto/email-password-reset-link.entity'; +import { ExchangeAuthCode } from 'src/engine/core-modules/auth/dto/exchange-auth-code.entity'; +import { ExchangeAuthCodeInput } from 'src/engine/core-modules/auth/dto/exchange-auth-code.input'; +import { InvalidatePassword } from 'src/engine/core-modules/auth/dto/invalidate-password.entity'; import { ApiKeyToken, AuthToken, AuthTokens, PasswordResetToken, } from 'src/engine/core-modules/auth/dto/token.entity'; -import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; -import { User } from 'src/engine/core-modules/user/user.entity'; -import { - AppToken, - AppTokenType, -} from 'src/engine/core-modules/app-token/app-token.entity'; import { ValidatePasswordResetToken } from 'src/engine/core-modules/auth/dto/validate-password-reset-token.entity'; -import { EmailService } from 'src/engine/integrations/email/email.service'; -import { InvalidatePassword } from 'src/engine/core-modules/auth/dto/invalidate-password.entity'; -import { EmailPasswordResetLink } from 'src/engine/core-modules/auth/dto/email-password-reset-link.entity'; +import { + JwtAuthStrategy, + JwtPayload, +} from 'src/engine/core-modules/auth/strategies/jwt.auth.strategy'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service'; +import { User } from 'src/engine/core-modules/user/user.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; -import { ExchangeAuthCodeInput } from 'src/engine/core-modules/auth/dto/exchange-auth-code.input'; -import { ExchangeAuthCode } from 'src/engine/core-modules/auth/dto/exchange-auth-code.entity'; +import { EmailService } from 'src/engine/integrations/email/email.service'; +import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; +import { assert } from 'src/utils/assert'; @Injectable() export class TokenService { constructor( - private readonly jwtService: JwtService, + private readonly jwtWrapperService: JwtWrapperService, private readonly jwtStrategy: JwtAuthStrategy, private readonly environmentService: EnvironmentService, @InjectRepository(User, 'core') @@ -90,7 +90,7 @@ export class TokenService { }; return { - token: this.jwtService.sign(jwtPayload), + token: this.jwtWrapperService.sign(jwtPayload), expiresAt, }; } @@ -116,7 +116,7 @@ export class TokenService { await this.appTokenRepository.save(refreshToken); return { - token: this.jwtService.sign(jwtPayload, { + token: this.jwtWrapperService.sign(jwtPayload, { secret, expiresIn, // Jwtid will be used to link RefreshToken entity to this token @@ -137,7 +137,7 @@ export class TokenService { }; return { - token: this.jwtService.sign(jwtPayload, { + token: this.jwtWrapperService.sign(jwtPayload, { secret, expiresIn, }), @@ -164,7 +164,7 @@ export class TokenService { }; return { - token: this.jwtService.sign(jwtPayload, { + token: this.jwtWrapperService.sign(jwtPayload, { secret, expiresIn, }), @@ -193,7 +193,7 @@ export class TokenService { } else { expiresIn = this.environmentService.get('API_TOKEN_EXPIRES_IN'); } - const token = this.jwtService.sign(jwtPayload, { + const token = this.jwtWrapperService.sign(jwtPayload, { secret, expiresIn, jwtid: apiKeyId, @@ -496,7 +496,10 @@ export class TokenService { async verifyJwt(token: string, secret?: string) { try { - return this.jwtService.verify(token, secret ? { secret } : undefined); + return this.jwtWrapperService.verify( + token, + secret ? { secret } : undefined, + ); } catch (error) { if (error instanceof TokenExpiredError) { throw new UnauthorizedException('Token has expired.'); @@ -668,12 +671,4 @@ export class TokenService { return { success: true }; } - - async encodePayload(payload: any, options?: any): Promise { - return this.jwtService.sign(payload, options); - } - - async decodePayload(payload: any, options?: any): Promise { - return this.jwtService.decode(payload, options); - } } diff --git a/packages/twenty-server/src/engine/core-modules/file/file.module.ts b/packages/twenty-server/src/engine/core-modules/file/file.module.ts index 0169906f0..dc9de78fd 100644 --- a/packages/twenty-server/src/engine/core-modules/file/file.module.ts +++ b/packages/twenty-server/src/engine/core-modules/file/file.module.ts @@ -1,15 +1,15 @@ -import { forwardRef, Module } from '@nestjs/common'; +import { Module } from '@nestjs/common'; -import { AuthModule } from 'src/engine/core-modules/auth/auth.module'; import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module'; import { FilePathGuard } from 'src/engine/core-modules/file/guards/file-path-guard'; +import { JwtModule } from 'src/engine/core-modules/jwt/jwt.module'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { FileController } from './controllers/file.controller'; import { FileService } from './services/file.service'; @Module({ - imports: [FileUploadModule, forwardRef(() => AuthModule)], + imports: [FileUploadModule, JwtModule], providers: [FileService, EnvironmentService, FilePathGuard], exports: [FileService], controllers: [FileController], diff --git a/packages/twenty-server/src/engine/core-modules/file/guards/file-path-guard.ts b/packages/twenty-server/src/engine/core-modules/file/guards/file-path-guard.ts index 082d6383c..db9f466c5 100644 --- a/packages/twenty-server/src/engine/core-modules/file/guards/file-path-guard.ts +++ b/packages/twenty-server/src/engine/core-modules/file/guards/file-path-guard.ts @@ -6,13 +6,13 @@ import { Injectable, } from '@nestjs/common'; -import { TokenService } from 'src/engine/core-modules/auth/services/token.service'; +import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; @Injectable() export class FilePathGuard implements CanActivate { constructor( - private readonly tokenService: TokenService, + private readonly jwtWrapperService: JwtWrapperService, private readonly environmentService: EnvironmentService, ) {} @@ -22,11 +22,11 @@ export class FilePathGuard implements CanActivate { if (query && query['token']) { const payloadToDecode = query['token']; - const decodedPayload = await this.tokenService.decodePayload( + const decodedPayload = await this.jwtWrapperService.decode( payloadToDecode, { secret: this.environmentService.get('FILE_TOKEN_SECRET'), - }, + } as any, ); const expirationDate = decodedPayload?.['expiration_date']; 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 dcce3f7d4..7b71fc59b 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 @@ -10,16 +10,16 @@ import { FileStorageExceptionCode, } from 'src/engine/integrations/file-storage/interfaces/file-storage-exception'; -import { TokenService } from 'src/engine/core-modules/auth/services/token.service'; +import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service'; import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service'; @Injectable() export class FileService { constructor( + private readonly jwtWrapperService: JwtWrapperService, private readonly fileStorageService: FileStorageService, private readonly environmentService: EnvironmentService, - private readonly tokenService: TokenService, ) {} async getFileStream( @@ -57,7 +57,7 @@ export class FileService { const expirationDate = addMilliseconds(new Date(), ms(fileTokenExpiresIn)); - const signedPayload = await this.tokenService.encodePayload( + const signedPayload = await this.jwtWrapperService.sign( { expiration_date: expirationDate, ...payloadToEncode, diff --git a/packages/twenty-server/src/engine/core-modules/jwt/jwt.module.ts b/packages/twenty-server/src/engine/core-modules/jwt/jwt.module.ts new file mode 100644 index 000000000..6acbd76a0 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/jwt/jwt.module.ts @@ -0,0 +1,27 @@ +/* eslint-disable no-restricted-imports */ +import { Module } from '@nestjs/common'; +import { JwtModule as NestJwtModule } from '@nestjs/jwt'; + +import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service'; +import { EnvironmentModule } from 'src/engine/integrations/environment/environment.module'; +import { EnvironmentService } from 'src/engine/integrations/environment/environment.service'; + +const InternalJwtModule = NestJwtModule.registerAsync({ + useFactory: async (environmentService: EnvironmentService) => { + return { + secret: environmentService.get('ACCESS_TOKEN_SECRET'), + signOptions: { + expiresIn: environmentService.get('ACCESS_TOKEN_EXPIRES_IN'), + }, + }; + }, + inject: [EnvironmentService], +}); + +@Module({ + imports: [InternalJwtModule, EnvironmentModule], + controllers: [], + providers: [JwtWrapperService], + exports: [JwtWrapperService], +}) +export class JwtModule {} diff --git a/packages/twenty-server/src/engine/core-modules/jwt/services/jwt-wrapper.service.ts b/packages/twenty-server/src/engine/core-modules/jwt/services/jwt-wrapper.service.ts new file mode 100644 index 000000000..43a4ea2cb --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/jwt/services/jwt-wrapper.service.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common'; +import { JwtService, JwtSignOptions, JwtVerifyOptions } from '@nestjs/jwt'; + +import * as jwt from 'jsonwebtoken'; + +@Injectable() +export class JwtWrapperService { + constructor(private readonly jwtService: JwtService) {} + + sign(payload: string, options?: JwtSignOptions): string { + return this.jwtService.sign(payload, options); + } + + verify(token: string, options?: JwtVerifyOptions): T { + return this.jwtService.verify(token, options); + } + + decode(payload: string, options: jwt.DecodeOptions): T { + return this.jwtService.decode(payload, options); + } +} 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 4a95a9588..a311201fd 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 @@ -70,20 +70,6 @@ export class UserResolver { assert(user, 'User not found'); - user.workspaces = await Promise.all( - user.workspaces.map(async (userWorkspace) => { - if (userWorkspace.workspace.logo) { - const workspaceLogoToken = await this.fileService.encodeFileToken({ - workspace_id: userWorkspace.workspace.id, - }); - - userWorkspace.workspace.logo = `${userWorkspace.workspace.logo}?token=${workspaceLogoToken}`; - } - - return userWorkspace; - }), - ); - return user; } @@ -186,7 +172,11 @@ export class UserResolver { workspaceId, }); - return paths[0]; + const fileToken = await this.fileService.encodeFileToken({ + workspace_id: workspaceId, + }); + + return `${paths[0]}?token=${fileToken}`; } @UseGuards(DemoEnvGuard) diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts index 213427eaf..a8255d899 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.module.ts @@ -7,6 +7,7 @@ import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { BillingModule } from 'src/engine/core-modules/billing/billing.module'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module'; +import { FileModule } from 'src/engine/core-modules/file/file.module'; import { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; @@ -17,7 +18,6 @@ import { WorkspaceResolver } from 'src/engine/core-modules/workspace/workspace.r import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module'; import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module'; -import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars.module'; import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts'; import { Workspace } from './workspace.entity'; @@ -30,6 +30,7 @@ import { WorkspaceService } from './services/workspace.service'; NestjsQueryGraphQLModule.forFeature({ imports: [ BillingModule, + FileModule, FileUploadModule, WorkspaceCacheVersionModule, NestjsQueryTypeOrmModule.forFeature( 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 3722d5457..cc390dfa9 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 @@ -15,6 +15,7 @@ import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder. import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity'; import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service'; 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 { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; import { User } from 'src/engine/core-modules/user/user.entity'; import { ActivateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/activate-workspace-input'; @@ -41,6 +42,7 @@ export class WorkspaceResolver { private readonly workspaceCacheVersionService: WorkspaceCacheVersionService, private readonly userWorkspaceService: UserWorkspaceService, private readonly fileUploadService: FileUploadService, + private readonly fileService: FileService, private readonly billingSubscriptionService: BillingSubscriptionService, ) {} @@ -92,7 +94,11 @@ export class WorkspaceResolver { logo: paths[0], }); - return paths[0]; + const workspaceLogoToken = await this.fileService.encodeFileToken({ + workspace_id: id, + }); + + return `${paths[0]}?token=${workspaceLogoToken}`; } @UseGuards(DemoEnvGuard) @@ -124,6 +130,19 @@ export class WorkspaceResolver { return await this.userWorkspaceService.getUserCount(workspace.id); } + @ResolveField(() => String) + async logo(@Parent() workspace: Workspace): Promise { + if (workspace.logo) { + const workspaceLogoToken = await this.fileService.encodeFileToken({ + workspace_id: workspace.id, + }); + + return `${workspace.logo}?token=${workspaceLogoToken}`; + } + + return workspace.logo ?? ''; + } + @Mutation(() => SendInviteLink) async sendInviteLink( @Args() sendInviteLinkInput: SendInviteLinkInput, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata-identifiers.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata-identifiers.service.ts index 54c781fcb..159db8ec9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata-identifiers.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata-identifiers.service.ts @@ -114,6 +114,8 @@ export class WorkspaceSyncObjectMetadataIdentifiersService { ...objectMetadata, labelIdentifierFieldMetadataId: labelIdentifierFieldMetadata?.id ?? null, + imageIdentifierFieldMetadataId: + imageIdentifierFieldMetadata?.id ?? null, }); } } @@ -161,9 +163,12 @@ export class WorkspaceSyncObjectMetadataIdentifiersService { ); } - if (imageIdentifierFieldMetadata) { + if ( + imageIdentifierFieldMetadata && + imageIdentifierFieldMetadata.type !== FieldMetadataType.TEXT + ) { throw new Error( - `Image identifier field for object ${objectMetadata.nameSingular} are not supported yet.`, + `Image identifier field for object ${objectMetadata.nameSingular} has invalid type ${imageIdentifierFieldMetadata.type}`, ); } } diff --git a/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts b/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts index 891b453ac..46300a145 100644 --- a/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts +++ b/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts @@ -39,6 +39,7 @@ import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-o description: 'A person', icon: 'IconUser', labelIdentifierStandardId: PERSON_STANDARD_FIELD_IDS.name, + imageIdentifierStandardId: PERSON_STANDARD_FIELD_IDS.avatarUrl, }) export class PersonWorkspaceEntity extends BaseWorkspaceEntity { @WorkspaceField({