import hashlib, secrets, json from flask import Blueprint, render_template, request, redirect, session, flash from database import get_db from auth import hash_password, verify_password import totp as totp_mod profile_bp = Blueprint("profile", __name__) def _verify_2fa_action(user, code): """Weryfikuje kod 2FA dla wrażliwych akcji.""" if not user["totp_enabled"]: return True return bool(code) and totp_mod.verify_totp(user["totp_secret"] or "", code) @profile_bp.route("/profile", methods=["GET", "POST"]) def profile(): if not session.get("username"): return redirect("/login?next=/profile") db = get_db() user = db.execute("SELECT * FROM users WHERE username=?", (session["username"],)).fetchone() if request.method == "POST": action = request.form.get("action", "") if action == "password": current = request.form.get("current_password", "") new = request.form.get("new_password", "") if not verify_password(current, user["password_hash"]): flash("Nieprawidłowe obecne hasło", "error") elif len(new) < 5: flash("Nowe hasło za krótkie (min. 5)", "error") elif user["totp_enabled"] and not _verify_2fa_action(user, request.form.get("2fa_code", "")): flash("❌ Wymagany kod 2FA do zmiany hasła", "error") else: db.execute("UPDATE users SET password_hash=? WHERE username=?", (hash_password(new), session["username"])) db.commit() flash("✅ Hasło zmienione", "ok") elif action == "email": email = request.form.get("email", "").strip() if "@" not in email: flash("Niepoprawny email", "error") else: try: db.execute("UPDATE users SET email=? WHERE username=?", (email, session["username"])) db.commit() session["email"] = email flash("✅ Email zaktualizowany", "ok") except Exception: flash("Email już zajęty", "error") elif action == "token": if user["totp_enabled"] and not _verify_2fa_action(user, request.form.get("2fa_code", "")): flash("❌ Wymagany kod 2FA do generowania tokena", "error") else: token = f"pag_pat_{secrets.token_hex(20)}" db.execute("UPDATE users SET git_token=? WHERE username=?", (token, session["username"])) db.commit() flash(f"Token: {token}", "ok") return render_template("profile/index.html", user=user, generated_token=token, totp_enabled=bool(user["totp_enabled"])) # ── 2FA ── elif action == "setup_2fa": secret = totp_mod.generate_secret() db.execute("UPDATE users SET totp_secret=? WHERE username=?", (secret, session["username"])) db.commit() user = dict(user) user["totp_secret"] = secret flash("Zeskanuj kod QR lub wpisz klucz ręcznie w aplikacji 2FA (Google Authenticator, Authy).", "ok") return render_template("profile/index.html", user=user, setup_2fa=True, generated_token=None) elif action == "verify_2fa": code = request.form.get("code", "").strip() if totp_mod.verify_totp(user["totp_secret"] or "", code): codes = totp_mod.generate_recovery_codes() hashed = totp_mod.hash_recovery_codes(codes) db.execute("UPDATE users SET totp_enabled=1, recovery_codes=? WHERE username=?", (json.dumps(hashed), session["username"])) db.commit() flash(f"✅ 2FA włączone! Zapisz kody zapasowe:\n" + "\n".join(codes), "ok") else: flash("❌ Nieprawidłowy kod. Spróbuj ponownie.", "error") return redirect("/profile") elif action == "disable_2fa": pwd = request.form.get("current_password", "") if not verify_password(pwd, user["password_hash"]): flash("Nieprawidłowe hasło", "error") else: db.execute("UPDATE users SET totp_enabled=0, totp_secret=NULL, recovery_codes=NULL WHERE username=?", (session["username"],)) db.commit() flash("2FA wyłączone", "ok") return redirect("/profile") db.close() totp_enabled = bool(user["totp_enabled"]) if "totp_enabled" in user.keys() else False return render_template("profile/index.html", user=user, setup_2fa=False, generated_token=None, totp_enabled=totp_enabled)