cooptilleuls / cooptilleulsforgotpasswordbundle Goto Github PK
View Code? Open in Web Editor NEWProvides a "forgot password" complete feature for your API through a Symfony bundle
License: MIT License
Provides a "forgot password" complete feature for your API through a Symfony bundle
License: MIT License
Hello ,
I Noticed everything is ok but when i submit my new password, in database it's not encoded,
How can i encode it in UpdatePassword methods ?
Thanks
Describe the bug
Hi all!
I'm trying to implement your bundle, but when I follow your instruction... the part where I get the mail is ok, but not the part to reset the password.
I don't understand the problem the UserManager type is not found.
To Reproduce
My controller : (ForgotPasswordEventSubscriber.php)
namespace App\EventSubscriber;
use CoopTilleuls\ForgotPasswordBundle\Event\CreateTokenEvent;
use CoopTilleuls\ForgotPasswordBundle\Event\UpdatePasswordEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Twig\Environment;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
final class ForgotPasswordEventSubscriber implements EventSubscriberInterface
{
private $mailer;
private $twig;
/** @var UserPasswordHasherInterface */
private $passwordHasher;
private $userManager;
public function __construct(MailerInterface $mailer, Environment $twig, UserPasswordHasherInterface $passwordHasher, UserManager $userManager)
{
$this->mailer = $mailer;
$this->twig = $twig;
$this->passwordHasher = $passwordHasher;
$this->userManager = $userManager;
}
public static function getSubscribedEvents()
{
return [
// Symfony 4.3 and inferior, use 'coop_tilleuls_forgot_password.create_token' event name
CreateTokenEvent::class => 'onCreateToken',
UpdatePasswordEvent::class => 'onUpdatePassword',
];
}
public function onCreateToken(CreateTokenEvent $event)
{
$passwordToken = $event->getPasswordToken();
$user = $passwordToken->getUser();
$message = (new Email())
->from('[email protected]')
->to($user->getEmail())
->subject('Reset your password')
->html($this->twig->render(
'resetpassword/reset_password_email.html.twig',
[
'signedUrl' => sprintf('http://localhost:3000/forgot-password/%s', $passwordToken->getToken()),
]
));
if (0 === $this->mailer->send($message)) {
throw new \RuntimeException('Unable to send email');
}
}
public function onUpdatePassword(UpdatePasswordEvent $event)
{
$passwordToken = $event->getPasswordToken();
$user = $passwordToken->getUser();
$password = $event->getPassword();
$hashedPassword = $this->passwordHasher->hashPassword($user, $password);
$user->setPassword($hashedPassword);
$this->userManager->persist($user);
$this->userManager->flush();
}
}
``
Additional context
Add any other context about the problem here.
Compile Error: Declaration of App\Entity\PasswordToken::setUser(App\Entity\User $user): App\Entity\PasswordToken must be compatible with CoopTilleuls\ForgotPasswordBundle\Entity\AbstractPasswordToken::setUser($user)
The User entity was created using the standard method from the documentation. What could be the problem?
Symfony 6.2.0
# config/packages/coop_tilleuls_forgot_password.yaml
coop_tilleuls_forgot_password:
password_token:
class: 'App\Entity\PasswordToken' # Token class fully qualified name (required)
expires_in: '1 day' # Token duration (optional, default value)
user_field: 'user' # User property in token class (optional, default value)
serialization_groups: [ ] # Serialization groups used in GET /forgot-password/{tokenValue} (optional, default value)
user:
class: 'App\Entity\User' # User class fully qualified name (required)
email_field: 'email' # Email property in user class (optional, default value)
password_field: 'password' # Password property in user class (optional, default value)
authorized_fields: [ 'email' ] # User properties authorized to reset the password (optional, default value)
use_jms_serializer: false # Switch between symfony's serializer component or JMS Serializer
// src/Entity/PasswordToken.php
<?php
namespace App\Entity;
use CoopTilleuls\ForgotPasswordBundle\Entity\AbstractPasswordToken;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
class PasswordToken extends AbstractPasswordToken
{
#[ORM\Id]
#[ORM\Column(type: 'integer', nullable: false)]
#[ORM\GeneratedValue(strategy: 'AUTO')]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: User::class)]
#[ORM\JoinColumn(nullable: false)]
private ?User $user = null;
public function getId(): ?int
{
return $this->id;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(User $user): self
{
$this->user = $user;
return $this;
}
}
// src/Entity/User.php
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Annotation\Ignore;
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $email = null;
#[ORM\Column]
private array $roles = [];
/**
* @var string The hashed password
*/
#[Ignore]
#[ORM\Column]
private ?string $password = null;
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
#[Ignore]
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* @see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
}
Is your feature request related to a problem? Please describe.
I think it would be fine implement the possibility of validating an account only validating the token without using a database, something similar to the LexikJWTAuthenticationBundle.
With this we would avoid unconfirmed tokens in the database for example.
Describe the solution you'd like
Create a token with the expiration date fields for example and validate that the token is valid and does not contain an expired expiration date.
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Additional context
Add any other context or screenshots about the feature request here.
Thx and regards.
Describe the bug
Got "Call to a member function withPost() on null" in vendor/tilleuls/forgot-password-bundle/Bridge/ApiPlatform/OpenApi/OpenApiFactory.php (line 46)
Unable to generate documentation
To Reproduce
Install bundle on api-platform 2.6
Expected behavior
Endpoints displayed on documentation
Dont know where is problem, in var $paths, bundle routes are not set, maybe "DocumentationNormalizer" not registered ?
I can PR if problem found
Check if integration with symfony-casts/reset-password-bundle is possible
JSON validation on POST /forgot_password/{tokenValue}
is incorrect. It doesn't skip extraneous members and incorrectly reports the missing password-field.
To Reproduce
POST these bodies to /forgot_password/{tokenValue}
.
Example body:
{
"ignoreMe": "foo",
"password": "bar"
}
Response:
Parameter "password" is missing.
No, it isn't.
Example body:
{
"ignoreMe": false,
"password": "foo"
}
Response:
Parameter "ignoreMe" is missing.
No, it isn't.
Currently, this bundle only support a single user. But in some projects, we may handle multiple users.
It should be possible in this bundle to support multiple users classes
On first request, before creating new token, check if there is an existing & valid one for the same user. In this case: return 400
I have overwritten the update password route and added my own controller so I could add a confirm_password parameter. So instead of only needing to fill in the password you now need to fill in:
{
"password": "string",
"confirm_password": "string"
}
I would now like to overwrite the existing endpoint documentation so I can add this new parameter like this, but I can't figure out how.
I have checked the documentation and the issues, but I can't really find anything about overwriting the documentation. Is there currently a way to do something like this?
Thanks in advance,
Ward
Steps:
mongo.persister:
class: App\Doctrine\MongoPersister
public: true
arguments: [ '@doctrine_mongodb.odm.default_document_manager']
coop_tilleuls_forgot_password:
manager: mongo.persister
password_token:
....
root@c28c46157c96:/auth_service# bin/console cache:clear
In CoopTilleulsForgotPasswordExtension.php line 58:
Service "mongo.persister" does not exist.
The reason of the issue is that the Symfony 5.1 uses a temporary container for the extension initializing and merges in to the main container after initializing. The temporary container doesn't have the previously initialized services.
Solution: remove the check from the extension:
if (!$container->hasDefinition($config['manager'])) {
throw new \LogicException(sprintf('Service "%s" does not exist.', $config['manager']));
}
I've just upgraded from Symfony 5 to Symfony 6 and I can see 2 deprecation warnings in the logs:
Method "Symfony\Component\EventDispatcher\EventSubscriberInterface::getSubscribedEvents()" might add "array" as a native return type declaration in the future. Do the same in implementation "App\EventSubscriber\ForgotPasswordEventSubscriber" now to avoid errors or add an explicit @return annotation to suppress this message.
Method "Symfony\Component\Config\Definition\ConfigurationInterface::getConfigTreeBuilder()" might add "TreeBuilder" as a native return type declaration in the future. Do the same in implementation "CoopTilleuls\ForgotPasswordBundle\DependencyInjection\Configuration" now to avoid errors or add an explicit @return annotation to suppress this message.
Describe the bug
Since version 1.3.5, Symfony tells me 'The service "coop_tilleuls_forgot_password.openapi.factory" has a dependency on a non-existent service "api_platform.openapi.factory".' when I clear the cache. I suppose it is because I don't use swagger with api platform.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
I think it should go on also for those who don't use swagger
Desktop (please complete the following information):
The GET /forgot_password/{tokenValue}
controller ultimately calls NormalizerInterface->normalize($token, ...)
, which, through Doctrine's lazy loading and Symfony's normalizer, serializes the entire User entity as JSON.
This seems unnecessary, we just need to know whether the token is valid. Why does it instead serialize the entire user?
The reason for this issue is that this throws in our setup: we have Ramsey UUIDs for all our entities, and the serializer complains about the binary representing our user's ID not being valid UTF-8, which indeed it isn't:
Malformed UTF-8 characters, possibly incorrectly encoded
In vendor/symfony/http-foundation/JsonResponse.php.
The workaround is a custom normalizer:
class PasswordTokenNormalizer implements NormalizerInterface
{
/**
* @param AbstractPasswordToken $object
*/
public function normalize($object, $format = null, array $context = []): array
{
return [
'isExpired' => $object->isExpired(),
];
}
public function supportsNormalization($data, $format = null): bool
{
return $data instanceof AbstractPasswordToken;
}
}
Or a serialization group configuration:
coop_tilleuls_forgot_password:
password_token:
class: App\Entity\PasswordToken
expires_in: 1 day
user_field: user
serialization_groups: [ 'forgotpassword:read' ]
App\Entity\PasswordToken:
attributes:
isExpired:
groups:
- 'forgotpassword:read'
Can it perhaps be documented what the endpoints do?
Hi,
Im again , using de new release im getting this error now:
PHP Fatal error: Uncaught Symfony\Component\Dep endencyInjection\Exception\ServiceNotFoundException: The service "coop_tilleuls_forgot_password.controller.forgot_ password" has a dependency on a non-existent serv ice "serializer". in /var/www/api/vendor/symfony/symfony/src/Symfony/Component/DependencyInjection/Compiler/Check
I think the problem is here new Definition($class, [new Reference('serializer')]))->setPublic(false);
Mi config file:
coop_tilleuls_forgot_password:
password_token_class: 'AppBundle\Entity\PasswordToken'
user_class: 'AppBundle\Entity\User'
use_jms_serializer: true
Regards!
Hello,
I'm trying to override post forgot password but its doesn't work for me.
i created my own controller to make my logic.
coop_tilleuls_forgot_password.update:
path: /forgot-password/{tokenValue}
methods: [ POST ]
defaults:
_controller: App\Controller\Action\UpdateResetPasswordActionController
--Error:
There is no extension able to load the configuration for "coop_tilleuls_forgot_password.update" (in "/var/www/html/config/packages/coop_tilleuls_forgot_password.yaml"). Looked for namespace "coop_tilleuls_forgot_password.up
date", found ""framework", "doctrine", "doctrine_migrations", "debug", "twig", "web_profiler", "twig_extra", "security", "monolog", "maker", "sensio_framework_extra", "doctrine_fixtures", "nelmio_api_doc", "nelmio_alice", "
fidry_alice_data_fixtures", "hautelook_alice", "nelmio_cors", "api_platform", "lexik_jwt_authentication", "vich_uploader", "coop_tilleuls_forgot_password"" in /var/www/html/config/packages/coop_tilleuls_forgot_password.yaml
(which is being imported from "/var/www/html/src/Kernel.php").
Please help me
Hi :)
Currently (unless I missed a config option), we can’t ask for a new token if a token has already been asked and still valid regarding its age.
I wonder why this restriction.
As a user, I would rather (and I think it’s what the majority of websites do) be able to ask for a new token even if one exists (invaliding the former one in the process).
The most evident use case I’m thinking of is the email with the token being never received.
If it’s for security reason because hashing is cpu consuming, we could set a delay minimum of a few seconds that would be sufficient in this regard I reckon.
I want to use your bundle to overcome the problem of forgotten password.
And I installed it and set it up as mentioned in github.
Now I want to know, what should I do to operate the event?
Should I use a controller? If yes, how ? Sorry about my question, I'm new to symfony.
Thanks
I have this error:
Problem 1
- laminas/laminas-code 4.4.0 requires php ^7.4 || ~8.0.0 -> your php version (8.2.0) does not satisfy that requirement.
I have symfony 4.4.2 and was trying to integrate CoopTilleulsForgotPasswordBundle. But I have issue on a last step when I suppose to receive POSt request with the new password and the token. Here is what I got:
Could not resolve argument $password of "coop_tilleuls_forgot_password.controller.update_password()", maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?
But when I try to register it as service it gives me error:
Controller "CoopTilleuls\ForgotPasswordBundle\Controller\UpdatePassword" requires that you provide a value for the "$password" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.
So what I'm missing here? Thank you
Hi ,
I'm using your bundle together with fosrestbundle and jms Serializer, but im getting this error:
Type error: Argument 2 passed to CoopTilleuls\ForgotPasswordBundle\Controller\ForgotPasswordController::__construct() must be an instance of Symfony\Component\Serializer\Normalizer\NormalizerInterface, instance of JMS\Serializer\Serializer given
Another question is.. how can i customize de exceptions text of your bundle?
Regards
The bundle return a 204 code without message on an inexistant user in database.
Why not try an exception?
If too many reset password requests are done in a short delay, we may want to add a mechanism to calm down the attacker.
First discussed here: #37 (comment)
Plus, in regard of #38, the response to the attacker should be the same whether the email exists or not. Hence we cannot protect by verifying a request counter on a token entity. The check can then only be made by data in the Request… soooo, maybe out of this bundle scope (hard to say)?
Note: This could be done by server configuration (fail2ban, etc).
Describe the bug
This nice bundle can provide a swagger decorator e.g. under the swagger ui url /api. Since the way a decorator is setup changed a lot in api-platform 2.6.1 (vs. 2.5.9), the decorator will fail now.
Old way: https://web.archive.org/web/20201024152406/https://api-platform.com/docs/core/jwt/
New way: https://api-platform.com/docs/core/jwt/
To Reproduce
Simply composer update a symfony 5.2.1 / api-platform 2.5.9 (or lower) project to recent versions.
Expected behavior
The endpoint decorator of this bundle should be still listed in the swagger ui.
Hello,
This commit: af60648 introduces a BC break by removing CREATE_TOKEN
and UPDATE_PASSWORD
from the ForgotPasswordEvent
class.
I wasn't expecting seeing my code broke after upgrading a PATCH version.
Hi,
Are you going to update this bundle in the future to use in symfony 4?
Thx and Regards.
Good morning Vincent,
I'm trying to install this package but seem to be having some problems. I have the following in my config.yml
coop_tilleuls_forgot_password:`
password_token:
class: UserBundle\Entity\PasswordToken # required
expires_in: 1 day
user_field: user
serialization_groups: []
user:
class: UserBundle\Entity\User # required
email_field: email
password_field: password
use_jms_serialize: false # Switch between symfony's serializer component or JMS Serializer
This seems to give me the error 'Unrecognized option "use_jms_serialize" under "coop_tilleuls_forgot_password'.
I've then commented out the 'use_jms_serialize' attribute. This then results me in getting another error
'TypeError: Argument 1 passed to CoopTilleuls\ForgotPasswordBundle\Normalizer\SymfonyNormalizer::__construct() must implement interface Symfony\Component\Serializer\Normalizer\NormalizerInterface, instance of JMS\Serializer\Serializer given, called in ..../appTestDebugProjectContainer.php on line 2709'
Do you have any idea why this config is not getting accepted?
Thank you
Harry
Hi!, I'm new to Symfony and Bundles.
I have configured this bundle, but I don't know how to use it after the basic config.
Someone can help me to give more detailed ?
Thank's
Describe the bug
Both Bridge/ApiPlatform/OpenApi/OpenApiFactory.php and Bridge/ApiPlatform/Serializer/DocumentationNormalizer.php use hardcoded paths to /forgot-password/and /forgot-password/{token}
To Reproduce
Use a routing like this:
coop_tilleuls_forgot_password:
resource: "@CoopTilleulsForgotPasswordBundle/Resources/config/routing.xml"
prefix: /api/forgot_password
Expected behavior
The bundle works at /api/forgot_password
Actual behavior
The routing is set to /api/forgot_password but the docs are still leading to /forgot-password/and /forgot-password/{token}
While it's possible to set the proper routes using OpenApi in application it seems to be not possible to remove the routes set by the bundle.
Proposed fix
Use prefix instead of hardcoded route in Bridge/ApiPlatform/OpenApi/OpenApiFactory.php and Bridge/ApiPlatform/Serializer/DocumentationNormalizer.php
In \CoopTilleuls\ForgotPasswordBundle\Bridge\ApiPlatform\Serializer\DocumentationNormalizer::normalize
, line 34 is $docs['paths']['/forgot-password/']['post'] = [
. Shouldn't it be $docs['paths']['/forgot-password']['post'] = [
?
Describe the bug
The API and Routing in the current version does expect the path to be something like /forgot_password/{tokenValue}
But the parameter in the OpenAPI specs is token only: https://github.com/coopTilleuls/CoopTilleulsForgotPasswordBundle/blob/main/Bridge/ApiPlatform/OpenApi/OpenApiFactory.php#L123
This leads to a 404 because the route /forgot_password/{tokenValue} will be called instead of /forgot_password/345234234
You can rename the route to /forgot_password/{token} easily.
But this will break the recognition of the token as the RequestEventListener will look for tokenValue at the moment:
https://github.com/coopTilleuls/CoopTilleulsForgotPasswordBundle/blob/main/EventListener/RequestEventListener.php#L108
To Reproduce
Setup a project with the current defaults
Expected behavior
Use eighter token or tokenValue through the whole bundle.
Additional context
I'd be happy to provide a PR renaming all instances of token to tokenValue or the other way around - depends on your preferences.
Could you please increase version of bundle requirements to version ^5.0?
Scenario: I can't reset my password with an invalid email address
When I reset my password using invalid email address
Then the request should be invalid with message 'User with field "email" equal to "[email protected]" cannot be found.'
This scenario is a security leak cause it allows any user to check which email is stored in the database. If email address is invalid, the following message should be visible: if the email address exists, an email has been sent to it.
.
Same for following scenario:
Scenario: I can't reset my password if I already request a token
Given I have a valid token
When I reset my password
Then the request should be invalid with message 'An unexpired token already exists for this user.'
Response should be 200 & message should be the same.
Good afternoon vincentchalamon,
I have followed all the steps on the getting_started.md tutorial but am getting a getRepository() error. Do you know what might be causing this?
Thank you.
Below is my Stack Trace:
`
in vendor/tilleuls/forgot-password-bundle/Manager/Bridge/DoctrineManager.php at line 37 -
public function findOneBy($class, array $criteria)
{
return $this->registry->getManagerForClass($class)
->getRepository($class)
->findOneBy($criteria);
}
at DoctrineManager ->findOneBy ('UserBundle\Entity\PasswordToken', array('user' => object(User)))
in vendor/tilleuls/forgot-password-bundle/Manager/PasswordTokenManager.php at line 94 +
at PasswordTokenManager ->findOneByUser (object(User))
in vendor/tilleuls/forgot-password-bundle/Manager/ForgotPasswordManager.php at line 63 +
at ForgotPasswordManager ->resetPassword ('[email protected]')
in vendor/tilleuls/forgot-password-bundle/Controller/ForgotPasswordController.php at line 51 +
at ForgotPasswordController ->resetPasswordAction ('[email protected]')
at call_user_func_array (array(object(ForgotPasswordController), 'resetPasswordAction'), array('[email protected]'))
in var/cache/dev/classes.php at line 4245 +
at HttpKernel ->handleRaw (object(Request), 1)
in var/cache/dev/classes.php at line 4200 +
at HttpKernel ->handle (object(Request), 1, true)
in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php at line 168 +
at Kernel ->handle (object(Request))
in web/app_dev.php at line 28 +
`
Related to #83
Hello @vincentchalamon @GregoireHebert,
What do you think about dispatching an event when the user is not found during the request for the password reset, in order to perform business actions listening to this event?
And updating the status code from 204 to 202?
I propose a pull request about this.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.