Skip to content

Commit 71be95c

Browse files
author
Benjamin Kasser
committed
feat: Implementiere IP-Tracking für fehlgeschlagene Anmeldeversuche und verbessere Zertifikatserstellung
1 parent 789a5cb commit 71be95c

1 file changed

Lines changed: 54 additions & 10 deletions

File tree

src/https_login/server.py

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
import base64
66
import hashlib
77
import hmac
8+
import ipaddress
89
import os
910
import secrets
1011
import ssl
1112
import sys
1213
import tempfile
1314
import time
14-
from datetime import datetime, timedelta
15+
import threading
16+
import socket
17+
from datetime import datetime, timedelta, timezone
1518
from http.server import HTTPServer, SimpleHTTPRequestHandler
1619
from urllib.parse import parse_qs
1720

@@ -110,7 +113,7 @@ def verify_password(password: str, stored: str) -> bool:
110113
<br><br>
111114
__ERROR__
112115
<br>
113-
<button type="submit">Anmelden</button>
116+
<button type="submit">Login</button>
114117
</form>
115118
</body>
116119
</html>
@@ -161,14 +164,31 @@ def do_POST(self):
161164
pw = (parse_qs(body).get("password") or [""])[0]
162165

163166
if not verify_password(pw, self.pw_hash):
164-
return self._send_login("Falsches Passwort.")
167+
# Track failed attempts by IP address
168+
client_ip = self.client_address[0]
169+
if not hasattr(LoginHandler, '_failed_attempts'):
170+
LoginHandler._failed_attempts = {}
171+
172+
LoginHandler._failed_attempts[client_ip] = LoginHandler._failed_attempts.get(client_ip, 0) + 1
173+
174+
if LoginHandler._failed_attempts[client_ip] >= 5:
175+
self.send_error(403, "Too many failed login attempts. Access blocked.")
176+
return
177+
178+
attempts_left = 5 - LoginHandler._failed_attempts[client_ip]
179+
return self._send_login(f"Wrong password. {attempts_left} attempts left.")
180+
# Reset failed attempts on successful login
181+
client_ip = self.client_address[0]
182+
if hasattr(LoginHandler, '_failed_attempts') and client_ip in LoginHandler._failed_attempts:
183+
del LoginHandler._failed_attempts[client_ip]
184+
165185

166186
cookie_val = make_cookie(self.secret)
167187
self.send_response(302)
168188
self.send_header("Location", "/")
169189
self.send_header(
170190
"Set-Cookie",
171-
# Secure ist bei HTTPS korrekt, HttpOnly verhindert JS-Zugriff
191+
# Secure is correct for HTTPS, HttpOnly prevents JS access
172192
f"{COOKIE_NAME}={cookie_val}; Path=/; HttpOnly; SameSite=Strict; Secure"
173193
)
174194
self.end_headers()
@@ -186,18 +206,44 @@ def generate_cert(cert_path: str, key_path: str, host: str) -> None:
186206
])
187207

188208
san_entries = []
189-
# DNSName ist ok für "localhost" etc. Für IPs ist eigentlich x509.IPAddress nötig.
190-
# Damit es robust bleibt, legen wir DNSName immer an. (Browserwarnung bleibt sowieso self-signed.)
209+
# DNSName is fine for "localhost" etc. For IPs, x509.IPAddress is actually needed.
210+
# To keep it robust, we always create DNSName. (Browser warning remains anyway due to self-signed.)
211+
# Add the provided host
191212
san_entries.append(x509.DNSName(host))
213+
214+
# Add localhost and 127.0.0.1
215+
san_entries.append(x509.DNSName("localhost"))
216+
try:
217+
san_entries.append(x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")))
218+
except Exception:
219+
pass
220+
221+
# Add network IP addresses
222+
try:
223+
hostname = socket.gethostname()
224+
for info in socket.getaddrinfo(hostname, None):
225+
ip = info[4][0]
226+
try:
227+
# Try IPv4
228+
san_entries.append(x509.IPAddress(ipaddress.IPv4Address(ip)))
229+
except Exception:
230+
try:
231+
# Try IPv6
232+
san_entries.append(x509.IPAddress(ipaddress.IPv6Address(ip)))
233+
except Exception:
234+
pass
235+
except Exception:
236+
pass
237+
192238

193239
cert = (
194240
x509.CertificateBuilder()
195241
.subject_name(subject)
196242
.issuer_name(issuer)
197243
.public_key(key.public_key())
198244
.serial_number(x509.random_serial_number())
199-
.not_valid_before(datetime.utcnow() - timedelta(minutes=1))
200-
.not_valid_after(datetime.utcnow() + timedelta(days=1))
245+
.not_valid_before(datetime.now(timezone.utc) - timedelta(minutes=1))
246+
.not_valid_after(datetime.now(timezone.utc) + timedelta(days=1))
201247
.add_extension(x509.SubjectAlternativeName(san_entries), critical=False)
202248
.sign(key, hashes.SHA256())
203249
)
@@ -289,8 +335,6 @@ def handler_factory(*h_args, **h_kwargs):
289335
print("self-signed cert => Browser warning is normal.")
290336
print("Press Ctrl+C to stop.")
291337

292-
import threading
293-
294338
server_thread = threading.Thread(target=httpd.serve_forever, daemon=True)
295339
server_thread.start()
296340

0 commit comments

Comments
 (0)