Sicherheits-Review 25.08.2026 vor geplanter öffentlicher Erreichbarkeit über Pangolin (bisher nur Pangolin-interne Auth vorgeschaltet): - auth.py: In-Memory-Rate-Limiting auf /login (je Email UND je Quell-IP, 8 Fehlversuche / 10 Minuten), UserManager.validate_password() setzt jetzt eine Mindestlänge von 12 Zeichen durch (fastapi_users' Default war ein No-Op, ließ auch leere Passwörter zu). - routers/admin_kunden.py, routers/organisation.py: fangen InvalidPasswordException/UserAlreadyExists jetzt sauber ab statt 500. - app.py: /docs, /redoc, /openapi.json deaktiviert (legten zuvor die komplette Routenstruktur inkl. /admin/* offen), Security-Header- Middleware (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, CSP). - scripts/reset_password.py: neues Skript, Gegenstück zu create_user.py, für die im selben Review gefundenen aktiven Testaccounts mit dem Passwort 'test123' (einer davon Superuser) gebraucht. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
58 lines
1.8 KiB
Python
58 lines
1.8 KiB
Python
"""
|
|
Passwort eines bestehenden Kontos zuruecksetzen (Gegenstueck zu
|
|
create_user.py). Beispiel:
|
|
|
|
KUNDENPLATTFORM_DATABASE_URL=... .venv/bin/python scripts/reset_password.py \\
|
|
--email admin@schule-beispiel.de
|
|
"""
|
|
|
|
import argparse
|
|
import asyncio
|
|
import getpass
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from fastapi_users import exceptions # noqa: E402
|
|
|
|
from auth import UserDatabase, UserManager # noqa: E402
|
|
from db import get_async_database_connection # noqa: E402
|
|
|
|
|
|
async def passwort_zuruecksetzen(email: str, password: str):
|
|
async with await get_async_database_connection() as connection:
|
|
user_manager = UserManager(UserDatabase(connection))
|
|
user = await user_manager.get_by_email(email)
|
|
|
|
await user_manager.validate_password(password, user)
|
|
await user_manager.user_db.update(user, {"hashed_password": user_manager.password_helper.hash(password)})
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--email", required=True)
|
|
args = parser.parse_args()
|
|
|
|
if not os.environ.get("KUNDENPLATTFORM_DATABASE_URL"):
|
|
sys.exit("KUNDENPLATTFORM_DATABASE_URL ist nicht gesetzt.")
|
|
|
|
password = getpass.getpass("Neues Passwort: ")
|
|
password_confirm = getpass.getpass("Neues Passwort (Wiederholung): ")
|
|
|
|
if password != password_confirm:
|
|
sys.exit("Passwörter stimmen nicht überein.")
|
|
|
|
try:
|
|
asyncio.run(passwort_zuruecksetzen(args.email, password))
|
|
except exceptions.UserNotExists:
|
|
sys.exit(f"Kein Konto mit der E-Mail {args.email} gefunden.")
|
|
except exceptions.InvalidPasswordException as fehler:
|
|
sys.exit(f"Passwort abgelehnt: {fehler.reason}")
|
|
|
|
print(f"Passwort für {args.email} zurückgesetzt.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|