Преглед изворни кода

Fix another user self-delete regression (#7877)

Regression from #7763 
Earlier regression which was fixed before #7626

In addition:
* get rid of `data-toggle` (refactor)
* show invalid login message if deleting account and entered incorrect password instead of redirect to 403
* remove unused reference to `r` parameter
* `forgetOpenCategories()` on login not on any crypto form
Inverle пре 6 месеци
родитељ
комит
ddb51c0e95

+ 6 - 3
app/Controllers/userController.php

@@ -635,13 +635,16 @@ class FreshRSS_user_Controller extends FreshRSS_ActionController {
 					$username, FreshRSS_Context::userConf()->passwordHash,
 					$nonce, $challenge
 				);
+				if (!$ok) {
+					Minz_Request::bad(_t('feedback.auth.login.invalid'), ['c' => 'user', 'a' => 'profile']);
+					return;
+				}
 			} elseif (self::reauthRedirect()) {
 				return;
 			}
 
-			if ($ok) {
-				$ok &= self::deleteUser($username);
-			}
+			$ok &= self::deleteUser($username);
+
 			if ($ok && $self_deletion) {
 				FreshRSS_Auth::removeAccess();
 				$redirect_url = ['c' => 'index', 'a' => 'index'];

+ 3 - 3
app/views/auth/formLogin.phtml

@@ -11,7 +11,7 @@
 		</div>
 	<?php } ?>
 
-	<form id="crypto-form" method="post" action="<?= _url('auth', 'login') ?>">
+	<form class="crypto-form" method="post" action="<?= _url('auth', 'login') ?>">
 		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
 		<input type="hidden" name="original_request" value="<?= Minz_Url::serialize(Minz_Request::originalRequest())?>" />
 
@@ -24,8 +24,8 @@
 		<div class="form-group">
 			<label for="passwordPlain"><?= _t('gen.auth.password') ?></label>
 			<div class="stick">
-				<input type="password" id="passwordPlain" required="required" />
-				<button type="button" class="btn toggle-password" data-toggle="passwordPlain"><?= _i('key') ?></button>
+				<input type="password" id="passwordPlain" class="passwordPlain" required="required" />
+				<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 			</div>
 			<input type="hidden" id="challenge" name="challenge" />
 			<noscript><strong><?= _t('gen.js.should_be_activated') ?></strong></noscript>

+ 4 - 4
app/views/auth/reauth.phtml

@@ -6,14 +6,14 @@
 <main class="prompt">
 	<h1><?= _t('gen.auth.reauth.header') ?></h1>
 
-	<form id="crypto-form" method="post">
+	<form class="crypto-form" method="post">
 		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
 		<input type="hidden" id="username" value="<?= Minz_User::name() ?>" />
 		<div class="form-group">
 			<label for="passwordPlain"><?= _t('gen.auth.password') ?></label>
 			<div class="stick">
-				<input type="password" id="passwordPlain" required="required" />
-				<button type="button" class="btn toggle-password" data-toggle="passwordPlain"><?= _i('key') ?></button>
+				<input type="password" id="passwordPlain" class="passwordPlain" required="required" />
+				<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 			</div>
 			<input type="hidden" id="challenge" name="challenge" />
 			<noscript><strong><?= _t('gen.js.should_be_activated') ?></strong></noscript>
@@ -23,7 +23,7 @@
 		?>
 		<p class="help"><?= _i('help') ?> <?= _t('gen.auth.reauth.tip', intval($reauth_time / 60)) ?></p>
 		<div class="form-group form-group-actions">
-			<button id="loginButton" type="submit" class="btn btn-important" disabled="disabled">
+			<button type="submit" class="btn btn-important" disabled="disabled">
 				<?= _t('gen.auth.login') ?>
 			</button>
 		</div>

+ 1 - 1
app/views/auth/register.phtml

@@ -50,7 +50,7 @@
 			<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,}" />
-				<button type="button" class="btn toggle-password" data-toggle="new_user_passwordPlain"><?= _i('key') ?></button>
+				<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 			</div>
 			<noscript><b><?= _t('gen.js.should_be_activated') ?></b></noscript>
 			<p class="help"><?= _i('help') ?> <?= _t('gen.auth.password.format') ?></p>

+ 1 - 1
app/views/feed/add.phtml

@@ -83,7 +83,7 @@
 				<div class="group-controls">
 					<div class="stick">
 						<input type="password" name="http_pass" id="http_pass" value="<?= $auth['password'] ?>" autocomplete="new-password" />
-						<button type="button" class="btn toggle-password" data-toggle="http_pass"><?= _i('key') ?></button>
+						<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 					</div>
 				</div>
 			</div>

+ 1 - 1
app/views/helpers/feed/update.phtml

@@ -231,7 +231,7 @@
 					<div class="stick w50">
 						<input type="password" name="http_pass_feed<?= $this->feed->id() ?>" id="http_pass_feed<?= $this->feed->id() ?>" value="<?=
 							$auth['password'] ?>" autocomplete="new-password" />
-						<button type="button" class="btn toggle-password" data-toggle="http_pass_feed<?= $this->feed->id() ?>"><?= _i('key') ?></button>
+						<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 					</div>
 				</div>
 			</div>

+ 1 - 1
app/views/subscription/add.phtml

@@ -277,7 +277,7 @@
 				<div class="group-controls">
 					<div class="stick">
 						<input id="http_pass" name="http_pass" type="password" value="" autocomplete="new-password" />
-						<button type="button" class="btn toggle-password" data-toggle="http_pass"><?= _i('key') ?></button>
+						<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 					</div>
 				</div>
 			</div>

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

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

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

@@ -71,7 +71,7 @@
 			<div class="group-controls">
 				<div class="stick">
 					<input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" autocomplete="new-password" pattern=".{7,}" />
-					<button type="button" class="btn toggle-password" data-toggle="new_user_passwordPlain"><?= _i('key') ?></button>
+					<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 				</div>
 				<p class="help"><?= _i('help') ?> <?= _t('admin.user.password_format') ?></p>
 				<noscript><b><?= _t('gen.js.should_be_activated') ?></b></noscript>

+ 11 - 18
app/views/user/profile.phtml

@@ -7,7 +7,7 @@
 ?>
 
 <main class="post">
-	<form id="crypto-form" method="post" action="<?= _url('user', 'profile') ?>" data-auto-leave-validation="1">
+	<form class="crypto-form" method="post" action="<?= _url('user', 'profile') ?>" data-auto-leave-validation="1">
 		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
 		<h1><?= _t('conf.profile') ?></h1>
 
@@ -58,12 +58,12 @@
 		<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>
+				<label class="group-name" for="currentPasswordPlain"><?= _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>
+						<input type="password" id="currentPasswordPlain" class="passwordPlain" />
+						<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 					</div>
 
 					<noscript>
@@ -77,10 +77,10 @@
 				<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>
+						<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 					</div>
 					<p class="help">
-						<img class="icon" src="../themes/icons/help.svg" loading="lazy" alt="ℹ️"> <?= _t('conf.profile.password_format') ?>
+						<?= _i('help') ?> <?= _t('conf.profile.password_format') ?>
 					</p>
 				</div>
 			</div>
@@ -89,7 +89,7 @@
 				<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>
+						<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 					</div>
 				</div>
 			</div>
@@ -120,7 +120,7 @@
 								placeholder="<?= _t('conf.profile.api.api_not_set') ?>"
 							<?php } ?>
 							pattern=".{7,}" <?= cryptAvailable() ? '' : 'disabled="disabled" ' ?>/>
-						<button type="button" class="btn toggle-password" data-toggle="apiPasswordPlain"><?= _i('key') ?></button>
+						<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 					</div>
 					<p class="help"><?= _i('help') ?> <?= _t('conf.profile.api.check_link', Minz_Url::display('/api/', 'html', true)) ?></p>
 					<p class="help"><?= _i('help') ?> <?= _t('conf.profile.api.documentation_link') ?></p>
@@ -146,7 +146,7 @@
 	<?php if (!FreshRSS_Auth::hasAccess('admin')) { ?>
 	<h2><?= _t('conf.profile.delete') ?></h2>
 
-	<form id="crypto-form" method="post" action="<?= _url('user', 'delete') ?>">
+	<form class="crypto-form" method="post" action="<?= _url('user', 'delete') ?>">
 		<input type="hidden" name="_csrf" value="<?= FreshRSS_Auth::csrfToken() ?>" />
 
 		<p class="alert alert-warn"><span class="alert-head"><?= _t('gen.short.attention') ?></span> <?= _t('conf.profile.delete.warn') ?></p>
@@ -155,8 +155,8 @@
 			<label class="group-name" for="passwordPlain"><?= _t('gen.auth.password') ?></label>
 			<div class="group-controls">
 				<div class="stick">
-					<input type="password" id="passwordPlain" required="required" />
-					<button type="button" class="btn toggle-password" data-toggle="passwordPlain"><?= _i('key') ?></button>
+					<input type="password" id="passwordPlain" class="passwordPlain" required="required" />
+					<button type="button" class="btn toggle-password"><?= _i('key') ?></button>
 				</div>
 					<input type="hidden" id="challenge" name="challenge" /><br />
 					<noscript><strong><?= _t('gen.js.should_be_activated') ?></strong></noscript>
@@ -165,13 +165,6 @@
 
 		<div class="form-group form-actions">
 			<div class="group-controls">
-				<?php
-					$redirect_url = urlencode(Minz_Url::display(
-						['c' => 'user', 'a' => 'profile'],
-						'php', true
-					));
-				?>
-				<input type="hidden" name="r" value="<?= $redirect_url ?>" />
 				<input type="hidden" name="username" id="username" value="<?= Minz_User::name() ?>" />
 				<button type="submit" class="btn btn-attention confirm"><?= _t('gen.action.remove') ?></button>
 			</div>

+ 75 - 78
p/scripts/extra.js

@@ -16,114 +16,107 @@ function forgetOpenCategories() {
 	localStorage.removeItem('FreshRSS_open_categories');
 }
 
-function init_crypto_form() {
-	/* globals bcrypt */
-	const crypto_form = document.getElementById('crypto-form');
-	if (!crypto_form) {
-		return;
-	}
-
+function init_crypto_forms() {
 	if (!(window.bcrypt)) {
 		if (window.console) {
 			console.log('FreshRSS waiting for bcrypt.js…');
 		}
-		setTimeout(init_crypto_form, 100);
+		setTimeout(init_crypto_forms, 100);
 		return;
 	}
 
-	forgetOpenCategories();
-
-	const submit_button = crypto_form.querySelector('[type="submit"]');
-	if (submit_button) {
-		submit_button.disabled = false;
-	}
+	/* globals bcrypt */
+	const crypto_forms = document.querySelectorAll('.crypto-form');
+	crypto_forms.forEach(crypto_form => {
+		const submit_button = crypto_form.querySelector('[type="submit"]');
+		if (submit_button) {
+			submit_button.disabled = false;
+		}
 
-	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');
-				}
-			});
+		crypto_form.onsubmit = function (e) {
+			let challenge = crypto_form.querySelector('#challenge');
 			if (!challenge) {
-				return true;
+				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();
+			e.preventDefault();
 
-		if (!submit_button) {
-			return false;
-		}
-		submit_button.disabled = true;
+			if (!submit_button) {
+				return false;
+			}
+			submit_button.disabled = true;
 
-		const req = new XMLHttpRequest();
-		req.open('GET', './?c=javascript&a=nonce&user=' + document.getElementById('username').value, true);
+			const req = new XMLHttpRequest();
+			req.open('GET', './?c=javascript&a=nonce&user=' + crypto_form.querySelector('#username').value, true);
 
-		req.onerror = function () {
-			openNotification('Communication error!', 'bad');
-			submit_button.disabled = false;
-		};
+			req.onerror = function () {
+				openNotification('Communication error!', 'bad');
+				submit_button.disabled = false;
+			};
 
-		req.onload = function () {
-			if (req.status == 200) {
-				const json = xmlHttpRequestJson(req);
-				if (!json.salt1 || !json.nonce) {
-					openNotification('Invalid user!', 'bad');
-				} else {
-					try {
-						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());
-						challenge.value = c;
-						if (!s || !c) {
-							openNotification('Crypto error!', 'bad');
-						} else {
-							crypto_form.removeEventListener('submit', crypto_form.onsubmit);
-							crypto_form.submit();
+			req.onload = function () {
+				if (req.status == 200) {
+					const json = xmlHttpRequestJson(req);
+					if (!json.salt1 || !json.nonce) {
+						openNotification('Invalid user!', 'bad');
+					} else {
+						try {
+							const strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function');
+							const s = bcrypt.hashSync(crypto_form.querySelector('.passwordPlain').value, json.salt1);
+							const c = bcrypt.hashSync(json.nonce + s, strong ? bcrypt.genSaltSync(4) : poormanSalt());
+							challenge.value = c;
+							if (!s || !c) {
+								openNotification('Crypto error!', 'bad');
+							} else {
+								crypto_form.removeEventListener('submit', crypto_form.onsubmit);
+								crypto_form.submit();
+							}
+						} catch (ex) {
+							openNotification('Crypto exception! ' + ex, 'bad');
 						}
-					} catch (ex) {
-						openNotification('Crypto exception! ' + ex, 'bad');
 					}
+				} else {
+					req.onerror();
 				}
-			} else {
-				req.onerror();
-			}
-			submit_button.disabled = false;
-		};
+				submit_button.disabled = false;
+			};
 
-		req.send();
-	};
+			req.send();
+		};
+	});
 }
 // </crypto form (Web login)>
 
 // <show password>
-let timeoutHide;
-
-function showPW_this() {
-	const id_passwordField = this.getAttribute('data-toggle');
-	if (this.classList.contains('active')) {
-		hidePW(id_passwordField);
+function togglePW(btn) {
+	if (btn.classList.contains('active')) {
+		hidePW(btn);
 	} else {
-		showPW(id_passwordField);
+		showPW(btn);
 	}
 	return false;
 }
 
-function showPW(id_passwordField) {
-	const passwordField = document.getElementById(id_passwordField);
+function showPW(btn) {
+	const passwordField = btn.previousElementSibling;
 	passwordField.setAttribute('type', 'text');
-	passwordField.nextElementSibling.classList.add('active');
-	clearTimeout(timeoutHide);
-	timeoutHide = setTimeout(function () { hidePW(id_passwordField); }, 5000);
+	btn.classList.add('active');
+	clearTimeout(btn.timeoutHide);
+	btn.timeoutHide = setTimeout(function () { hidePW(btn); }, 5000);
 	return false;
 }
 
-function hidePW(id_passwordField) {
-	clearTimeout(timeoutHide);
-	const passwordField = document.getElementById(id_passwordField);
+function hidePW(btn) {
+	clearTimeout(btn.timeoutHide);
+	const passwordField = btn.previousElementSibling;
 	passwordField.setAttribute('type', 'password');
 	passwordField.nextElementSibling.classList.remove('active');
 	return false;
@@ -131,7 +124,7 @@ function hidePW(id_passwordField) {
 
 function init_password_observers(parent) {
 	parent.querySelectorAll('.toggle-password').forEach(function (btn) {
-		btn.addEventListener('click', showPW_this);
+		btn.onclick = () => togglePW(btn);
 	});
 }
 // </show password>
@@ -500,8 +493,12 @@ function init_extra_afterDOM() {
 		setTimeout(init_extra_afterDOM, 50);
 		return;
 	}
+	const loginButton = document.querySelector('#loginButton');
+	if (loginButton) {
+		loginButton.addEventListener('click', forgetOpenCategories);
+	}
 	if (!['normal', 'global', 'reader'].includes(context.current_view)) {
-		init_crypto_form();
+		init_crypto_forms();
 		init_password_observers(document.body);
 		init_select_observers();
 		init_configuration_alert();