Skip to content

Commit 03dca8e

Browse files
feat: introduce TokenGenerator
1 parent 2e85869 commit 03dca8e

9 files changed

+113
-5
lines changed

config/services.xml

+3
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,15 @@
3030

3131
<service id="coop_tilleuls_forgot_password.manager.password_token" class="CoopTilleuls\ForgotPasswordBundle\Manager\PasswordTokenManager" public="true">
3232
<argument type="service" id="coop_tilleuls_forgot_password.provider_chain"/>
33+
<argument/> <!-- token_generator -->
3334
</service>
3435

3536
<service id="coop_tilleuls_forgot_password.manager.doctrine" class="CoopTilleuls\ForgotPasswordBundle\Manager\Bridge\DoctrineManager" public="false">
3637
<argument type="service" id="doctrine" on-invalid="null" />
3738
</service>
3839

40+
<service id="coop_tilleuls_forgot_password.token_generator.bin2hex" class="CoopTilleuls\ForgotPasswordBundle\TokenGenerator\Bridge\Bin2HexTokenGenerator" public="false" />
41+
3942
<service id="coop_tilleuls_forgot_password.event_listener.request" class="CoopTilleuls\ForgotPasswordBundle\EventListener\RequestEventListener">
4043
<argument type="service" id="coop_tilleuls_forgot_password.manager.password_token" />
4144
<argument type="service" id="coop_tilleuls_forgot_password.provider_chain"/>

docs/index.md

+8
Original file line numberDiff line numberDiff line change
@@ -324,3 +324,11 @@ Read full documentation about [usage](usage.md).
324324
By default, this bundle works with Doctrine ORM, but you're free to connect with any system.
325325

326326
Read full documentation about [how to connect your manager](use_custom_manager.md).
327+
328+
## Generate your own token
329+
330+
By default, this bundle works uses [`bin2hex`](https://www.php.net/bin2hex) combined with
331+
[`random_bytes`](https://www.php.net/random_bytes) to generate the token, but you're free to create your own
332+
TokenGenerator to create your token.
333+
334+
Read full documentation about [how to generate your own token](use_custom_token_generator.md).

docs/use_custom_token_generator.md

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Use custom token generator
2+
3+
By default, this bundle works uses [`bin2hex`](https://www.php.net/bin2hex) combined with
4+
[`random_bytes`](https://www.php.net/random_bytes) to generate the token, but you're free to create your own
5+
TokenGenerator to create your token.
6+
7+
## Create your custom token generator
8+
9+
Supposing you want to generate your own token, you'll have to create a service that will implement
10+
`CoopTilleuls\ForgotPasswordBundle\TokenGenerator\TokenGeneratorInterface`:
11+
12+
```php
13+
// src/TokenGenerator/FooTokenGenerator.php
14+
namespace App\TokenGenerator;
15+
16+
use CoopTilleuls\ForgotPasswordBundle\TokenGenerator\TokenGeneratorInterface;
17+
18+
final class FooTokenGenerator implements TokenGeneratorInterface
19+
{
20+
public function generate(): string
21+
{
22+
// generate your own token and return it as string
23+
}
24+
}
25+
```
26+
27+
## Update configuration
28+
29+
Update your configuration to set your service as default one to use by this bundle:
30+
31+
```yaml
32+
# config/packages/coop_tilleuls_forgot_password.yaml
33+
coop_tilleuls_forgot_password:
34+
# ...
35+
token_generator: 'App\TokenGenerator\FooTokenGenerator'
36+
```

src/DependencyInjection/Configuration.php

+5
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ public function getConfigTreeBuilder(): TreeBuilder
134134
->booleanNode('use_jms_serializer')
135135
->defaultFalse()
136136
->end()
137+
->scalarNode('token_generator')
138+
->defaultValue('coop_tilleuls_forgot_password.token_generator.bin2hex')
139+
->cannotBeEmpty()
140+
->info('Persistence manager service to handle the token storage.')
141+
->end()
137142
->end();
138143

139144
return $treeBuilder;

src/DependencyInjection/CoopTilleulsForgotPasswordExtension.php

+4
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ public function load(array $configs, ContainerBuilder $container): void
7979
$class = true === $config['use_jms_serializer'] ? JMSNormalizer::class : SymfonyNormalizer::class;
8080
$serializerId = true === $config['use_jms_serializer'] ? 'jms_serializer.serializer' : 'serializer';
8181
$container->setDefinition('coop_tilleuls_forgot_password.normalizer', new Definition($class, [new Reference($serializerId)]))->setPublic(false);
82+
83+
$container
84+
->getDefinition('coop_tilleuls_forgot_password.manager.password_token')
85+
->replaceArgument(1, new Reference($config['token_generator']));
8286
}
8387

8488
private function buildProvider(array $config, ContainerBuilder $container): void

src/Manager/PasswordTokenManager.php

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
use CoopTilleuls\ForgotPasswordBundle\Provider\Provider;
1818
use CoopTilleuls\ForgotPasswordBundle\Provider\ProviderChainInterface;
1919
use CoopTilleuls\ForgotPasswordBundle\Provider\ProviderInterface;
20+
use CoopTilleuls\ForgotPasswordBundle\TokenGenerator\TokenGeneratorInterface;
2021

2122
/**
2223
* @author Vincent CHALAMON <[email protected]>
2324
*/
2425
class PasswordTokenManager
2526
{
26-
public function __construct(private readonly ProviderChainInterface $providerChain)
27+
public function __construct(private readonly ProviderChainInterface $providerChain, private readonly TokenGeneratorInterface $tokenGenerator)
2728
{
2829
}
2930

@@ -47,7 +48,7 @@ public function createPasswordToken($user, ?\DateTime $expiresAt = null, ?Provid
4748

4849
/** @var AbstractPasswordToken $passwordToken */
4950
$passwordToken = new $tokenClass();
50-
$passwordToken->setToken(bin2hex(random_bytes(25)));
51+
$passwordToken->setToken($this->tokenGenerator->generate());
5152
$passwordToken->setUser($user);
5253
$passwordToken->setExpiresAt($expiresAt);
5354
$provider->getManager()->persist($passwordToken);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the CoopTilleulsForgotPasswordBundle package.
5+
*
6+
* (c) Vincent CHALAMON <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace CoopTilleuls\ForgotPasswordBundle\TokenGenerator\Bridge;
15+
16+
use CoopTilleuls\ForgotPasswordBundle\TokenGenerator\TokenGeneratorInterface;
17+
18+
final class Bin2HexTokenGenerator implements TokenGeneratorInterface
19+
{
20+
public function generate(): string
21+
{
22+
return bin2hex(random_bytes(25));
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the CoopTilleulsForgotPasswordBundle package.
5+
*
6+
* (c) Vincent CHALAMON <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace CoopTilleuls\ForgotPasswordBundle\TokenGenerator;
15+
16+
/**
17+
* @author Vincent CHALAMON <[email protected]>
18+
*/
19+
interface TokenGeneratorInterface
20+
{
21+
public function generate(): string;
22+
}

tests/Manager/PasswordTokenManagerTest.php

+8-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use CoopTilleuls\ForgotPasswordBundle\Manager\PasswordTokenManager;
1919
use CoopTilleuls\ForgotPasswordBundle\Provider\ProviderChainInterface;
2020
use CoopTilleuls\ForgotPasswordBundle\Provider\ProviderInterface;
21+
use CoopTilleuls\ForgotPasswordBundle\TokenGenerator\TokenGeneratorInterface;
2122
use PHPUnit\Framework\TestCase;
2223
use Symfony\Component\Security\Core\User\UserInterface;
2324

@@ -35,6 +36,7 @@ final class PasswordTokenManagerTest extends TestCase
3536
private $tokenMock;
3637
private $providerChainMock;
3738
private $providerMock;
39+
private $tokenGeneratorMock;
3840

3941
protected function setUp(): void
4042
{
@@ -43,33 +45,36 @@ protected function setUp(): void
4345
$this->tokenMock = $this->createMock(AbstractPasswordToken::class);
4446
$this->providerChainMock = $this->createMock(ProviderChainInterface::class);
4547
$this->providerMock = $this->createMock(ProviderInterface::class);
48+
$this->tokenGeneratorMock = $this->createMock(TokenGeneratorInterface::class);
4649

47-
$this->manager = new PasswordTokenManager($this->providerChainMock);
50+
$this->manager = new PasswordTokenManager($this->providerChainMock, $this->tokenGeneratorMock);
4851
}
4952

5053
public function testCreatePasswordToken(): void
5154
{
5255
$this->managerMock->expects($this->once())->method('persist')->with($this->callback(fn ($object) => $object instanceof AbstractPasswordToken
5356
&& '2016-10-11 10:00:00' === $object->getExpiresAt()->format('Y-m-d H:i:s')
54-
&& preg_match('/^[A-z\d]{50}$/', $object->getToken())
57+
&& '12345' === $object->getToken()
5558
&& $this->userMock === $object->getUser()));
5659

5760
$this->providerChainMock->expects($this->once())->method('get')->willReturn($this->providerMock);
5861
$this->providerMock->expects($this->once())->method('getPasswordTokenClass')->willReturn(PasswordToken::class);
5962
$this->providerMock->expects($this->once())->method('getManager')->willReturn($this->managerMock);
63+
$this->tokenGeneratorMock->expects($this->once())->method('generate')->willReturn('12345');
6064

6165
$this->manager->createPasswordToken($this->userMock, new \DateTime('2016-10-11 10:00:00'));
6266
}
6367

6468
public function testCreatePasswordTokenWithoutExpirationDate(): void
6569
{
6670
$this->managerMock->expects($this->once())->method('persist')->with($this->callback(fn ($object) => $object instanceof AbstractPasswordToken
67-
&& preg_match('/^[A-z\d]{50}$/', $object->getToken())
71+
&& '12345' === $object->getToken()
6872
&& $this->userMock === $object->getUser()));
6973

7074
$this->providerChainMock->expects($this->once())->method('get')->willReturn($this->providerMock);
7175
$this->providerMock->expects($this->once())->method('getPasswordTokenClass')->willReturn(PasswordToken::class);
7276
$this->providerMock->expects($this->once())->method('getManager')->willReturn($this->managerMock);
77+
$this->tokenGeneratorMock->expects($this->once())->method('generate')->willReturn('12345');
7378

7479
$this->manager->createPasswordToken($this->userMock);
7580
}

0 commit comments

Comments
 (0)