Browse Source

Require current password when setting new password (#7763)

* Require current password when setting new password

* i18n: fr

---------

Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
Inverle 8 months ago
parent
commit
f85333e98a
66 changed files with 246 additions and 64 deletions
  1. 44 11
      app/Controllers/userController.php
  2. 4 1
      app/i18n/cs/conf.php
  3. 1 0
      app/i18n/cs/feedback.php
  4. 4 1
      app/i18n/de/conf.php
  5. 1 0
      app/i18n/de/feedback.php
  6. 4 1
      app/i18n/el/conf.php
  7. 1 0
      app/i18n/el/feedback.php
  8. 4 1
      app/i18n/en-us/conf.php
  9. 1 0
      app/i18n/en-us/feedback.php
  10. 4 1
      app/i18n/en/conf.php
  11. 1 0
      app/i18n/en/feedback.php
  12. 4 1
      app/i18n/es/conf.php
  13. 1 0
      app/i18n/es/feedback.php
  14. 4 1
      app/i18n/fa/conf.php
  15. 1 0
      app/i18n/fa/feedback.php
  16. 4 1
      app/i18n/fi/conf.php
  17. 1 0
      app/i18n/fi/feedback.php
  18. 4 1
      app/i18n/fr/conf.php
  19. 1 0
      app/i18n/fr/feedback.php
  20. 4 1
      app/i18n/he/conf.php
  21. 1 0
      app/i18n/he/feedback.php
  22. 4 1
      app/i18n/hu/conf.php
  23. 1 0
      app/i18n/hu/feedback.php
  24. 4 1
      app/i18n/id/conf.php
  25. 1 0
      app/i18n/id/feedback.php
  26. 4 1
      app/i18n/it/conf.php
  27. 1 0
      app/i18n/it/feedback.php
  28. 4 1
      app/i18n/ja/conf.php
  29. 1 0
      app/i18n/ja/feedback.php
  30. 4 1
      app/i18n/ko/conf.php
  31. 1 0
      app/i18n/ko/feedback.php
  32. 4 1
      app/i18n/lv/conf.php
  33. 1 0
      app/i18n/lv/feedback.php
  34. 4 1
      app/i18n/nl/conf.php
  35. 1 0
      app/i18n/nl/feedback.php
  36. 4 1
      app/i18n/oc/conf.php
  37. 1 0
      app/i18n/oc/feedback.php
  38. 4 1
      app/i18n/pl/conf.php
  39. 1 0
      app/i18n/pl/feedback.php
  40. 4 1
      app/i18n/pt-br/conf.php
  41. 1 0
      app/i18n/pt-br/feedback.php
  42. 4 1
      app/i18n/pt-pt/conf.php
  43. 1 0
      app/i18n/pt-pt/feedback.php
  44. 4 1
      app/i18n/ru/conf.php
  45. 1 0
      app/i18n/ru/feedback.php
  46. 4 1
      app/i18n/sk/conf.php
  47. 1 0
      app/i18n/sk/feedback.php
  48. 4 1
      app/i18n/tr/conf.php
  49. 1 0
      app/i18n/tr/feedback.php
  50. 4 1
      app/i18n/zh-cn/conf.php
  51. 1 0
      app/i18n/zh-cn/feedback.php
  52. 4 1
      app/i18n/zh-tw/conf.php
  53. 1 0
      app/i18n/zh-tw/feedback.php
  54. 1 1
      app/views/user/details.phtml
  55. 47 15
      app/views/user/profile.phtml
  56. 1 1
      docs/i18n/flags/gen/cs.svg
  57. 1 1
      docs/i18n/flags/gen/es.svg
  58. 1 1
      docs/i18n/flags/gen/hu.svg
  59. 1 1
      docs/i18n/flags/gen/ja.svg
  60. 1 1
      docs/i18n/flags/gen/ko.svg
  61. 1 1
      docs/i18n/flags/gen/lv.svg
  62. 1 1
      docs/i18n/flags/gen/ru.svg
  63. 1 1
      docs/i18n/flags/gen/sk.svg
  64. 1 1
      docs/i18n/flags/gen/zh-cn.svg
  65. 1 1
      docs/i18n/flags/gen/zh-tw.svg
  66. 14 1
      p/scripts/extra.js

+ 44 - 11
app/Controllers/userController.php

@@ -51,6 +51,9 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
 		if ($passwordPlain != '') {
 			$passwordHash = FreshRSS_password_Util::hash($passwordPlain);
 			$userConfig->passwordHash = $passwordHash;
+			if ($user === Minz_User::name()) {
+				FreshRSS_Context::userConf()->passwordHash = $passwordHash;
+			}
 		}
 
 		foreach ($userConfigUpdated as $configName => $configValue) {
@@ -69,18 +72,16 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
 		}
 
 		if (Minz_Request::isPost()) {
-			$passwordPlain = Minz_Request::paramString('newPasswordPlain', true);
-			Minz_Request::_param('newPasswordPlain');	//Discard plain-text password ASAP
-			$_POST['newPasswordPlain'] = '';
-
 			$username = Minz_Request::paramString('username');
-			$ok = self::updateUser($username, null, $passwordPlain, [
+			$newPasswordPlain = Minz_User::name() !== $username ? Minz_Request::paramString('newPasswordPlain', true) : '';
+
+			$ok = self::updateUser($username, null, $newPasswordPlain, [
 				'token' => Minz_Request::paramString('token') ?: null,
 			]);
 
 			if ($ok) {
 				$isSelfUpdate = Minz_User::name() === $username;
-				if ($passwordPlain == '' || !$isSelfUpdate) {
+				if ($newPasswordPlain == '' || !$isSelfUpdate) {
 					Minz_Request::good(_t('feedback.user.updated', $username), ['c' => 'user', 'a' => 'manage']);
 				} else {
 					Minz_Request::good(_t('feedback.profile.updated'), ['c' => 'index', 'a' => 'index']);
@@ -114,9 +115,41 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
 			$old_email = FreshRSS_Context::userConf()->mail_login;
 
 			$email = Minz_Request::paramString('email');
-			$passwordPlain = Minz_Request::paramString('newPasswordPlain', true);
-			Minz_Request::_param('newPasswordPlain');	//Discard plain-text password ASAP
-			$_POST['newPasswordPlain'] = '';
+
+			$challenge = Minz_Request::paramString('challenge');
+			$newPasswordPlain = '';
+			if ($challenge !== '') {
+				$username = Minz_User::name();
+				$nonce = Minz_Session::paramString('nonce');
+
+				$newPasswordPlain = Minz_Request::paramString('newPasswordPlain', plaintext: true);
+				$confirmPasswordPlain = Minz_Request::paramString('confirmPasswordPlain', plaintext: true);
+
+				if (!FreshRSS_FormAuth::checkCredentials(
+					$username, FreshRSS_Context::userConf()->passwordHash, $nonce, $challenge
+					) || strlen($newPasswordPlain) < 7) {
+					Minz_Session::_param('open', true); // Auto-expand `change password` section
+					Minz_Request::bad(
+						_t('feedback.auth.login.invalid'),
+						['c' => 'user', 'a' => 'profile']
+					);
+					return;
+				}
+
+				if ($newPasswordPlain !== $confirmPasswordPlain) {
+					Minz_Session::_param('open', true); // Auto-expand `change password` section
+					Minz_Request::bad(
+						_t('feedback.profile.passwords_dont_match'),
+						['c' => 'user', 'a' => 'profile']
+					);
+					return;
+				}
+
+				ini_set('session.use_cookies', '1');
+				Minz_Session::lock();
+				Minz_Session::regenerateID();
+				Minz_Session::unlock();
+			}
 
 			if (FreshRSS_Context::systemConf()->force_email_validation && empty($email)) {
 				Minz_Request::bad(
@@ -135,7 +168,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
 			$ok = self::updateUser(
 				Minz_User::name(),
 				$email,
-				$passwordPlain,
+				$newPasswordPlain,
 				[
 					'token' => Minz_Request::paramString('token'),
 				]
@@ -146,7 +179,7 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
 			if ($ok) {
 				if (FreshRSS_Context::systemConf()->force_email_validation && $email !== $old_email) {
 					Minz_Request::good(_t('feedback.profile.updated'), ['c' => 'user', 'a' => 'validateEmail']);
-				} elseif ($passwordPlain == '') {
+				} elseif ($newPasswordPlain == '') {
 					Minz_Request::good(_t('feedback.profile.updated'), ['c' => 'user', 'a' => 'profile']);
 				} else {
 					Minz_Request::good(_t('feedback.profile.updated'), ['c' => 'index', 'a' => 'index']);

+ 4 - 1
app/i18n/cs/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Odstranění účtu',
 			'warn' => 'Váš účet bude odstraněn spolu se všemi souvisejícími daty.',
 		),
 		'email' => 'E-mailová adresa',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'Heslo API<br /><small>(např. pro mobilní aplikace)</small>',
-		'password_form' => 'Heslo<br /><small>(pro přihlášení webovým formulářem)</small>',
 		'password_format' => 'Alespoň 7 znaků',
 		'title' => 'Profil',
 	),

+ 1 - 0
app/i18n/cs/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Váš profil nelze změnit',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Váš profil byl změněn',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/de/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'Siehe die <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">Dokumentation und die Liste der bekannten Apps</a>',
 			'help' => 'Siehe <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>Dokumentation</a>',
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Accountlöschung',
 			'warn' => 'Dieser Account und alle damit bezogenen Daten werden gelöscht.',
 		),
 		'email' => 'E-Mail-Adresse',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'API-Passwort<br /><small>(z.B. für mobile Anwendungen)</small>',
-		'password_form' => 'Passwort<br /><small>(für die Anmeldemethode per Webformular)</small>',
 		'password_format' => 'mindestens 7 Zeichen',
 		'title' => 'Profil',
 	),

+ 1 - 0
app/i18n/de/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Ihr Profil kann nicht geändert werden',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Ihr Profil ist geändert worden',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/el/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Account deletion',	// TODO
 			'warn' => 'Your account and all related data will be deleted.',	// TODO
 		),
 		'email' => 'Email address',	// TODO
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'API password<br /><small>(e.g., for mobile apps)</small>',	// TODO
-		'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'password_format' => 'At least 7 characters',	// TODO
 		'title' => 'Profile',	// TODO
 	),

+ 1 - 0
app/i18n/el/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Your profile cannot be modified',	// TODO
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Your profile has been modified',	// TODO
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/en-us/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// IGNORE
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// IGNORE
 		),
+		'change_password' => 'Change password',	// IGNORE
+		'confirm_new_password' => 'Confirm new password',	// IGNORE
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// IGNORE
 		'delete' => array(
 			'_' => 'Account deletion',	// IGNORE
 			'warn' => 'Your account and all related data will be deleted.',	// IGNORE
 		),
 		'email' => 'Email address',	// IGNORE
+		'new_password' => 'New password',	// IGNORE
 		'password_api' => 'API password<br /><small>(e.g., for mobile apps)</small>',	// IGNORE
-		'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',	// IGNORE
 		'password_format' => 'At least 7 characters',	// IGNORE
 		'title' => 'Profile',	// IGNORE
 	),

+ 1 - 0
app/i18n/en-us/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Your profile cannot be modified',	// IGNORE
+		'passwords_dont_match' => 'Passwords don’t match',	// IGNORE
 		'updated' => 'Your profile has been modified',	// IGNORE
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/en/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',
 		),
+		'change_password' => 'Change password',
+		'confirm_new_password' => 'Confirm new password',
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',
 		'delete' => array(
 			'_' => 'Account deletion',
 			'warn' => 'Your account and all related data will be deleted.',
 		),
 		'email' => 'Email address',
+		'new_password' => 'New password',
 		'password_api' => 'API password<br /><small>(e.g., for mobile apps)</small>',
-		'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
 		'password_format' => 'At least 7 characters',
 		'title' => 'Profile',
 	),

+ 1 - 0
app/i18n/en/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Your profile cannot be modified',
+		'passwords_dont_match' => 'Passwords don’t match',
 		'updated' => 'Your profile has been modified',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/es/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Borrar cuenta',
 			'warn' => 'Tu cuenta y todos los datos asociados serán eliminados.',
 		),
 		'email' => 'Correo electrónico',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'Contraseña API <br /><small>(para apps móviles, por ej.)</small>',
-		'password_form' => 'Contraseña<br /><small>(para el método de identificación por formulario web)</small>',
 		'password_format' => 'Mínimo de 7 caracteres',
 		'title' => 'Perfil',
 	),

+ 1 - 0
app/i18n/es/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Tu perfil no puede ser modificado',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Tu perfil ha sido modificado',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/fa/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => ' حذف اکانت',
 			'warn' => ' حساب شما و تمام داده های مرتبط حذف خواهد شد.',
 		),
 		'email' => ' آدرس ایمیل',
+		'new_password' => 'New password',	// TODO
 		'password_api' => ' رمز عبور API<br /><small>(مثلاً برای برنامه های تلفن همراه)</small>',
-		'password_form' => ' رمز عبور<br /><small>(برای روش ورود به فرم وب)</small>',
 		'password_format' => ' حداقل 7 کاراکتر',
 		'title' => ' نمایه',
 	),

+ 1 - 0
app/i18n/fa/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => ' نمایه شما قابل تغییر نیست',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => ' نمایه شما اصلاح شده است',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/fi/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Tilin poisto',
 			'warn' => 'Tilisi ja kaikki siihen kuuluvat tiedot poistetaan.',
 		),
 		'email' => 'Sähköpostiosoite',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'API-salasana<br /><small>(esimerkiksi kännykkäsovelluksille)</small>',
-		'password_form' => 'Salasana<br /><small>(Web-lomakkeella kirjautumista varten)</small>',
 		'password_format' => 'Vähintään 7 merkkiä',
 		'title' => 'Profiili',
 	),

+ 1 - 0
app/i18n/fi/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Profiilia ei voi muokata',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Profiilia on muokattu',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/fr/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'Voir <a href="https://freshrss.github.io/FreshRSS/fr/users/06_Mobile_access.html" target="_blank">la documentation et une liste d’applications compatibles</a>',
 			'help' => 'Voir <a href="https://freshrss.github.io/FreshRSS/fr/users/06_Mobile_access.html" target=_blank>la documentation</a>',
 		),
+		'change_password' => 'Changer le mot de passe',
+		'confirm_new_password' => 'Confirmer le mot de passe',
+		'current_password' => 'Mot de passe actuel<br /><small>(pour connexion par formulaire)</small>',
 		'delete' => array(
 			'_' => 'Suppression du compte',
 			'warn' => 'Le compte et toutes les données associées vont être supprimées.',
 		),
 		'email' => 'Adresse électronique',
+		'new_password' => 'Nouveau mot de passe',
 		'password_api' => 'Mot de passe API<br /><small>(ex. : pour applis mobiles)</small>',
-		'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>',
 		'password_format' => '7 caractères minimum',
 		'title' => 'Profil',
 	),

+ 1 - 0
app/i18n/fr/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Votre profil n’a pas pu être mis à jour',
+		'passwords_dont_match' => 'Les mots de passe ne correspondent pas',
 		'updated' => 'Votre profil a été mis à jour',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/he/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Account deletion',	// TODO
 			'warn' => 'Your account and all related data will be deleted.',	// TODO
 		),
 		'email' => 'Email address',	// TODO
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'סיסמת API<br /><small>(לדוגמה ליישומים סלולריים)</small>',
-		'password_form' => 'סיסמה<br /><small>(לשימוש בטפוס ההרשמה)</small>',
 		'password_format' => 'At least 7 characters',	// TODO
 		'title' => 'Profile',	// TODO
 	),

+ 1 - 0
app/i18n/he/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Your profile cannot be modified',	// TODO
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Your profile has been modified',	// TODO
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/hu/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'Lásd az <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">ismert appok dokumentációját és listáját</a>',
 			'help' => 'Lásd a <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>dokumentációt</a>',
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Profil törlése',
 			'warn' => 'A profilod és minden hozzá tartozó adat törölve lesz.',
 		),
 		'email' => 'Email cím',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'API jelszó<br /><small>(például mobil appoknak)</small>',
-		'password_form' => 'Jelszó<br /><small>(a Web-form belépési módhoz)</small>',
 		'password_format' => 'Legalább 7 karakter',
 		'title' => 'Profil',
 	),

+ 1 - 0
app/i18n/hu/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'A profilod nem módosítható',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'A profilod módosítása megtörtént',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/id/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'Lihat <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">dokumentasi dan daftar aplikasi yang diketahui</a>',
 			'help' => 'Lihat <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>dokumentasi</a>',
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Hapus akun',
 			'warn' => 'Akun Anda dan semua data terkait akan dihapus.',
 		),
 		'email' => 'Alamat surel',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'Sandi API<br /><small>(contoh: untuk aplikasi ponsel)</small>',
-		'password_form' => 'Kata sandi<br /><small>(untuk metode masuk formulir web)</small>',
 		'password_format' => 'Minimal 7 karakter',
 		'title' => 'Profil',
 	),

+ 1 - 0
app/i18n/id/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Profil Anda tidak dapat diubah',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Profil Anda telah diubah',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/it/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'Vedi la <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentazione e l’elenco delle applicazioni</a>',
 			'help' => 'leggi la <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentazione</a>',
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Cancellazione account',
 			'warn' => 'Il tuo account e tutti i dati associati saranno cancellati.',
 		),
 		'email' => 'Indirizzo email',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'Password API<br /><small>(e.g., per applicazioni mobili)</small>',
-		'password_form' => 'Password<br /><small>(per il login classico)</small>',
 		'password_format' => 'Almeno 7 caratteri',
 		'title' => 'Profilo',
 	),

+ 1 - 0
app/i18n/it/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Il tuo profilo non può essere modificato',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Il tuo profilo è stato modificato',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/ja/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => '既知のアプリの一覧は<a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">ドキュメント</a>を参照してください',
 			'help' => '<a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>ドキュメント</a>を参照します',
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'アカウント消去',
 			'warn' => 'あなたのアカウントとそれに関連したデータが消去されます。',
 		),
 		'email' => 'Eメールアドレス',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'APIのパスワード<br /><small>(モバイルアプリなど)</small>',
-		'password_form' => 'パスワード<br /><small>(Web-formのログイン時に使われます)</small>',
 		'password_format' => '最低7文字必要です',
 		'title' => 'プロフィール',
 	),

+ 1 - 0
app/i18n/ja/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'あなたのプロフィールを変更することはできません',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'あなたのプロフィールを変更されました',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/ko/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => '계정 삭제',
 			'warn' => '당신의 계정과 관련된 모든 데이터가 삭제됩니다.',
 		),
 		'email' => '메일 주소',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'API 암호<br /><small>(예: 모바일 애플리케이션)</small>',
-		'password_form' => '암호<br /><small>(웹폼 로그인 방식 사용시)</small>',
 		'password_format' => '7 글자 이상이어야 합니다',
 		'title' => '프로필',
 	),

+ 1 - 0
app/i18n/ko/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => '프로필을 변경할 수 없습니다',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => '프로필을 변경했습니다',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/lv/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Konta dzēšana',
 			'warn' => 'Jūsu konts un visi saistītie dati tiks dzēsti..',
 		),
 		'email' => 'E-pasta adrese',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'API parole<br /><small>(piem., priekš mobilajām lietotnēm)</small>',
-		'password_form' => 'Parole<br /><small>(Web-formas pieteikšanās metodei)</small>',
 		'password_format' => 'Vismaz 7 rakstzīmes',
 		'title' => 'Profils',
 	),

+ 1 - 0
app/i18n/lv/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Jūsu profilu nevar mainīt',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Jūsu profils tika mainīts',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/nl/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Account verwijderen',
 			'warn' => 'Uw account en alle gerelateerde gegvens worden verwijderd.',
 		),
 		'email' => 'Email adres',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'Wachtwoord API<br /><small>(e.g., voor mobiele apps)</small>',
-		'password_form' => 'Wachtwoord<br /><small>(voor de Web-formulier log in methode)</small>',
 		'password_format' => 'Ten minste 7 tekens',
 		'title' => 'Profiel',
 	),

+ 1 - 0
app/i18n/nl/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Uw profiel kan niet worden aangepast',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Uw profiel is aangepast',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/oc/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Supression del compte',
 			'warn' => 'Lo compte e totas las donadas ligadas seràn suprimits.',
 		),
 		'email' => 'Adreça de corrièl',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'Senhal API<br /><small>(ex. : per las aplicacions mobil)</small>',
-		'password_form' => 'Senhal API<br /><small>(ex. : per la connexion via formulari)</small>',
 		'password_format' => 'Almens 7 caractèrs',
 		'title' => 'Pefil',
 	),

+ 1 - 0
app/i18n/oc/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Impossible d’actualizar vòstre perfil',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Vòstre perfil es estat actualizat',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/pl/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'Zobacz <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">dokumentację i listę aplikacji na telefon</a>',
 			'help' => 'Zobacz <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>dokumentację</a>',
 		),
+		'change_password' => 'Zmień hasło',
+		'confirm_new_password' => 'Potwierdź nowe hasło',
+		'current_password' => 'Obecne hasło<br /><small>(do logowania przez formularz na stronie)</small>',
 		'delete' => array(
 			'_' => 'Usunięcie konta',
 			'warn' => 'Twoje konto i wszystkie powiązane z nim dane zostaną usunięte.',
 		),
 		'email' => 'Adres e-mail',
+		'new_password' => 'Nowe hasło',
 		'password_api' => 'Hasło API<br /><small>(np. do aplikacji na telefony)</small>',
-		'password_form' => 'Hasło<br /><small>(do logowania przez formularz na stronie)</small>',
 		'password_format' => 'przynajmniej 7 znaków',
 		'title' => 'Profil',
 	),

+ 1 - 0
app/i18n/pl/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Nie można modyfikować profilu',
+		'passwords_dont_match' => 'Hasła się nie zgadzają',
 		'updated' => 'Profil został zmodyfikowany',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/pt-br/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Remover conta',
 			'warn' => 'Sua conta e todos os dados relacionados serão removidos.',
 		),
 		'email' => 'Endereço de e-mail',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'Senha da API<br /><small>(p.s., para aplicativos móveis)</small>',
-		'password_form' => 'Senha<br /><small>(para o método de formulário web)</small>',
 		'password_format' => 'Ao menos 7 caracteres',
 		'title' => 'Perfil',
 	),

+ 1 - 0
app/i18n/pt-br/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Seu perfil não pode ser editado',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Seu perfil foi editado com sucesso',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/pt-pt/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Remover conta',
 			'warn' => 'A conta e todos os dados relacionados serão removidos.',
 		),
 		'email' => 'Endereço de e-mail',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'Senha da API<br /><small>(p.s., para aplicativos móveis)</small>',
-		'password_form' => 'Senha<br /><small>(para o método de formulário web)</small>',
 		'password_format' => 'Ao menos 7 caracteres',
 		'title' => 'Perfil',
 	),

+ 1 - 0
app/i18n/pt-pt/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Seu perfil não pode ser editado',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Seu perfil foi editado com sucesso',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/ru/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Удаление аккаунта',
 			'warn' => 'Ваш аккаунт и вся связанная с ним информация будут удалены.',
 		),
 		'email' => 'Адрес электронной почты',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'Пароль API<br /><small>(например, для мобильных приложений)</small>',
-		'password_form' => 'Пароль<br /><small>(для входа через веб-форму)</small>',
 		'password_format' => 'Не менее 7 символов',
 		'title' => 'Профиль',
 	),

+ 1 - 0
app/i18n/ru/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Ваш профиль не может быть изменён',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Ваш профиль изменён',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/sk/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Vymazanie účtu',
 			'warn' => 'Váš účet a všetky údaje v ňom budú vymazané.',
 		),
 		'email' => 'E-mailová adresa',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'Heslo API<br /><small>(pre mobilné aplikácie)</small>',
-		'password_form' => 'Heslo<br /><small>(pre spôsob prihlásenia cez webový formulár)</small>',
 		'password_format' => 'Najmenej 7 znakov',
 		'title' => 'Profil',
 	),

+ 1 - 0
app/i18n/sk/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Váš profil nie je možné upraviť',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Váš profil bol upravený',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/tr/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => '<a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">Belgeleri ve bilinen uygulamaların listesini</a> gör',
 			'help' => '<a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">Belgeleri</a> gör',
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => 'Hesap silme',
 			'warn' => 'Hesabınız ve ilgili tüm veriler silinecek.',
 		),
 		'email' => 'E-posta adresi',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'API parolası<br /><small>(örneğin, mobil uygulamalar için)</small>',
-		'password_form' => 'Parola<br /><small>(Web formuyla giriş yöntemi için)</small>',
 		'password_format' => 'En az 7 karakter',
 		'title' => 'Profil',
 	),

+ 1 - 0
app/i18n/tr/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => 'Profiliniz değiştirilemedi',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => 'Profiliniz değiştirildi',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/zh-cn/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => '账户删除',
 			'warn' => '你的帐户以及所有相关数据将被删除。',
 		),
 		'email' => '邮箱地址',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'API 密码<br /><small>(例如用于手机应用)</small>',
-		'password_form' => '密码<br /><small>(用于 Web-form 登录方式)</small>',
 		'password_format' => '至少 7 个字符',
 		'title' => '账户',
 	),

+ 1 - 0
app/i18n/zh-cn/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => '你的帐户无法修改',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => '你的帐户已修改',
 	),
 	'sub' => array(

+ 4 - 1
app/i18n/zh-tw/conf.php

@@ -120,13 +120,16 @@ return array(
 			'documentation_link' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target="_blank">documentation and list of known apps</a>',	// TODO
 			'help' => 'See <a href="http://freshrss.github.io/FreshRSS/en/users/06_Mobile_access.html#access-via-mobile-app" target=_blank>documentation</a>',	// TODO
 		),
+		'change_password' => 'Change password',	// TODO
+		'confirm_new_password' => 'Confirm new password',	// TODO
+		'current_password' => 'Current password<br /><small>(for the Web-form login method)</small>',	// TODO
 		'delete' => array(
 			'_' => '帳號刪除',
 			'warn' => '你的帳號及所有相關資料將被刪除。',
 		),
 		'email' => '郵箱地址',
+		'new_password' => 'New password',	// TODO
 		'password_api' => 'API 密碼<br /><small>(例如用於手機應用)</small>',
-		'password_form' => '密碼<br /><small>(用於 Web-form 登入方式)</small>',
 		'password_format' => '至少 7 個字元',
 		'title' => '個人資料',
 	),

+ 1 - 0
app/i18n/zh-tw/feedback.php

@@ -66,6 +66,7 @@ return array(
 	),
 	'profile' => array(
 		'error' => '你的帳戶修改失敗',
+		'passwords_dont_match' => 'Passwords don’t match',	// TODO
 		'updated' => '你的帳戶已修改',
 	),
 	'sub' => array(

+ 1 - 1
app/views/user/details.phtml

@@ -60,7 +60,7 @@
 			<div class="group-controls">
 				<div class="stick">
 					<input type="password" id="newPasswordPlain" name="newPasswordPlain" autocomplete="new-password"
-						pattern=".{7,}" <?= cryptAvailable() ? '' : 'disabled="disabled" ' ?>/>
+						pattern=".{7,}" <?= cryptAvailable() && Minz_User::name() !== $this->username ? '' : 'disabled="disabled" ' ?>/>
 					<button type="button" class="btn toggle-password" data-toggle="newPasswordPlain"><?= _i('key') ?></button>
 				</div>
 				<p class="help"><?= _i('help'); ?> <?= _t('admin.user.password_format') ?></p>

+ 47 - 15
app/views/user/profile.phtml

@@ -7,7 +7,7 @@
 ?>
 
 <main class="post">
-	<form method="post" action="<?= _url('user', 'profile') ?>">
+	<form id="crypto-form" method="post" action="<?= _url('user', 'profile') ?>">
 		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
 		<h1><?= _t('conf.profile') ?></h1>
 
@@ -32,20 +32,7 @@
 		<div class="form-group">
 			<label class="group-name" for="email"><?= _t('conf.profile.email') ?></label>
 			<div class="group-controls">
-				<input id="email" name="email" type="email" value="<?= FreshRSS_Context::userConf()->mail_login ?>" />
-			</div>
-		</div>
-
-		<div class="form-group">
-			<label class="group-name" for="newPasswordPlain"><?= _t('conf.profile.password_form') ?></label>
-			<div class="group-controls">
-				<div class="stick">
-					<input type="password" id="newPasswordPlain" name="newPasswordPlain" autocomplete="new-password"
-						pattern=".{7,}" <?= cryptAvailable() ? '' : 'disabled="disabled" ' ?>/>
-					<button type="button" class="btn toggle-password" data-toggle="newPasswordPlain"><?= _i('key') ?></button>
-				</div>
-				<p class="help"><?= _i('help') ?> <?= _t('conf.profile.password_format') ?></p>
-				<noscript><b><?= _t('gen.js.should_be_activated') ?></b></noscript>
+				<input id="email" name="email" type="email" autocomplete="new-password" value="<?= FreshRSS_Context::userConf()->mail_login ?>" />
 			</div>
 		</div>
 
@@ -63,6 +50,51 @@
 		</div>
 		<?php } ?>
 
+		<?php
+			$open = Minz_Session::paramBoolean('open');
+			Minz_Session::_param('open', false);
+		?>
+
+		<details class="form-advanced" data-challenge-if-not-empty="1"<?= $open ? ' open="open"' : ''?>>
+			<summary class="form-advanced-title"><?= _t('conf.profile.change_password') ?></summary>
+			<div class="form-group">
+				<label class="group-name" for="passwordPlain"><?= _t('conf.profile.current_password') ?></label>
+				<div class="group-controls">
+					<input type="hidden" id="username" value="<?= Minz_User::name() ?? '' ?>" />
+					<div class="stick">
+						<input type="password" id="passwordPlain" />
+						<button type="button" class="btn toggle-password" data-toggle="passwordPlain"><img class="icon" src="../themes/icons/key.svg" loading="lazy" alt="🔑"></button>
+					</div>
+
+					<noscript>
+						<br />
+						<strong><?= _t('gen.js.should_be_activated') ?></strong>
+					</noscript>
+				</div>
+			</div>
+			<div class="form-group">
+				<label class="group-name" for="newPasswordPlain"><?= _t('conf.profile.new_password') ?></label>
+				<div class="group-controls">
+					<div class="stick">
+						<input type="password" id="newPasswordPlain" name="newPasswordPlain" autocomplete="new-password" pattern=".{7,}" />
+						<button type="button" class="btn toggle-password" data-toggle="newPasswordPlain"><img class="icon" src="../themes/icons/key.svg" loading="lazy" alt="🔑"></button>
+					</div>
+					<p class="help">
+						<img class="icon" src="../themes/icons/help.svg" loading="lazy" alt="ℹ️"> <?= _t('conf.profile.password_format') ?>
+					</p>
+				</div>
+			</div>
+			<div class="form-group">
+				<label class="group-name" for="confirmPasswordPlain"><?= _t('conf.profile.confirm_new_password') ?></label>
+				<div class="group-controls">
+					<div class="stick">
+						<input type="password" id="confirmPasswordPlain" name="confirmPasswordPlain" autocomplete="new-password" pattern=".{7,}" />
+						<button type="button" class="btn toggle-password" data-toggle="confirmPasswordPlain"><img class="icon" src="../themes/icons/key.svg" loading="lazy" alt="🔑"></button>
+					</div>
+				</div>
+			</div>
+		</details>
+
 		<div class="form-group form-actions">
 			<div class="group-controls">
 				<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>

+ 1 - 1
docs/i18n/flags/gen/cs.svg

@@ -2,6 +2,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
 	<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
 		<rect rx="3" width="70" height="20" fill="green" />
-		<text x="34" y="14">🇨🇿 91%</text>
+		<text x="34" y="14">🇨🇿 90%</text>
 	</g>
 </svg>

+ 1 - 1
docs/i18n/flags/gen/es.svg

@@ -2,6 +2,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
 	<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
 		<rect rx="3" width="70" height="20" fill="green" />
-		<text x="34" y="14">🇪🇸 94%</text>
+		<text x="34" y="14">🇪🇸 93%</text>
 	</g>
 </svg>

+ 1 - 1
docs/i18n/flags/gen/hu.svg

@@ -2,6 +2,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
 	<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
 		<rect rx="3" width="70" height="20" fill="green" />
-		<text x="34" y="14">🇭🇺 98%</text>
+		<text x="34" y="14">🇭🇺 97%</text>
 	</g>
 </svg>

+ 1 - 1
docs/i18n/flags/gen/ja.svg

@@ -2,6 +2,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
 	<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
 		<rect rx="3" width="70" height="20" fill="green" />
-		<text x="34" y="14">🇯🇵 98%</text>
+		<text x="34" y="14">🇯🇵 97%</text>
 	</g>
 </svg>

+ 1 - 1
docs/i18n/flags/gen/ko.svg

@@ -2,6 +2,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
 	<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
 		<rect rx="3" width="70" height="20" fill="green" />
-		<text x="34" y="14">🇰🇷 91%</text>
+		<text x="34" y="14">🇰🇷 90%</text>
 	</g>
 </svg>

+ 1 - 1
docs/i18n/flags/gen/lv.svg

@@ -2,6 +2,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
 	<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
 		<rect rx="3" width="70" height="20" fill="gold" />
-		<text x="34" y="14">🇱🇻 85%</text>
+		<text x="34" y="14">🇱🇻 84%</text>
 	</g>
 </svg>

+ 1 - 1
docs/i18n/flags/gen/ru.svg

@@ -2,6 +2,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
 	<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
 		<rect rx="3" width="70" height="20" fill="green" />
-		<text x="34" y="14">🇷🇺 91%</text>
+		<text x="34" y="14">🇷🇺 90%</text>
 	</g>
 </svg>

+ 1 - 1
docs/i18n/flags/gen/sk.svg

@@ -2,6 +2,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
 	<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
 		<rect rx="3" width="70" height="20" fill="green" />
-		<text x="34" y="14">🇸🇰 91%</text>
+		<text x="34" y="14">🇸🇰 90%</text>
 	</g>
 </svg>

+ 1 - 1
docs/i18n/flags/gen/zh-cn.svg

@@ -2,6 +2,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
 	<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
 		<rect rx="3" width="70" height="20" fill="green" />
-		<text x="34" y="14">🇨🇳 91%</text>
+		<text x="34" y="14">🇨🇳 90%</text>
 	</g>
 </svg>

+ 1 - 1
docs/i18n/flags/gen/zh-tw.svg

@@ -2,6 +2,6 @@
 <svg xmlns="http://www.w3.org/2000/svg" width="70" height="20">
 	<g fill="white" font-size="12" font-family="Verdana" text-anchor="middle">
 		<rect rx="3" width="70" height="20" fill="green" />
-		<text x="34" y="14">🇹🇼 91%</text>
+		<text x="34" y="14">🇹🇼 90%</text>
 	</g>
 </svg>

+ 14 - 1
p/scripts/extra.js

@@ -39,6 +39,19 @@ function init_crypto_form() {
 	}
 
 	crypto_form.onsubmit = function (e) {
+		let challenge = crypto_form.querySelector('#challenge');
+		if (!challenge) {
+			crypto_form.querySelectorAll('[data-challenge-if-not-empty] input[type="password"]').forEach(el => {
+				if (el.value !== '' && !challenge) {
+					crypto_form.insertAdjacentHTML('beforeend', '<input type="hidden" id="challenge" name="challenge" />');
+					challenge = crypto_form.querySelector('#challenge');
+				}
+			});
+			if (!challenge) {
+				return true;
+			}
+		}
+
 		e.preventDefault();
 
 		if (!submit_button) {
@@ -64,7 +77,7 @@ function init_crypto_form() {
 						const strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function');
 						const s = bcrypt.hashSync(document.getElementById('passwordPlain').value, json.salt1);
 						const c = bcrypt.hashSync(json.nonce + s, strong ? bcrypt.genSaltSync(4) : poormanSalt());
-						document.getElementById('challenge').value = c;
+						challenge.value = c;
 						if (!s || !c) {
 							openNotification('Crypto error!', 'bad');
 						} else {