diff --git a/com.woltlab.wcf/templates/email_lostPassword.tpl b/com.woltlab.wcf/templates/email_lostPassword.tpl
index e8aba2978e..d21bebe78e 100644
--- a/com.woltlab.wcf/templates/email_lostPassword.tpl
+++ b/com.woltlab.wcf/templates/email_lostPassword.tpl
@@ -7,7 +7,7 @@
{lang}wcf.user.lostPassword.mail.html.intro{/lang}
{capture assign=button}
-
+
{lang}wcf.user.lostPassword.mail.html.reset{/lang}
{/capture}
diff --git a/com.woltlab.wcf/templates/email_sendNewPassword.tpl b/com.woltlab.wcf/templates/email_sendNewPassword.tpl
index 1a96db9c68..6f57c7be21 100644
--- a/com.woltlab.wcf/templates/email_sendNewPassword.tpl
+++ b/com.woltlab.wcf/templates/email_sendNewPassword.tpl
@@ -7,7 +7,7 @@
{lang}wcf.acp.user.sendNewPassword.mail.html.intro{/lang}
{capture assign=button}
-
+
{lang}wcf.acp.user.sendNewPassword.mail.html.reset{/lang}
{/capture}
diff --git a/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php b/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php
index dc17365b4d..652ee94424 100644
--- a/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php
+++ b/wcfsetup/install/files/acp/database/update_com.woltlab.wcf_6.3_step1.php
@@ -8,6 +8,7 @@
* @license GNU Lesser General Public License
*/
+use wcf\system\database\table\column\CharDatabaseTableColumn;
use wcf\system\database\table\column\DefaultFalseBooleanDatabaseTableColumn;
use wcf\system\database\table\column\JsonDatabaseTableColumn;
use wcf\system\database\table\column\MediumintDatabaseTableColumn;
@@ -62,4 +63,9 @@
NotNullVarchar255DatabaseTableColumn::create('emoji')
->defaultValue(''),
]),
+ PartialDatabaseTable::create('wcf1_user')
+ ->columns([
+ CharDatabaseTableColumn::create('lostPasswordKey')
+ ->length(64),
+ ]),
];
diff --git a/wcfsetup/install/files/lib/data/user/User.class.php b/wcfsetup/install/files/lib/data/user/User.class.php
index 5c921ceb46..c54510fd44 100644
--- a/wcfsetup/install/files/lib/data/user/User.class.php
+++ b/wcfsetup/install/files/lib/data/user/User.class.php
@@ -40,7 +40,7 @@
* @property-read int $activationCode flag which determines, whether the user is activated (for legacy reasons an random integer, if the user is *not* activated)
* @property-read ?string $emailConfirmed code sent to the user's email address used for account activation or null if the email is confirmed
* @property-read int $lastLostPasswordRequestTime timestamp at which the user has reported that they lost their password or 0 if password has not been reported as lost
- * @property-read ?string $lostPasswordKey code used for authenticating setting new password after password loss or empty if password has not been reported as lost
+ * @property-read ?string $lostPasswordKey SHA-256 hash of the code used for authenticating setting new password after password loss or empty if password has not been reported as lost
* @property-read int $lastUsernameChange timestamp at which the user changed their name the last time or 0 if username has not been changed
* @property-read string $newEmail new email address of the user that has to be manually confirmed or empty if no new email address has been set
* @property-read string $oldUsername previous name of the user or empty if they have had no previous name
diff --git a/wcfsetup/install/files/lib/form/LostPasswordForm.class.php b/wcfsetup/install/files/lib/form/LostPasswordForm.class.php
index f1532c1479..440d23671e 100644
--- a/wcfsetup/install/files/lib/form/LostPasswordForm.class.php
+++ b/wcfsetup/install/files/lib/form/LostPasswordForm.class.php
@@ -158,10 +158,10 @@ public function save()
// generate a new lost password key
$lostPasswordKey = Hex::encode(\random_bytes(20));
- // save key and request time in database
+ // save hashed key and request time in database
$this->objectAction = new UserAction([$this->user], 'update', [
'data' => \array_merge($this->additionalFields, [
- 'lostPasswordKey' => $lostPasswordKey,
+ 'lostPasswordKey' => \hash('sha256', $lostPasswordKey),
'lastLostPasswordRequestTime' => \TIME_NOW,
]),
]);
@@ -174,8 +174,8 @@ public function save()
$email->addRecipient(new UserMailbox($this->user));
$email->setSubject($this->user->getLanguage()->getDynamicVariable('wcf.user.lostPassword.mail.subject'));
$email->setBody(new MimePartFacade([
- new RecipientAwareTextMimePart('text/html', 'email_lostPassword'),
- new RecipientAwareTextMimePart('text/plain', 'email_lostPassword'),
+ new RecipientAwareTextMimePart('text/html', 'email_lostPassword', 'wcf', ['lostPasswordKey' => $lostPasswordKey]),
+ new RecipientAwareTextMimePart('text/plain', 'email_lostPassword', 'wcf', ['lostPasswordKey' => $lostPasswordKey]),
]));
$email->send();
diff --git a/wcfsetup/install/files/lib/form/NewPasswordForm.class.php b/wcfsetup/install/files/lib/form/NewPasswordForm.class.php
index 69c5b4866a..30fdd546d6 100644
--- a/wcfsetup/install/files/lib/form/NewPasswordForm.class.php
+++ b/wcfsetup/install/files/lib/form/NewPasswordForm.class.php
@@ -56,7 +56,7 @@ public function readParameters()
if (!$this->user->lostPasswordKey) {
$this->throwInvalidLinkException();
}
- if (!\hash_equals($this->user->lostPasswordKey, $this->lostPasswordKey)) {
+ if (!\hash_equals($this->user->lostPasswordKey, \hash('sha256', $this->lostPasswordKey))) {
$this->throwInvalidLinkException();
}
// expire lost password requests after a day
@@ -66,7 +66,7 @@ public function readParameters()
WCF::getSession()->register('lostPasswordRequest', [
'userID' => $this->user->userID,
- 'key' => $this->user->lostPasswordKey,
+ 'key' => $this->lostPasswordKey,
]);
} else {
if (!\is_array(WCF::getSession()->getVar('lostPasswordRequest'))) {
@@ -78,7 +78,7 @@ public function readParameters()
if (!$this->user->userID) {
throw new IllegalLinkException();
}
- if (!\hash_equals($this->user->lostPasswordKey, WCF::getSession()->getVar('lostPasswordRequest')['key'])) {
+ if (!\hash_equals($this->user->lostPasswordKey, \hash('sha256', WCF::getSession()->getVar('lostPasswordRequest')['key']))) {
$this->throwInvalidLinkException();
}
}
diff --git a/wcfsetup/install/files/lib/system/worker/SendNewPasswordWorker.class.php b/wcfsetup/install/files/lib/system/worker/SendNewPasswordWorker.class.php
index 0ce7518ec3..bde3504904 100644
--- a/wcfsetup/install/files/lib/system/worker/SendNewPasswordWorker.class.php
+++ b/wcfsetup/install/files/lib/system/worker/SendNewPasswordWorker.class.php
@@ -32,6 +32,11 @@ class SendNewPasswordWorker extends AbstractWorker
*/
protected $limit = 20;
+ /**
+ * @var array
+ */
+ private array $lostPasswordKeys = [];
+
#[\Override]
public function countObjects()
{
@@ -104,11 +109,13 @@ protected function resetPassword(UserEditor $userEditor)
$userAction = new UserAction([$userEditor], 'update', [
'data' => [
'password' => null,
- 'lostPasswordKey' => $lostPasswordKey,
+ 'lostPasswordKey' => \hash('sha256', $lostPasswordKey),
'lastLostPasswordRequestTime' => $lastLostPasswordRequestTime,
],
]);
$userAction->executeAction();
+
+ $this->lostPasswordKeys[$userEditor->userID] = $lostPasswordKey;
}
/**
@@ -128,9 +135,10 @@ protected function sendLink(User $user)
));
$email->addRecipient(new UserMailbox($user));
$email->setSubject($user->getLanguage()->getDynamicVariable('wcf.acp.user.sendNewPassword.mail.subject'));
+ $lostPasswordKey = $this->lostPasswordKeys[$user->userID] ?? '';
$email->setBody(new MimePartFacade([
- new RecipientAwareTextMimePart('text/html', 'email_sendNewPassword'),
- new RecipientAwareTextMimePart('text/plain', 'email_sendNewPassword'),
+ new RecipientAwareTextMimePart('text/html', 'email_sendNewPassword', 'wcf', ['lostPasswordKey' => $lostPasswordKey]),
+ new RecipientAwareTextMimePart('text/plain', 'email_sendNewPassword', 'wcf', ['lostPasswordKey' => $lostPasswordKey]),
]));
$jobs = $email->getJobs();
foreach ($jobs as $job) {
diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml
index d016a8a20b..5d309aed30 100644
--- a/wcfsetup/install/lang/de.xml
+++ b/wcfsetup/install/lang/de.xml
@@ -3215,7 +3215,7 @@ erforderlich, dass {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} ein neues
Benutzerkonto {@$mailbox->getUser()->username} auf der Seite {@PAGE_TITLE|phrase} [URL:{link isEmail=true}{/link}]
weiterhin verwenden {if LANGUAGE_USE_INFORMAL_VARIANT}kannst{else}können{/if}:
- {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link} {* this line ends with a space *}
+ {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$lostPasswordKey}{/link} {* this line ends with a space *}
{if LANGUAGE_USE_INFORMAL_VARIANT}Solltest du{else}Sollten Sie{/if} diese Nachricht erst nach dem {@$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime} lesen, ist es
aus Sicherheitsgründen erforderlich, dass {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} die Kennwort vergessen-Funktion [URL:{link controller='LostPassword' isEmail=true}{/link}] {if LANGUAGE_USE_INFORMAL_VARIANT}nutzt{else}nutzen{/if}.]]>
@@ -4607,7 +4607,7 @@ Erlaubte Dateiendungen: gif, jpg, jpeg, png, webp]]>
{@$mailbox->getUser()->username} auf der Seite {@PAGE_TITLE|phrase} [URL:{link isEmail=true}{/link}] vergessen zu haben. {if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst dein{else}Sie können Ihr{/if} Kennwort
nach einem Klick auf den folgenden Link ändern:
- {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link} {* this line ends with a space *}
+ {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$lostPasswordKey}{/link} {* this line ends with a space *}
Wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du dein{else}Sie Ihr{/if} Kennwort nicht ändern {if LANGUAGE_USE_INFORMAL_VARIANT}möchtest{else}möchten{/if}, dann wird diese Anfrage
am {@$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime} automatisch ablaufen.]]>
diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml
index d5766cefdd..055e65c046 100644
--- a/wcfsetup/install/lang/en.xml
+++ b/wcfsetup/install/lang/en.xml
@@ -3142,7 +3142,7 @@ If you have already bought the licenses for the listed apps, th
An administrator has reset your password. You are now required to set a new password to be able to use your
user account {@$mailbox->getUser()->username} on the website {@PAGE_TITLE|phrase} [URL:{link isEmail=true}{/link}] again:
- {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link} {* this line ends with a space *}
+ {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$lostPasswordKey}{/link} {* this line ends with a space *}
If you read this message after {@$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime} you’ll have to use
the lost password form [URL:{link controller='LostPassword' isEmail=true}{/link}] for security reasons.]]>
@@ -4606,7 +4606,7 @@ You (or someone else) claimed to have lost the password for the user account {@$
the website {@PAGE_TITLE|phrase} [URL:{link isEmail=true}{/link}]. You can change your password after clicking
the following link:
- {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$mailbox->getUser()->lostPasswordKey}{/link} {* this line ends with a space *}
+ {link controller='NewPassword' object=$mailbox->getUser() isEmail=true}k={@$lostPasswordKey}{/link} {* this line ends with a space *}
If you don’t want to change your password you can simply wait. The request will expire at {@$mailbox->getUser()->lastLostPasswordRequestTime+86400|plainTime}.]]>
- getUser()->username},]]>
diff --git a/wcfsetup/setup/db/install_com.woltlab.wcf.php b/wcfsetup/setup/db/install_com.woltlab.wcf.php
index 6b40769b76..c288809281 100644
--- a/wcfsetup/setup/db/install_com.woltlab.wcf.php
+++ b/wcfsetup/setup/db/install_com.woltlab.wcf.php
@@ -3786,7 +3786,7 @@
NotNullInt10DatabaseTableColumn::create('lastLostPasswordRequestTime')
->defaultValue(0),
CharDatabaseTableColumn::create('lostPasswordKey')
- ->length(40),
+ ->length(64),
NotNullInt10DatabaseTableColumn::create('lastUsernameChange')
->defaultValue(0),
NotNullVarchar255DatabaseTableColumn::create('newEmail')