mirror of
https://github.com/standardnotes/server
synced 2026-01-16 20:04:32 -05:00
feat(syncing-server): reduced abuse thresholds for free users (#1021)
This commit is contained in:
@@ -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),
|
||||
)
|
||||
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user