1 Commits

Author SHA1 Message Date
81841a88a6 Fixed resetting password bypasses account verification 2025-11-06 11:43:18 +00:00
2 changed files with 107 additions and 0 deletions

View File

@@ -16,4 +16,6 @@ $app->group('/account', function () {
$this->get('/register', 'UserFrosting\Sprinkle\UFTweaks\Controller\AccountController:pageRegister') $this->get('/register', 'UserFrosting\Sprinkle\UFTweaks\Controller\AccountController:pageRegister')
->add('checkEnvironment') ->add('checkEnvironment')
->setName('register'); ->setName('register');
$this->post('/forgot-password', 'UserFrosting\Sprinkle\UFTweaks\Controller\AccountController:forgotPassword');
})->add(new NoCache()); })->add(new NoCache());

View File

@@ -29,6 +29,111 @@ use UserFrosting\Support\Exception\NotFoundException;
*/ */
class AccountController extends UFAccountController class AccountController extends UFAccountController
{ {
/**
* Processes a request to email a forgotten password reset link to the user.
*
* Processes the request from the form on the "forgot password" page, checking that:
* 1. The rate limit for this type of request is being observed.
* 2. The provided email address belongs to a registered account;
* 3. The submitted data is valid.
* Note that we have removed the requirement that a password reset request not already be in progress.
* This is because we need to allow users to re-request a reset, even if they lose the first reset email.
* This route is "public access".
*
* @todo require additional user information
* @todo prevent password reset requests for root account?
*
* AuthGuard: false
* Route: /account/forgot-password
* Route Name: {none}
* Request type: POST
*
* @param Request $request
* @param Response $response
* @param array $args
*/
public function forgotPassword(Request $request, Response $response, $args)
{
/** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
$ms = $this->ci->alerts;
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = $this->ci->classMapper;
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
// Get POST parameters
$params = $request->getParsedBody();
// Load the request schema
$schema = new RequestSchema('schema://requests/forgot-password.yaml');
// Whitelist and set parameter defaults
$transformer = new RequestDataTransformer($schema);
$data = $transformer->transform($params);
// Validate, and halt on validation errors. Failed validation attempts do not count towards throttling limit.
$validator = new ServerSideValidator($schema, $this->ci->translator);
if (!$validator->validate($data)) {
$ms->addValidationErrors($validator);
return $response->withJson([], 400);
}
// Throttle requests
/** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */
$throttler = $this->ci->throttler;
$throttleData = [
'email' => $data['email'],
];
$delay = $throttler->getDelay('password_reset_request', $throttleData);
if ($delay > 0) {
$ms->addMessageTranslated('danger', 'RATE_LIMIT_EXCEEDED', ['delay' => $delay]);
return $response->withJson([], 429);
}
// Load the user, by email address
$user = $classMapper->getClassMapping('user')::where('email', $data['email'])->first();
if ($user) {
if (!$user->flag_verified) {
$ms->addMessageTranslated('danger', 'ACCOUNT.UNVERIFIED');
return $response->withJson([], 400);
}
}
// All checks passed! log events/activities, update user, and send email
// Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($classMapper, $data, $throttler, $throttleData, $config, $user) {
// Log throttleable event
$throttler->logEvent('password_reset_request', $throttleData);
// Check that the email exists.
// If there is no user with that email address, we should still pretend like we succeeded, to prevent account enumeration
if ($user) {
// Try to generate a new password reset request.
// Use timeout for "reset password"
$passwordReset = $this->ci->repoPasswordReset->create($user, $config['password_reset.timeouts.reset']);
// Create and send email
$message = new TwigMailMessage($this->ci->view, 'mail/password-reset.html.twig');
$message->from($config['address_book.admin'])
->addEmailRecipient(new EmailRecipient($user->email, $user->full_name))
->addParams([
'user' => $user,
'token' => $passwordReset->getToken(),
'request_date' => Carbon::now()->format('Y-m-d H:i:s'),
]);
$this->ci->mailer->send($message);
}
});
}
/** /**
* Account settings page. * Account settings page.
* *