Skip to content

Commit 63709d6

Browse files
committed
Added login rate limiting
1 parent 6c05fe6 commit 63709d6

2 files changed

Lines changed: 79 additions & 0 deletions

File tree

manager/src/app.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from datetime import datetime, timedelta
12
import os
23
from flask import Flask, render_template, request, redirect, url_for, session, flash, jsonify
34
import requests
@@ -17,6 +18,8 @@
1718

1819
Logger()
1920

21+
failed_login_attempts = dict() # Dictionary to track failed login attempts by username
22+
timeouts = dict() # Dictionary to track timeouts by client IP
2023

2124
@app.route('/')
2225
def home():
@@ -29,17 +32,49 @@ def home():
2932

3033
@app.route('/login', methods=['GET', 'POST'])
3134
def login():
35+
36+
# check if the client is timed out
37+
client_ip = request.remote_addr
38+
if client_ip in timeouts:
39+
timeout = timeouts[client_ip]
40+
if datetime.now() < timeout:
41+
flash("Too many failed login attempts. Please try again later.", "error")
42+
return render_template('lockout.html')
43+
else:
44+
del timeouts[client_ip]
45+
3246
if request.method == 'POST':
3347
username = request.form['username']
3448
password = request.form['password']
3549

3650
auth_res = authenticate_basic(username, password)
3751
if auth_res and auth_res.get_success():
52+
# Reset failed login attempts for this client IP
53+
if client_ip in failed_login_attempts:
54+
del failed_login_attempts[client_ip]
55+
3856
# session.permanent = False # <- this line ensures session ends on browser close
3957
session['username'] = username
4058
session['token'] = auth_res.get_payload()[0]
4159
return redirect(url_for('main_menu'))
4260
else:
61+
if client_ip not in failed_login_attempts:
62+
failed_login_attempts[client_ip] = 0
63+
64+
# Increment failed login attempts for this client IP
65+
failed_login_attempts[client_ip] += 1
66+
67+
# increase the timeout based on the number of failed attempts
68+
if failed_login_attempts[client_ip] >= 10:
69+
Logger.log_info(f"Locking out client {client_ip} for 30 minutes due to too many failed login attempts.")
70+
timeouts[client_ip] = datetime.now() + timedelta(minutes=30)
71+
elif failed_login_attempts[client_ip] >= 5:
72+
Logger.log_info(f"Locking out client {client_ip} for 10 minutes due to too many failed login attempts.")
73+
timeouts[client_ip] = datetime.now() + timedelta(minutes=10)
74+
elif failed_login_attempts[client_ip] >= 3:
75+
Logger.log_info(f"Locking out client {client_ip} for 5 minutes due to too many failed login attempts.")
76+
timeouts[client_ip] = datetime.now() + timedelta(minutes=5)
77+
4378
flash("Invalid credentials", "error")
4479

4580
return render_template('login.html')

manager/src/templates/lockout.html

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Login Temporarily Disabled</title>
6+
<style>
7+
body {
8+
font-family: Arial, sans-serif;
9+
background-color: #fdf2f2;
10+
color: #b91c1c;
11+
display: flex;
12+
justify-content: center;
13+
align-items: center;
14+
height: 100vh;
15+
}
16+
17+
.container {
18+
background-color: #fff0f0;
19+
border: 2px solid #fca5a5;
20+
padding: 2rem 3rem;
21+
border-radius: 12px;
22+
box-shadow: 0 0 20px rgba(0, 0, 0, 0.05);
23+
text-align: center;
24+
}
25+
26+
h1 {
27+
font-size: 2em;
28+
margin-bottom: 0.5em;
29+
}
30+
31+
p {
32+
font-size: 1.2em;
33+
margin-top: 0;
34+
}
35+
</style>
36+
</head>
37+
<body>
38+
<div class="container">
39+
<h1>Too Many Login Attempts</h1>
40+
<p>You have been temporarily blocked due to too many failed login attempts.</p>
41+
<p>Please try again later.</p>
42+
</div>
43+
</body>
44+
</html>

0 commit comments

Comments
 (0)