diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index f9c150f2c7..729829159c 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -35,7 +35,7 @@ jobs: tools: none - name: Clone addon repository - run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - name: Install Composer dependencies uses: "ramsey/composer-install@v2" @@ -68,7 +68,7 @@ jobs: tools: none - name: Clone addon repository - run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - name: Install Composer dependencies uses: "ramsey/composer-install@v2" @@ -100,7 +100,7 @@ jobs: tools: none - name: Clone addon repository - run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - name: Install Composer dependencies uses: "ramsey/composer-install@v2" @@ -132,7 +132,7 @@ jobs: tools: none - name: Clone addon repository - run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - name: Install Composer dependencies uses: "ramsey/composer-install@v2" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 31b92fefd6..d0aa623842 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,7 +34,7 @@ jobs: tools: none - name: Clone addon repository - run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - name: Install Composer dependencies uses: "ramsey/composer-install@v2" @@ -88,7 +88,7 @@ jobs: ini-values: apc.enabled=1, apc.enable_cli=1 - name: Clone addon repository - run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon # Install composer dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer diff --git a/.woodpecker/.phpunit.yml b/.woodpecker/.phpunit.yml index c13f00b589..997d50a52e 100644 --- a/.woodpecker/.phpunit.yml +++ b/.woodpecker/.phpunit.yml @@ -57,7 +57,7 @@ steps: composer_install: image: friendicaci/php${PHP_MAJOR_VERSION}:php${PHP_VERSION} commands: - - git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + - git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - export COMPOSER_HOME=.composer - ./bin/composer.phar validate - ./bin/composer.phar install --prefer-dist diff --git a/mod/lostpass.php b/mod/lostpass.php deleted file mode 100644 index 39c1e14a81..0000000000 --- a/mod/lostpass.php +++ /dev/null @@ -1,172 +0,0 @@ -redirect(); - } - - $condition = ['(`email` = ? OR `nickname` = ?) AND `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`', $loginame, $loginame]; - $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'language'], $condition); - if (!DBA::isResult($user)) { - DI::sysmsg()->addNotice(DI::l10n()->t('No valid account found.')); - DI::baseUrl()->redirect(); - } - - $pwdreset_token = Strings::getRandomHex(32); - - $fields = [ - 'pwdreset' => hash('sha256', $pwdreset_token), - 'pwdreset_time' => DateTimeFormat::utcNow() - ]; - $result = DBA::update('user', $fields, ['uid' => $user['uid']]); - if ($result) { - DI::sysmsg()->addInfo(DI::l10n()->t('Password reset request issued. Check your email.')); - } - - $sitename = DI::config()->get('config', 'sitename'); - $resetlink = DI::baseUrl() . '/lostpass/' . $pwdreset_token; - - $preamble = Strings::deindent(DI::l10n()->t(' - Dear %1$s, - A request was recently received at "%2$s" to reset your account - password. In order to confirm this request, please select the verification link - below or paste it into your web browser address bar. - - If you did NOT request this change, please DO NOT follow the link - provided and ignore and/or delete this email, the request will expire shortly. - - Your password will not be changed unless we can verify that you - issued this request.', $user['username'], $sitename)); - $body = Strings::deindent(DI::l10n()->t(' - Follow this link soon to verify your identity: - - %1$s - - You will then receive a follow-up message containing the new password. - You may change that password from your account settings page after logging in. - - The login details are as follows: - - Site Location: %2$s - Login Name: %3$s', $resetlink, DI::baseUrl(), $user['nickname'])); - - $email = DI::emailer() - ->newSystemMail() - ->withMessage(DI::l10n()->t('Password reset requested at %s', $sitename), $preamble, $body) - ->forUser($user) - ->withRecipient($user['email']) - ->build(); - - DI::emailer()->send($email); - DI::baseUrl()->redirect(); -} - -function lostpass_content() -{ - if (DI::args()->getArgc() > 1) { - $pwdreset_token = DI::args()->getArgv()[1]; - - $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'pwdreset_time', 'language'], ['pwdreset' => hash('sha256', $pwdreset_token)]); - if (!DBA::isResult($user)) { - DI::sysmsg()->addNotice(DI::l10n()->t("Request could not be verified. \x28You may have previously submitted it.\x29 Password reset failed.")); - - return lostpass_form(); - } - - // Password reset requests expire in 60 minutes - if ($user['pwdreset_time'] < DateTimeFormat::utc('now - 1 hour')) { - $fields = [ - 'pwdreset' => null, - 'pwdreset_time' => null - ]; - DBA::update('user', $fields, ['uid' => $user['uid']]); - - DI::sysmsg()->addNotice(DI::l10n()->t('Request has expired, please make a new one.')); - - return lostpass_form(); - } - - return lostpass_generate_password($user); - } else { - return lostpass_form(); - } -} - -function lostpass_form() -{ - $tpl = Renderer::getMarkupTemplate('lostpass.tpl'); - $o = Renderer::replaceMacros($tpl, [ - '$title' => DI::l10n()->t('Forgot your Password?'), - '$desc' => DI::l10n()->t('Enter your email address and submit to have your password reset. Then check your email for further instructions.'), - '$name' => DI::l10n()->t('Nickname or email'), - '$submit' => DI::l10n()->t('Reset my password') - ]); - - return $o; -} - -function lostpass_generate_password($user) -{ - $o = ''; - - $new_password = User::generateNewPassword(); - $result = User::updatePassword($user['uid'], $new_password); - if (DBA::isResult($result)) { - $tpl = Renderer::getMarkupTemplate('pwdreset.tpl'); - $o .= Renderer::replaceMacros($tpl, [ - '$lbl1' => DI::l10n()->t('Password Reset'), - '$lbl2' => DI::l10n()->t('Your password has been reset as requested.'), - '$lbl3' => DI::l10n()->t('Your new password is'), - '$lbl4' => DI::l10n()->t('Save or copy your new password - and then'), - '$lbl5' => '' . DI::l10n()->t('click here to login') . '.', - '$lbl6' => DI::l10n()->t('Your password may be changed from the Settings page after successful login.'), - '$newpass' => $new_password, - ]); - - DI::sysmsg()->addInfo(DI::l10n()->t("Your password has been reset.")); - - $sitename = DI::config()->get('config', 'sitename'); - $preamble = Strings::deindent(DI::l10n()->t(' - Dear %1$s, - Your password has been changed as requested. Please retain this - information for your records ' . "\x28" . 'or change your password immediately to - something that you will remember' . "\x29" . '. - ', $user['username'])); - $body = Strings::deindent(DI::l10n()->t(' - Your login details are as follows: - - Site Location: %1$s - Login Name: %2$s - Password: %3$s - - You may change that password from your account settings page after logging in. - ', DI::baseUrl(), $user['nickname'], $new_password)); - - $email = DI::emailer() - ->newSystemMail() - ->withMessage(DI::l10n()->t('Your password has been changed at %s', $sitename), $preamble, $body) - ->forUser($user) - ->withRecipient($user['email']) - ->build(); - DI::emailer()->send($email); - } - - return $o; -} diff --git a/src/Module/LostPass.php b/src/Module/LostPass.php new file mode 100644 index 0000000000..d14856c841 --- /dev/null +++ b/src/Module/LostPass.php @@ -0,0 +1,236 @@ +sysMessages = $sysMessages; + $this->config = $config; + $this->emailer = $emailer; + } + + /** + * Handle POST requests for password reset form submission + * + * @param array $request + * @return void + */ + protected function post(array $request = []) + { + $loginame = trim($request['login-name'] ?? ''); + if (!$loginame) { + $this->baseUrl->redirect(); + } + + $condition = ['(`email` = ? OR `nickname` = ?) AND `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`', $loginame, $loginame]; + $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'language'], $condition); + if (!DBA::isResult($user)) { + $this->sysMessages->addNotice($this->l10n->t('No valid account found.')); + $this->baseUrl->redirect(); + } + + $pwdreset_token = Strings::getRandomHex(32); + + $fields = [ + 'pwdreset' => hash('sha256', $pwdreset_token), + 'pwdreset_time' => DateTimeFormat::utcNow(), + ]; + $result = DBA::update('user', $fields, ['uid' => $user['uid']]); + if ($result) { + $this->sysMessages->addInfo($this->l10n->t('Password reset request issued. Check your email.')); + } + + $sitename = $this->config->get('config', 'sitename'); + $resetlink = $this->baseUrl . '/lostpass/' . $pwdreset_token; + + $preamble = Strings::deindent($this->l10n->t(' + Dear %1$s, + A request was recently received at "%2$s" to reset your account + password. In order to confirm this request, please select the verification link + below or paste it into your web browser address bar. + + If you did NOT request this change, please DO NOT follow the link + provided and ignore and/or delete this email, the request will expire shortly. + + Your password will not be changed unless we can verify that you + issued this request.', $user['username'], $sitename)); + $body = Strings::deindent($this->l10n->t(' + Follow this link soon to verify your identity: + + %1$s + + You will then receive a follow-up message containing the new password. + You may change that password from your account settings page after logging in. + + The login details are as follows: + + Site Location: %2$s + Login Name: %3$s', $resetlink, $this->baseUrl, $user['nickname'])); + + $email = $this->emailer->newSystemMail() + ->withMessage($this->l10n->t('Password reset requested at %s', $sitename), $preamble, $body) + ->forUser($user) + ->withRecipient($user['email']) + ->build(); + + $this->emailer->send($email); + $this->baseUrl->redirect(); + } + + /** + * Render page content + * + * @param array $request + * @return string Rendered page content + */ + protected function content(array $request = []): string + { + if (isset($this->parameters['token'])) { + $pwdreset_token = $this->parameters['token']; + + $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'pwdreset_time', 'language'], ['pwdreset' => hash('sha256', $pwdreset_token)]); + if (!DBA::isResult($user)) { + $this->sysMessages->addNotice($this->l10n->t("Request could not be verified. \x28You may have previously submitted it.\x29 Password reset failed.")); + + return $this->form(); + } + + // Password reset requests expire in 60 minutes + if ($user['pwdreset_time'] < DateTimeFormat::utc('now - 1 hour')) { + $fields = [ + 'pwdreset' => null, + 'pwdreset_time' => null, + ]; + DBA::update('user', $fields, ['uid' => $user['uid']]); + + $this->sysMessages->addNotice($this->l10n->t('Request has expired, please make a new one.')); + + return $this->form(); + } + + return $this->generatePassword($user); + } else { + return $this->form(); + } + } + + /** + * Render the password reset form + * + * @return string Rendered form HTML + */ + private function form(): string + { + $tpl = Renderer::getMarkupTemplate('lostpass.tpl'); + $o = Renderer::replaceMacros($tpl, [ + '$title' => $this->l10n->t('Forgot your Password?'), + '$desc' => $this->l10n->t('Enter your email address and submit to have your password reset. Then check your email for further instructions.'), + '$name' => $this->l10n->t('Nickname or email'), + '$submit' => $this->l10n->t('Reset my password'), + ]); + + return $o; + } + + /** + * Generate and send a new password to the user + * + * @param array $user User data array + * @return string Rendered success message HTML + */ + private function generatePassword(array $user): string + { + $o = ''; + + $new_password = User::generateNewPassword(); + $result = User::updatePassword($user['uid'], $new_password); + if (DBA::isResult($result)) { + $tpl = Renderer::getMarkupTemplate('pwdreset.tpl'); + $o .= Renderer::replaceMacros($tpl, [ + '$lbl1' => $this->l10n->t('Password Reset'), + '$lbl2' => $this->l10n->t('Your password has been reset as requested.'), + '$lbl3' => $this->l10n->t('Your new password is'), + '$lbl4' => $this->l10n->t('Save or copy your new password - and then'), + '$lbl5' => '' . $this->l10n->t('click here to login') . '.', + '$lbl6' => $this->l10n->t('Your password may be changed from the Settings page after successful login.'), + '$newpass' => $new_password, + ]); + + $this->sysMessages->addInfo($this->l10n->t("Your password has been reset.")); + + $sitename = $this->config->get('config', 'sitename'); + $preamble = Strings::deindent($this->l10n->t(' + Dear %1$s, + Your password has been changed as requested. Please retain this + information for your records ' . "\x28" . 'or change your password immediately to + something that you will remember' . "\x29" . '. + ', $user['username'])); + $body = Strings::deindent($this->l10n->t(' + Your login details are as follows: + + Site Location: %1$s + Login Name: %2$s + Password: %3$s + + You may change that password from your account settings page after logging in. + ', $this->baseUrl, $user['nickname'], $new_password)); + + $email = $this->emailer->newSystemMail() + ->withMessage($this->l10n->t('Your password has been changed at %s', $sitename), $preamble, $body) + ->forUser($user) + ->withRecipient($user['email']) + ->build(); + $this->emailer->send($email); + } + + return $o; + } +} diff --git a/static/routes.config.php b/static/routes.config.php index 9cf450338f..269dddad9d 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -479,6 +479,7 @@ return [ '/localtime' => [Module\Debug\Localtime::class, [R::GET, R::POST]], '/login' => [Module\Security\Login::class, [R::GET, R::POST]], '/logout' => [Module\Security\Logout::class, [R::GET, R::POST]], + '/lostpass[/{token}]' => [Module\LostPass::class, [R::GET, R::POST]], '/magic' => [Module\Magic::class, [R::GET]], '/manifest' => [Module\Manifest::class, [R::GET]], '/manifest.json' => [Module\Manifest::class, [R::GET]], diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index f561bc9a67..b2f3fb2840 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -82,134 +82,6 @@ msgstr "" msgid "Permission denied." msgstr "" -#: mod/lostpass.php:28 -msgid "No valid account found." -msgstr "" - -#: mod/lostpass.php:40 -msgid "Password reset request issued. Check your email." -msgstr "" - -#: mod/lostpass.php:46 -#, php-format -msgid "" -"\n" -"\t\tDear %1$s,\n" -"\t\t\tA request was recently received at \"%2$s\" to reset your account\n" -"\t\tpassword. In order to confirm this request, please select the verification link\n" -"\t\tbelow or paste it into your web browser address bar.\n" -"\n" -"\t\tIf you did NOT request this change, please DO NOT follow the link\n" -"\t\tprovided and ignore and/or delete this email, the request will expire shortly.\n" -"\n" -"\t\tYour password will not be changed unless we can verify that you\n" -"\t\tissued this request." -msgstr "" - -#: mod/lostpass.php:57 -#, php-format -msgid "" -"\n" -"\t\tFollow this link soon to verify your identity:\n" -"\n" -"\t\t%1$s\n" -"\n" -"\t\tYou will then receive a follow-up message containing the new password.\n" -"\t\tYou may change that password from your account settings page after logging in.\n" -"\n" -"\t\tThe login details are as follows:\n" -"\n" -"\t\tSite Location:\t%2$s\n" -"\t\tLogin Name:\t%3$s" -msgstr "" - -#: mod/lostpass.php:72 -#, php-format -msgid "Password reset requested at %s" -msgstr "" - -#: mod/lostpass.php:88 -msgid "Request could not be verified. (You may have previously submitted it.) Password reset failed." -msgstr "" - -#: mod/lostpass.php:101 -msgid "Request has expired, please make a new one." -msgstr "" - -#: mod/lostpass.php:116 -msgid "Forgot your Password?" -msgstr "" - -#: mod/lostpass.php:117 -msgid "Enter your email address and submit to have your password reset. Then check your email for further instructions." -msgstr "" - -#: mod/lostpass.php:118 src/Module/Security/Login.php:167 -msgid "Nickname or email" -msgstr "" - -#: mod/lostpass.php:119 -msgid "Reset my password" -msgstr "" - -#: mod/lostpass.php:134 src/Module/Security/Login.php:179 -msgid "Password Reset" -msgstr "" - -#: mod/lostpass.php:135 -msgid "Your password has been reset as requested." -msgstr "" - -#: mod/lostpass.php:136 -msgid "Your new password is" -msgstr "" - -#: mod/lostpass.php:137 -msgid "Save or copy your new password - and then" -msgstr "" - -#: mod/lostpass.php:138 -msgid "click here to login" -msgstr "" - -#: mod/lostpass.php:139 -msgid "Your password may be changed from the Settings page after successful login." -msgstr "" - -#: mod/lostpass.php:143 -msgid "Your password has been reset." -msgstr "" - -#: mod/lostpass.php:146 -#, php-format -msgid "" -"\n" -"\t\t\tDear %1$s,\n" -"\t\t\t\tYour password has been changed as requested. Please retain this\n" -"\t\t\tinformation for your records (or change your password immediately to\n" -"\t\t\tsomething that you will remember).\n" -"\t\t" -msgstr "" - -#: mod/lostpass.php:152 -#, php-format -msgid "" -"\n" -"\t\t\tYour login details are as follows:\n" -"\n" -"\t\t\tSite Location:\t%1$s\n" -"\t\t\tLogin Name:\t%2$s\n" -"\t\t\tPassword:\t%3$s\n" -"\n" -"\t\t\tYou may change that password from your account settings page after logging in.\n" -"\t\t" -msgstr "" - -#: mod/lostpass.php:164 -#, php-format -msgid "Your password has been changed at %s" -msgstr "" - #: mod/message.php:28 mod/message.php:124 src/Content/Nav.php:315 #: view/theme/frio/theme.php:229 msgid "Messages" @@ -7237,6 +7109,134 @@ msgstr "" msgid "Unable to follow this item." msgstr "" +#: src/Module/LostPass.php:50 +msgid "No valid account found." +msgstr "" + +#: src/Module/LostPass.php:62 +msgid "Password reset request issued. Check your email." +msgstr "" + +#: src/Module/LostPass.php:68 +#, php-format +msgid "" +"\n" +"\t\t\tDear %1$s,\n" +"\t\t\t\tA request was recently received at \"%2$s\" to reset your account\n" +"\t\t\tpassword. In order to confirm this request, please select the verification link\n" +"\t\t\tbelow or paste it into your web browser address bar.\n" +"\n" +"\t\t\tIf you did NOT request this change, please DO NOT follow the link\n" +"\t\t\tprovided and ignore and/or delete this email, the request will expire shortly.\n" +"\n" +"\t\t\tYour password will not be changed unless we can verify that you\n" +"\t\t\tissued this request." +msgstr "" + +#: src/Module/LostPass.php:79 +#, php-format +msgid "" +"\n" +"\t\t\tFollow this link soon to verify your identity:\n" +"\n" +"\t\t\t%1$s\n" +"\n" +"\t\t\tYou will then receive a follow-up message containing the new password.\n" +"\t\t\tYou may change that password from your account settings page after logging in.\n" +"\n" +"\t\t\tThe login details are as follows:\n" +"\n" +"\t\t\tSite Location:\t%2$s\n" +"\t\t\tLogin Name:\t%3$s" +msgstr "" + +#: src/Module/LostPass.php:93 +#, php-format +msgid "Password reset requested at %s" +msgstr "" + +#: src/Module/LostPass.php:109 +msgid "Request could not be verified. (You may have previously submitted it.) Password reset failed." +msgstr "" + +#: src/Module/LostPass.php:122 +msgid "Request has expired, please make a new one." +msgstr "" + +#: src/Module/LostPass.php:137 +msgid "Forgot your Password?" +msgstr "" + +#: src/Module/LostPass.php:138 +msgid "Enter your email address and submit to have your password reset. Then check your email for further instructions." +msgstr "" + +#: src/Module/LostPass.php:139 src/Module/Security/Login.php:167 +msgid "Nickname or email" +msgstr "" + +#: src/Module/LostPass.php:140 +msgid "Reset my password" +msgstr "" + +#: src/Module/LostPass.php:155 src/Module/Security/Login.php:179 +msgid "Password Reset" +msgstr "" + +#: src/Module/LostPass.php:156 +msgid "Your password has been reset as requested." +msgstr "" + +#: src/Module/LostPass.php:157 +msgid "Your new password is" +msgstr "" + +#: src/Module/LostPass.php:158 +msgid "Save or copy your new password - and then" +msgstr "" + +#: src/Module/LostPass.php:159 +msgid "click here to login" +msgstr "" + +#: src/Module/LostPass.php:160 +msgid "Your password may be changed from the Settings page after successful login." +msgstr "" + +#: src/Module/LostPass.php:164 +msgid "Your password has been reset." +msgstr "" + +#: src/Module/LostPass.php:167 +#, php-format +msgid "" +"\n" +"\t\t\t\tDear %1$s,\n" +"\t\t\t\t\tYour password has been changed as requested. Please retain this\n" +"\t\t\t\tinformation for your records (or change your password immediately to\n" +"\t\t\t\tsomething that you will remember).\n" +"\t\t\t" +msgstr "" + +#: src/Module/LostPass.php:173 +#, php-format +msgid "" +"\n" +"\t\t\t\tYour login details are as follows:\n" +"\n" +"\t\t\t\tSite Location:\t%1$s\n" +"\t\t\t\tLogin Name:\t%2$s\n" +"\t\t\t\tPassword:\t%3$s\n" +"\n" +"\t\t\t\tYou may change that password from your account settings page after logging in.\n" +"\t\t\t" +msgstr "" + +#: src/Module/LostPass.php:184 +#, php-format +msgid "Your password has been changed at %s" +msgstr "" + #: src/Module/Maintenance.php:34 src/Module/Maintenance.php:39 msgid "System down for maintenance" msgstr ""