class TestFailedLoginView: exc = TooManyFailedLogins(resets_in=datetime.timedelta(seconds=600)) request = pretend.stub() resp = views.failed_logins(exc, request) assert resp.status == "429 Too Many Failed Login Attempts" assert resp.detail == ( "There have been too many unsuccessful login attempts. " "Try again later.") assert dict(resp.headers).get("Retry-After") == "600"
def check_password(self, userid, password): # The very first thing we want to do is check to see if we've hit our # global rate limit or not, assuming that we've been configured with a # global rate limiter anyways. if not self.ratelimiters["global"].test(): logger.warning("Global failed login threshold reached.") raise TooManyFailedLogins( resets_in=self.ratelimiters["global"].resets_in()) user = self.get_user(userid) if user is not None: # Now, check to make sure that we haven't hitten a rate limit on a # per user basis. if not self.ratelimiters["user"].test(user.id): raise TooManyFailedLogins( resets_in=self.ratelimiters["user"].resets_in(user.id)) # Actually check our hash, optionally getting a new hash for it if # we should upgrade our saved hashed. ok, new_hash = self.hasher.verify_and_update( password, user.password) # First, check to see if the password that we were given was OK. if ok: # Then, if the password was OK check to see if we've been given # a new password hash from the hasher, if so we'll want to save # that hash. if new_hash: user.password = new_hash return True # If we've gotten here, then we'll want to record a failed login in our # rate limiting before returning False to indicate a failed password # verification. if user is not None: self.ratelimiters["user"].hit(user.id) self.ratelimiters["global"].hit() return False
def _check_ratelimits(self, userid=None, tags=None): tags = tags if tags is not None else [] # First we want to check if a single IP is exceeding our rate limiter. if self.remote_addr is not None: if not self.ratelimiters["ip.login"].test(self.remote_addr): logger.warning("IP failed login threshold reached.") self._metrics.increment( "warehouse.authentication.ratelimited", tags=tags + ["ratelimiter:ip"], ) raise TooManyFailedLogins( resets_in=self.ratelimiters["ip.login"].resets_in(self.remote_addr) ) # Next check to see if we've hit our global rate limit or not, # assuming that we've been configured with a global rate limiter anyways. if not self.ratelimiters["global.login"].test(): logger.warning("Global failed login threshold reached.") self._metrics.increment( "warehouse.authentication.ratelimited", tags=tags + ["ratelimiter:global"], ) raise TooManyFailedLogins( resets_in=self.ratelimiters["global.login"].resets_in() ) # Now, check to make sure that we haven't hitten a rate limit on a # per user basis. if userid is not None: if not self.ratelimiters["user.login"].test(userid): self._metrics.increment( "warehouse.authentication.ratelimited", tags=tags + ["ratelimiter:user"], ) raise TooManyFailedLogins( resets_in=self.ratelimiters["user.login"].resets_in(userid) )
def check_password(userid, password, tags=None): raise TooManyFailedLogins(resets_in=None)
def check_totp_value(self, user_id, totp_value, *, tags=None): """ Returns True if the given TOTP is valid against the user's secret. If the user doesn't have a TOTP secret or isn't allowed to use second factor methods, returns False. """ tags = tags if tags is not None else [] self._metrics.increment("warehouse.authentication.two_factor.start", tags=tags) # The very first thing we want to do is check to see if we've hit our # global rate limit or not, assuming that we've been configured with a # global rate limiter anyways. if not self.ratelimiters["global"].test(): logger.warning("Global failed login threshold reached.") self._metrics.increment( "warehouse.authentication.two_factor.ratelimited", tags=tags + ["ratelimiter:global"], ) raise TooManyFailedLogins( resets_in=self.ratelimiters["global"].resets_in()) # Now, check to make sure that we haven't hitten a rate limit on a # per user basis. if not self.ratelimiters["user"].test(user_id): self._metrics.increment( "warehouse.authentication.two_factor.ratelimited", tags=tags + ["ratelimiter:user"], ) raise TooManyFailedLogins( resets_in=self.ratelimiters["user"].resets_in(user_id)) totp_secret = self.get_totp_secret(user_id) if totp_secret is None: self._metrics.increment( "warehouse.authentication.two_factor.failure", tags=tags + ["failure_reason:no_totp"], ) # If we've gotten here, then we'll want to record a failed attempt in our # rate limiting before returning False to indicate a failed totp # verification. self.ratelimiters["user"].hit(user_id) self.ratelimiters["global"].hit() return False valid = otp.verify_totp(totp_secret, totp_value) if valid: self._metrics.increment("warehouse.authentication.two_factor.ok", tags=tags) else: self._metrics.increment( "warehouse.authentication.two_factor.failure", tags=tags + ["failure_reason:invalid_totp"], ) # If we've gotten here, then we'll want to record a failed attempt in our # rate limiting before returning False to indicate a failed totp # verification. self.ratelimiters["user"].hit(user_id) self.ratelimiters["global"].hit() return valid
def check_recovery_code(self, user_id, code): self._metrics.increment("warehouse.authentication.recovery_code.start") # The very first thing we want to do is check to see if we've hit our # global rate limit or not, assuming that we've been configured with a # global rate limiter anyways. if not self.ratelimiters["global.login"].test(): logger.warning("Global failed login threshold reached.") self._metrics.increment( "warehouse.authentication.recovery_code.ratelimited", tags=["ratelimiter:global"], ) raise TooManyFailedLogins( resets_in=self.ratelimiters["global.login"].resets_in()) # Now, check to make sure that we haven't hitten a rate limit on a # per user basis. if not self.ratelimiters["user.login"].test(user_id): self._metrics.increment( "warehouse.authentication.recovery_code.ratelimited", tags=["ratelimiter:user"], ) raise TooManyFailedLogins( resets_in=self.ratelimiters["user.login"].resets_in(user_id)) user = self.get_user(user_id) if not user.has_recovery_codes: self._metrics.increment( "warehouse.authentication.recovery_code.failure", tags=["failure_reason:no_recovery_codes"], ) # If we've gotten here, then we'll want to record a failed attempt in our # rate limiting before returning False to indicate a failed recovery code # verification. self.ratelimiters["user.login"].hit(user_id) self.ratelimiters["global.login"].hit() return False valid = False for stored_recovery_code in self.get_recovery_codes(user.id): if self.hasher.verify(code, stored_recovery_code.code): self.db.delete(stored_recovery_code) self.db.flush() valid = True if valid: self._metrics.increment( "warehouse.authentication.recovery_code.ok") else: self._metrics.increment( "warehouse.authentication.recovery_code.failure", tags=["failure_reason:invalid_recovery_code"], ) # If we've gotten here, then we'll want to record a failed attempt in our # rate limiting before returning False to indicate a failed recovery code # verification. self.ratelimiters["user.login"].hit(user_id) self.ratelimiters["global.login"].hit() return valid