{% extends "base.html" %} {% block title %}Security{% endblock %} {% block head %}{% endblock %} {% block body %} {% from "_icons.html" import icon %}

Sign-in and security

{% if strong %}

Signing in needs a passkey, or an email code plus your authenticator app or a recovery code. An email code alone is not enough.

{% endif %}
{% if not strong %} {% endif %} {% if error %}
{{ error }}
{% endif %} {% if codes %}

Your recovery codes

Save these now; they are shown only once. Each one works once, together with an email code, if you lose your passkeys and authenticator app.

{% for c in codes %}
{{ c }}
{% endfor %}

I saved them

{% endif %}
Passkeys{{ passkeys|length }}{% if passkeys %}Last used {{ passkeys|map(attribute='last_used_at')|select|max|ago }}{% else %}None yet{% endif %} Authenticator app{{ 'On' if has_totp else 'Off' }}Backup for devices without passkeys {% if strong %} Recovery codes {{ recovery_left if recovery_ever else 0 }} of 10 {% endif %}
{% if confirmed %} {% else %} {% endif %}

Passkeys

{% for p in passkeys %}
Added {{ p.created_at|when('%Y-%m-%d') }} · used {{ p.last_used_at|ago }} {% if confirmed %}
{% endif %}
{% else %}
No passkeys yet.
{% endfor %} {% if confirmed %} {% endif %}

Authenticator app

{{ 'On' if has_totp else 'Off' }}
{% if totp_secret %}

Scan this with your authenticator app (1Password, Google Authenticator, …), then enter the 6-digit code it shows.

{{ totp_qr|safe }}

Can’t scan? Enter this key by hand: {{ totp_secret }}

{% elif has_totp %}

A 6-digit code from your app works with an email code when a passkey isn’t at hand.

{% if confirmed %}
{% endif %} {% else %}

Off. Use it as a backup for devices without passkeys.

{% if confirmed %}
{% endif %} {% endif %}
{% if strong %}

Recovery codes

{% if recovery_ever %}

{{ recovery_left }} of 10 unused. Each works once, with an email code, if you lose your passkeys and authenticator app.

{% else %}

You have no recovery codes. Without them, losing your passkeys locks you out until an admin resets your account.

{% endif %} {% if confirmed %}
{% if recovery_ever %}

Making new codes cancels the old ones.

{% endif %}{% endif %}
{% endif %}

Recent sign-ins

{% for l in logins %}
{{ l.at|when('%m-%d %H:%M') }}{{ l.detail or 'email' }}{{ l.ip_prefix }}
{% else %}
None yet.
{% endfor %}
{% endblock %}