Skip to content

Commit

Permalink
feat: resend activation key
Browse files Browse the repository at this point in the history
In case the first email is lost,
you can now send again an activation key,
which now may arrive at your email.

closes ecamp#4670
  • Loading branch information
BacLuc committed Jun 19, 2024
1 parent 5508f1d commit ef74f66
Show file tree
Hide file tree
Showing 17 changed files with 704 additions and 3 deletions.
36 changes: 36 additions & 0 deletions api/src/DTO/UserActivation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace App\DTO;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
use ApiPlatform\OpenApi\Model\Operation as OpenApiOperation;
use App\InputFilter;
use App\State\ResendActivationProcessor;
use Symfony\Component\Serializer\Annotation\Groups;

#[ApiResource(
operations: [
new Post(
processor: ResendActivationProcessor::class,
uriTemplate: '/resend_activation{._format}',
security: 'true',
status: 204,
output: false,
denormalizationContext: ['groups' => ['create']],
openapi: new OpenApiOperation(summary: 'Request activation email again', description: 'Activation email will be sent to the given email again.')
),
],
routePrefix: '/auth'
)]
class UserActivation {
#[InputFilter\Trim]
#[ApiProperty(readable: false, writable: true)]
#[Groups(['create'])]
public ?string $email = null;

#[ApiProperty(readable: false, writable: true)]
#[Groups(['create'])]
public ?string $recaptchaToken = null;
}
1 change: 1 addition & 0 deletions api/src/Serializer/Normalizer/UriTemplateNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public function normalize($object, $format = null, array $context = []): null|ar
$result['_links']['oauthCevidb'] = ['href' => $this->urlGenerator->generate('connect_cevidb_start').'{?callback}', 'templated' => true];
$result['_links']['oauthJubladb'] = ['href' => $this->urlGenerator->generate('connect_jubladb_start').'{?callback}', 'templated' => true];
$result['_links']['resetPassword'] = ['href' => $this->urlGenerator->generate('_api_/auth/reset_password{._format}_post').'{/id}', 'templated' => true];
$result['_links']['resendActivation'] = ['href' => $this->urlGenerator->generate('_api_/auth/resend_activation{._format}_post'), 'templated' => false];

return $result;
}
Expand Down
59 changes: 59 additions & 0 deletions api/src/State/ResendActivationProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\DTO\UserActivation;
use App\Entity\User;
use App\Repository\UserRepository;
use App\Security\ReCaptcha\ReCaptchaWrapper;
use App\Service\MailService;
use App\Util\IdGenerator;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\ORM\NoResultException;
use Symfony\Component\HttpKernel\Exception\HttpException;

/**
* @implements ProcessorInterface<UserActivation,null>
*/
readonly class ResendActivationProcessor implements ProcessorInterface {
public function __construct(
private ReCaptchaWrapper $reCaptcha,
private EntityManagerInterface $em,
private UserRepository $userRepository,
private MailService $mailService,
) {}

/**
* @param UserActivation $data
*
* @throws NoResultException
* @throws NonUniqueResultException
*/
public function process($data, Operation $operation, array $uriVariables = [], array $context = []): null {
$resp = $this->reCaptcha->verify($data->recaptchaToken);
if (!$resp->isSuccess()) {
throw new HttpException(422, 'ReCaptcha failed');
}

$user = $this->userRepository->loadUserByIdentifier($data->email);

if (null == $user) {
return null;
}

if (User::STATE_REGISTERED !== $user->state) {
return null;
}

$user->activationKey = IdGenerator::generateRandomHexString(64);
$user->activationKeyHash = md5($user->activationKey);
$this->em->flush();

$this->mailService->sendUserActivationMail($user, $user->activationKey);

return null;
}
}
1 change: 1 addition & 0 deletions api/tests/Api/FirewallTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ private static function isProtectedByFirewall(mixed $endpoint): bool {
'/auth/cevidb' => false,
'/auth/jubladb' => false,
'/auth/reset_password' => false,
'/auth/resend_activation' => false,
'/content_types' => false,
'/invitations' => false,
default => true
Expand Down
1 change: 1 addition & 0 deletions api/tests/Api/SnapshotTests/EndpointPerformanceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ private static function getCollectionEndpoints() {
'/auth/cevidb' => false,
'/auth/jubladb' => false,
'/auth/reset_password' => false,
'/auth/resend_activation' => false,
'/invitations' => false,
'/personal_invitations' => false,
default => true
Expand Down
1 change: 1 addition & 0 deletions api/tests/Api/SnapshotTests/ResponseSnapshotTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public static function getCollectionEndpoints() {
'/auth/cevidb' => false,
'/auth/jubladb' => false,
'/auth/reset_password' => false,
'/auth/resend_activation' => false,
'/invitations' => false,
'/personal_invitations' => false,
'/users' => false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20667,6 +20667,84 @@ components:
- password
- profile
type: object
UserActivation-create:
deprecated: false
description: ''
properties:
email:
type:
- 'null'
- string
writeOnly: true
recaptchaToken:
type:
- 'null'
- string
writeOnly: true
type: object
UserActivation.jsonapi:
deprecated: false
description: ''
properties:
data:
properties:
attributes:
properties:
email:
type: ['null', string]
writeOnly: true
recaptchaToken:
type: ['null', string]
writeOnly: true
type: object
id:
type: string
type:
type: string
required:
- id
- type
type: object
type: object
UserActivation.jsonhal-create:
deprecated: false
description: ''
properties:
_links:
properties:
self:
properties:
href:
format: iri-reference
type: string
type: object
type: object
email:
type:
- 'null'
- string
writeOnly: true
recaptchaToken:
type:
- 'null'
- string
writeOnly: true
type: object
UserActivation.jsonld-create:
deprecated: false
description: ''
properties:
email:
type:
- 'null'
- string
writeOnly: true
recaptchaToken:
type:
- 'null'
- string
writeOnly: true
type: object
securitySchemes:
JWT:
bearerFormat: JWT
Expand Down Expand Up @@ -21497,6 +21575,54 @@ paths:
- OAuth
parameters: []
ref: 'PBS MiData OAuth'
/auth/resend_activation:
parameters: []
post:
deprecated: false
description: 'Activation email will be sent to the given email again.'
operationId: api_authresend_activation_post
parameters: []
requestBody:
content:
application/hal+json:
schema:
$ref: '#/components/schemas/UserActivation.jsonhal-create'
application/json:
schema:
$ref: '#/components/schemas/UserActivation-create'
application/ld+json:
schema:
$ref: '#/components/schemas/UserActivation.jsonld-create'
application/vnd.api+json:
schema:
$ref: '#/components/schemas/UserActivation.jsonapi'
text/html:
schema:
$ref: '#/components/schemas/UserActivation-create'
description: 'The new UserActivation resource'
required: true
responses:
204:
content:
application/hal+json:
schema: []
application/json:
schema: []
application/ld+json:
schema: []
application/vnd.api+json:
schema: []
text/html:
schema: []
description: 'UserActivation resource created'
links: []
400:
description: 'Invalid input'
422:
description: 'Unprocessable entity'
summary: 'Request activation email again'
tags:
- UserActivation
/auth/reset_password:
parameters: []
post:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@
"href": "\/profiles{\/id}{?user.collaborations.camp,user.collaborations.camp[]}",
"templated": true
},
"resendActivation": {
"href": "\/auth\/resend_activation",
"templated": false
},
"resetPassword": {
"href": "\/auth\/reset_password{\/id}",
"templated": true
Expand Down
Loading

0 comments on commit ef74f66

Please sign in to comment.