1919require 'openc3/models/auth_model'
2020
2121class AuthController < ApplicationController
22+ MAX_BAD_ATTEMPTS = ENV . fetch ( 'OPENC3_AUTH_RATE_LIMIT_TO' , '10' ) . to_i
23+ BAD_ATTEMPTS_WINDOW = ENV . fetch ( 'OPENC3_AUTH_RATE_LIMIT_WITHIN' , '120' ) . to_i
24+
25+ @@user_bad_attempts_count = 0
26+ @@user_bad_attempts_first_time = nil
27+ @@user_bad_attempts_mutex = Mutex . new
28+
29+ @@service_bad_attempts_count = 0
30+ @@service_bad_attempts_first_time = nil
31+ @@service_bad_attempts_mutex = Mutex . new
32+
2233 def token_exists
2334 result = OpenC3 ::AuthModel . set?
2435 render json : {
@@ -27,10 +38,16 @@ def token_exists
2738 end
2839
2940 def verify
41+ if user_rate_limited?
42+ head :too_many_requests
43+ return
44+ end
45+
3046 begin
3147 if OpenC3 ::AuthModel . verify_no_service ( params [ :password ] , no_password : false )
3248 render :plain => OpenC3 ::AuthModel . generate_session ( )
3349 else
50+ record_user_bad_attempt
3451 head :unauthorized
3552 end
3653 rescue StandardError => e
@@ -40,10 +57,16 @@ def verify
4057 end
4158
4259 def verify_service
60+ if service_rate_limited?
61+ head :too_many_requests
62+ return
63+ end
64+
4365 begin
4466 if OpenC3 ::AuthModel . verify ( params [ :password ] , service_only : true )
4567 render :plain => OpenC3 ::AuthModel . generate_session ( )
4668 else
69+ record_service_bad_attempt
4770 head :unauthorized
4871 end
4972 rescue StandardError => e
@@ -53,14 +76,84 @@ def verify_service
5376 end
5477
5578 def set
79+ if user_rate_limited?
80+ head :too_many_requests
81+ return
82+ end
83+
5684 begin
5785 # Set throws an exception if it fails for any reason
5886 OpenC3 ::AuthModel . set ( params [ :password ] , params [ :old_password ] )
5987 OpenC3 ::Logger . info ( "Password changed" , user : username ( ) )
6088 render :plain => OpenC3 ::AuthModel . generate_session ( )
6189 rescue StandardError => e
90+ if e . message == "old_password incorrect"
91+ record_user_bad_attempt
92+ end
6293 log_error ( e )
6394 render json : { status : 'error' , message : e . message , type : e . class } , status : 500
6495 end
6596 end
97+
98+ private
99+
100+ # Checks to see if the user password has been rate limited due to bad attempts
101+ def user_rate_limited?
102+ @@user_bad_attempts_mutex . synchronize do
103+ time = Time . now
104+
105+ # Reset counter if window has expired
106+ if @@user_bad_attempts_first_time && ( time - @@user_bad_attempts_first_time ) > BAD_ATTEMPTS_WINDOW
107+ @@user_bad_attempts_count = 0
108+ @@user_bad_attempts_first_time = nil
109+ end
110+
111+ return @@user_bad_attempts_count >= MAX_BAD_ATTEMPTS
112+ end
113+ end
114+
115+ # Initializes or increments the bad attempt counter for the user password
116+ def record_user_bad_attempt
117+ @@user_bad_attempts_mutex . synchronize do
118+ time = Time . now
119+
120+ # Start new window if this is the first attempt or window expired
121+ if @@user_bad_attempts_first_time . nil? || ( time - @@user_bad_attempts_first_time ) > BAD_ATTEMPTS_WINDOW
122+ @@user_bad_attempts_count = 1
123+ @@user_bad_attempts_first_time = time
124+ else
125+ @@user_bad_attempts_count += 1
126+ end
127+ end
128+ end
129+
130+ # Checks to see if the service password has been rate limited due to bad attempts
131+ def service_rate_limited?
132+ @@service_bad_attempts_mutex . synchronize do
133+ time = Time . now
134+
135+ # Reset counter if window has expired
136+ if @@service_bad_attempts_first_time && ( time - @@service_bad_attempts_first_time ) > BAD_ATTEMPTS_WINDOW
137+ @@service_bad_attempts_count = 0
138+ @@service_bad_attempts_first_time = nil
139+ end
140+
141+ return @@service_bad_attempts_count >= MAX_BAD_ATTEMPTS
142+ end
143+ end
144+
145+ # Initializes or increments the bad attempt counter for the service password
146+ def record_service_bad_attempt
147+ @@service_bad_attempts_mutex . synchronize do
148+ time = Time . now
149+
150+ # Start new window if this is the first attempt or window expired
151+ if @@service_bad_attempts_first_time . nil? || ( time - @@service_bad_attempts_first_time ) > BAD_ATTEMPTS_WINDOW
152+ @@service_bad_attempts_count = 1
153+ @@service_bad_attempts_first_time = time
154+ else
155+ @@service_bad_attempts_count += 1
156+ end
157+ end
158+ end
66159end
0 commit comments