Skip to content

[Web] translate password errors in the forced password change modal#7344

Open
smpaz7467 wants to merge 1 commit into
mailcow:stagingfrom
smpaz7467:fix/forced-pw-change-raw-lang-key
Open

[Web] translate password errors in the forced password change modal#7344
smpaz7467 wants to merge 1 commit into
mailcow:stagingfrom
smpaz7467:fix/forced-pw-change-raw-lang-key

Conversation

@smpaz7467

Copy link
Copy Markdown

What

When a user is forced to change their password at next login and enters one that
fails the complexity policy, the modal shows the literal string
password_complexity instead of a message.

Why

The forced password change modal is the only password form that posts to the JSON
API (/api/v1/edit/self, or /api/v1/edit/admin for admins) and renders
data[0].msg straight into the alert. The API intentionally returns raw language
keys — every other password form goes through alertbox_log_parser(), which
resolves the key against $lang. This modal never did.

Fix

Resolve the key against lang_danger, which base.twig already exposes for
exactly this purpose (it is json_encode($lang['danger']), see
footer.inc.php:74, and is used the same way in admin.js). No new machinery, no
API change — the API keeps returning stable keys for external consumers, since the
bug is in the presentation layer.

This also fixes password_mismatch, password_empty and access_denied in the
same modal, and covers the admin path through the same handler.

The lookup is guarded with hasOwnProperty: msg is dynamic, and a bare
lang_danger[msg] would resolve inherited Object.prototype members like
constructor to a function, which jQuery's .text() treats as a callback and
invokes.

No language files are touched. prerequisites.inc.php:269 loads
lang.en-gb.json as the base and merges the active locale over it, so the four
locales that currently lack danger.password_complexity (az, gr, lt, nb) inherit
the English string rather than falling back to the raw key.

Verification

  • base.twig parses clean under Twig (with mailcow's custom query_string /
    is_uri / rot13 / formatBytes extensions registered), before and after.
  • Exercised the lookup against the real danger section of lang.en-gb.json:
API returns before after
password_complexity password_complexity Password does not meet the policy
password_mismatch password_mismatch Confirmation password does not match
password_empty password_empty Password must not be empty
access_denied access_denied Access denied or invalid form data
unknown key unchanged unchanged
'Password change failed.' (JS fallback) unchanged unchanged
array form (vsprintf) unchanged unchanged
  • Confirmed all 33 locale files resolve to a real string via the en-gb base merge.

Fixes #7301

The forced password change modal posts to the JSON API and renders
data[0].msg directly. The API returns raw language keys rather than
translated strings, so a user who fails the complexity policy is shown
the literal text "password_complexity" instead of a message.

Every other password form renders errors through alertbox_log_parser(),
which resolves the key against $lang. The modal is the only one that
talks to the API directly, and it never resolved the key.

Resolve it against lang_danger, which base.twig already exposes for this
purpose. This also covers password_mismatch, password_empty and
access_denied, and applies to the admin path as well. Unknown keys still
fall through unchanged.

The lookup is guarded with hasOwnProperty because msg is attacker-
independent but dynamic: a bare lang_danger[msg] would resolve inherited
Object.prototype members such as "constructor" to a function, which
jQuery's .text() would then invoke as a callback.

Language files are untouched: prerequisites.inc.php loads lang.en-gb.json
as the base and merges the active locale over it, so locales that lack the
key inherit the English string.

Fixes mailcow#7301

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant