Răsfoiți Sursa

Separate the update API password endpoint (#2675)

* Extract hashPassword method from userController

* Extract and refactor fever key-related methods

* Move update of API password to dedicated action

* Simplify the controller by refactoring feverUtil

* Add locales
Marien Fressinaud 6 ani în urmă
părinte
comite
d0f1f9f141

+ 47 - 0
app/Controllers/apiController.php

@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * This controller manage API-related features.
+ */
+class FreshRSS_api_Controller extends Minz_ActionController {
+	/**
+	 * This action updates the user API password.
+	 *
+	 * Parameter is:
+	 * - apiPasswordPlain: the new user password
+	 */
+	public function updatePasswordAction() {
+		if (!FreshRSS_Auth::hasAccess()) {
+			Minz_Error::error(403);
+		}
+
+		$return_url = array('c' => 'user', 'a' => 'profile');
+
+		if (!Minz_Request::isPost()) {
+			Minz_Request::forward($return_url, true);
+		}
+
+		$apiPasswordPlain = Minz_Request::param('apiPasswordPlain', '', true);
+		if ($apiPasswordPlain == '') {
+			Minz_Request::forward($return_url, true);
+		}
+
+		$username = Minz_Session::param('currentUser');
+		$userConfig = FreshRSS_Context::$user_conf;
+
+		$apiPasswordHash = FreshRSS_password_Util::hash($apiPasswordPlain);
+		$userConfig->apiPasswordHash = $apiPasswordHash;
+
+		$feverKey = FreshRSS_fever_Util::updateKey($username, $apiPasswordPlain);
+		if (!$feverKey) {
+			Minz_Request::bad(_t('feedback.api.password.failed'), $return_url);
+		}
+
+		$userConfig->feverKey = $feverKey;
+		if ($userConfig->save()) {
+			Minz_Request::good(_t('feedback.api.password.updated'), $return_url);
+		} else {
+			Minz_Request::bad(_t('feedback.api.password.failed'), $return_url);
+		}
+	}
+}

+ 1 - 1
app/Controllers/javascriptController.php

@@ -47,7 +47,7 @@ class FreshRSS_javascript_Controller extends Minz_ActionController {
 			Minz_Log::notice('Nonce failure due to invalid username!');
 		}
 		//Failure: Return random data.
-		$this->view->salt1 = sprintf('$2a$%02d$', FreshRSS_user_Controller::BCRYPT_COST);
+		$this->view->salt1 = sprintf('$2a$%02d$', FreshRSS_password_Util::BCRYPT_COST);
 		$alphabet = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
 		for ($i = 22; $i > 0; $i--) {
 			$this->view->salt1 .= $alphabet[mt_rand(0, 63)];

+ 7 - 54
app/Controllers/userController.php

@@ -4,17 +4,6 @@
  * Controller to handle user actions.
  */
 class FreshRSS_user_Controller extends Minz_ActionController {
-	// Will also have to be computed client side on mobile devices,
-	// so do not use a too high cost
-	const BCRYPT_COST = 9;
-
-	public static function hashPassword($passwordPlain) {
-		$passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
-		$passwordPlain = '';
-		$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);	//Compatibility with bcrypt.js
-		return $passwordHash == '' ? '' : $passwordHash;
-	}
-
 	/**
 	 * The username is also used as folder name, file name, and part of SQL table name.
 	 * '_' is a reserved internal username.
@@ -25,15 +14,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 		return preg_match('/^' . self::USERNAME_PATTERN . '$/', $username) === 1;
 	}
 
-	public static function deleteFeverKey($username) {
-		$userConfig = get_user_configuration($username);
-		if ($userConfig !== null && ctype_xdigit($userConfig->feverKey)) {
-			return @unlink(DATA_PATH . '/fever/.key-' . sha1(FreshRSS_Context::$system_conf->salt) . '-' . $userConfig->feverKey . '.txt');
-		}
-		return false;
-	}
-
-	public static function updateUser($user, $email, $passwordPlain, $apiPasswordPlain, $userConfigUpdated = array()) {
+	public static function updateUser($user, $email, $passwordPlain, $userConfigUpdated = array()) {
 		$userConfig = get_user_configuration($user);
 		if ($userConfig === null) {
 			return false;
@@ -51,33 +32,10 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 		}
 
 		if ($passwordPlain != '') {
-			$passwordHash = self::hashPassword($passwordPlain);
+			$passwordHash = FreshRSS_password_Util::hash($passwordPlain);
 			$userConfig->passwordHash = $passwordHash;
 		}
 
-		if ($apiPasswordPlain != '') {
-			$apiPasswordHash = self::hashPassword($apiPasswordPlain);
-			$userConfig->apiPasswordHash = $apiPasswordHash;
-
-			$feverPath = DATA_PATH . '/fever/';
-
-			if (!file_exists($feverPath)) {
-				@mkdir($feverPath, 0770, true);
-			}
-
-			if (!is_writable($feverPath)) {
-				Minz_Log::error("Could not save Fever API credentials. The directory does not have write access.");
-			} else {
-				self::deleteFeverKey($user);
-				$userConfig->feverKey = strtolower(md5("{$user}:{$apiPasswordPlain}"));
-				$ok = file_put_contents($feverPath . '.key-' . sha1(FreshRSS_Context::$system_conf->salt) . '-' . $userConfig->feverKey . '.txt', $user) !== false;
-
-				if (!$ok) {
-					Minz_Log::warning('Could not save Fever API credentials. Unknown error.', ADMIN_LOG);
-				}
-			}
-		}
-
 		if (is_array($userConfigUpdated)) {
 			foreach ($userConfigUpdated as $configName => $configValue) {
 				if ($configValue !== null) {
@@ -100,10 +58,8 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 			Minz_Request::_param('newPasswordPlain');	//Discard plain-text password ASAP
 			$_POST['newPasswordPlain'] = '';
 
-			$apiPasswordPlain = Minz_Request::param('apiPasswordPlain', '', true);
-
 			$username = Minz_Request::param('username');
-			$ok = self::updateUser($username, null, $passwordPlain, $apiPasswordPlain, array(
+			$ok = self::updateUser($username, null, $passwordPlain, array(
 				'token' => Minz_Request::param('token', null),
 			));
 
@@ -150,8 +106,6 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 			Minz_Request::_param('newPasswordPlain');	//Discard plain-text password ASAP
 			$_POST['newPasswordPlain'] = '';
 
-			$apiPasswordPlain = Minz_Request::param('apiPasswordPlain', '', true);
-
 			if ($system_conf->force_email_validation && empty($email)) {
 				Minz_Request::bad(
 					_t('user.email.feedback.required'),
@@ -170,7 +124,6 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 				Minz_Session::param('currentUser'),
 				$email,
 				$passwordPlain,
-				$apiPasswordPlain,
 				array(
 					'token' => Minz_Request::param('token', null),
 				)
@@ -239,7 +192,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 		}
 	}
 
-	public static function createUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain = '', $userConfigOverride = [], $insertDefaultFeeds = true) {
+	public static function createUser($new_user_name, $email, $passwordPlain, $userConfigOverride = [], $insertDefaultFeeds = true) {
 		$userConfig = [];
 
 		$customUserConfigPath = join_path(DATA_PATH, 'config-user.custom.php');
@@ -291,7 +244,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 				}
 			}
 
-			$ok &= self::updateUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain);
+			$ok &= self::updateUser($new_user_name, $email, $passwordPlain);
 		}
 		return $ok;
 	}
@@ -346,7 +299,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 				);
 			}
 
-			$ok = self::createUser($new_user_name, $email, $passwordPlain, '', array('language' => $new_user_language));
+			$ok = self::createUser($new_user_name, $email, $passwordPlain, array('language' => $new_user_language));
 			Minz_Request::_param('new_user_passwordPlain');	//Discard plain-text password ASAP
 			$_POST['new_user_passwordPlain'] = '';
 			invalidateHttpCache();
@@ -386,7 +339,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 		$user_data = join_path(DATA_PATH, 'users', $username);
 		$ok &= is_dir($user_data);
 		if ($ok) {
-			self::deleteFeverKey($username);
+			FreshRSS_fever_Util::deleteKey($username);
 			$oldUserDAO = FreshRSS_Factory::createUserDao($username);
 			$ok &= $oldUserDAO->deleteUser();
 			$ok &= recursive_unlink($user_data);

+ 80 - 0
app/Utils/feverUtil.php

@@ -0,0 +1,80 @@
+<?php
+
+class FreshRSS_fever_Util {
+	const FEVER_PATH = DATA_PATH . '/fever';
+
+	/**
+	 * Make sure the fever path exists and is writable.
+	 *
+	 * @return boolean true if the path is writable, else false.
+	 */
+	public static function checkFeverPath() {
+		if (!file_exists(self::FEVER_PATH)) {
+			@mkdir(self::FEVER_PATH, 0770, true);
+		}
+
+		$ok = is_writable(self::FEVER_PATH);
+		if (!$ok) {
+			Minz_Log::error("Could not save Fever API credentials. The directory does not have write access.");
+		}
+		return $ok;
+	}
+
+	/**
+	 * Return the corresponding path for a fever key.
+	 *
+	 * @param string
+	 * @return string
+	 */
+	public static function getKeyPath($feverKey) {
+		$salt = sha1(FreshRSS_Context::$system_conf->salt);
+		return self::FEVER_PATH . '/.key-' . $salt . '-' . $feverKey . '.txt';
+	}
+
+	/**
+	 * Update the fever key of a user.
+	 *
+	 * @param string
+	 * @param string
+	 * @return string the Fever key, or false if the update failed
+	 */
+	public static function updateKey($username, $passwordPlain) {
+		$ok = self::checkFeverPath();
+		if (!$ok) {
+			return false;
+		}
+
+		self::deleteKey($username);
+
+		$feverKey = strtolower(md5("{$username}:{$passwordPlain}"));
+		$feverKeyPath = self::getKeyPath($feverKey);
+		$res = file_put_contents($feverKeyPath, $username);
+		if ($res !== false) {
+			return $feverKey;
+		} else {
+			Minz_Log::warning('Could not save Fever API credentials. Unknown error.', ADMIN_LOG);
+			return false;
+		}
+	}
+
+	/**
+	 * Delete the Fever key of a user.
+	 *
+	 * @param string
+	 * @return boolean true if the deletion succeeded, else false.
+	 */
+	public static function deleteKey($username) {
+		$userConfig = get_user_configuration($username);
+		if ($userConfig === null) {
+			return false;
+		}
+
+		$feverKey = $userConfig->feverKey;
+		if (!ctype_xdigit($feverKey)) {
+			return false;
+		}
+
+		$feverKeyPath = self::getKeyPath($feverKey);
+		return @unlink($feverKeyPath);
+	}
+}

+ 27 - 0
app/Utils/passwordUtil.php

@@ -0,0 +1,27 @@
+<?php
+
+class FreshRSS_password_Util {
+	// Will also have to be computed client side on mobile devices,
+	// so do not use a too high cost
+	const BCRYPT_COST = 9;
+
+	/**
+	 * Return a hash of a plain password, using BCRYPT
+	 *
+	 * @param string
+	 * @return string
+	 */
+	public static function hash($passwordPlain) {
+		$passwordHash = password_hash(
+			$passwordPlain,
+			PASSWORD_BCRYPT,
+			array('cost' => self::BCRYPT_COST)
+		);
+		$passwordPlain = '';
+
+		// Compatibility with bcrypt.js
+		$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);
+
+		return $passwordHash == '' ? '' : $passwordHash;
+	}
+}

+ 1 - 0
app/i18n/cz/conf.php

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Správa profilu',
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => 'Smazání účtu',
 			'warn' => 'Váš účet bude smazán spolu se všemi souvisejícími daty',

+ 6 - 0
app/i18n/cz/feedback.php

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'Nemáte oprávnění přistupovat na tuto stránku',
 		'not_found' => 'Tato stránka neexistuje',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'Nastal problém s konfigurací přihlašovacího systému. Zkuste to prosím později.',

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

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Profil-Verwaltung',
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => 'Accountlöschung',
 			'warn' => 'Dein Account und alle damit bezogenen Daten werden gelöscht.',

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'Sie haben nicht die Berechtigung, diese Seite aufzurufen',
 		'not_found' => 'Sie suchen nach einer Seite, die nicht existiert',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'Während der Konfiguration des Authentifikationssystems trat ein Fehler auf. Bitte versuchen Sie es später erneut.',

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

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Profile management',
+		'api' => 'API management',
 		'delete' => array(
 			'_' => 'Account deletion',
 			'warn' => 'Your account and all related data will be deleted.',

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'You don’t have permission to access this page',
 		'not_found' => 'You are looking for a page which doesn’t exist',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified',
+			'updated' => 'Your password has been modified',
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'A problem occured during authentication system configuration. Please retry later.',

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

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Administración de perfiles',
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => 'Borrar cuenta',
 			'warn' => 'Tu cuenta y todos los datos asociados serán eliminados.',

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'No dispones de permiso para acceder a esta página',
 		'not_found' => 'La página que buscas no existe',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'Hubo un problema durante la configuración del sistema de idenfificación. Por favor, inténtalo más tarde.',

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

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Gestion du profil',
+		'api' => 'Gestion de l’API',
 		'delete' => array(
 			'_' => 'Suppression du compte',
 			'warn' => 'Le compte et toutes les données associées vont être supprimées.',

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'Vous n’avez pas le droit d’accéder à cette page !',
 		'not_found' => 'La page que vous cherchez n’existe pas !',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Votre mot de passe n’a pas pu être mis à jour',
+			'updated' => 'Votre mot de passe a été mis à jour',
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'Un problème est survenu lors de la configuration de votre système d’authentification. Veuillez réessayer plus tard.',

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

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Profile management',	//TODO - Translation
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => 'Account deletion',	//TODO - Translation
 			'warn' => 'Your account and all related data will be deleted.',	//TODO - Translation

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'אין לך הרשאות לצפות בדף זה',
 		'not_found' => 'הדף הזה לא נמצא',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'אירעה שגיאה במהלך הגדרת מערכת האימיות. אנא נסו שוב מאוחר יותר.',

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

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Gestione profili',
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => 'Cancellazione account',
 			'warn' => 'Il tuo account e tutti i dati associati saranno cancellati.',

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'Non hai i permessi per accedere a questa pagina',
 		'not_found' => 'Pagina non disponibile',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'Si è verificato un problema alla configurazione del sistema di autenticazione. Per favore riprova più tardi.',

+ 1 - 0
app/i18n/kr/conf.php

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => '프로필 관리',
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => '계정 삭제',
 			'warn' => '당신의 계정과 관련된 모든 데이터가 삭제됩니다.',

+ 6 - 0
app/i18n/kr/feedback.php

@@ -8,6 +8,12 @@ return array(
 		'denied' => '이 페이지에 접근할 수 있는 권한이 없습니다',
 		'not_found' => '이 페이지는 존재하지 않습니다',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => '인증 시스템을 설정하는 동안 문제가 발생했습니다. 잠시 후 다시 시도하세요.',

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

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Profiel beheer',
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => 'Account verwijderen',
 			'warn' => 'Uw account en alle gerelateerde gegvens worden verwijderd.',

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'U hebt geen rechten om deze pagina te bekijken.',
 		'not_found' => 'Deze pagina bestaat niet',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'Er is een probleem opgetreden tijdens de controle van de systeemconfiguratie. Probeer het later nog eens.',

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

@@ -51,6 +51,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Gestion del perfil',
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => 'Supression del compte',
 			'warn' => 'Lo compte e totas las donadas ligadas seràn suprimits.',

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'Avètz pas l’autorizacion d’accedir a aquesta pagina',
 		'not_found' => 'La pagina que cercatz existís pas',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'Un problèma es aparegut pendent la configuracion del sistèma d’autentificacion. Tonatz ensajar ai tard.',

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

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Gerenciamento de perfil',
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => 'Remover conta',
 			'warn' => 'Sua conta e todos os dados relacionados serão removidos.',

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'Você não tem permissão para acessar esta página',
 		'not_found' => 'VocÊ está buscando por uma página que não existe',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'Um problema ocorreu durante o sistema de configuração para autenticação. Por favor tente mais tarde.',

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

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Profile management',	//TODO - Translation
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => 'Account deletion',	//TODO - Translation
 			'warn' => 'Your account and all the related data will be deleted.',	//TODO - Translation

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'You don’t have permission to access this page',	//TODO - Translation
 		'not_found' => 'You are looking for a page which doesn’t exist',	//TODO - Translation
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'A problem occured during authentication system configuration. Please retry later.',	//TODO - Translation

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

@@ -42,6 +42,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Správca profilu',
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => 'Vymazanie účtu',
 			'warn' => 'Váš účet a všetky údaje v ňom budú vymazané.',

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'Na prístup k tejto stránke nemáte oprávnenie',
 		'not_found' => 'Hľadáte stránku, ktorá neexistuje',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'Nastavl problém pri nastavovaní prihlasovacieho systému. Prosím, skúste to znova neskôr.',

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

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => 'Profil yönetimi',
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => 'Hesap silme',
 			'warn' => 'Hesabınız ve tüm verileriniz silinecek.',

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => 'Bu sayfaya erişim yetkiniz yok',
 		'not_found' => 'Varolmayan bir sayfa arıyorsunuz',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => 'Sistem yapılandırma kimlik doğrulaması sırasında hata oldu. Lütfen daha sonra tekrar deneyin.',

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

@@ -50,6 +50,7 @@ return array(
 	),
 	'profile' => array(
 		'_' => '帐户管理',
+		'api' => 'API management', // TODO - Translation
 		'delete' => array(
 			'_' => '账户删除',
 			'warn' => '你的帐户和所有相关数据都将被删除。',

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

@@ -8,6 +8,12 @@ return array(
 		'denied' => '你无权访问此页面',
 		'not_found' => '你寻找的页面不存在',
 	),
+	'api' => array(
+		'password' => array(
+			'failed' => 'Your password cannot be modified', // TODO - Translation
+			'updated' => 'Your password has been modified', // TODO - Translation
+		),
+	),
 	'auth' => array(
 		'form' => array(
 			'not_set' => '配置认证方式时出错。请稍后重试。',

+ 0 - 1
app/install.php

@@ -221,7 +221,6 @@ function saveStep3() {
 				$_SESSION['default_user'],
 				'',	//TODO: Add e-mail
 				$password_plain,
-				'',
 				[
 					'language' => $_SESSION['language'],
 				]

+ 24 - 13
app/views/user/profile.phtml

@@ -48,19 +48,6 @@
 			</div>
 		</div>
 
-		<?php if (FreshRSS_Context::$system_conf->api_enabled) { ?>
-		<div class="form-group">
-			<label class="group-name" for="apiPasswordPlain"><?= _t('conf.profile.password_api') ?></label>
-			<div class="group-controls">
-				<div class="stick">
-					<input type="password" id="apiPasswordPlain" name="apiPasswordPlain" autocomplete="new-password" pattern=".{7,}" <?= cryptAvailable() ? '' : 'disabled="disabled" ' ?>/>
-					<a class="btn toggle-password" data-toggle="apiPasswordPlain"><?= _i('key') ?></a>
-				</div>
-				<?= _i('help') ?> <kbd><a href="../api/"><?= Minz_Url::display('/api/', 'html', true) ?></a></kbd>
-			</div>
-		</div>
-		<?php } ?>
-
 		<?php if (FreshRSS_Auth::accessNeedsAction()) { ?>
 		<div class="form-group">
 			<label class="group-name" for="token"><?= _t('admin.auth.token') ?></label>
@@ -82,6 +69,30 @@
 		</div>
 	</form>
 
+	<?php if (FreshRSS_Context::$system_conf->api_enabled) { ?>
+		<form method="post" action="<?= _url('api', 'updatePassword') ?>">
+			<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
+			<legend><?= _t('conf.profile.api') ?></legend>
+
+			<div class="form-group">
+				<label class="group-name" for="apiPasswordPlain"><?= _t('conf.profile.password_api') ?></label>
+				<div class="group-controls">
+					<div class="stick">
+						<input type="password" id="apiPasswordPlain" name="apiPasswordPlain" autocomplete="new-password" pattern=".{7,}" <?= cryptAvailable() ? '' : 'disabled="disabled" ' ?>/>
+						<a class="btn toggle-password" data-toggle="apiPasswordPlain"><?= _i('key') ?></a>
+					</div>
+					<?= _i('help') ?> <kbd><a href="../api/"><?= Minz_Url::display('/api/', 'html', true) ?></a></kbd>
+				</div>
+			</div>
+
+			<div class="form-group form-actions">
+				<div class="group-controls">
+					<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
+				</div>
+			</div>
+		</form>
+	<?php } ?>
+
 	<?php if (!FreshRSS_Auth::hasAccess('admin')) { ?>
 	<form id="crypto-form" method="post" action="<?= _url('user', 'delete') ?>">
 		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />

+ 1 - 2
cli/_update-or-create-user.php

@@ -4,7 +4,6 @@ require(__DIR__ . '/_cli.php');
 $params = array(
 		'user:',
 		'password:',
-		'api_password:',
 		'language:',
 		'email:',
 		'token:',
@@ -24,7 +23,7 @@ $options = getopt('', $params);
 
 if (!validateOptions($argv, $params) || empty($options['user'])) {
 	fail('Usage: ' . basename($_SERVER['SCRIPT_FILENAME']) .
-		" --user username ( --password 'password' --api_password 'api_password'" .
+		" --user username ( --password 'password'" .
 		" --language en --email user@example.net --token 'longRandomString'" .
 		($isUpdate ? '' : '--no_default_feeds') .
 		" --purge_after_months 3 --feed_min_articles_default 50 --feed_ttl_default 3600" .

+ 0 - 1
cli/create-user.php

@@ -20,7 +20,6 @@ $ok = FreshRSS_user_Controller::createUser(
 	$username,
 	empty($options['mail_login']) ? '' : $options['mail_login'],
 	empty($options['password']) ? '' : $options['password'],
-	empty($options['api_password']) ? '' : $options['api_password'],
 	$values,
 	!isset($options['no_default_feeds'])
 );

+ 0 - 1
cli/update-user.php

@@ -11,7 +11,6 @@ $ok = FreshRSS_user_Controller::updateUser(
 	$username,
 	empty($options['mail_login']) ? null : $options['mail_login'],
 	empty($options['password']) ? '' : $options['password'],
-	empty($options['api_password']) ? '' : $options['api_password'],
 	$values);
 
 if (!$ok) {