Răsfoiți Sursa

New hook: `Minz_HookType::AutofillField` for auth views (#9362)

* New hook: `Minz_HookType::AutofillField` for auth views

Currently to be used in the Demo extension, because it overrides both the formLogin and reauth views (for autofilling username/password), it can be broken every time there is a slight change in core.
For example, after https://github.com/FreshRSS/FreshRSS/pull/9358.

In the future the hook can be extended to support more fields.

Demo extension PR: https://github.com/FreshRSS/xExtension-Demo/pull/6

* Suggested alternative without returning HTML code
Inverle 10 ore în urmă
părinte
comite
b33749fe6c

+ 3 - 0
app/Controllers/authController.php

@@ -109,6 +109,7 @@ class FreshRSS_auth_Controller extends FreshRSS_ActionController {
 
 		FreshRSS_View::prependTitle(_t('gen.auth.login') . ' · ');
 		FreshRSS_View::appendScript(Minz_Url::display('/scripts/vendor/bcrypt.js?' . @filemtime(PUBLIC_PATH . '/scripts/vendor/bcrypt.js')));
+		$this->view->auth_autofills = FreshRSS_FormAuth::getAutofilledFields();
 
 		$limits = FreshRSS_Context::systemConf()->limits;
 		$this->view->cookie_days = (int)round($limits['cookie_duration'] / 86400, 1);
@@ -238,6 +239,7 @@ class FreshRSS_auth_Controller extends FreshRSS_ActionController {
 		}
 		FreshRSS_View::prependTitle(_t('gen.auth.reauth.title') . ' · ');
 		FreshRSS_View::appendScript(Minz_Url::display('/scripts/vendor/bcrypt.js?' . @filemtime(PUBLIC_PATH . '/scripts/vendor/bcrypt.js')));
+		$this->view->auth_autofills = FreshRSS_FormAuth::getAutofilledFields();
 	}
 
 	/**
@@ -282,6 +284,7 @@ class FreshRSS_auth_Controller extends FreshRSS_ActionController {
 		$this->view->show_tos_checkbox = file_exists(TOS_FILENAME);
 		$this->view->show_email_field = FreshRSS_Context::systemConf()->force_email_validation;
 		$this->view->preferred_language = Minz_Translate::getLanguage(null, Minz_Request::getPreferredLanguages(), FreshRSS_Context::systemConf()->language);
+		$this->view->auth_autofills = FreshRSS_FormAuth::getAutofilledFields();
 		FreshRSS_View::prependTitle(_t('gen.auth.registration.title') . ' · ');
 	}
 

+ 21 - 0
app/Models/FormAuth.php

@@ -21,6 +21,27 @@ class FreshRSS_FormAuth {
 		return password_verify($hash . $nonce, $challenge);
 	}
 
+	/**
+	 * @return array{username: string, password: string}
+	 */
+	public static function getAutofilledFields(bool $raw = false): array {
+		$username = Minz_ExtensionManager::callHook(Minz_HookType::AutofillField, 'username');
+		$password = Minz_ExtensionManager::callHook(Minz_HookType::AutofillField, 'password');
+
+		$fields = [
+			'username' => is_string($username) ? $username : '',
+			'password' => is_string($password) ? $password : '',
+		];
+
+		if (!$raw) {
+			foreach ($fields as $k => $v) {
+				$fields[$k] = htmlspecialchars($v, ENT_COMPAT, 'UTF-8');
+			}
+		}
+
+		return $fields;
+	}
+
 	/** @return list<string> */
 	public static function getCredentialsFromCookie(): array {
 		$token = Minz_Session::getLongTermCookie('FreshRSS_login');

+ 4 - 0
app/Models/View.php

@@ -103,6 +103,10 @@ class FreshRSS_View extends Minz_View {
 	public string $sqlitePath;
 	public string $sqliteName;
 
+	// Form auth
+	/** @var array{username: string, password: string} */
+	public array $auth_autofills;
+
 	// Form login
 	public int $cookie_days;
 

+ 4 - 2
app/views/auth/formLogin.phtml

@@ -24,14 +24,16 @@
 
 		<div class="form-group">
 			<label for="username"><?= _t('gen.auth.username') ?></label>
-			<input type="text" id="username" name="username" autocomplete="username" size="16" required="required"
+			<input type="text" id="username" name="username"<?= $this->auth_autofills['username'] !== '' ? ' value="' . $this->auth_autofills['username'] . '"' : '' ?>
+				autocomplete="username" size="16" required="required"
 				pattern="<?= FreshRSS_user_Controller::USERNAME_PATTERN ?>" autofocus="autofocus" autocapitalize="off" />
 		</div>
 
 		<div class="form-group">
 			<label for="passwordPlain"><?= _t('gen.auth.password') ?></label>
 			<div class="stick">
-				<input type="password" id="passwordPlain" class="passwordPlain" required="required" />
+				<input type="password" id="passwordPlain"<?= $this->auth_autofills['password'] !== '' ? ' value="' . $this->auth_autofills['password'] . '"' : '' ?>
+					class="passwordPlain" required="required" />
 				<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 			</div>
 			<input type="hidden" id="challenge" name="challenge" />

+ 2 - 1
app/views/auth/reauth.phtml

@@ -12,7 +12,8 @@
 		<div class="form-group">
 			<label for="passwordPlain"><?= _t('gen.auth.password') ?></label>
 			<div class="stick">
-				<input type="password" id="passwordPlain" class="passwordPlain" required="required" />
+				<input type="password" id="passwordPlain"<?= $this->auth_autofills['password'] !== '' ? ' value="' . $this->auth_autofills['password'] . '"' : '' ?>
+					class="passwordPlain" required="required" />
 				<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 			</div>
 			<input type="hidden" id="challenge" name="challenge" />

+ 4 - 2
app/views/auth/register.phtml

@@ -32,7 +32,8 @@
 
 		<div class="form-group">
 			<label for="new_user_name"><?= _t('gen.auth.username') ?></label>
-			<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" autocomplete="off"
+			<input id="new_user_name" name="new_user_name" type="text"<?= $this->auth_autofills['username'] !== '' ? ' value="' . $this->auth_autofills['username'] . '"' : '' ?>
+				size="16" required="required" autocomplete="off"
 				pattern="<?= FreshRSS_user_Controller::USERNAME_PATTERN ?>" autocapitalize="off" />
 			<p class="help"><?= _i('help') ?> <?= _t('gen.auth.username.format') ?></p>
 		</div>
@@ -49,7 +50,8 @@
 		<div class="form-group">
 			<label for="new_user_passwordPlain"><?= _t('gen.auth.password') ?></label>
 			<div class="stick">
-				<input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" required="required" autocomplete="new-password" pattern=".{7,}" />
+				<input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain"<?= $this->auth_autofills['password'] !== '' ? ' value="' . $this->auth_autofills['password'] . '"' : '' ?>
+				required="required" autocomplete="new-password" pattern=".{7,}" />
 				<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 			</div>
 			<noscript><b><?= _t('gen.js.should_be_activated') ?></b></noscript>

+ 5 - 0
docs/en/developers/03_Backend/05_Extensions.md

@@ -171,6 +171,11 @@ The following events are available:
 	By returning `true`, you allow the original action to proceed further; `false` stops further execution of the action.
 	This hook can be used for adding extra functionality to an existing action. See also: `Minz_Request::is()`, `Minz_Request::controllerName()`, `Minz_Request::actionName()`.
 * `Minz_HookType::ApiMisc` (`function(): void`): to allow extensions to have their own API endpoint
+* `Minz_HookType::AutofillField` (`function(string $field_name): string | null`): For autofilling a field in a form, to be used for example in the authentication views.
+	If it gets called with a `username` value, you may return `demo` to autofill the username field.
+	Currently supported fields:
+	* `username` - in `register/formLogin/reauth`
+	* `password` - in `register/formLogin/reauth`
 * `Minz_HookType::BeforeLoginBtn` (`function(): string`): Allows to insert HTML before the login button. Applies to the create button on the register page as well. Example use case is inserting a captcha widget.
 * `Minz_HookType::CheckUrlBeforeAdd` (`function($url) -> Url | null`): will be executed every time a URL is added. The URL itself will be passed as parameter. This way a website known to have feeds which doesn’t advertise it in the header can still be automatically supported.
 * `Minz_HookType::CustomFaviconBtnUrl` (`function(FreshRSS_Feed $feed): string | null`): Allows extensions to implement a button for setting a custom favicon for individual feeds by providing an URL. The URL will be sent a POST request with the `extAction` field set to either `query_icon_info` or `update_icon`, along with an `id` field which describes the feed's ID.

+ 2 - 0
lib/Minz/HookType.php

@@ -4,6 +4,7 @@ declare(strict_types=1);
 enum Minz_HookType: string {
 	case ActionExecute = 'action_execute'; // function(Minz_ActionController $controller): bool
 	case ApiMisc = 'api_misc';	// function(): void
+	case AutofillField = 'autofill_field';	// function(string $field_name): string | null
 	case BeforeLoginBtn = 'before_login_btn';	// function(): string
 	case CheckUrlBeforeAdd = 'check_url_before_add';	// function(string $url) -> string | null
 	case CustomFaviconBtnUrl = 'custom_favicon_btn_url';	// function(FreshRSS_Feed $feed): string | null
@@ -60,6 +61,7 @@ enum Minz_HookType: string {
 			case self::NavReadingModes:
 			case self::ViewModes:
 				return Minz_HookSignature::OneToOne;
+			case self::AutofillField:
 			case self::CustomFaviconBtnUrl:
 			case self::CustomFaviconHash:
 			case self::EntriesFavorite: