diff --git a/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidator.php b/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidator.php
index 7fa6e6bbd7..c3dcec19ea 100644
--- a/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidator.php
+++ b/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidator.php
@@ -48,7 +48,7 @@ class UrlValidator
{
$siteUrl = rtrim($this->config->get('siteUrl') ?? '', '/');
- if (str_starts_with($url, $siteUrl)) {
+ if (UrlValidatorUtil::validate($url, $siteUrl)) {
return;
}
@@ -60,7 +60,7 @@ class UrlValidator
foreach ($portals as $portal) {
$siteUrl = rtrim($portal->getUrl() ?? '', '/');
- if (str_starts_with($url, $siteUrl)) {
+ if (UrlValidatorUtil::validate($url, $siteUrl)) {
return;
}
}
diff --git a/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidatorUtil.php b/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidatorUtil.php
new file mode 100644
index 0000000000..3428ca2cbf
--- /dev/null
+++ b/application/Espo/Tools/UserSecurity/Password/Recovery/UrlValidatorUtil.php
@@ -0,0 +1,59 @@
+.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU Affero General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+
+namespace Espo\Tools\UserSecurity\Password\Recovery;
+
+use const FILTER_VALIDATE_URL;
+use const PHP_URL_HOST;
+
+/**
+ * @internal
+ */
+class UrlValidatorUtil
+{
+ public static function validate(string $url, string $siteUrl): bool
+ {
+ $host = parse_url($url, PHP_URL_HOST);
+ $siteHost = parse_url($siteUrl, PHP_URL_HOST);
+
+ if ($host !== $siteHost) {
+ return false;
+ }
+
+ if (!filter_var($url, FILTER_VALIDATE_URL)) {
+ return false;
+ }
+
+ if (!str_starts_with($url, $siteUrl)) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/client/src/views/user/password-change-request.js b/client/src/views/user/password-change-request.js
index 8b9e14c29c..50e69f6011 100644
--- a/client/src/views/user/password-change-request.js
+++ b/client/src/views/user/password-change-request.js
@@ -139,12 +139,17 @@ export default class extends View {
const url = data.url || this.baseUrl;
- const msg = this.translate('passwordChangedByRequest', 'messages', 'User') +
- ' ' + this.translate('Login', 'labels', 'User') + '.';
+ const a = document.createElement('a');
+ a.href = url;
+ a.innerText = this.translate('Login', 'labels', 'User');
+
+ const message = this.translate('passwordChangedByRequest', 'messages', 'User');
+
+ const html = this.getHelper().escapeString(message) + ' ' + a.outerHTML;
this.$el.find('.msg-box')
.removeClass('hidden')
- .html('' + msg + '');
+ .html('' + html + '');
})
.catch(() => {
return $submit.removeClass('disabled');
diff --git a/package-lock.json b/package-lock.json
index de61853286..e4177ee4a0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "espocrm",
- "version": "9.2.5",
+ "version": "9.2.6",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "espocrm",
- "version": "9.2.5",
+ "version": "9.2.6",
"hasInstallScript": true,
"license": "AGPL-3.0-or-later",
"dependencies": {
diff --git a/package.json b/package.json
index a35a6ce338..94fb93b7a9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "espocrm",
- "version": "9.2.5",
+ "version": "9.2.6",
"description": "Open-source CRM.",
"repository": {
"type": "git",
diff --git a/tests/unit/Espo/Tools/UserSecurity/Password/UrlValidatorUtilTest.php b/tests/unit/Espo/Tools/UserSecurity/Password/UrlValidatorUtilTest.php
new file mode 100644
index 0000000000..8ef985c7e3
--- /dev/null
+++ b/tests/unit/Espo/Tools/UserSecurity/Password/UrlValidatorUtilTest.php
@@ -0,0 +1,59 @@
+.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU Affero General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+
+namespace tests\unit\Espo\Tools\UserSecurity\Password;
+
+use Espo\Tools\UserSecurity\Password\Recovery\UrlValidatorUtil;
+use PHPUnit\Framework\TestCase;
+
+class UrlValidatorUtilTest extends TestCase
+{
+ public function testValidate(): void
+ {
+ $this->assertTrue(
+ UrlValidatorUtil::validate('https://test.com', 'https://test.com')
+ );
+
+ $this->assertTrue(
+ UrlValidatorUtil::validate('https://test.com/test', 'https://test.com')
+ );
+
+ $this->assertTrue(
+ UrlValidatorUtil::validate('https://test.com/test', 'https://test.com/test')
+ );
+
+ $this->assertFalse(
+ UrlValidatorUtil::validate('https://test.com.test', 'https://test.com')
+ );
+
+ $this->assertFalse(
+ UrlValidatorUtil::validate('https://test.com.test