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)
This commit is contained in:
@ -51,7 +51,7 @@ export const ProfilePictureUploader = () => {
|
|||||||
setUploadController(null);
|
setUploadController(null);
|
||||||
setErrorMessage(null);
|
setErrorMessage(null);
|
||||||
|
|
||||||
const avatarUrl = result?.data?.uploadProfilePicture;
|
const avatarUrl = result?.data?.uploadProfilePicture.split('?')[0];
|
||||||
|
|
||||||
if (!avatarUrl) {
|
if (!avatarUrl) {
|
||||||
throw new Error('Avatar URL not found');
|
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;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -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 { DEFAULT_WORKSPACE_NAME } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceName';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { NavigationDrawerCollapseButton } from './NavigationDrawerCollapseButton';
|
import { NavigationDrawerCollapseButton } from './NavigationDrawerCollapseButton';
|
||||||
|
|
||||||
const StyledContainer = styled.div<{ isMultiWorkspace: boolean }>`
|
const StyledContainer = styled.div<{ isMultiWorkspace: boolean }>`
|
||||||
@ -65,7 +66,9 @@ export const NavigationDrawerHeader = ({
|
|||||||
<MultiWorkspaceDropdownButton workspaces={workspaces} />
|
<MultiWorkspaceDropdownButton workspaces={workspaces} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<StyledLogo logo={logo} />
|
<StyledLogo
|
||||||
|
logo={isNonEmptyString(logo) ? logo : DEFAULT_WORKSPACE_LOGO}
|
||||||
|
/>
|
||||||
<StyledName>{name}</StyledName>
|
<StyledName>{name}</StyledName>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable no-restricted-imports */
|
/* eslint-disable no-restricted-imports */
|
||||||
import { HttpModule } from '@nestjs/axios';
|
import { HttpModule } from '@nestjs/axios';
|
||||||
import { forwardRef, Module } from '@nestjs/common';
|
import { forwardRef, Module } from '@nestjs/common';
|
||||||
import { JwtModule } from '@nestjs/jwt';
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
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 { TokenService } from 'src/engine/core-modules/auth/services/token.service';
|
||||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
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 { 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 { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module';
|
||||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { UserModule } from 'src/engine/core-modules/user/user.module';
|
import { UserModule } from 'src/engine/core-modules/user/user.module';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
|
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 { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.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 { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
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 { 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 { AuthResolver } from './auth.resolver';
|
||||||
|
|
||||||
import { AuthService } from './services/auth.service';
|
import { AuthService } from './services/auth.service';
|
||||||
import { JwtAuthStrategy } from './strategies/jwt.auth.strategy';
|
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({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
jwtModule,
|
JwtModule,
|
||||||
FileUploadModule,
|
FileUploadModule,
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
forwardRef(() => UserModule),
|
forwardRef(() => UserModule),
|
||||||
@ -89,6 +76,6 @@ const jwtModule = JwtModule.registerAsync({
|
|||||||
GoogleAPIsService,
|
GoogleAPIsService,
|
||||||
AppTokenService,
|
AppTokenService,
|
||||||
],
|
],
|
||||||
exports: [jwtModule, TokenService],
|
exports: [TokenService],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|||||||
@ -1,25 +1,25 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { JwtService } from '@nestjs/jwt';
|
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
|
||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto';
|
||||||
|
|
||||||
import { IsNull, MoreThan, Repository } from 'typeorm';
|
import { IsNull, MoreThan, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
|
||||||
import {
|
import {
|
||||||
AppToken,
|
AppToken,
|
||||||
AppTokenType,
|
AppTokenType,
|
||||||
} from 'src/engine/core-modules/app-token/app-token.entity';
|
} 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 { 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 { 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';
|
import { TokenService } from './token.service';
|
||||||
|
|
||||||
|
|||||||
@ -7,50 +7,50 @@ import {
|
|||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
UnprocessableEntityException,
|
UnprocessableEntityException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import crypto from 'crypto';
|
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 { 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 { PasswordResetLinkEmail } from 'twenty-emails';
|
||||||
|
import { IsNull, MoreThan, Repository } from 'typeorm';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
JwtAuthStrategy,
|
AppToken,
|
||||||
JwtPayload,
|
AppTokenType,
|
||||||
} from 'src/engine/core-modules/auth/strategies/jwt.auth.strategy';
|
} from 'src/engine/core-modules/app-token/app-token.entity';
|
||||||
import { assert } from 'src/utils/assert';
|
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 {
|
import {
|
||||||
ApiKeyToken,
|
ApiKeyToken,
|
||||||
AuthToken,
|
AuthToken,
|
||||||
AuthTokens,
|
AuthTokens,
|
||||||
PasswordResetToken,
|
PasswordResetToken,
|
||||||
} from 'src/engine/core-modules/auth/dto/token.entity';
|
} 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 { ValidatePasswordResetToken } from 'src/engine/core-modules/auth/dto/validate-password-reset-token.entity';
|
||||||
import { EmailService } from 'src/engine/integrations/email/email.service';
|
import {
|
||||||
import { InvalidatePassword } from 'src/engine/core-modules/auth/dto/invalidate-password.entity';
|
JwtAuthStrategy,
|
||||||
import { EmailPasswordResetLink } from 'src/engine/core-modules/auth/dto/email-password-reset-link.entity';
|
JwtPayload,
|
||||||
|
} from 'src/engine/core-modules/auth/strategies/jwt.auth.strategy';
|
||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
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 { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { ExchangeAuthCodeInput } from 'src/engine/core-modules/auth/dto/exchange-auth-code.input';
|
import { EmailService } from 'src/engine/integrations/email/email.service';
|
||||||
import { ExchangeAuthCode } from 'src/engine/core-modules/auth/dto/exchange-auth-code.entity';
|
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||||
|
import { assert } from 'src/utils/assert';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TokenService {
|
export class TokenService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly jwtService: JwtService,
|
private readonly jwtWrapperService: JwtWrapperService,
|
||||||
private readonly jwtStrategy: JwtAuthStrategy,
|
private readonly jwtStrategy: JwtAuthStrategy,
|
||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
@InjectRepository(User, 'core')
|
@InjectRepository(User, 'core')
|
||||||
@ -90,7 +90,7 @@ export class TokenService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token: this.jwtService.sign(jwtPayload),
|
token: this.jwtWrapperService.sign(jwtPayload),
|
||||||
expiresAt,
|
expiresAt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -116,7 +116,7 @@ export class TokenService {
|
|||||||
await this.appTokenRepository.save(refreshToken);
|
await this.appTokenRepository.save(refreshToken);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token: this.jwtService.sign(jwtPayload, {
|
token: this.jwtWrapperService.sign(jwtPayload, {
|
||||||
secret,
|
secret,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
// Jwtid will be used to link RefreshToken entity to this token
|
// Jwtid will be used to link RefreshToken entity to this token
|
||||||
@ -137,7 +137,7 @@ export class TokenService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token: this.jwtService.sign(jwtPayload, {
|
token: this.jwtWrapperService.sign(jwtPayload, {
|
||||||
secret,
|
secret,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
}),
|
}),
|
||||||
@ -164,7 +164,7 @@ export class TokenService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
token: this.jwtService.sign(jwtPayload, {
|
token: this.jwtWrapperService.sign(jwtPayload, {
|
||||||
secret,
|
secret,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
}),
|
}),
|
||||||
@ -193,7 +193,7 @@ export class TokenService {
|
|||||||
} else {
|
} else {
|
||||||
expiresIn = this.environmentService.get('API_TOKEN_EXPIRES_IN');
|
expiresIn = this.environmentService.get('API_TOKEN_EXPIRES_IN');
|
||||||
}
|
}
|
||||||
const token = this.jwtService.sign(jwtPayload, {
|
const token = this.jwtWrapperService.sign(jwtPayload, {
|
||||||
secret,
|
secret,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
jwtid: apiKeyId,
|
jwtid: apiKeyId,
|
||||||
@ -496,7 +496,10 @@ export class TokenService {
|
|||||||
|
|
||||||
async verifyJwt(token: string, secret?: string) {
|
async verifyJwt(token: string, secret?: string) {
|
||||||
try {
|
try {
|
||||||
return this.jwtService.verify(token, secret ? { secret } : undefined);
|
return this.jwtWrapperService.verify(
|
||||||
|
token,
|
||||||
|
secret ? { secret } : undefined,
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof TokenExpiredError) {
|
if (error instanceof TokenExpiredError) {
|
||||||
throw new UnauthorizedException('Token has expired.');
|
throw new UnauthorizedException('Token has expired.');
|
||||||
@ -668,12 +671,4 @@ export class TokenService {
|
|||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
async encodePayload(payload: any, options?: any): Promise<string> {
|
|
||||||
return this.jwtService.sign(payload, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async decodePayload(payload: any, options?: any): Promise<string> {
|
|
||||||
return this.jwtService.decode(payload, options);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 { 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 { 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 { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||||
|
|
||||||
import { FileController } from './controllers/file.controller';
|
import { FileController } from './controllers/file.controller';
|
||||||
import { FileService } from './services/file.service';
|
import { FileService } from './services/file.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [FileUploadModule, forwardRef(() => AuthModule)],
|
imports: [FileUploadModule, JwtModule],
|
||||||
providers: [FileService, EnvironmentService, FilePathGuard],
|
providers: [FileService, EnvironmentService, FilePathGuard],
|
||||||
exports: [FileService],
|
exports: [FileService],
|
||||||
controllers: [FileController],
|
controllers: [FileController],
|
||||||
|
|||||||
@ -6,13 +6,13 @@ import {
|
|||||||
Injectable,
|
Injectable,
|
||||||
} from '@nestjs/common';
|
} 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';
|
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FilePathGuard implements CanActivate {
|
export class FilePathGuard implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly tokenService: TokenService,
|
private readonly jwtWrapperService: JwtWrapperService,
|
||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -22,11 +22,11 @@ export class FilePathGuard implements CanActivate {
|
|||||||
|
|
||||||
if (query && query['token']) {
|
if (query && query['token']) {
|
||||||
const payloadToDecode = query['token'];
|
const payloadToDecode = query['token'];
|
||||||
const decodedPayload = await this.tokenService.decodePayload(
|
const decodedPayload = await this.jwtWrapperService.decode(
|
||||||
payloadToDecode,
|
payloadToDecode,
|
||||||
{
|
{
|
||||||
secret: this.environmentService.get('FILE_TOKEN_SECRET'),
|
secret: this.environmentService.get('FILE_TOKEN_SECRET'),
|
||||||
},
|
} as any,
|
||||||
);
|
);
|
||||||
|
|
||||||
const expirationDate = decodedPayload?.['expiration_date'];
|
const expirationDate = decodedPayload?.['expiration_date'];
|
||||||
|
|||||||
@ -10,16 +10,16 @@ import {
|
|||||||
FileStorageExceptionCode,
|
FileStorageExceptionCode,
|
||||||
} from 'src/engine/integrations/file-storage/interfaces/file-storage-exception';
|
} 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 { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||||
import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service';
|
import { FileStorageService } from 'src/engine/integrations/file-storage/file-storage.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FileService {
|
export class FileService {
|
||||||
constructor(
|
constructor(
|
||||||
|
private readonly jwtWrapperService: JwtWrapperService,
|
||||||
private readonly fileStorageService: FileStorageService,
|
private readonly fileStorageService: FileStorageService,
|
||||||
private readonly environmentService: EnvironmentService,
|
private readonly environmentService: EnvironmentService,
|
||||||
private readonly tokenService: TokenService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getFileStream(
|
async getFileStream(
|
||||||
@ -57,7 +57,7 @@ export class FileService {
|
|||||||
|
|
||||||
const expirationDate = addMilliseconds(new Date(), ms(fileTokenExpiresIn));
|
const expirationDate = addMilliseconds(new Date(), ms(fileTokenExpiresIn));
|
||||||
|
|
||||||
const signedPayload = await this.tokenService.encodePayload(
|
const signedPayload = await this.jwtWrapperService.sign(
|
||||||
{
|
{
|
||||||
expiration_date: expirationDate,
|
expiration_date: expirationDate,
|
||||||
...payloadToEncode,
|
...payloadToEncode,
|
||||||
|
|||||||
@ -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 {}
|
||||||
@ -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<T extends object = any>(token: string, options?: JwtVerifyOptions): T {
|
||||||
|
return this.jwtService.verify(token, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
decode<T = any>(payload: string, options: jwt.DecodeOptions): T {
|
||||||
|
return this.jwtService.decode(payload, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -70,20 +70,6 @@ export class UserResolver {
|
|||||||
|
|
||||||
assert(user, 'User not found');
|
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;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +172,11 @@ export class UserResolver {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return paths[0];
|
const fileToken = await this.fileService.encodeFileToken({
|
||||||
|
workspace_id: workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${paths[0]}?token=${fileToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(DemoEnvGuard)
|
@UseGuards(DemoEnvGuard)
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
|||||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
||||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
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 { 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 { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module';
|
||||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
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 { 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 { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
|
||||||
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.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 { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts';
|
||||||
import { Workspace } from './workspace.entity';
|
import { Workspace } from './workspace.entity';
|
||||||
@ -30,6 +30,7 @@ import { WorkspaceService } from './services/workspace.service';
|
|||||||
NestjsQueryGraphQLModule.forFeature({
|
NestjsQueryGraphQLModule.forFeature({
|
||||||
imports: [
|
imports: [
|
||||||
BillingModule,
|
BillingModule,
|
||||||
|
FileModule,
|
||||||
FileUploadModule,
|
FileUploadModule,
|
||||||
WorkspaceCacheVersionModule,
|
WorkspaceCacheVersionModule,
|
||||||
NestjsQueryTypeOrmModule.forFeature(
|
NestjsQueryTypeOrmModule.forFeature(
|
||||||
|
|||||||
@ -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 { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||||
import { BillingSubscriptionService } from 'src/engine/core-modules/billing/services/billing-subscription.service';
|
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 { 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 { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { ActivateWorkspaceInput } from 'src/engine/core-modules/workspace/dtos/activate-workspace-input';
|
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 workspaceCacheVersionService: WorkspaceCacheVersionService,
|
||||||
private readonly userWorkspaceService: UserWorkspaceService,
|
private readonly userWorkspaceService: UserWorkspaceService,
|
||||||
private readonly fileUploadService: FileUploadService,
|
private readonly fileUploadService: FileUploadService,
|
||||||
|
private readonly fileService: FileService,
|
||||||
private readonly billingSubscriptionService: BillingSubscriptionService,
|
private readonly billingSubscriptionService: BillingSubscriptionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -92,7 +94,11 @@ export class WorkspaceResolver {
|
|||||||
logo: paths[0],
|
logo: paths[0],
|
||||||
});
|
});
|
||||||
|
|
||||||
return paths[0];
|
const workspaceLogoToken = await this.fileService.encodeFileToken({
|
||||||
|
workspace_id: id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${paths[0]}?token=${workspaceLogoToken}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(DemoEnvGuard)
|
@UseGuards(DemoEnvGuard)
|
||||||
@ -124,6 +130,19 @@ export class WorkspaceResolver {
|
|||||||
return await this.userWorkspaceService.getUserCount(workspace.id);
|
return await this.userWorkspaceService.getUserCount(workspace.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ResolveField(() => String)
|
||||||
|
async logo(@Parent() workspace: Workspace): Promise<string> {
|
||||||
|
if (workspace.logo) {
|
||||||
|
const workspaceLogoToken = await this.fileService.encodeFileToken({
|
||||||
|
workspace_id: workspace.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${workspace.logo}?token=${workspaceLogoToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return workspace.logo ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
@Mutation(() => SendInviteLink)
|
@Mutation(() => SendInviteLink)
|
||||||
async sendInviteLink(
|
async sendInviteLink(
|
||||||
@Args() sendInviteLinkInput: SendInviteLinkInput,
|
@Args() sendInviteLinkInput: SendInviteLinkInput,
|
||||||
|
|||||||
@ -114,6 +114,8 @@ export class WorkspaceSyncObjectMetadataIdentifiersService {
|
|||||||
...objectMetadata,
|
...objectMetadata,
|
||||||
labelIdentifierFieldMetadataId:
|
labelIdentifierFieldMetadataId:
|
||||||
labelIdentifierFieldMetadata?.id ?? null,
|
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(
|
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}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,7 @@ import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-o
|
|||||||
description: 'A person',
|
description: 'A person',
|
||||||
icon: 'IconUser',
|
icon: 'IconUser',
|
||||||
labelIdentifierStandardId: PERSON_STANDARD_FIELD_IDS.name,
|
labelIdentifierStandardId: PERSON_STANDARD_FIELD_IDS.name,
|
||||||
|
imageIdentifierStandardId: PERSON_STANDARD_FIELD_IDS.avatarUrl,
|
||||||
})
|
})
|
||||||
export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
||||||
@WorkspaceField({
|
@WorkspaceField({
|
||||||
|
|||||||
Reference in New Issue
Block a user