Changelog
v2.2 — Upcoming
Fix eihwaz_history
recorded the value of every changed field, password columns included. Editing a user through the admin stored the old and new Argon2 hashes in the history table, and the history diff view displayed them. Impact: credential material duplicated outside the users table, retained indefinitely, and readable by anyone holding the history permission without access to the users table — a permission-boundary bypass, plus offline-cracking material for weak passwords. Sensitive values are now replaced by •••••• at five chokepoints — two on write (diff_fields, summary_json covering all four bulk paths) and two on read (DiffField, parse_diff). The field is still listed: that a password changed is legitimate audit data, its value is not. Redaction on read also neutralizes rows written before this release, with no data migration.
Fix FieldConfig::is_password
private, one-way, derived from the type. Any field built with type_field == "password" is marked at construction — the protection cannot be lost by forgetting to ask for it. mark_password() sets it; no operation clears it. The reader is_password() also re-tests the type, so even a deserialization setting the flag to false is ineffective. It replaces the field_type() == "password" string comparisons previously scattered across fill(), and now drives widget rendering, the GET skip, the required relaxation on edit, and log masking.
Fix form_fields
no longer carries password values. They were serialized into the create/edit page context — not rendered by the built-in templates, but a project template iterating form_fields would have printed them
Fix Template form
9 of the 18 field renderers never inserted them — Tera 1 evaluated the missing variable as false, so the attribute was simply never emitted, silently. text.rs even carried an // IMPORTANT: Inject readonly/disabled config comment followed by nothing. Fixed by collapsing the contract into a single path: FormField::base_context() provides field, readonly and disabled, and all 18 renderers start from it. A new field type inherits the contract without thinking about it.
Fix Runique
Nine admin templates recomputed permissions in Tera by walking current_user.groupes[].permissions[], shadowing the flags inject_context already provides. That copy tested only can_update / can_delete and ignored can_update_own / can_delete_own: a user holding own-record rights on a record they own was granted access by the server and refused by the UI. The duplicated computation is removed from create, edit, delete and bulk_edit, whose guards were unreachable anyway (authorize_get / MemberAction::authorize redirect before rendering
Fix Runique
Eleven admin templates declared {% block title %} while the root template hardcoded its with no such block: every admin page shared one browser-tab title. The root now declares the block, defaulting to site_title, then to the translated admin.base.title — so per-page titles work, an unset title is localized, and the value configured in main.rs finally reaches the tab.
Fix Runique
Tera 2 resolves {% include %} and {% extends %} when a template is added, so a loop over add_raw_template rejected any template whose dependency came later in iteration order (debug.html includes six partials declared after it) — the framework would not boot. Internal templates are now added as a single batch via add_raw_templates, which validates once at the end and rolls back atomically on failure.
Fix Runique login
login.html dereferenced form.username and errors.username, neither of which is inserted by any of the five login render paths — a fatal error under Tera 2. Beyond the crash, it means the per-field error messages of the login form could never be displayed: the only place inserting errors is a local _context in renderer.rs that is built and discarded.
Fix Runique debug page
from_runique_error called tera.get_template(name).unwrap_err() and parsed a line number out of the resulting "template not found" error — unrelated to where rendering actually failed. It now reads the message carried by RuniqueError::Template. (get_template also became private upstream.)
Fix Runique
admin.base.theme_toggle, admin.base.back_to_site, admin.dashboard.kpi_resources, admin.dashboard.kpi_entries, admin.dashboard.kpi_largest, admin.dashboard.th_count, admin.detail.btn_reset_password and its new short variant were referenced by templates but existed neither in ADMIN_MESSAGE_KEYS nor in any translation file. The {% if key %}…{% else %}French{% endif %} pattern hid the gap: eight admin labels were hardcoded French in a framework shipping nine languages. Added across all nine
Ajouté Changed
JsonMap is now HashMap. It was typed on tera::Value, which in Tera 1 was serde_json::Value; the two types diverged in 2.0, dragging the whole forms layer into the migration for no reason. Tera 2 passes filter arguments as Kwargs, so no alias is needed there.
Ajouté Changed
Custom filters and functions ported to the Tera 2 signature (Fn(Arg, Kwargs, &State) -> Res). markdown, sanitize, form and csrf_field now declare is_safe() and return Value::safe_string, which removes MARKDOWN_REGEX and SANITIZE_REGEX from the template preprocessor: safety is now an invariant sitting next to the sanitizer rather than a | safe injected into template source by a regex. plaintext is deliberately left escaped.
Ajouté Changed
Both CSRF entry points (csrf_field filter, csrf_token() function) now run the token through escape_html before interpolation — the tag is emitted unescaped, so its safety must not depend on a guarantee made in another module.
Ajouté Changed
Test helper tests/helpers/tera.rs (kwargs(), no_kwargs()) and a new tests/context/test_autoescape.rs covering the escaping contract end to end: a plain variable is escaped, plaintext stays escaped, markdown emits HTML without | safe, and the same value is escaped without the filter.
Ajouté Tera
upgrate tera to 2.1
Ajouté Sea-orm
changed internal synthax to sea-orm 2.0.1