Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions api_views/users.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import re
import time
import jsonschema
import jwt

from collections import defaultdict, deque

from config import db, vuln_app
from api_views.json_schemas import *
from flask import jsonify, Response, request, json
Expand All @@ -16,6 +19,26 @@ def error_message_helper(msg):
return '{ "status": "fail", "message": "' + msg + '"}'


# Lack of Rate Limiting: /users/v1/login had no limit on how many attempts a client could
# make, making credential stuffing / password spraying / brute force trivial. Tracks login
# attempts per client IP in a sliding window and rejects further attempts once the limit is
# hit within the window.
LOGIN_RATE_LIMIT_MAX_ATTEMPTS = 5
LOGIN_RATE_LIMIT_WINDOW_SECONDS = 60
_login_attempts_by_ip = defaultdict(deque)


def _is_login_rate_limited(client_ip):
now = time.time()
attempts = _login_attempts_by_ip[client_ip]
while attempts and now - attempts[0] > LOGIN_RATE_LIMIT_WINDOW_SECONDS:
attempts.popleft()
if len(attempts) >= LOGIN_RATE_LIMIT_MAX_ATTEMPTS:
return True
attempts.append(now)
return False


def get_all_users():
return_value = jsonify({'users': User.get_all_users()})
return return_value
Expand Down Expand Up @@ -83,6 +106,11 @@ def register_user():


def login_user():
if _is_login_rate_limited(request.remote_addr):
return Response(
error_message_helper("Too many login attempts. Please try again later."), 429,
mimetype="application/json")

request_data = request.get_json()

try:
Expand Down
Loading