From 5ee6656e0d80d2a91b3463c842c7540f64ba2121 Mon Sep 17 00:00:00 2001 From: Andres Pinto Date: Wed, 28 Feb 2024 15:47:04 -0400 Subject: [PATCH] feat: add endpoint to check if file size exceeds limit --- .../middleware/feature-limits.middleware.ts | 34 +++++++++++---- src/app/routes/storage.ts | 43 ++++++++++++++++++- .../services/errors/FeatureLimitsErrors.ts | 8 ++++ src/app/services/featureLimit.js | 2 +- 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/app/middleware/feature-limits.middleware.ts b/src/app/middleware/feature-limits.middleware.ts index 84acf05b..aa1ba951 100644 --- a/src/app/middleware/feature-limits.middleware.ts +++ b/src/app/middleware/feature-limits.middleware.ts @@ -1,5 +1,9 @@ import { Request, Response, NextFunction } from 'express'; import { AuthorizedUser } from '../routes/types'; +import { MissingValuesForFeatureLimit, NoLimitFoundForUserTierAndLabel } from '../services/errors/FeatureLimitsErrors'; +import Logger from '../../lib/logger'; + +const logger = Logger.getInstance(); type User = AuthorizedUser['user']; type Middleware = (req: Request & { behalfUser?: User }, res: Response, next: NextFunction) => Promise; @@ -27,15 +31,29 @@ const build = (Service: { return next(); } - const extractedData = extractDataFromRequest(req, dataSources); - const shouldLimitBeEnforced = await Service.FeatureLimits.shouldLimitBeEnforced( - user, - limitLabel, - extractedData, - ); + try { + const extractedData = extractDataFromRequest(req, dataSources); + const shouldLimitBeEnforced = await Service.FeatureLimits.shouldLimitBeEnforced( + user, + limitLabel, + extractedData, + ); + + if (shouldLimitBeEnforced) { + return res.status(402).send('You reached the limit for your tier!'); + } + } catch (err) { + if (err instanceof MissingValuesForFeatureLimit) { + return res.status(400).send('You reached the limit for your tier!'); + } + + if (err instanceof NoLimitFoundForUserTierAndLabel) { + logger.error('[FEATURE_LIMIT]: Error getting user limit, bypassing it userUuid: %s', user.uuid); + next(); + } - if (shouldLimitBeEnforced) { - return res.status(402).send('You reached the limit for your tier!'); + logger.error('[FEATURE_LIMIT]: Unexpected error ', err); + return res.status(400).send('Internal Server error'); } next(); diff --git a/src/app/routes/storage.ts b/src/app/routes/storage.ts index d5cc9dcb..9720a31c 100644 --- a/src/app/routes/storage.ts +++ b/src/app/routes/storage.ts @@ -21,7 +21,7 @@ import { FolderWithNameAlreadyExistsError } from '../services/errors/FolderWithNameAlreadyExistsError'; import * as resourceSharingMiddlewareBuilder from '../middleware/resource-sharing.middleware'; -import * as featureLimitsMiddlewareBuilder from '../middleware/feature-limits.middleware'; +import { build as featureLimitsMiddlewareBuilder, LimitLabels } from '../middleware/feature-limits.middleware'; import {validate } from 'uuid'; type AuthorizedRequest = Request & { user: UserAttributes }; @@ -31,6 +31,7 @@ interface Services { Folder: any; UsersReferrals: any; Analytics: any; + FeatureLimits: any; User: any; Notifications: any; Share: any; @@ -106,6 +107,36 @@ export class StorageController { } } + public async checkFileSizeLimit(req: Request, res: Response) { + const { behalfUser } = req as SharedRequest; + const { file } = req.body; + try { + if (!file || file.size === undefined || file.size === null) { + this.logger.error( + `Invalid metadata for file limit check ${behalfUser.email}: ${JSON.stringify(file, null, 2)}`, + ); + return res.status(400).json({ error: 'Invalid metadata for limit check' }); + } + const shouldLimitBeEnforced = await this.services.FeatureLimits.shouldLimitBeEnforced( + behalfUser, + LimitLabels.MaxFileUploadSize, + {file}, + ); + + if (shouldLimitBeEnforced) { + return res.status(402).send('This file size exceeds the limit for your tier!'); + } + return res.status(200).send('File can be upload'); + } catch (err) { + this.logger.error( + `[FEATURE_LIMIT] ERROR: ${(err as Error).message}, BODY ${JSON.stringify(file)}, STACK: ${ + (err as Error).stack + } USER: ${behalfUser.email}`, + ); + res.status(500).send({ error: 'Internal Server Error' }); + } + } + public async checkFileExistence(req: Request, res: Response) { const { behalfUser } = req as SharedRequest; const { file } = req.body as { file: { name: string; folderId: number; type: string } }; @@ -812,7 +843,7 @@ export default (router: Router, service: any) => { const sharedAdapter = sharedMiddlewareBuilder.build(service); const teamsAdapter = teamsMiddlewareBuilder.build(service); const resourceSharingAdapter = resourceSharingMiddlewareBuilder.build(service); - const featureLimitsAdapter = featureLimitsMiddlewareBuilder.build(service); + const featureLimitsAdapter = featureLimitsMiddlewareBuilder(service); const controller = new StorageController(service, Logger); router.post('/storage/file', @@ -822,6 +853,14 @@ export default (router: Router, service: any) => { featureLimitsAdapter.UploadFile, controller.createFile.bind(controller) ); + + router.post('/storage/file/check-limit', + passportAuth, + sharedAdapter, + resourceSharingAdapter.UploadFile, + controller.checkFileSizeLimit.bind(controller) + ); + router.post('/storage/file/exists', passportAuth, sharedAdapter, diff --git a/src/app/services/errors/FeatureLimitsErrors.ts b/src/app/services/errors/FeatureLimitsErrors.ts index ce1d3b8f..8d694289 100644 --- a/src/app/services/errors/FeatureLimitsErrors.ts +++ b/src/app/services/errors/FeatureLimitsErrors.ts @@ -5,3 +5,11 @@ export class NoLimitFoundForUserTierAndLabel extends Error { Object.setPrototypeOf(this, NoLimitFoundForUserTierAndLabel.prototype); } } + +export class MissingValuesForFeatureLimit extends Error { + constructor(message: string) { + super(message); + + Object.setPrototypeOf(this, MissingValuesForFeatureLimit.prototype); + } +} diff --git a/src/app/services/featureLimit.js b/src/app/services/featureLimit.js index b60b47aa..3375aabf 100644 --- a/src/app/services/featureLimit.js +++ b/src/app/services/featureLimit.js @@ -54,7 +54,7 @@ module.exports = (Model, App) => { case LimitLabels.MaxFileUploadSize: return isMaxFileSizeLimitSurprassed({ limit, data }); default: - return null; + return false; } };