diff --git a/packages/syncing-server/bin/server.ts b/packages/syncing-server/bin/server.ts index f13acfe5a..bda76e003 100644 --- a/packages/syncing-server/bin/server.ts +++ b/packages/syncing-server/bin/server.ts @@ -119,8 +119,10 @@ void container.load().then((container) => { container.get(TYPES.Sync_STRICT_ABUSE_PROTECTION), container.get(TYPES.Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES), container.get(TYPES.Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD), - container.get(TYPES.Sync_PAYLOAD_SIZE_ABUSE_THRESHOLD), - container.get(TYPES.Sync_PAYLOAD_SIZE_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES), + container.get(TYPES.Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD), + container.get(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD), + container.get(TYPES.Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD), + container.get(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES), container.get(TYPES.Sync_Logger), ) diff --git a/packages/syncing-server/src/Bootstrap/Container.ts b/packages/syncing-server/src/Bootstrap/Container.ts index aed9d085a..cf8fc11d6 100644 --- a/packages/syncing-server/src/Bootstrap/Container.ts +++ b/packages/syncing-server/src/Bootstrap/Container.ts @@ -479,7 +479,14 @@ export class ContainerConfigLoader { container .bind(TYPES.Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD) .toConstantValue( - env.get('ITEM_OPERATIONS_ABUSE_THRESHOLD', true) ? +env.get('ITEM_OPERATIONS_ABUSE_THRESHOLD', true) : 500, + env.get('ITEM_OPERATIONS_ABUSE_THRESHOLD', true) ? +env.get('ITEM_OPERATIONS_ABUSE_THRESHOLD', true) : 1000, + ) + container + .bind(TYPES.Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD) + .toConstantValue( + env.get('FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD', true) + ? +env.get('FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD', true) + : 500, ) container .bind(TYPES.Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES) @@ -489,15 +496,24 @@ export class ContainerConfigLoader { : 5, ) container - .bind(TYPES.Sync_PAYLOAD_SIZE_ABUSE_THRESHOLD) + .bind(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD) .toConstantValue( - env.get('PAYLOAD_SIZE_ABUSE_THRESHOLD', true) ? +env.get('PAYLOAD_SIZE_ABUSE_THRESHOLD', true) : 20_000_000, + env.get('UPLOAD_BANDWIDTH_ABUSE_THRESHOLD', true) + ? +env.get('UPLOAD_BANDWIDTH_ABUSE_THRESHOLD', true) + : 100_000_000, ) container - .bind(TYPES.Sync_PAYLOAD_SIZE_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES) + .bind(TYPES.Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD) .toConstantValue( - env.get('PAYLOAD_SIZE_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES', true) - ? +env.get('PAYLOAD_SIZE_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES', true) + env.get('FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD', true) + ? +env.get('FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD', true) + : 50_000_000, + ) + container + .bind(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES) + .toConstantValue( + env.get('UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES', true) + ? +env.get('UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES', true) : 5, ) container.bind(TYPES.Sync_AUTH_JWT_SECRET).toConstantValue(env.get('AUTH_JWT_SECRET')) @@ -1145,8 +1161,10 @@ export class ContainerConfigLoader { container.get(TYPES.Sync_STRICT_ABUSE_PROTECTION), container.get(TYPES.Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES), container.get(TYPES.Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD), - container.get(TYPES.Sync_PAYLOAD_SIZE_ABUSE_THRESHOLD), - container.get(TYPES.Sync_PAYLOAD_SIZE_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES), + container.get(TYPES.Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD), + container.get(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD), + container.get(TYPES.Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD), + container.get(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES), container.get(TYPES.Sync_ControllerContainer), ), ) diff --git a/packages/syncing-server/src/Bootstrap/Types.ts b/packages/syncing-server/src/Bootstrap/Types.ts index d1f45b9fb..36b5a933e 100644 --- a/packages/syncing-server/src/Bootstrap/Types.ts +++ b/packages/syncing-server/src/Bootstrap/Types.ts @@ -47,9 +47,11 @@ const TYPES = { 'Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES', ), Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD: Symbol.for('Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD'), - Sync_PAYLOAD_SIZE_ABUSE_THRESHOLD: Symbol.for('Sync_PAYLOAD_SIZE_ABUSE_THRESHOLD'), - Sync_PAYLOAD_SIZE_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES: Symbol.for( - 'Sync_PAYLOAD_SIZE_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES', + Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD: Symbol.for('Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD'), + Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD: Symbol.for('Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD'), + Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD: Symbol.for('Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD'), + Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES: Symbol.for( + 'Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES', ), // use cases Sync_SyncItems: Symbol.for('Sync_SyncItems'), diff --git a/packages/syncing-server/src/Infra/InversifyExpressUtils/AnnotatedItemsController.ts b/packages/syncing-server/src/Infra/InversifyExpressUtils/AnnotatedItemsController.ts index d981bd91a..c262591f3 100644 --- a/packages/syncing-server/src/Infra/InversifyExpressUtils/AnnotatedItemsController.ts +++ b/packages/syncing-server/src/Infra/InversifyExpressUtils/AnnotatedItemsController.ts @@ -29,8 +29,11 @@ export class AnnotatedItemsController extends BaseItemsController { @inject(TYPES.Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES) override itemOperationsAbuseTimeframeLengthInMinutes: number, @inject(TYPES.Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD) override itemOperationsAbuseThreshold: number, - @inject(TYPES.Sync_PAYLOAD_SIZE_ABUSE_THRESHOLD) override payloadSizeAbuseThreshold: number, - @inject(TYPES.Sync_PAYLOAD_SIZE_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES) + @inject(TYPES.Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD) + override freeUsersItemOperationsAbuseThreshold: number, + @inject(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD) override payloadSizeAbuseThreshold: number, + @inject(TYPES.Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD) override freeUsersPayloadSizeAbuseThreshold: number, + @inject(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES) override payloadSizeAbuseTimeframeLengthInMinutes: number, ) { super( @@ -44,7 +47,9 @@ export class AnnotatedItemsController extends BaseItemsController { strictAbuseProtection, itemOperationsAbuseTimeframeLengthInMinutes, itemOperationsAbuseThreshold, + freeUsersItemOperationsAbuseThreshold, payloadSizeAbuseThreshold, + freeUsersPayloadSizeAbuseThreshold, payloadSizeAbuseTimeframeLengthInMinutes, ) } diff --git a/packages/syncing-server/src/Infra/InversifyExpressUtils/Base/BaseItemsController.ts b/packages/syncing-server/src/Infra/InversifyExpressUtils/Base/BaseItemsController.ts index 903466694..f83b918d7 100644 --- a/packages/syncing-server/src/Infra/InversifyExpressUtils/Base/BaseItemsController.ts +++ b/packages/syncing-server/src/Infra/InversifyExpressUtils/Base/BaseItemsController.ts @@ -28,7 +28,9 @@ export class BaseItemsController extends BaseHttpController { protected strictAbuseProtection: boolean, protected itemOperationsAbuseTimeframeLengthInMinutes: number, protected itemOperationsAbuseThreshold: number, + protected freeUsersItemOperationsAbuseThreshold: number, protected payloadSizeAbuseThreshold: number, + protected freeUsersPayloadSizeAbuseThreshold: number, protected payloadSizeAbuseTimeframeLengthInMinutes: number, private controllerContainer?: ControllerContainerInterface, ) { @@ -46,7 +48,7 @@ export class BaseItemsController extends BaseHttpController { const checkForItemOperationsAbuseResult = await this.checkForTrafficAbuse.execute({ metricToCheck: Metric.NAMES.ItemOperation, userUuid: locals.user.uuid, - threshold: this.itemOperationsAbuseThreshold, + threshold: locals.isFreeUser ? this.freeUsersItemOperationsAbuseThreshold : this.itemOperationsAbuseThreshold, timeframeLengthInMinutes: this.itemOperationsAbuseTimeframeLengthInMinutes, }) if (checkForItemOperationsAbuseResult.isFailed()) { @@ -54,14 +56,22 @@ export class BaseItemsController extends BaseHttpController { userId: locals.user.uuid, }) if (this.strictAbuseProtection) { - return this.json({ error: { message: checkForItemOperationsAbuseResult.getError() } }, 429) + return this.json( + { + error: { + message: + 'You have exceeded the maximum bandwidth allotted to your account in a 5-minute period. Please wait to try again, or upgrade your account for increased limits.', + }, + }, + 429, + ) } } const checkForPayloadSizeAbuseResult = await this.checkForTrafficAbuse.execute({ metricToCheck: Metric.NAMES.ContentSizeUtilized, userUuid: locals.user.uuid, - threshold: this.payloadSizeAbuseThreshold, + threshold: locals.isFreeUser ? this.freeUsersPayloadSizeAbuseThreshold : this.payloadSizeAbuseThreshold, timeframeLengthInMinutes: this.payloadSizeAbuseTimeframeLengthInMinutes, }) if (checkForPayloadSizeAbuseResult.isFailed()) { @@ -70,7 +80,15 @@ export class BaseItemsController extends BaseHttpController { }) if (this.strictAbuseProtection) { - return this.json({ error: { message: checkForPayloadSizeAbuseResult.getError() } }, 429) + return this.json( + { + error: { + message: + 'You have exceeded the maximum bandwidth allotted to your account in a 5-minute period. Please wait to try again, or upgrade your account for increased limits.', + }, + }, + 429, + ) } } diff --git a/packages/syncing-server/src/Infra/gRPC/SyncingServer.ts b/packages/syncing-server/src/Infra/gRPC/SyncingServer.ts index e45667d9a..fe8471b82 100644 --- a/packages/syncing-server/src/Infra/gRPC/SyncingServer.ts +++ b/packages/syncing-server/src/Infra/gRPC/SyncingServer.ts @@ -21,7 +21,9 @@ export class SyncingServer implements ISyncingServer { private strictAbuseProtection: boolean, private itemOperationsAbuseTimeframeLengthInMinutes: number, private itemOperationsAbuseThreshold: number, + private freeUsersItemOperationsAbuseThreshold: number, private payloadSizeAbuseThreshold: number, + private freeUsersPayloadSizeAbuseThreshold: number, private payloadSizeAbuseTimeframeLengthInMinutes: number, private logger: Logger, ) {} @@ -32,11 +34,12 @@ export class SyncingServer implements ISyncingServer { ): Promise { try { const userUuid = call.metadata.get('x-user-uuid').pop() as string + const isFreeUser = call.metadata.get('x-is-free-user').pop() === 'true' const checkForItemOperationsAbuseResult = await this.checkForTrafficAbuse.execute({ metricToCheck: Metric.NAMES.ItemOperation, userUuid, - threshold: this.itemOperationsAbuseThreshold, + threshold: isFreeUser ? this.freeUsersItemOperationsAbuseThreshold : this.itemOperationsAbuseThreshold, timeframeLengthInMinutes: this.itemOperationsAbuseTimeframeLengthInMinutes, }) if (checkForItemOperationsAbuseResult.isFailed()) { @@ -45,7 +48,10 @@ export class SyncingServer implements ISyncingServer { }) if (this.strictAbuseProtection) { const metadata = new grpc.Metadata() - metadata.set('x-sync-error-message', checkForItemOperationsAbuseResult.getError()) + metadata.set( + 'x-sync-error-message', + 'You have exceeded the maximum bandwidth allotted to your account in a 5-minute period. Please wait to try again, or upgrade your account for increased limits.', + ) metadata.set('x-sync-error-response-code', '429') return callback( @@ -63,7 +69,7 @@ export class SyncingServer implements ISyncingServer { const checkForPayloadSizeAbuseResult = await this.checkForTrafficAbuse.execute({ metricToCheck: Metric.NAMES.ContentSizeUtilized, userUuid, - threshold: this.payloadSizeAbuseThreshold, + threshold: isFreeUser ? this.freeUsersPayloadSizeAbuseThreshold : this.payloadSizeAbuseThreshold, timeframeLengthInMinutes: this.payloadSizeAbuseTimeframeLengthInMinutes, }) if (checkForPayloadSizeAbuseResult.isFailed()) { @@ -73,7 +79,10 @@ export class SyncingServer implements ISyncingServer { if (this.strictAbuseProtection) { const metadata = new grpc.Metadata() - metadata.set('x-sync-error-message', checkForPayloadSizeAbuseResult.getError()) + metadata.set( + 'x-sync-error-message', + 'You have exceeded the maximum bandwidth allotted to your account in a 5-minute period. Please wait to try again, or upgrade your account for increased limits.', + ) metadata.set('x-sync-error-response-code', '429') return callback( @@ -158,7 +167,7 @@ export class SyncingServer implements ISyncingServer { readOnlyAccess, sessionUuid: call.metadata.get('x-session-uuid').pop() as string, sharedVaultUuids, - isFreeUser: call.metadata.get('x-is-free-user').pop() === 'true', + isFreeUser, }) if (syncResult.isFailed()) { const metadata = new grpc.Metadata()