feat(syncing-server): reduced abuse thresholds for free users (#1021)

This commit is contained in:
Karol Sójko 2024-01-12 15:45:00 +01:00 committed by GitHub
parent f830bac873
commit 0443de88ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 78 additions and 24 deletions

View file

@ -119,8 +119,10 @@ void container.load().then((container) => {
container.get<boolean>(TYPES.Sync_STRICT_ABUSE_PROTECTION),
container.get<number>(TYPES.Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES),
container.get<number>(TYPES.Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD),
container.get<number>(TYPES.Sync_PAYLOAD_SIZE_ABUSE_THRESHOLD),
container.get<number>(TYPES.Sync_PAYLOAD_SIZE_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES),
container.get<number>(TYPES.Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD),
container.get<number>(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD),
container.get<number>(TYPES.Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD),
container.get<number>(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES),
container.get<winston.Logger>(TYPES.Sync_Logger),
)

View file

@ -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<boolean>(TYPES.Sync_STRICT_ABUSE_PROTECTION),
container.get<number>(TYPES.Sync_ITEM_OPERATIONS_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES),
container.get<number>(TYPES.Sync_ITEM_OPERATIONS_ABUSE_THRESHOLD),
container.get<number>(TYPES.Sync_PAYLOAD_SIZE_ABUSE_THRESHOLD),
container.get<number>(TYPES.Sync_PAYLOAD_SIZE_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES),
container.get<number>(TYPES.Sync_FREE_USERS_ITEM_OPERATIONS_ABUSE_THRESHOLD),
container.get<number>(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD),
container.get<number>(TYPES.Sync_FREE_USERS_UPLOAD_BANDWIDTH_ABUSE_THRESHOLD),
container.get<number>(TYPES.Sync_UPLOAD_BANDWIDTH_ABUSE_TIMEFRAME_LENGTH_IN_MINUTES),
container.get<ControllerContainerInterface>(TYPES.Sync_ControllerContainer),
),
)

View file

@ -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'),

View file

@ -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,
)
}

View file

@ -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,
)
}
}

View file

@ -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<void> {
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()