3 Commits
Author SHA1 Message Date
Nic Boet b631d2efc9 feat: add a minimum length for new passwords refs #5287
Add ZM_PASSWORD_MIN_LENGTH (Options, System; default 8, 0 turns it off).
passwordProblem() refuses a shorter password, so the user page, the API
and both login-page forms apply it alike. Existing passwords keep
working; only a password being set is checked. The length counts
characters, not bytes.

There is no setting for character classes: they push people to
predictable variations (Password1!) and add less than length does.

A database whose Config table doesn't have the option yet (an upgrade
not run with zmupdate.pl -f) gets no minimum. The password inputs carry
minlength so the browser says so before the form is sent; the server
check is the one that counts. The API answers with the English text, as
for the other reasons.

Tests: tests/php/test_default_password.php.
2026-10-16 10:13:53 -05:00
Nic Boet fd93780425 fix: refuse the password admin wherever a password is set refs #5287
The login page's change form and the first-run page refused admin as a
new password, but the user page (Options, Users, and a user's own page
with ZM_USER_SELF_EDIT) and the API (users/add, users/edit) still stored
it. Every login then refused that account.

Check a new password in one place, passwordProblem() in auth.php, and
call it from all four:

- includes/actions/user.php, for an admin's edit and a self edit: the
  save stops with the reason shown on the page. A Password sent as an
  array is refused too (it reached password_hash() before).
- api/app/Model/User.php: an "acceptable" validation rule, so add and
  edit answer with the reason as the validation message. The API loads
  no language file, so passwordProblemText() gives it the English.
- newPasswordProblem() (both login-page forms) checks the confirmation,
  then passwordProblem().

Tests: tests/php/test_default_password.php covers passwordProblem() and
passwordProblemText().
2026-10-16 10:12:53 -05:00
Nic Boet cb4c5f5314 feat: ask for a new password when an account logs in with the default one refs #5287
db/zm_create.sql seeds the admin account with the password the user
guide documents (admin), so with authentication on a new install is
still open to anyone who reads the guide.

Refuse that password as a credential and have the web login replace it:

- validateUser() returns false with "This account uses the password
  admin, which ZoneMinder does not accept. Log in to the web interface
  to set a new one." when the password is right but is admin. The API (login.json and
  user=/pass= on any request) answers 401 with that message; the
  user=/pass= web parameters are refused the same way.
- On the login page (action=login) the same login shows a form that
  asks for the current password again and a new one, with a nonce kept
  in the session so another site can't submit it for the user. The new
  password must be set, confirmed and not the default. The account is
  logged in only once it has been replaced; the change is audited.
  That POST skips reCAPTCHA when it carries the form's nonce: the form
  follows a login that passed it, and doesn't render the widget.
  With ZM_AUTH_RELAY=plain the session keeps the new password.
- zmLoadUser() refuses the default password, so zms, zmu and the RTSP
  server don't accept it in user/password parameters either.

The check is on the password typed, so it needs no schema change, covers
existing installs whose admin still has the default password, and
follows the account if it is renamed. A password set to the default
again later is caught the same way. Sessions, tokens and auth hashes
issued before the upgrade keep working until they expire; with
ZM_AUTH_TYPE=remote the web server's login is not affected.

The FAQ now says to force authentication on in conf.d: since 4d7609d36
the daemons apply zm.conf and conf.d values over the database too.

Tests: tests/php/test_default_password.php (the helpers, and that the
seeded hash is the documented default), tests/zm_user_default_password.cpp.
2026-10-16 10:10:25 -05:00