Skip to content

Commit 60bb9af

Browse files
authored
Add temporary per-day user throttling (#392)
* update daily blocking logic * add config for dev setup * updates * decrease dev limit * update constants * update string * update configs * update prod to have no daily limit * make no limit clearer * try out -1 as no limit
1 parent e1e609d commit 60bb9af

File tree

5 files changed

+50
-24
lines changed

5 files changed

+50
-24
lines changed

cicd/3-app/javabuilder/config/production-demo.config.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"BaseDomainNameHostedZonedID": "Z2LCOI49SCXUGU",
66
"ProvisionedConcurrentExecutions": "1",
77
"ReservedConcurrentExecutions": "5",
8-
"LimitPerHour": "25",
9-
"LimitPerDay": "100",
8+
"LimitPerHour": "-1",
9+
"LimitPerDay": "50",
1010
"SilenceAlerts": "false"
1111
},
1212
"Tags": {

cicd/3-app/javabuilder/config/production.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"ProvisionedConcurrentExecutions": "50",
66
"ReservedConcurrentExecutions": "500",
77
"LimitPerHour": "1000",
8-
"LimitPerDay": "5000",
8+
"LimitPerDay": "-1",
99
"SilenceAlerts": "false",
1010
"TeacherLimitPerHour": "25000"
1111
},

cicd/3-app/javabuilder/template.yml.erb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ Parameters:
2626
Default: 3
2727
LimitPerHour:
2828
Type: Number
29-
Description: The number of Javabuilder invocations allowed per user per hour.
30-
MinValue: 1
29+
Description: The number of Javabuilder invocations allowed per user per hour. If the value is -1, then there is no limit on the number of invocations per hour.
30+
MinValue: -1
3131
Default: 50
3232
LimitPerDay:
3333
Type: Number
34-
Description: The number of Javabuilder invocations allowed per user per day.
35-
MinValue: 1
34+
Description: The number of Javabuilder invocations allowed per user per day. If the value is -1, then there is no limit on the number of invocations per day.
35+
MinValue: -1
3636
Default: 150
3737
TeacherLimitPerHour:
3838
Type: Number

javabuilder-authorizer/token_status.rb

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ module TokenStatus
22
### Token statuses used in HTTP authorizer (first step in token validation)
33
# Token was validated by HTTP authorizer
44
VALID_HTTP = 'VALID_HTTP'.freeze
5-
# User has been blocked for violating hourly or daily throttle limits
5+
# User has been blocked for violating hourly throttle limits
66
USER_BLOCKED = 'USER_BLOCKED'.freeze
77
# All of a user's teachers (or the teacher themselves, if the user is a teacher)
88
# has been blocked for violating hourly throttle limits
@@ -24,12 +24,15 @@ module TokenStatus
2424
UNKNOWN_ID = 'UNKNOWN_ID'.freeze
2525
# Token provided was not vetted by hTTP authorizer
2626
NOT_VETTED = 'NOT_VETTED'.freeze
27+
# User has been blocks for violating daily limits. They will be automatically unblocked
28+
# once they are no longer over the limit.
29+
USER_BLOCKED_TEMPORARY = 'USER_BLOCKED_TEMPORARY'.freeze
2730

2831
### Token status used by both authorizers
2932
# Token provided to the authorizer has already been used
3033
TOKEN_USED = 'TOKEN_USED'.freeze
3134

32-
ERROR_STATES = [USER_BLOCKED, CLASSROOM_BLOCKED, USER_OVER_DAILY_LIMIT, USER_OVER_HOURLY_LIMIT, TEACHERS_OVER_HOURLY_LIMIT, UNKNOWN_ID, NOT_VETTED, TOKEN_USED]
35+
ERROR_STATES = [USER_BLOCKED, CLASSROOM_BLOCKED, USER_OVER_DAILY_LIMIT, USER_OVER_HOURLY_LIMIT, TEACHERS_OVER_HOURLY_LIMIT, UNKNOWN_ID, NOT_VETTED, TOKEN_USED, USER_BLOCKED_TEMPORARY]
3336
WARNING_STATES = [NEAR_LIMIT]
3437
VALID_STATES = [VALID_HTTP, VALID_WEBSOCKET]
3538

@@ -38,10 +41,19 @@ module TokenStatus
3841
CLASSROOM_BLOCKED => 'ClassroomBlocked',
3942
UNKNOWN_ID => 'TokenUnknownId',
4043
NOT_VETTED => 'TokenNotVetted',
41-
TOKEN_USED => 'TokenUsed'
44+
TOKEN_USED => 'TokenUsed',
45+
USER_BLOCKED_TEMPORARY => 'UserBlockedTemporary',
4246
}.freeze
4347

4448
NEW_USER_BLOCKED = 'NewUserBlocked'.freeze
4549
NEW_CLASSROOM_BLOCKED = 'NewClassroomBlocked'.freeze
4650
CLASSROOM_HOURLY_REQUEST_COUNT = 'ClassroomHourlyRequestCount'.freeze
51+
52+
# Lockout statuses
53+
PERMANENT_LOCKOUT = 'PERMANENT'.freeze
54+
TEMPORARY_LOCKOUT = 'TEMPORARY'.freeze
55+
56+
# Lockout periods
57+
DAY = 'DAY'.freeze
58+
HOUR = 'HOUR'.freeze
4759
end

javabuilder-authorizer/token_validator.rb

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class TokenValidator
1111
USER_REQUEST_RECORD_TTL_SECONDS = 25 * ONE_HOUR_SECONDS
1212
TEACHER_ASSOCIATED_REQUEST_TTL_SECONDS = 25 * ONE_HOUR_SECONDS
1313
NEAR_LIMIT_BUFFER = 10
14+
NO_LIMIT = -1
1415

1516
def initialize(payload, origin, context)
1617
@token_id = payload['sid']
@@ -29,10 +30,14 @@ def validate
2930
return error(CLASSROOM_BLOCKED) if teachers_blocked?
3031
hourly_usage_response = user_usage(ONE_HOUR_SECONDS)
3132
return error(USER_BLOCKED) if user_over_hourly_limit?(hourly_usage_response)
32-
# return error(USER_BLOCKED) if user_over_daily_limit?
33+
daily_usage_response = user_usage(ONE_DAY_SECONDS)
34+
return error(USER_BLOCKED_TEMPORARY) if user_over_daily_limit?(daily_usage_response)
3335
return error(CLASSROOM_BLOCKED) if teachers_over_hourly_limit?
3436

3537
near_limit_detail = get_user_near_hourly_limit_detail(hourly_usage_response.count)
38+
if !near_limit_detail
39+
near_limit_detail = get_user_near_daily_limit_detail(daily_usage_response.count)
40+
end
3641
log_requests
3742
mark_token_as_vetted
3843
set_token_warning(NEAR_LIMIT, near_limit_detail) if near_limit_detail
@@ -88,23 +93,32 @@ def teachers_blocked?
8893
end
8994

9095
def user_over_hourly_limit?(hourly_usage_response)
96+
limit_per_hour = ENV['limit_per_hour'].to_i
97+
return false if limit_per_hour == NO_LIMIT
9198
user_over_limit?(
9299
hourly_usage_response,
93-
ENV['limit_per_hour'].to_i,
94-
USER_OVER_HOURLY_LIMIT
100+
limit_per_hour,
101+
USER_OVER_HOURLY_LIMIT,
102+
true # Should block permanently if the limit has been reached.
95103
)
96104
end
97105

98106
def get_user_near_hourly_limit_detail(hourly_usage_count)
99-
get_user_near_limit_detail(hourly_usage_count, ENV['limit_per_hour'].to_i)
107+
get_user_near_limit_detail(hourly_usage_count, ENV['limit_per_hour'].to_i, HOUR, PERMANENT_LOCKOUT)
108+
end
109+
110+
def get_user_near_daily_limit_detail(daily_usage_count)
111+
get_user_near_limit_detail(daily_usage_count, ENV['limit_per_day'].to_i, DAY, TEMPORARY_LOCKOUT)
100112
end
101113

102-
def user_over_daily_limit?
103-
usage_response = user_usage(ONE_DAY_SECONDS)
114+
def user_over_daily_limit?(daily_usage_response)
115+
limit_per_day = ENV['limit_per_day'].to_i
116+
return false if limit_per_day == NO_LIMIT
104117
user_over_limit?(
105-
usage_response,
106-
ENV['limit_per_day'].to_i,
107-
USER_OVER_DAILY_LIMIT
118+
daily_usage_response,
119+
limit_per_day,
120+
USER_OVER_DAILY_LIMIT,
121+
false # Should not block permanently if the limit has been reached.
108122
)
109123
end
110124

@@ -226,17 +240,17 @@ def user_usage(time_range_seconds)
226240
response
227241
end
228242

229-
def get_user_near_limit_detail(count, limit)
230-
if count <= limit && count >= (limit - NEAR_LIMIT_BUFFER)
231-
return {remaining: limit - count}
243+
def get_user_near_limit_detail(count, limit, period, lockout_type)
244+
if limit != NO_LIMIT && count <= limit && count >= (limit - NEAR_LIMIT_BUFFER)
245+
return {remaining: limit - count, period: period, lockout_type: lockout_type}
232246
else
233247
return nil
234248
end
235249
end
236250

237-
def user_over_limit?(query_response, limit, logging_message)
251+
def user_over_limit?(query_response, limit, logging_message, should_block_permanently)
238252
over_limit = query_response.count > limit
239-
if over_limit
253+
if over_limit && should_block_permanently
240254
# logging could be improved,
241255
# [{"ttl"=>0.1648766446e10, "user_id"=>"611", "issued_at"=>0.1648680046e10}, {...
242256
begin

0 commit comments

Comments
 (0)