Merge dev to main: Security improvements and comprehensive test infrastructure #39
@@ -224,14 +224,26 @@ def verify_totp(token: str, secret: str = None):
|
|||||||
if not secret:
|
if not secret:
|
||||||
return True # 2FA not enabled
|
return True # 2FA not enabled
|
||||||
totp = pyotp.TOTP(secret)
|
totp = pyotp.TOTP(secret)
|
||||||
if not totp.verify(token):
|
if not totp.verify(token, valid_window=1): # Allow 1 time step before/after (±30s)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def mutator(data):
|
def mutator(data):
|
||||||
current_ts = int(time.time()) // 30
|
# Track recently used codes to prevent replay attacks
|
||||||
if data.get("last_totp_ts") == current_ts:
|
used_codes = data.get("used_totp_codes", [])
|
||||||
|
current_time = int(time.time())
|
||||||
|
|
||||||
|
# Check if this code was already used
|
||||||
|
if token in [code for code, _ in used_codes]:
|
||||||
return False
|
return False
|
||||||
data["last_totp_ts"] = current_ts
|
|
||||||
|
# Add current code with timestamp
|
||||||
|
used_codes.append((token, current_time))
|
||||||
|
|
||||||
|
# Clean up codes older than 90 seconds (3 time windows)
|
||||||
|
used_codes = [(code, ts) for code, ts in used_codes if current_time - ts < 90]
|
||||||
|
|
||||||
|
# Keep only last 5 codes to limit memory
|
||||||
|
data["used_totp_codes"] = used_codes[-5:]
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return _update_auth_data(mutator)
|
return _update_auth_data(mutator)
|
||||||
|
|||||||
Reference in New Issue
Block a user