Skip to content

Commit 90dd6e8

Browse files
JanCizmarclaude
andcommitted
feat: add per-user rate limits for translation and activity endpoints
Prevent translation and activity API endpoints from overwhelming the database by adding configurable per-user rate limits (100 req / 5 min), matching the existing export endpoint pattern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a33e1d0 commit 90dd6e8

3 files changed

Lines changed: 39 additions & 0 deletions

File tree

backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/ProjectActivityController.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import io.swagger.v3.oas.annotations.Parameter
99
import io.swagger.v3.oas.annotations.media.ExampleObject
1010
import io.swagger.v3.oas.annotations.tags.Tag
1111
import io.tolgee.activity.ActivityService
12+
import io.tolgee.configuration.tolgee.TolgeeProperties
1213
import io.tolgee.constants.Feature
1314
import io.tolgee.exceptions.NotFoundException
1415
import io.tolgee.hateoas.activity.ModifiedEntityModel
@@ -21,6 +22,7 @@ import io.tolgee.model.views.activity.ProjectActivityView
2122
import io.tolgee.security.ProjectHolder
2223
import io.tolgee.security.authentication.AllowApiAccess
2324
import io.tolgee.security.authorization.RequiresProjectPermissions
25+
import io.tolgee.security.ratelimit.RateLimitService
2426
import io.tolgee.service.branching.BranchService
2527
import io.tolgee.service.project.ProjectFeatureGuard
2628
import org.springdoc.core.annotations.ParameterObject
@@ -33,6 +35,7 @@ import org.springframework.web.bind.annotation.PathVariable
3335
import org.springframework.web.bind.annotation.RequestMapping
3436
import org.springframework.web.bind.annotation.RequestParam
3537
import org.springframework.web.bind.annotation.RestController
38+
import java.time.Duration
3639

3740
@Suppress("MVCPathVariableInspection", "SpringJavaInjectionPointsAutowiringInspection")
3841
@RestController
@@ -48,6 +51,8 @@ class ProjectActivityController(
4851
private val modifiedEntityModelAssembler: ModifiedEntityModelAssembler,
4952
private val branchService: BranchService,
5053
private val projectFeatureGuard: ProjectFeatureGuard,
54+
private val rateLimitService: RateLimitService,
55+
private val tolgeeProperties: TolgeeProperties,
5156
) {
5257
@Operation(summary = "Get project activity")
5358
@GetMapping("")
@@ -57,6 +62,11 @@ class ProjectActivityController(
5762
@ParameterObject pageable: Pageable,
5863
@RequestParam(required = false) branch: String? = null,
5964
): PagedModel<ProjectActivityModel> {
65+
rateLimitService.checkPerUserRateLimit(
66+
"activity",
67+
limit = tolgeeProperties.rateLimits.activityRequestLimit,
68+
refillDuration = Duration.ofMillis(tolgeeProperties.rateLimits.activityRequestWindow),
69+
)
6070
projectFeatureGuard.checkIfUsed(Feature.BRANCHING, branch)
6171
val views =
6272
activityService.findProjectActivity(

backend/api/src/main/kotlin/io/tolgee/api/v2/controllers/translation/TranslationsController.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import io.tolgee.activity.RequestActivity
1616
import io.tolgee.activity.data.ActivityType
1717
import io.tolgee.api.v2.controllers.IController
1818
import io.tolgee.component.ProjectLastModifiedManager
19+
import io.tolgee.configuration.tolgee.TolgeeProperties
1920
import io.tolgee.constants.Feature
2021
import io.tolgee.constants.Message
2122
import io.tolgee.dtos.queryResults.TranslationHistoryView
@@ -41,6 +42,7 @@ import io.tolgee.security.authentication.AllowApiAccess
4142
import io.tolgee.security.authentication.AuthenticationFacade
4243
import io.tolgee.security.authorization.RequiresProjectPermissions
4344
import io.tolgee.security.authorization.UseDefaultPermissions
45+
import io.tolgee.security.ratelimit.RateLimitService
4446
import io.tolgee.service.key.ScreenshotService
4547
import io.tolgee.service.language.LanguageService
4648
import io.tolgee.service.project.ProjectFeatureGuard
@@ -74,6 +76,7 @@ import org.springframework.web.bind.annotation.RequestMapping
7476
import org.springframework.web.bind.annotation.RequestParam
7577
import org.springframework.web.bind.annotation.RestController
7678
import org.springframework.web.context.request.WebRequest
79+
import java.time.Duration
7780

7881
@Suppress("MVCPathVariableInspection", "SpringJavaInjectionPointsAutowiringInspection")
7982
@RestController
@@ -107,6 +110,8 @@ class TranslationsController(
107110
private val translationSuggestionService: TranslationSuggestionService,
108111
private val projectLastModifiedManager: ProjectLastModifiedManager,
109112
private val projectFeatureGuard: ProjectFeatureGuard,
113+
private val rateLimitService: RateLimitService,
114+
private val tolgeeProperties: TolgeeProperties,
110115
) : IController {
111116
@GetMapping(value = ["/{languages}"])
112117
@Operation(
@@ -167,6 +172,11 @@ When null, resulting file will be a flat key-value object.
167172
): ResponseEntity<Map<String, Any>>? {
168173
projectFeatureGuard.checkIfUsed(Feature.BRANCHING, branch)
169174
return projectLastModifiedManager.onlyWhenProjectDataChanged(request) {
175+
rateLimitService.checkPerUserRateLimit(
176+
"translations",
177+
limit = tolgeeProperties.rateLimits.translationRequestLimit,
178+
refillDuration = Duration.ofMillis(tolgeeProperties.rateLimits.translationRequestWindow),
179+
)
170180
val permittedTags =
171181
securityService
172182
.filterViewPermissionByTag(projectId = projectHolder.project.id, languageTags = languages)
@@ -251,6 +261,11 @@ When null, resulting file will be a flat key-value object.
251261
params: GetTranslationsParams,
252262
@ParameterObject pageable: Pageable,
253263
): KeysWithTranslationsPageModel {
264+
rateLimitService.checkPerUserRateLimit(
265+
"translations",
266+
limit = tolgeeProperties.rateLimits.translationRequestLimit,
267+
refillDuration = Duration.ofMillis(tolgeeProperties.rateLimits.translationRequestWindow),
268+
)
254269
projectFeatureGuard.checkIfUsed(Feature.BRANCHING, params.branch)
255270
val languages =
256271
languageService.getLanguagesForTranslationsView(

backend/data/src/main/kotlin/io/tolgee/configuration/tolgee/RateLimitProperties.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ class RateLimitProperties(
6262
var exportRequestLimit: Int = 100,
6363
@DocProperty(description = "Size, in milliseconds, of the time window for export-based limiting.")
6464
var exportRequestWindow: Long = 5 * 60 * 1000,
65+
@DocProperty(description = "Amount of translation requests a user can do in a single time window.")
66+
var translationRequestLimit: Int = 100,
67+
@DocProperty(
68+
description = "Size, in milliseconds, of the time window for translation-based limiting.",
69+
defaultExplanation = "= 5 minutes",
70+
)
71+
var translationRequestWindow: Long = 5 * 60 * 1000,
72+
@DocProperty(description = "Amount of activity requests a user can do in a single time window.")
73+
var activityRequestLimit: Int = 100,
74+
@DocProperty(
75+
description = "Size, in milliseconds, of the time window for activity-based limiting.",
76+
defaultExplanation = "= 5 minutes",
77+
)
78+
var activityRequestWindow: Long = 5 * 60 * 1000,
6579
@DocProperty(
6680
description =
6781
"Number of rate limit violations before the server stops responding to the client.\n" +

0 commit comments

Comments
 (0)