mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-28 14:52:28 +03:00
Add getters factory for attachements (#4567)
* Add getter factory for attachements * Override guard in test * Add secret in env variables * Return custom message on expiration * Rename to signPayload --------- Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
parent
9f6c578a46
commit
e579554d47
@ -1,7 +1,9 @@
|
||||
import { QueryResultGettersFactory } from './query-result-getters.factory';
|
||||
import { RecordPositionFactory } from './record-position.factory';
|
||||
import { QueryRunnerArgsFactory } from './query-runner-args.factory';
|
||||
|
||||
export const workspaceQueryRunnerFactories = [
|
||||
QueryRunnerArgsFactory,
|
||||
RecordPositionFactory,
|
||||
QueryResultGettersFactory,
|
||||
];
|
||||
|
@ -0,0 +1,75 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { addMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
|
||||
import { ObjectMetadataInterface } from 'src/engine-metadata/field-metadata/interfaces/object-metadata.interface';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { TokenService } from 'src/engine/modules/auth/services/token.service';
|
||||
|
||||
@Injectable()
|
||||
export class QueryResultGettersFactory {
|
||||
constructor(
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
async create<Result>(
|
||||
result: Result,
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
): Promise<Result> {
|
||||
// TODO: look for file type once implemented
|
||||
switch (objectMetadataItem.nameSingular) {
|
||||
case 'attachment':
|
||||
return this.applyAttachmentGetters(result);
|
||||
default:
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private async applyAttachmentGetters<Result>(
|
||||
attachments: any,
|
||||
): Promise<Result> {
|
||||
if (!attachments || !attachments.edges) {
|
||||
return attachments;
|
||||
}
|
||||
|
||||
const fileTokenExpiresIn = this.environmentService.get(
|
||||
'FILE_TOKEN_EXPIRES_IN',
|
||||
);
|
||||
const secret = this.environmentService.get('FILE_TOKEN_SECRET');
|
||||
|
||||
const mappedEdges = await Promise.all(
|
||||
attachments.edges.map(async (attachment: any) => {
|
||||
if (!attachment.node.id || !attachment?.node?.fullPath) {
|
||||
return attachment;
|
||||
}
|
||||
|
||||
const expirationDate = addMilliseconds(
|
||||
new Date(),
|
||||
ms(fileTokenExpiresIn),
|
||||
);
|
||||
|
||||
const signedPayload = await this.tokenService.encodePayload(
|
||||
{
|
||||
expiration_date: expirationDate,
|
||||
attachment_id: attachment.node.id,
|
||||
},
|
||||
{
|
||||
secret,
|
||||
},
|
||||
);
|
||||
|
||||
attachment.node.fullPath = `${attachment.node.fullPath}?token=${signedPayload}`;
|
||||
|
||||
return attachment;
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
...attachments,
|
||||
edges: mappedEdges,
|
||||
} as Result;
|
||||
}
|
||||
}
|
@ -5,11 +5,13 @@ import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/works
|
||||
import { WorkspacePreQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-pre-query-hook/workspace-pre-query-hook.module';
|
||||
import { workspaceQueryRunnerFactories } from 'src/engine/api/graphql/workspace-query-runner/factories';
|
||||
import { RecordPositionListener } from 'src/engine/api/graphql/workspace-query-runner/listeners/record-position.listener';
|
||||
import { AuthModule } from 'src/engine/modules/auth/auth.module';
|
||||
|
||||
import { WorkspaceQueryRunnerService } from './workspace-query-runner.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AuthModule,
|
||||
WorkspaceQueryBuilderModule,
|
||||
WorkspaceDataSourceModule,
|
||||
WorkspacePreQueryHookModule,
|
||||
|
@ -45,6 +45,7 @@ import { WorkspacePreQueryHookService } from 'src/engine/api/graphql/workspace-q
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { NotFoundError } from 'src/engine/filters/utils/graphql-errors.util';
|
||||
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
||||
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters.factory';
|
||||
|
||||
import { WorkspaceQueryRunnerOptions } from './interfaces/query-runner-option.interface';
|
||||
import {
|
||||
@ -61,6 +62,7 @@ export class WorkspaceQueryRunnerService {
|
||||
private readonly workspaceQueryBuilderFactory: WorkspaceQueryBuilderFactory,
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
private readonly queryRunnerArgsFactory: QueryRunnerArgsFactory,
|
||||
private readonly queryResultGettersFactory: QueryResultGettersFactory,
|
||||
@Inject(MessageQueue.webhookQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
@ -133,7 +135,7 @@ export class WorkspaceQueryRunnerService {
|
||||
);
|
||||
|
||||
const result = await this.execute(query, workspaceId);
|
||||
const parsedResult = this.parseResult<IConnection<Record>>(
|
||||
const parsedResult = await this.parseResult<IConnection<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'',
|
||||
@ -174,7 +176,7 @@ export class WorkspaceQueryRunnerService {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const parsedResult = this.parseResult<Record<string, unknown>>(
|
||||
const parsedResult = await this.parseResult<Record<string, unknown>>(
|
||||
existingRecordResult,
|
||||
objectMetadataItem,
|
||||
'',
|
||||
@ -227,10 +229,12 @@ export class WorkspaceQueryRunnerService {
|
||||
|
||||
const result = await this.execute(query, workspaceId);
|
||||
|
||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'insertInto',
|
||||
const parsedResults = (
|
||||
await this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'insertInto',
|
||||
)
|
||||
)?.records;
|
||||
|
||||
await this.triggerWebhooks<Record>(
|
||||
@ -280,10 +284,12 @@ export class WorkspaceQueryRunnerService {
|
||||
|
||||
const result = await this.execute(query, workspaceId);
|
||||
|
||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'update',
|
||||
const parsedResults = (
|
||||
await this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'update',
|
||||
)
|
||||
)?.records;
|
||||
|
||||
await this.triggerWebhooks<Record>(
|
||||
@ -316,10 +322,12 @@ export class WorkspaceQueryRunnerService {
|
||||
|
||||
const result = await this.execute(query, workspaceId);
|
||||
|
||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'update',
|
||||
const parsedResults = (
|
||||
await this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'update',
|
||||
)
|
||||
)?.records;
|
||||
|
||||
await this.triggerWebhooks<Record>(
|
||||
@ -349,10 +357,12 @@ export class WorkspaceQueryRunnerService {
|
||||
|
||||
const result = await this.execute(query, workspaceId);
|
||||
|
||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'deleteFrom',
|
||||
const parsedResults = (
|
||||
await this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'deleteFrom',
|
||||
)
|
||||
)?.records;
|
||||
|
||||
await this.triggerWebhooks<Record>(
|
||||
@ -382,10 +392,12 @@ export class WorkspaceQueryRunnerService {
|
||||
);
|
||||
const result = await this.execute(query, workspaceId);
|
||||
|
||||
const parsedResults = this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'deleteFrom',
|
||||
const parsedResults = (
|
||||
await this.parseResult<PGGraphQLMutation<Record>>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
'deleteFrom',
|
||||
)
|
||||
)?.records;
|
||||
|
||||
await this.triggerWebhooks<Record>(
|
||||
@ -445,11 +457,11 @@ export class WorkspaceQueryRunnerService {
|
||||
return results;
|
||||
}
|
||||
|
||||
private parseResult<Result>(
|
||||
private async parseResult<Result>(
|
||||
graphqlResult: PGGraphQLResult | undefined,
|
||||
objectMetadataItem: ObjectMetadataInterface,
|
||||
command: string,
|
||||
): Result {
|
||||
): Promise<Result> {
|
||||
const entityKey = `${command}${computeObjectTargetTable(
|
||||
objectMetadataItem,
|
||||
)}Collection`;
|
||||
@ -481,7 +493,12 @@ export class WorkspaceQueryRunnerService {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return parseResult(result);
|
||||
const resultWithGetters = await this.queryResultGettersFactory.create(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
);
|
||||
|
||||
return parseResult(resultWithGetters);
|
||||
}
|
||||
|
||||
async executeAndParse<Result>(
|
||||
|
@ -129,6 +129,13 @@ export class EnvironmentVariables {
|
||||
@IsOptional()
|
||||
LOGIN_TOKEN_EXPIRES_IN: string = '15m';
|
||||
|
||||
@IsString()
|
||||
FILE_TOKEN_SECRET: string;
|
||||
|
||||
@IsDuration()
|
||||
@IsOptional()
|
||||
FILE_TOKEN_EXPIRES_IN: string;
|
||||
|
||||
// Auth
|
||||
@IsUrl({ require_tld: false })
|
||||
@IsOptional()
|
||||
|
@ -0,0 +1,79 @@
|
||||
import { EmailDriver } from 'src/engine/integrations/email/interfaces/email.interface';
|
||||
import { SupportDriver } from 'src/engine/integrations/environment/interfaces/support.interface';
|
||||
|
||||
import { ExceptionHandlerDriver } from 'src/engine/integrations/exception-handler/interfaces';
|
||||
import { StorageDriverType } from 'src/engine/integrations/file-storage/interfaces';
|
||||
import { LoggerDriverType } from 'src/engine/integrations/logger/interfaces';
|
||||
import { MessageQueueDriverType } from 'src/engine/integrations/message-queue/interfaces';
|
||||
import { EnvironmentVariables } from 'src/engine/integrations/environment/environment-variables';
|
||||
|
||||
const EnvironmentDefault = new EnvironmentVariables();
|
||||
|
||||
EnvironmentDefault.DEBUG_MODE = false;
|
||||
EnvironmentDefault.SIGN_IN_PREFILLED = false;
|
||||
EnvironmentDefault.IS_BILLING_ENABLED = false;
|
||||
EnvironmentDefault.BILLING_PLAN_REQUIRED_LINK = '';
|
||||
EnvironmentDefault.BILLING_STRIPE_BASE_PLAN_PRODUCT_ID = '';
|
||||
EnvironmentDefault.BILLING_FREE_TRIAL_DURATION_IN_DAYS = 7;
|
||||
EnvironmentDefault.BILLING_STRIPE_API_KEY = '';
|
||||
EnvironmentDefault.BILLING_STRIPE_WEBHOOK_SECRET = '';
|
||||
EnvironmentDefault.TELEMETRY_ENABLED = true;
|
||||
EnvironmentDefault.TELEMETRY_ANONYMIZATION_ENABLED = true;
|
||||
EnvironmentDefault.PORT = 3000;
|
||||
EnvironmentDefault.REDIS_HOST = '127.0.0.1';
|
||||
EnvironmentDefault.REDIS_PORT = 6379;
|
||||
EnvironmentDefault.PG_DATABASE_URL = '';
|
||||
EnvironmentDefault.FRONT_BASE_URL = '';
|
||||
EnvironmentDefault.SERVER_URL = '';
|
||||
EnvironmentDefault.ACCESS_TOKEN_SECRET = 'random_string';
|
||||
EnvironmentDefault.ACCESS_TOKEN_EXPIRES_IN = '30m';
|
||||
EnvironmentDefault.REFRESH_TOKEN_SECRET = 'random_string';
|
||||
EnvironmentDefault.REFRESH_TOKEN_EXPIRES_IN = '30m';
|
||||
EnvironmentDefault.REFRESH_TOKEN_COOL_DOWN = '1m';
|
||||
EnvironmentDefault.LOGIN_TOKEN_SECRET = 'random_string';
|
||||
EnvironmentDefault.LOGIN_TOKEN_EXPIRES_IN = '30m';
|
||||
EnvironmentDefault.FILE_TOKEN_SECRET = 'random_string';
|
||||
EnvironmentDefault.FILE_TOKEN_EXPIRES_IN = '1d';
|
||||
EnvironmentDefault.API_TOKEN_EXPIRES_IN = '100y';
|
||||
EnvironmentDefault.SHORT_TERM_TOKEN_EXPIRES_IN = '5m';
|
||||
EnvironmentDefault.FRONT_AUTH_CALLBACK_URL = '';
|
||||
EnvironmentDefault.MESSAGING_PROVIDER_GMAIL_ENABLED = false;
|
||||
EnvironmentDefault.MESSAGING_PROVIDER_GMAIL_CALLBACK_URL = '';
|
||||
EnvironmentDefault.AUTH_GOOGLE_ENABLED = false;
|
||||
EnvironmentDefault.AUTH_GOOGLE_CLIENT_ID = '';
|
||||
EnvironmentDefault.AUTH_GOOGLE_CLIENT_SECRET = '';
|
||||
EnvironmentDefault.AUTH_GOOGLE_CALLBACK_URL = '';
|
||||
EnvironmentDefault.STORAGE_TYPE = StorageDriverType.Local;
|
||||
EnvironmentDefault.STORAGE_S3_REGION = 'aws-east-1';
|
||||
EnvironmentDefault.STORAGE_S3_NAME = '';
|
||||
EnvironmentDefault.STORAGE_S3_ENDPOINT = '';
|
||||
EnvironmentDefault.STORAGE_LOCAL_PATH = '.local-storage';
|
||||
EnvironmentDefault.MESSAGE_QUEUE_TYPE = MessageQueueDriverType.Sync;
|
||||
EnvironmentDefault.EMAIL_FROM_ADDRESS = 'noreply@yourdomain.com';
|
||||
EnvironmentDefault.EMAIL_SYSTEM_ADDRESS = 'system@yourdomain.com';
|
||||
EnvironmentDefault.EMAIL_FROM_NAME = 'John from Twenty';
|
||||
EnvironmentDefault.EMAIL_DRIVER = EmailDriver.Logger;
|
||||
EnvironmentDefault.EMAIL_SMTP_HOST = '';
|
||||
EnvironmentDefault.EMAIL_SMTP_PORT = 587;
|
||||
EnvironmentDefault.EMAIL_SMTP_USER = '';
|
||||
EnvironmentDefault.EMAIL_SMTP_PASSWORD = '';
|
||||
EnvironmentDefault.SUPPORT_DRIVER = SupportDriver.None;
|
||||
EnvironmentDefault.SUPPORT_FRONT_CHAT_ID = '';
|
||||
EnvironmentDefault.SUPPORT_FRONT_HMAC_KEY = '';
|
||||
EnvironmentDefault.LOGGER_DRIVER = LoggerDriverType.Console;
|
||||
EnvironmentDefault.EXCEPTION_HANDLER_DRIVER = ExceptionHandlerDriver.Console;
|
||||
EnvironmentDefault.LOG_LEVELS = ['log', 'error', 'warn'];
|
||||
EnvironmentDefault.SENTRY_DSN = '';
|
||||
EnvironmentDefault.DEMO_WORKSPACE_IDS = [];
|
||||
EnvironmentDefault.OPENROUTER_API_KEY = '';
|
||||
EnvironmentDefault.PASSWORD_RESET_TOKEN_EXPIRES_IN = '5m';
|
||||
EnvironmentDefault.WORKSPACE_INACTIVE_DAYS_BEFORE_NOTIFICATION = 30;
|
||||
EnvironmentDefault.WORKSPACE_INACTIVE_DAYS_BEFORE_DELETION = 60;
|
||||
EnvironmentDefault.IS_SIGN_UP_DISABLED = false;
|
||||
EnvironmentDefault.API_RATE_LIMITING_TTL = 100;
|
||||
EnvironmentDefault.API_RATE_LIMITING_LIMIT = 500;
|
||||
EnvironmentDefault.MUTATION_MAXIMUM_RECORD_AFFECTED = 100;
|
||||
EnvironmentDefault.CACHE_STORAGE_TYPE = 'memory';
|
||||
EnvironmentDefault.CACHE_STORAGE_TTL = 3600 * 24 * 7;
|
||||
|
||||
export { EnvironmentDefault };
|
@ -5,7 +5,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { HttpModule } from '@nestjs/axios';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { FileModule } from 'src/engine/modules/file/file.module';
|
||||
import { Workspace } from 'src/engine/modules/workspace/workspace.entity';
|
||||
import { User } from 'src/engine/modules/user/user.entity';
|
||||
import { RefreshToken } from 'src/engine/modules/refresh-token/refresh-token.entity';
|
||||
@ -22,6 +21,7 @@ import { UserWorkspaceModule } from 'src/engine/modules/user-workspace/user-work
|
||||
import { SignUpService } from 'src/engine/modules/auth/services/sign-up.service';
|
||||
import { GoogleGmailAuthController } from 'src/engine/modules/auth/controllers/google-gmail-auth.controller';
|
||||
import { FeatureFlagEntity } from 'src/engine/modules/feature-flag/feature-flag.entity';
|
||||
import { FileUploadModule } from 'src/engine/modules/file/file-upload/file-upload.module';
|
||||
|
||||
import { AuthResolver } from './auth.resolver';
|
||||
|
||||
@ -42,7 +42,7 @@ const jwtModule = JwtModule.registerAsync({
|
||||
@Module({
|
||||
imports: [
|
||||
jwtModule,
|
||||
FileModule,
|
||||
FileUploadModule,
|
||||
DataSourceModule,
|
||||
UserModule,
|
||||
WorkspaceManagerModule,
|
||||
|
@ -6,7 +6,7 @@ import { Workspace } from 'src/engine/modules/workspace/workspace.entity';
|
||||
import { User } from 'src/engine/modules/user/user.entity';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { SignUpService } from 'src/engine/modules/auth/services/sign-up.service';
|
||||
import { FileUploadService } from 'src/engine/modules/file/services/file-upload.service';
|
||||
import { FileUploadService } from 'src/engine/modules/file/file-upload/services/file-upload.service';
|
||||
import { UserWorkspaceService } from 'src/engine/modules/user-workspace/user-workspace.service';
|
||||
|
||||
describe('SignUpService', () => {
|
||||
|
@ -19,7 +19,7 @@ import {
|
||||
} from 'src/engine/modules/auth/auth.util';
|
||||
import { User } from 'src/engine/modules/user/user.entity';
|
||||
import { Workspace } from 'src/engine/modules/workspace/workspace.entity';
|
||||
import { FileUploadService } from 'src/engine/modules/file/services/file-upload.service';
|
||||
import { FileUploadService } from 'src/engine/modules/file/file-upload/services/file-upload.service';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { getImageBufferFromUrl } from 'src/utils/image';
|
||||
import { UserWorkspaceService } from 'src/engine/modules/user-workspace/user-workspace.service';
|
||||
|
@ -7,7 +7,6 @@ import { RefreshToken } from 'src/engine/modules/refresh-token/refresh-token.ent
|
||||
import { User } from 'src/engine/modules/user/user.entity';
|
||||
import { JwtAuthStrategy } from 'src/engine/modules/auth/strategies/jwt.auth.strategy';
|
||||
import { EmailService } from 'src/engine/integrations/email/email.service';
|
||||
import { UserWorkspaceService } from 'src/engine/modules/user-workspace/user-workspace.service';
|
||||
import { Workspace } from 'src/engine/modules/workspace/workspace.entity';
|
||||
|
||||
import { TokenService } from './token.service';
|
||||
@ -35,10 +34,6 @@ describe('TokenService', () => {
|
||||
provide: EmailService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: UserWorkspaceService,
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(User, 'core'),
|
||||
useValue: {},
|
||||
|
@ -40,7 +40,6 @@ import { EmailService } from 'src/engine/integrations/email/email.service';
|
||||
import { InvalidatePassword } from 'src/engine/modules/auth/dto/invalidate-password.entity';
|
||||
import { EmailPasswordResetLink } from 'src/engine/modules/auth/dto/email-password-reset-link.entity';
|
||||
import { JwtData } from 'src/engine/modules/auth/types/jwt-data.type';
|
||||
import { UserWorkspaceService } from 'src/engine/modules/user-workspace/user-workspace.service';
|
||||
import { Workspace } from 'src/engine/modules/workspace/workspace.entity';
|
||||
|
||||
@Injectable()
|
||||
@ -56,7 +55,6 @@ export class TokenService {
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly emailService: EmailService,
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
) {}
|
||||
|
||||
async generateAccessToken(
|
||||
@ -528,4 +526,12 @@ export class TokenService {
|
||||
|
||||
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,11 +1,14 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { CanActivate } from '@nestjs/common';
|
||||
|
||||
import { FileService } from 'src/engine/modules/file/services/file.service';
|
||||
import { FilePathGuard } from 'src/engine/modules/file/guards/file-path-guard';
|
||||
|
||||
import { FileController } from './file.controller';
|
||||
|
||||
describe('FileController', () => {
|
||||
let controller: FileController;
|
||||
const mock_FilePathGuard: CanActivate = { canActivate: jest.fn(() => true) };
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@ -16,7 +19,10 @@ describe('FileController', () => {
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
})
|
||||
.overrideGuard(FilePathGuard)
|
||||
.useValue(mock_FilePathGuard)
|
||||
.compile();
|
||||
|
||||
controller = module.get<FileController>(FileController);
|
||||
});
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Controller, Get, Param, Res } from '@nestjs/common';
|
||||
import { Controller, Get, Param, Res, UseGuards } from '@nestjs/common';
|
||||
|
||||
import { Response } from 'express';
|
||||
|
||||
import { FilePathGuard } from 'src/engine/modules/file/guards/file-path-guard';
|
||||
import {
|
||||
checkFilePath,
|
||||
checkFilename,
|
||||
@ -10,6 +11,7 @@ import { FileService } from 'src/engine/modules/file/services/file.service';
|
||||
|
||||
// TODO: Add cookie authentication
|
||||
@Controller('files')
|
||||
@UseGuards(FilePathGuard)
|
||||
export class FileController {
|
||||
constructor(private readonly fileService: FileService) {}
|
||||
|
||||
|
@ -0,0 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { FileUploadResolver } from 'src/engine/modules/file/file-upload/resolvers/file-upload.resolver';
|
||||
import { FileUploadService } from 'src/engine/modules/file/file-upload/services/file-upload.service';
|
||||
|
||||
@Module({
|
||||
providers: [FileUploadService, FileUploadResolver, EnvironmentService],
|
||||
exports: [FileUploadService, FileUploadResolver],
|
||||
})
|
||||
export class FileUploadModule {}
|
@ -1,6 +1,6 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
|
||||
import { FileUploadService } from 'src/engine/modules/file/services/file-upload.service';
|
||||
import { FileUploadService } from 'src/engine/modules/file/file-upload/services/file-upload.service';
|
||||
|
||||
import { FileUploadResolver } from './file-upload.resolver';
|
||||
|
@ -5,7 +5,7 @@ import { GraphQLUpload, FileUpload } from 'graphql-upload';
|
||||
|
||||
import { FileFolder } from 'src/engine/modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { FileUploadService } from 'src/engine/modules/file/services/file-upload.service';
|
||||
import { FileUploadService } from 'src/engine/modules/file/file-upload/services/file-upload.service';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
|
@ -1,20 +1,17 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { FilePathGuard } from 'src/engine/modules/file/guards/file-path-guard';
|
||||
import { AuthModule } from 'src/engine/modules/auth/auth.module';
|
||||
import { FileUploadModule } from 'src/engine/modules/file/file-upload/file-upload.module';
|
||||
|
||||
import { FileService } from './services/file.service';
|
||||
import { FileUploadService } from './services/file-upload.service';
|
||||
import { FileUploadResolver } from './resolvers/file-upload.resolver';
|
||||
import { FileController } from './controllers/file.controller';
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
FileService,
|
||||
FileUploadService,
|
||||
FileUploadResolver,
|
||||
EnvironmentService,
|
||||
],
|
||||
exports: [FileService, FileUploadService],
|
||||
imports: [FileUploadModule, AuthModule],
|
||||
providers: [FileService, EnvironmentService, FilePathGuard],
|
||||
exports: [FileService],
|
||||
controllers: [FileController],
|
||||
})
|
||||
export class FileModule {}
|
||||
|
@ -0,0 +1,52 @@
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { TokenService } from 'src/engine/modules/auth/services/token.service';
|
||||
|
||||
@Injectable()
|
||||
export class FilePathGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly environmentService: EnvironmentService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const query = context.switchToHttp().getRequest().query;
|
||||
|
||||
if (query && query['token']) {
|
||||
return !(await this.isExpired(query['token']));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async isExpired(signedExpirationDate: string): Promise<boolean> {
|
||||
const decodedPayload = await this.tokenService.decodePayload(
|
||||
signedExpirationDate,
|
||||
{
|
||||
secret: this.environmentService.get('FILE_TOKEN_SECRET'),
|
||||
},
|
||||
);
|
||||
|
||||
const expirationDate = decodedPayload?.['expiration_date'];
|
||||
|
||||
if (!expirationDate) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (new Date(expirationDate) < new Date()) {
|
||||
throw new HttpException(
|
||||
'This url has expired. Please reload twenty page and open file again.',
|
||||
HttpStatus.FORBIDDEN,
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ import { Module } from '@nestjs/common';
|
||||
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { FileModule } from 'src/engine/modules/file/file.module';
|
||||
import { User } from 'src/engine/modules/user/user.entity';
|
||||
import { UserResolver } from 'src/engine/modules/user/user.resolver';
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
@ -12,6 +11,7 @@ import { DataSourceModule } from 'src/engine-metadata/data-source/data-source.mo
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { UserWorkspaceModule } from 'src/engine/modules/user-workspace/user-workspace.module';
|
||||
import { UserWorkspace } from 'src/engine/modules/user-workspace/user-workspace.entity';
|
||||
import { FileUploadModule } from 'src/engine/modules/file/file-upload/file-upload.module';
|
||||
|
||||
import { userAutoResolverOpts } from './user.auto-resolver-opts';
|
||||
|
||||
@ -27,7 +27,7 @@ import { UserService } from './services/user.service';
|
||||
resolvers: userAutoResolverOpts,
|
||||
}),
|
||||
DataSourceModule,
|
||||
FileModule,
|
||||
FileUploadModule,
|
||||
UserWorkspaceModule,
|
||||
],
|
||||
exports: [UserService],
|
||||
|
@ -20,7 +20,7 @@ import { FileFolder } from 'src/engine/modules/file/interfaces/file-folder.inter
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { EnvironmentService } from 'src/engine/integrations/environment/environment.service';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { FileUploadService } from 'src/engine/modules/file/services/file-upload.service';
|
||||
import { FileUploadService } from 'src/engine/modules/file/file-upload/services/file-upload.service';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { DemoEnvGuard } from 'src/engine/guards/demo.env.guard';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
|
@ -3,7 +3,6 @@ import { Module } from '@nestjs/common';
|
||||
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { FileModule } from 'src/engine/modules/file/file.module';
|
||||
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
|
||||
import { WorkspaceResolver } from 'src/engine/modules/workspace/workspace.resolver';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
@ -12,6 +11,7 @@ import { UserWorkspace } from 'src/engine/modules/user-workspace/user-workspace.
|
||||
import { User } from 'src/engine/modules/user/user.entity';
|
||||
import { UserWorkspaceModule } from 'src/engine/modules/user-workspace/user-workspace.module';
|
||||
import { BillingModule } from 'src/engine/modules/billing/billing.module';
|
||||
import { FileUploadModule } from 'src/engine/modules/file/file-upload/file-upload.module';
|
||||
|
||||
import { Workspace } from './workspace.entity';
|
||||
import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts';
|
||||
@ -24,7 +24,7 @@ import { WorkspaceService } from './services/workspace.service';
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [
|
||||
BillingModule,
|
||||
FileModule,
|
||||
FileUploadModule,
|
||||
NestjsQueryTypeOrmModule.forFeature(
|
||||
[User, Workspace, UserWorkspace, FeatureFlagEntity],
|
||||
'core',
|
||||
|
@ -13,7 +13,7 @@ import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
import { FileFolder } from 'src/engine/modules/file/interfaces/file-folder.interface';
|
||||
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { FileUploadService } from 'src/engine/modules/file/services/file-upload.service';
|
||||
import { FileUploadService } from 'src/engine/modules/file/file-upload/services/file-upload.service';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
|
||||
|
Loading…
Reference in New Issue
Block a user