Compare commits

...

12 Commits

Author SHA1 Message Date
standardci
c00c7becae chore(release): publish new version
- @standardnotes/home-server@1.22.58
 - @standardnotes/syncing-server@1.134.1
2024-01-16 10:41:23 +00:00
Karol Sójko
89dc6c19bf fix(syncing-server): missing item operations metric store expiry 2024-01-16 11:20:35 +01:00
standardci
972a91d59f chore(release): publish new version
- @standardnotes/auth-server@1.177.18
 - @standardnotes/home-server@1.22.57
2024-01-15 12:09:42 +00:00
Karol Sójko
045358ddbf fix(auth): add renewal for shared offline subscriptions 2024-01-15 12:42:54 +01:00
Karol Sójko
c7217a92ba fix(auth): add more logs to syncing subscription 2024-01-15 12:39:27 +01:00
standardci
3da7a21cde chore(release): publish new version
- @standardnotes/auth-server@1.177.17
 - @standardnotes/home-server@1.22.56
2024-01-15 10:27:24 +00:00
Karol Sójko
351e18f638 fix(auth): add debug logs for subscription sync requested event 2024-01-15 11:06:19 +01:00
standardci
4f2129c4e0 chore(release): publish new version
- @standardnotes/auth-server@1.177.16
 - @standardnotes/home-server@1.22.55
2024-01-15 09:44:23 +00:00
Karol Sójko
d7a1c667dd fix(auth): update shared subscriptions upon subscription sync (#1022) 2024-01-15 10:23:51 +01:00
standardci
4de0bfa36d chore(release): publish new version
- @standardnotes/home-server@1.22.54
 - @standardnotes/syncing-server@1.134.0
2024-01-12 15:06:26 +00:00
Karol Sójko
0443de88ce feat(syncing-server): reduced abuse thresholds for free users (#1021) 2024-01-12 15:45:00 +01:00
Karol Sójko
f830bac873 fix: reduce the transfer limit on e2e tests (#1020) 2024-01-11 13:12:33 +01:00
19 changed files with 210 additions and 33 deletions

2
.github/ci.env vendored
View File

@@ -27,4 +27,4 @@ AUTH_JWT_SECRET=f95259c5e441f5a4646d76422cfb3df4c4488842901aa50b6c51b8be2e0040e9
AUTH_SERVER_ENCRYPTION_SERVER_KEY=1087415dfde3093797f9a7ca93a49e7d7aa1861735eb0d32aae9c303b8c3d060
VALET_TOKEN_SECRET=4b886819ebe1e908077c6cae96311b48a8416bd60cc91c03060e15bdf6b30d1f
SYNCING_SERVER_CONTENT_SIZE_TRANSFER_LIMIT=1000000
SYNCING_SERVER_CONTENT_SIZE_TRANSFER_LIMIT=100000

View File

@@ -70,7 +70,7 @@ jobs:
echo "ACCESS_TOKEN_AGE=4" >> packages/home-server/.env
echo "REFRESH_TOKEN_AGE=10" >> packages/home-server/.env
echo "REVISIONS_FREQUENCY=2" >> packages/home-server/.env
echo "CONTENT_SIZE_TRANSFER_LIMIT=1000000" >> packages/home-server/.env
echo "CONTENT_SIZE_TRANSFER_LIMIT=100000" >> packages/home-server/.env
echo "DB_HOST=localhost" >> packages/home-server/.env
echo "DB_PORT=3306" >> packages/home-server/.env
echo "DB_DATABASE=standardnotes" >> packages/home-server/.env

View File

@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.177.18](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.17...@standardnotes/auth-server@1.177.18) (2024-01-15)
### Bug Fixes
* **auth:** add more logs to syncing subscription ([c7217a9](https://github.com/standardnotes/server/commit/c7217a92ba89d8b5f4963a832aa7561dd146ca0d))
* **auth:** add renewal for shared offline subscriptions ([045358d](https://github.com/standardnotes/server/commit/045358ddbf300996a23bba8d6945b1d7b5f6e862))
## [1.177.17](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.16...@standardnotes/auth-server@1.177.17) (2024-01-15)
### Bug Fixes
* **auth:** add debug logs for subscription sync requested event ([351e18f](https://github.com/standardnotes/server/commit/351e18f6389c2dbaa2107e6549be9928c2e8834f))
## [1.177.16](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.15...@standardnotes/auth-server@1.177.16) (2024-01-15)
### Bug Fixes
* **auth:** update shared subscriptions upon subscription sync ([#1022](https://github.com/standardnotes/server/issues/1022)) ([d7a1c66](https://github.com/standardnotes/server/commit/d7a1c667dd62dacc1ef15f2a4f408dc07045fcad))
## [1.177.15](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.177.14...@standardnotes/auth-server@1.177.15) (2024-01-09)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.177.15",
"version": "1.177.18",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -1421,6 +1421,7 @@ export class ContainerConfigLoader {
container.get<SetSettingValue>(TYPES.Auth_SetSettingValue),
container.get<OfflineSettingServiceInterface>(TYPES.Auth_OfflineSettingService),
container.get<ContentDecoderInterface>(TYPES.Auth_ContenDecoder),
container.get<RenewSharedSubscriptions>(TYPES.Auth_RenewSharedSubscriptions),
container.get<winston.Logger>(TYPES.Auth_Logger),
),
)

View File

@@ -34,6 +34,19 @@ export class SubscriptionPurchasedEventHandler implements DomainEventHandlerInte
event.payload.timestamp,
)
const renewalResult = await this.renewSharedSubscriptions.execute({
inviterEmail: event.payload.userEmail,
newSubscriptionId: event.payload.subscriptionId,
newSubscriptionName: event.payload.subscriptionName,
newSubscriptionExpiresAt: event.payload.subscriptionExpiresAt,
timestamp: event.payload.timestamp,
})
if (renewalResult.isFailed()) {
this.logger.error(`Could not renew shared offline subscriptions: ${renewalResult.getError()}`, {
subscriptionId: event.payload.subscriptionId,
})
}
await this.roleService.setOfflineUserRole(offlineUserSubscription)
return

View File

@@ -16,6 +16,7 @@ import { OfflineSettingName } from '../Setting/OfflineSettingName'
import { UserSubscriptionType } from '../Subscription/UserSubscriptionType'
import { ApplyDefaultSubscriptionSettings } from '../UseCase/ApplyDefaultSubscriptionSettings/ApplyDefaultSubscriptionSettings'
import { SetSettingValue } from '../UseCase/SetSettingValue/SetSettingValue'
import { RenewSharedSubscriptions } from '../UseCase/RenewSharedSubscriptions/RenewSharedSubscriptions'
export class SubscriptionSyncRequestedEventHandler implements DomainEventHandlerInterface {
constructor(
@@ -27,11 +28,20 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
private setSettingValue: SetSettingValue,
private offlineSettingService: OfflineSettingServiceInterface,
private contentDecoder: ContentDecoderInterface,
private renewSharedSubscriptions: RenewSharedSubscriptions,
private logger: Logger,
) {}
async handle(event: SubscriptionSyncRequestedEvent): Promise<void> {
this.logger.info('Subscription sync requested', {
subscriptionId: event.payload.subscriptionId,
})
if (event.payload.offline) {
this.logger.info('Syncing offline subscription', {
subscriptionId: event.payload.subscriptionId,
})
const offlineUserSubscription = await this.createOrUpdateOfflineSubscription(
event.payload.subscriptionId,
event.payload.subscriptionName,
@@ -41,6 +51,19 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
event.payload.timestamp,
)
const renewalResult = await this.renewSharedSubscriptions.execute({
inviterEmail: event.payload.userEmail,
newSubscriptionId: event.payload.subscriptionId,
newSubscriptionName: event.payload.subscriptionName,
newSubscriptionExpiresAt: event.payload.subscriptionExpiresAt,
timestamp: event.payload.timestamp,
})
if (renewalResult.isFailed()) {
this.logger.error(`Could not renew shared offline subscriptions for user: ${renewalResult.getError()}`, {
subscriptionId: event.payload.subscriptionId,
})
}
await this.roleService.setOfflineUserRole(offlineUserSubscription)
const offlineFeaturesTokenDecoded = this.contentDecoder.decode(
@@ -60,11 +83,19 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
value: offlineFeaturesTokenDecoded.extensionKey,
})
this.logger.info('Offline subscription synced', {
subscriptionId: event.payload.subscriptionId,
})
return
}
const usernameOrError = Username.create(event.payload.userEmail)
if (usernameOrError.isFailed()) {
this.logger.warn(`Could not sync subscription: ${usernameOrError.getError()}`, {
subscriptionId: event.payload.subscriptionId,
})
return
}
const username = usernameOrError.getValue()
@@ -72,10 +103,18 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
const user = await this.userRepository.findOneByUsernameOrEmail(username)
if (user === null) {
this.logger.warn(`Could not find user with email: ${username.value}`)
this.logger.warn(`Could not find user with email: ${username.value}`, {
subscriptionId: event.payload.subscriptionId,
})
return
}
this.logger.info('Syncing subscription', {
userId: user.uuid,
subscriptionId: event.payload.subscriptionId,
})
const userSubscription = await this.createOrUpdateSubscription(
event.payload.subscriptionId,
event.payload.subscriptionName,
@@ -85,6 +124,19 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
event.payload.timestamp,
)
const renewalResult = await this.renewSharedSubscriptions.execute({
inviterEmail: user.email,
newSubscriptionId: event.payload.subscriptionId,
newSubscriptionName: event.payload.subscriptionName,
newSubscriptionExpiresAt: event.payload.subscriptionExpiresAt,
timestamp: event.payload.timestamp,
})
if (renewalResult.isFailed()) {
this.logger.error(`Could not renew shared subscriptions for user: ${renewalResult.getError()}`, {
userId: user.uuid,
})
}
await this.roleService.addUserRoleBasedOnSubscription(user, event.payload.subscriptionName)
const applyingSettingsResult = await this.applyDefaultSubscriptionSettings.execute({
@@ -107,6 +159,11 @@ export class SubscriptionSyncRequestedEventHandler implements DomainEventHandler
if (result.isFailed()) {
this.logger.error(`Could not set extension key for user ${user.uuid}`)
}
this.logger.info('Subscription synced', {
userId: user.uuid,
subscriptionId: event.payload.subscriptionId,
})
}
private async createOrUpdateSubscription(

View File

@@ -84,6 +84,7 @@ export class TypeORMUserSubscriptionRepository implements UserSubscriptionReposi
userUuid,
subscriptionId,
})
.orderBy('ends_at', 'DESC')
.getOne()
}

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.22.58](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.57...@standardnotes/home-server@1.22.58) (2024-01-16)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.57](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.56...@standardnotes/home-server@1.22.57) (2024-01-15)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.56](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.55...@standardnotes/home-server@1.22.56) (2024-01-15)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.55](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.54...@standardnotes/home-server@1.22.55) (2024-01-15)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.54](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.53...@standardnotes/home-server@1.22.54) (2024-01-12)
**Note:** Version bump only for package @standardnotes/home-server
## [1.22.53](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.22.52...@standardnotes/home-server@1.22.53) (2024-01-10)
### Bug Fixes

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.22.53",
"version": "1.22.58",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.134.1](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.134.0...@standardnotes/syncing-server@1.134.1) (2024-01-16)
### Bug Fixes
* **syncing-server:** missing item operations metric store expiry ([89dc6c1](https://github.com/standardnotes/server/commit/89dc6c19bf0e4a8c715f085ace5a717151d8fe9f))
# [1.134.0](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.133.6...@standardnotes/syncing-server@1.134.0) (2024-01-12)
### Features
* **syncing-server:** reduced abuse thresholds for free users ([#1021](https://github.com/standardnotes/server/issues/1021)) ([0443de8](https://github.com/standardnotes/server/commit/0443de88ceae3cb7c0793a3457753806b51db6e2))
## [1.133.6](https://github.com/standardnotes/server/compare/@standardnotes/syncing-server@1.133.5...@standardnotes/syncing-server@1.133.6) (2024-01-10)
### Bug Fixes

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

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.133.6",
"version": "1.134.1",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

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

@@ -82,16 +82,16 @@ export class RedisMetricStore implements MetricsStoreInterface {
const date = this.timer.convertMicrosecondsToDate(metric.props.timestamp)
const dateToTheMinuteString = this.timer.convertDateToFormattedString(date, 'YYYY-MM-DD HH:mm')
const key = `${this.METRIC_PER_USER_PREFIX}:${userUuid.value}:${metric.props.name}:${dateToTheMinuteString}`
const itemOperationKey = `${this.METRIC_PER_USER_PREFIX}:${userUuid.value}:${Metric.NAMES.ItemOperation}:${dateToTheMinuteString}`
const pipeline = this.redisClient.pipeline()
pipeline.incrbyfloat(key, value)
pipeline.incr(
`${this.METRIC_PER_USER_PREFIX}:${userUuid.value}:${Metric.NAMES.ItemOperation}:${dateToTheMinuteString}`,
)
pipeline.incr(itemOperationKey)
const expirationTime = 60 * 60 * 24
pipeline.expire(key, expirationTime)
pipeline.expire(itemOperationKey, expirationTime)
await pipeline.exec()
}

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()