{# Two-factor authentication, as one swappable section. Every state of the flow renders through this fragment - off, enrolling, freshly confirmed, on - so the settings page and the htmx swaps can never disagree about what the section looks like. The QR is drawn client-side by panel.js from data-totp-uri, so the secret never leaves this host in any form but this page. The base32 key and the otpauth URI are always shown as selectable text as well, for an operator enrolling by hand or on a browser where the script never ran. A refused code renders at 200 with the refusal inline: htmx does not swap an error status, so answering 400 would freeze the screen and report nothing. #} {% import "_macros.html" as m %}
Two-factor authentication is on. Each of these backup codes opens one sign-in if the authenticator is lost. They are shown once and stored hashed, so copy them somewhere safe now.
{{ totp_backup_codes | join(" ") }}
From the next sign-in on, the form asks for the access token and a code.
{% elif totp_enroll %}Scan this with your authenticator app, then confirm with the code it shows.
No camera at hand? Enter the key manually instead:
{{ totp_enroll.secret }}
{{ totp_enroll.uri }}
{% elif totp.enabled %}Enabled. Signing in asks for the access token and a code from the authenticator app. {{ totp.backup_codes_remaining }} backup codes left.
{% else %}Off. Signing in asks only for the access token. With two-factor on, a stolen token is not enough to reach this panel.
{% endif %}