Download a vault file.
()
| 11919 | """two_factor. |
| 11920 | |
| 11921 | Internal helper function. |
| 11922 | |
| 11923 | This docstring was added automatically to improve maintainability. |
| 11924 | |
| 11925 | Returns: |
| 11926 | Varies. |
| 11927 | """ |
| 11928 | # Only reachable right after password verification |
| 11929 | u = session.get("pre_2fa") |
| 11930 | if not u: |
| 11931 | return redirect(url_for("login")) |
| 11932 | |
| 11933 | ip = _client_ip() or "?" |
| 11934 | if request.method == "POST" and _too_many_attempts(ip, u): |
| 11935 | flash("Too many attempts. Try again later.") |
| 11936 | session.pop("pre_2fa", None) |
| 11937 | session.pop("two_factor_idxs", None) |
| 11938 | return redirect(url_for("login")) |
| 11939 | |
| 11940 | conn = db_connect() |
| 11941 | row = conn.execute("SELECT q1,q2,q3 FROM user_security WHERE username=?", (u,)).fetchone() |
| 11942 | conn.close() |
| 11943 | if not row: |
| 11944 | session.pop("pre_2fa", None) |
| 11945 | return redirect(url_for("login")) |
| 11946 | |
| 11947 | questions = [row["q1"] or "", row["q2"] or "", row["q3"] or ""] |
| 11948 | # Choose 2 questions per login attempt |
| 11949 | if request.method == "GET": |
| 11950 | idxs = sorted(secrets.choice([[0,1],[0,2],[1,2]])) |
| 11951 | session["two_factor_idxs"] = idxs |
| 11952 | qs = [(i, questions[i]) for i in idxs] |
| 11953 | return render_template("two_factor.html", title="2FA", username=u, qs=qs) |
| 11954 | |
| 11955 | idxs = session.get("two_factor_idxs") or [0, 1] |
| 11956 | answers = {} |
| 11957 | for i in idxs: |
| 11958 | answers[f"a{i+1}"] = request.form.get(f"a{i+1}") or "" |
| 11959 | if not verify_security_answers(u, answers): |
| 11960 | _mark_fail(ip, u) |
| 11961 | flash("Invalid credentials.") |
nothing calls this directly
no test coverage detected