diff --git a/README.md b/README.md index 547dd59..9daf481 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,27 @@ If you are interested in the source code, or want to contribute, please check [t For any further questions, don't hesitate to contact me under bfstop@bfroehler.info +## Security changes in this release + +- The link in the "you were blocked" email now only asks for confirmation when + opened; the unblock itself is a POST request, and only works from the IP + address which was blocked (so mail scanners and link previews can't use it). +- The log view and the IP information view now escape everything they show. +- The unblock page answers with an error status if the link can't be used (400, + 403, 404 or 500; a GET request just asks for confirmation and stays a 200), + unless the plugin's "Use HTTP Error" setting is off. Because the link contains + a secret, the page is not cached, sends no Referer and asks search engines + not to index it. +- New setting "Usernames Not Matching an Account" (default: hash), see the + plugin's CHANGELOG. +- New setting "IPv6 Tracking Granularity" (default: /64). +- The "User Block Message" setting has a new value: the email with the unblock + link is by default only sent if the user has logged in from the blocked IP + address before, so it can't be used to flood somebody's inbox. +- Sending the test email needs the permission to change the settings, and the + .htaccess and GeoIP database path settings are validated. +- Subnets with a prefix length like `1e1` or `0x8` are no longer accepted. + ## 2.0.0: Joomla 5/6 migration Version 2.0.0 migrates the component to PSR-4 namespaced classes diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..6350ec2 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,37 @@ +# Security Policy + +## Supported versions + +Security fixes are made for the latest release of the BFStop plugin +(`plg_system_bfstop`) and the BFStop component (`com_bfstop`). The two are +released together and need to be on the same major and minor version, so +please update both. + +## Reporting a vulnerability + +Please report suspected vulnerabilities by email to **security@bfstop.de** +rather than in a public issue or pull request, so that a fix can be +available before details are public. + +It helps to include: + +- the affected version(s) of plugin and component, and the Joomla! and PHP + versions, +- the relevant settings (blocking mode, proxy settings, ...), +- steps to reproduce, or a proof of concept, and what an attacker gains. + +## What is and isn't in scope + +BFStop sits in front of the login of a Joomla! site and decides which +requests to reject, so these are the kind of reports which are most useful: + +- ways around a block or the failed-login counting (for example by faking + the client address, or by reusing an unblock link), +- ways to get someone else blocked, or to make the plugin tie up the server, +- ways to read or change data (settings, blocks, logged usernames) without + the necessary permissions, and cross-site scripting in the backend views. + +Settings are changed by users with the `core.admin` permission for +`com_bfstop`. They can, among other things, choose the directory in which the +plugin writes its `.htaccess` file, so grant that permission only to people +you trust with the web server's configuration for your site. diff --git a/admin/forms/settings.xml b/admin/forms/settings.xml index 6cd6627..6d9cfea 100644 --- a/admin/forms/settings.xml +++ b/admin/forms/settings.xml @@ -1,5 +1,5 @@ -
+
- + + PLG_SYSTEM_BFSTOP_INFO + + + + + + + + + + @@ -180,6 +195,7 @@ label="PLG_SYSTEM_BFSTOP_DELETE_OLD_ENTRIES_LABEL" description="PLG_SYSTEM_BFSTOP_DELETE_OLD_ENTRIES_DESC" />
@@ -229,6 +245,7 @@
diff --git a/admin/language/de-DE/de-DE.com_bfstop.ini b/admin/language/de-DE/de-DE.com_bfstop.ini index fbcddd3..130e64e 100644 --- a/admin/language/de-DE/de-DE.com_bfstop.ini +++ b/admin/language/de-DE/de-DE.com_bfstop.ini @@ -113,3 +113,13 @@ COM_BFSTOP_USERNAMESTATS_PURGE_INVALID_AGE="Bitte eine Anzahl an Tagen von minde COM_BFSTOP_USERNAMESTATS_PURGE_FAILED="Löschen der Statistik veralteter Benutzernamen fehlgeschlagen: %s" COM_BFSTOP_FAILEDLOGIN_FILTERED_BY_USERNAME="Es werden nur fehlgeschlagene Logins für den Benutzernamen '%s' angezeigt." COM_BFSTOP_FAILEDLOGIN_FILTER_CLEAR="Alle anzeigen" +COM_BFSTOP_SETTINGS_UNKNOWN_USERNAME_MODE_LABEL="Benutzernamen ohne passendes Konto" +COM_BFSTOP_SETTINGS_UNKNOWN_USERNAME_MODE_DESC="Benutzer tippen oft ihr Passwort in das Benutzernamenfeld. Mit 'Hash' wird der Benutzername eines fehlgeschlagenen Logins nur dann so wie eingegeben gespeichert (angezeigt, protokolliert und per E-Mail gesendet), wenn er zu einem bestehenden Konto gehört oder in der Liste der häufigen Benutzernamen steht; alles andere wird durch einen unlesbaren Wert ersetzt, der für dieselbe Eingabe immer gleich ist, sodass Versuche damit zusammen gezählt werden. 'Wie eingegeben speichern' lässt jeden eingegebenen Benutzernamen lesbar, speichert dann aber auch Passwörter, die ins Benutzernamenfeld getippt wurden." +COM_BFSTOP_SETTINGS_UNKNOWN_USERNAME_MODE_HASH="Hash (empfohlen)" +COM_BFSTOP_SETTINGS_UNKNOWN_USERNAME_MODE_PLAIN="Wie eingegeben speichern" +COM_BFSTOP_SETTINGS_HTACCESS_PATH_INVALID="Der .htaccess-Pfad muss ein existierendes Verzeichnis im Dateisystem des Servers sein." +COM_BFSTOP_SETTINGS_GEOIP_DB_PATH_INVALID="Der Pfad der GeoIP-Datenbank muss der Name einer MaxMind-Datenbankdatei (Endung .mmdb) im Dateisystem des Servers sein." +COM_BFSTOP_SETTINGS_IPV6_PREFIX_LABEL="Genauigkeit bei IPv6" +COM_BFSTOP_SETTINGS_IPV6_PREFIX_DESC="Ein Client mit IPv6-Adresse kontrolliert meist ein ganzes Netz und kann nach Belieben zu einer anderen Adresse darin wechseln. Fehlgeschlagene Logins und Sperren von IPv6-Clients werden deshalb pro Netz dieser Größe statt pro Adresse erfasst. /64 ist die Größe eines üblichen Heim- oder Mobilfunkanschlusses. Wähle eine einzelne Adresse nur, wenn du weißt, dass Clients deiner Seite sich ihr /64 mit anderen teilen, die nicht mitgesperrt werden sollen." +COM_BFSTOP_SETTINGS_IPV6_PREFIX_64="/64 (empfohlen)" +COM_BFSTOP_SETTINGS_IPV6_PREFIX_128="Einzelne Adresse (/128)" diff --git a/admin/language/en-GB/en-GB.com_bfstop.ini b/admin/language/en-GB/en-GB.com_bfstop.ini index f437d4e..b147ad9 100644 --- a/admin/language/en-GB/en-GB.com_bfstop.ini +++ b/admin/language/en-GB/en-GB.com_bfstop.ini @@ -165,3 +165,13 @@ COM_BFSTOP_USERNAMESTATS_PURGE_INVALID_AGE="Please enter a number of days of at COM_BFSTOP_USERNAMESTATS_PURGE_FAILED="Deleting the statistics of stale usernames failed: %s" COM_BFSTOP_FAILEDLOGIN_FILTERED_BY_USERNAME="Only showing failed logins for username '%s'." COM_BFSTOP_FAILEDLOGIN_FILTER_CLEAR="Show all" +COM_BFSTOP_SETTINGS_UNKNOWN_USERNAME_MODE_LABEL="Usernames Not Matching an Account" +COM_BFSTOP_SETTINGS_UNKNOWN_USERNAME_MODE_DESC="Users often type their password into the username field. With 'Hash', the username of a failed login is only stored (and shown, logged and mailed) as typed if it belongs to an existing account or is on the list of common usernames; anything else is replaced by an unreadable value, which is still the same for the same input, so attempts against it are counted together. 'Store as typed' keeps every entered username readable, but then also stores passwords typed into the username field." +COM_BFSTOP_SETTINGS_UNKNOWN_USERNAME_MODE_HASH="Hash (recommended)" +COM_BFSTOP_SETTINGS_UNKNOWN_USERNAME_MODE_PLAIN="Store as typed" +COM_BFSTOP_SETTINGS_HTACCESS_PATH_INVALID="The .htaccess path has to be an existing directory of the server's file system." +COM_BFSTOP_SETTINGS_GEOIP_DB_PATH_INVALID="The GeoIP database path has to be the name of a MaxMind database file (ending in .mmdb) in the server's file system." +COM_BFSTOP_SETTINGS_IPV6_PREFIX_LABEL="IPv6 Tracking Granularity" +COM_BFSTOP_SETTINGS_IPV6_PREFIX_DESC="A client with an IPv6 address usually controls a whole network and can switch to another address in it at will. Failed logins and blocks of IPv6 clients are therefore tracked per network of this size instead of per address. /64 is the size of a typical home or mobile connection. Choose a single address only if you know that clients on your site share their /64 with others whom you don't want to be blocked together." +COM_BFSTOP_SETTINGS_IPV6_PREFIX_64="/64 (recommended)" +COM_BFSTOP_SETTINGS_IPV6_PREFIX_128="Single address (/128)" diff --git a/admin/rules/geodbpath.php b/admin/rules/geodbpath.php new file mode 100644 index 0000000..f8d015d --- /dev/null +++ b/admin/rules/geodbpath.php @@ -0,0 +1,33 @@ +get('params.blockMode') === 'htaccess') + { + $valid = is_dir($value); + } + if (!$valid) + { + return new UnexpectedValueException(Text::_('COM_BFSTOP_SETTINGS_HTACCESS_PATH_INVALID')); + } + return true; + } +} diff --git a/admin/src/Controller/SettingsController.php b/admin/src/Controller/SettingsController.php index f4b12e7..2cb52d2 100644 --- a/admin/src/Controller/SettingsController.php +++ b/admin/src/Controller/SettingsController.php @@ -63,6 +63,14 @@ protected function getRedirectToItemAppend($recordId = null, $urlVar = 'id') public function testNotify() { $this->checkToken(); + // part of the settings, so reserved for who may change them: the + // mail goes to the notification addresses configured there + if (!$this->allowSave()) + { + $this->setRedirect(Route::_('index.php?option=com_bfstop&view=settings', false), + Text::_('COM_BFSTOP_NOT_AUTHORISED'), 'error'); + return; + } $emailAddress = ParamHelper::get('emailaddress', 'params', ''); $userID = (int) ParamHelper::get('userID', 'params', -1); $userGroup = (int) ParamHelper::get('userGroup', 'params', -1); diff --git a/admin/src/Helper/IpValidateHelper.php b/admin/src/Helper/IpValidateHelper.php index 4377fd7..9f26fed 100644 --- a/admin/src/Helper/IpValidateHelper.php +++ b/admin/src/Helper/IpValidateHelper.php @@ -26,7 +26,10 @@ public static function validIPRange($address) $subnet = $parts[1]; $ip = $parts[0]; $maxBits = IpRangeHelper::isIPv6($ip) ? 128 : 32; - if (!is_numeric($subnet) || $subnet < 0 || $subnet > $maxBits) + // digits only: is_numeric() would also accept "1e1", "0x8" or " 8", + // which don't mean what they look like in the places the range ends + // up (e.g. the web server's configuration) + if (preg_match('/^[0-9]{1,3}\z/', $subnet) !== 1 || (int) $subnet > $maxBits) { Factory::getApplication()->enqueueMessage(Text::sprintf('COM_BFSTOP_IP_INVALID_SUBNET', $subnet), 'warning'); return false; diff --git a/admin/src/Helper/ParamHelper.php b/admin/src/Helper/ParamHelper.php index 355c6ad..49cbef4 100644 --- a/admin/src/Helper/ParamHelper.php +++ b/admin/src/Helper/ParamHelper.php @@ -20,7 +20,7 @@ public static function get($name, $column, $defaultValue) $sql = "SELECT $column FROM #__extensions WHERE name = 'plg_system_bfstop'"; $db->setQuery($sql); $rawSettings = $db->loadResult(); - $settings = json_decode($rawSettings, true); - return array_key_exists($name, $settings) ? $settings[$name] : $defaultValue; + $settings = json_decode((string) $rawSettings, true); + return (is_array($settings) && array_key_exists($name, $settings)) ? $settings[$name] : $defaultValue; } } diff --git a/admin/src/Model/LogModel.php b/admin/src/Model/LogModel.php index be728af..f8f7b3d 100644 --- a/admin/src/Model/LogModel.php +++ b/admin/src/Model/LogModel.php @@ -50,7 +50,7 @@ private function getLogLines($start, $count, $ordering, $direction) if (count($logItems) < 3) { $application = Factory::getApplication(); - $application->enqueueMessage("Invalid entry in line number ".$lineNumber.": '".$line."' (expected 3 items - date, priority and message but that line only has ".count($logItems).")", 'error'); + $application->enqueueMessage("Invalid entry in line number ".$lineNumber.": '".htmlspecialchars($line, ENT_QUOTES, 'UTF-8')."' (expected 3 items - date, priority and message but that line only has ".count($logItems).")", 'error'); } else { diff --git a/admin/src/View/Ipinfo/HtmlView.php b/admin/src/View/Ipinfo/HtmlView.php index 40cde03..37f2b97 100644 --- a/admin/src/View/Ipinfo/HtmlView.php +++ b/admin/src/View/Ipinfo/HtmlView.php @@ -38,7 +38,9 @@ public function display($tpl = null) } else { - $this->ipInfo = "
".Text::sprintf("COM_BFSTOP_IPINFO_DETAILS",
+			// the address comes from the request and the rest from the GeoIP
+			// database file, so none of it is trusted to be free of markup
+			$this->ipInfo = "
".$this->escape(Text::sprintf("COM_BFSTOP_IPINFO_DETAILS",
 				$details->ip,
 				$details->countryCode,
 				$details->countryName,
@@ -46,7 +48,7 @@ public function display($tpl = null)
 				$details->city,
 				$details->postalCode,
 				$details->latitude,
-				$details->longitude)."
"; + $details->longitude))."
"; } $this->addToolbar(); parent::display($tpl); @@ -54,7 +56,8 @@ public function display($tpl = null) protected function addToolbar() { - ToolbarHelper::title(Text::sprintf('COM_BFSTOP_HEADING_IPINFO', $this->ipAddress), 'bfstop'); + // the toolbar title is output as HTML + ToolbarHelper::title(Text::sprintf('COM_BFSTOP_HEADING_IPINFO', $this->escape((string) $this->ipAddress)), 'bfstop'); ToolbarHelper::divider(); ToolbarHelper::back(); } diff --git a/admin/tmpl/log/default_body.php b/admin/tmpl/log/default_body.php index 663c9f6..30a72c8 100644 --- a/admin/tmpl/log/default_body.php +++ b/admin/tmpl/log/default_body.php @@ -8,8 +8,8 @@ defined('_JEXEC') or die; foreach ($this->items as $i => $logline): ?> - date; ?> - priority; ?> - message; ?> + escape($logline->date); ?> + escape($logline->priority); ?> + escape($logline->message); ?> Login-Seite ein. Du wirst allerdings erneut gesperrt werden, solltest du zu oft einen falschen Benutzernamen/Passwort eingeben. Alternativ kannst du die Funktion zum Passwort zurücksetzen benutzen." COM_BFSTOP_UNBLOCKTOKEN_INVALID="Ungültiger Aufruf!" +COM_BFSTOP_UNBLOCKTOKEN_CONFIRM="Bitte bestätige, dass deine IP-Adresse entsperrt werden soll. Dieser Link funktioniert nur von der IP-Adresse aus, die gesperrt wurde." +COM_BFSTOP_UNBLOCKTOKEN_BUTTON="Meine IP-Adresse entsperren" +COM_BFSTOP_UNBLOCKTOKEN_WRONG_IP="Dieser Link kann nur die IP-Adresse entsperren, für die er ausgestellt wurde. Bitte öffne ihn erneut auf dem Gerät und im Netzwerk, von dem aus du gesperrt wurdest." diff --git a/site/language/en-GB/en-GB.com_bfstop.ini b/site/language/en-GB/en-GB.com_bfstop.ini index d99d27f..e7a659a 100644 --- a/site/language/en-GB/en-GB.com_bfstop.ini +++ b/site/language/en-GB/en-GB.com_bfstop.ini @@ -2,3 +2,6 @@ COM_BFSTOP_UNBLOCKTOKEN_HEADING="Unblock by Token" COM_BFSTOP_UNBLOCKTOKEN_FAILED="Could not unblock. Either the token was invalid, it has already expired or an error has occurred. Please contact the system administrator." COM_BFSTOP_UNBLOCKTOKEN_SUCCESS="Successfully unblocked your IP address. If you think you now know your account data, you can try again to log in on the Login Page. But you will be blocked again if you enter your username/password wrong again multiple times. You can also use the Reset password functionality to have password reset instructions sent to your email address." COM_BFSTOP_UNBLOCKTOKEN_INVALID="Invalid call!" +COM_BFSTOP_UNBLOCKTOKEN_CONFIRM="Please confirm that you want to unblock your IP address. This link only works from the IP address which was blocked." +COM_BFSTOP_UNBLOCKTOKEN_BUTTON="Unblock my IP address" +COM_BFSTOP_UNBLOCKTOKEN_WRONG_IP="This link can only unblock the IP address it was issued for. Please open it again on the device and network from which you were blocked." diff --git a/site/src/Model/TokenunblockModel.php b/site/src/Model/TokenunblockModel.php index 91d0792..676b99d 100644 --- a/site/src/Model/TokenunblockModel.php +++ b/site/src/Model/TokenunblockModel.php @@ -11,14 +11,120 @@ defined('_JEXEC') or die; use Codeling\Component\Bfstop\Administrator\Helper\UnblockHelper; +use Codeling\Plugin\System\Bfstop\Helper\IpHelper; use Joomla\CMS\Log\Log; use Joomla\CMS\MVC\Model\BaseDatabaseModel; class TokenunblockModel extends BaseDatabaseModel { + // must stay in sync with the plugin's DatabaseHelper::$UNBLOCK_TOKEN_VALID_DAYS const TokenValidDays = 3; + // outcomes of process() + const ResultInvalid = 'invalid'; + const ResultConfirm = 'confirm'; + const ResultWrongIp = 'wrongip'; + const ResultUnblocked = 'unblocked'; + // no such token (it never existed, expired or was used already) + const ResultNotFound = 'notfound'; + // the token exists, but unblocking failed (database error) + const ResultFailed = 'failed'; + + /** + * The HTTP status to answer an outcome of process() with. A GET request + * for a link only asks for confirmation whatever the token is (which keeps + * mail scanners and link previews, which open every link, from learning + * anything and from marking links as dead), so it is a 200, as is success. + */ + public static function httpStatus($result) + { + switch ($result) + { + case self::ResultInvalid: + return 400; + case self::ResultWrongIp: + return 403; + case self::ResultNotFound: + return 404; + case self::ResultFailed: + return 500; + default: + return 200; + } + } + + /** + * The IP address the block belongs to which the token was issued for, or + * null if there is no such token. + */ + private function getBlockedIp($token) + { + try + { + $this->_db->setQuery('SELECT b.ipaddress FROM #__bfstop_unblock_token t '. + 'INNER JOIN #__bfstop_bannedip b ON b.id = t.block_id '. + 'WHERE t.token='.$this->_db->quote($token)); + $ip = $this->_db->loadResult(); + return ($ip === null || $ip === false) ? null : (string) $ip; + } + catch (\RuntimeException $e) + { + return null; + } + } + + // $blocked is an address, or a network in CIDR notation for clients which + // are tracked (and blocked) by their IPv6 network + private static function sameAddress($blocked, $address) + { + if (strpos($blocked, '/') !== false) + { + return IpHelper::isInSubnet($address, $blocked); + } + $binA = @inet_pton((string) $blocked); + return $binA !== false && $binA === @inet_pton((string) $address); + } + + /** + * Handles a request to the unblock page. + * + * Opening the link from the email (a GET request) must not unblock + * anything by itself: mail security scanners and link previews open every + * link in a message, which would let anyone who triggers an unblock email + * (by failing logins for a user whose mailbox is scanned) get unblocked + * again automatically. So a GET only asks for confirmation; the actual + * unblock needs a POST. In addition, the token only unblocks from the IP + * address it was issued for - the one that got blocked - so a scanner or + * anyone else to whom the link was forwarded can't use it from elsewhere. + * + * @return string one of the Result* constants + */ + public function process($token, $isPost, $requestIp, $logger) + { + if ($token === '') + { + return self::ResultInvalid; + } + if (!$isPost) + { + return self::ResultConfirm; + } + $blockedIp = $this->getBlockedIp($token); + if ($blockedIp !== null && !self::sameAddress($blockedIp, $requestIp)) + { + $logger->log("com_bfstop-tokenunblock: Token used from an IP address ". + "other than the blocked one.", Log::WARNING); + return self::ResultWrongIp; + } + return $this->unblockWithResult($token, $logger); + } + public function unblock($token, $logger) + { + return $this->unblockWithResult($token, $logger) === self::ResultUnblocked; + } + + private function unblockWithResult($token, $logger) { // prune old tokens: try @@ -35,7 +141,7 @@ public function unblock($token, $logger) if ($unblockTokenEntry == null) { $logger->log("com_bfstop-tokenunblock: Token not found.", Log::ERROR); - return false; + return self::ResultNotFound; } UnblockHelper::unblockDB($this->_db, array($unblockTokenEntry->block_id), 1, $logger); $sql = 'DELETE FROM #__bfstop_unblock_token WHERE token='. @@ -56,6 +162,6 @@ public function unblock($token, $logger) { $logger->log("com_bfstop-tokenunblock: Successfully unblocked with token.", Log::INFO); } - return $success; + return $success ? self::ResultUnblocked : self::ResultFailed; } } diff --git a/site/src/View/Tokenunblock/HtmlView.php b/site/src/View/Tokenunblock/HtmlView.php index 0110613..f6777fc 100644 --- a/site/src/View/Tokenunblock/HtmlView.php +++ b/site/src/View/Tokenunblock/HtmlView.php @@ -11,6 +11,9 @@ defined('_JEXEC') or die; use Codeling\Component\Bfstop\Administrator\Helper\LogHelper; +use Codeling\Component\Bfstop\Administrator\Helper\ParamHelper; +use Codeling\Component\Bfstop\Site\Model\TokenunblockModel; +use Codeling\Plugin\System\Bfstop\Helper\IpHelper; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView; @@ -33,24 +36,54 @@ function display($tpl = null) // clear the messages still enqueued from the invalid login attempt: $session = Factory::getSession(); $session->set('application.queue', null); - // try to unblock: - $input = Factory::getApplication()->input; - $token = $input->getString('token', ''); + $app = Factory::getApplication(); + $input = $app->input; + $this->token = $input->getString('token', ''); + $this->showConfirmation = false; $logger = LogHelper::getLogger(); - if (strcmp($token, '') != 0) + $this->model = $this->getModel(); + $result = $this->model->process($this->token, + $input->getMethod() === 'POST', + IpHelper::getAddress($logger), + $logger); + switch ($result) { - $this->model = $this->getModel(); - $unblockSuccess = $this->model->unblock($token, $logger); - $this->message = ($unblockSuccess) - ? Text::sprintf('COM_BFSTOP_UNBLOCKTOKEN_SUCCESS', + case TokenunblockModel::ResultConfirm: + $this->showConfirmation = true; + $this->message = Text::_('COM_BFSTOP_UNBLOCKTOKEN_CONFIRM'); + break; + case TokenunblockModel::ResultUnblocked: + $this->message = Text::sprintf('COM_BFSTOP_UNBLOCKTOKEN_SUCCESS', $this->getLoginLink(), - $this->getPasswordResetLink()) - : Text::_('COM_BFSTOP_UNBLOCKTOKEN_FAILED'); + $this->getPasswordResetLink()); + break; + case TokenunblockModel::ResultWrongIp: + $this->message = Text::_('COM_BFSTOP_UNBLOCKTOKEN_WRONG_IP'); + break; + case TokenunblockModel::ResultNotFound: + case TokenunblockModel::ResultFailed: + $this->message = Text::_('COM_BFSTOP_UNBLOCKTOKEN_FAILED'); + break; + default: + $this->message = Text::_('COM_BFSTOP_UNBLOCKTOKEN_INVALID'); } - else + // The status is only set if the plugin's "Use HTTP Error" setting is + // on: a web server or CDN replacing error pages with its own would + // otherwise hide the message from the user. + if ((bool) ParamHelper::get('useHttpError', 'params', true)) { - $this->message = Text::_('COM_BFSTOP_UNBLOCKTOKEN_INVALID'); + $status = TokenunblockModel::httpStatus($result); + if ($status !== 200) + { + $app->setHeader('status', $status, true); + } } + // The link carries a secret: neither the page nor the address may be + // kept by caches, passed on to other sites in the Referer header of + // requests for resources the template loads from them, or indexed. + $app->allowCache(false); + $app->setHeader('Referrer-Policy', 'no-referrer', true); + $app->setHeader('X-Robots-Tag', 'noindex, nofollow', true); parent::display($tpl); } } diff --git a/site/tmpl/tokenunblock/default.php b/site/tmpl/tokenunblock/default.php index 0eb4fe4..e9189fe 100644 --- a/site/tmpl/tokenunblock/default.php +++ b/site/tmpl/tokenunblock/default.php @@ -12,3 +12,10 @@ ?>

message; ?>
+showConfirmation): ?> + + + + + +