Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/email verification #53

Draft
wants to merge 21 commits into
base: feat/reset-password
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ services:
CADDY_PROTOCOL: ${CADDY_PROTOCOL:-https}
HTTP_PORT: ${HTTP_PORT:-80}
HTTPS_PORT: ${HTTPS_PORT:-443}
MAILER_DSN: ${MAILER_DSN:-smtp://localhost}
MAILER_DSN: ${MAILER_DSN:-smtp://mail.puc.local:1025}
cap_add:
- NET_ADMIN
depends_on:
Expand Down
1 change: 1 addition & 0 deletions symfony/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"symfony/uid": "7.1.*",
"symfony/validator": "7.1.*",
"symfony/yaml": "7.1.*",
"symfonycasts/verify-email-bundle": "^1.17",
"tilleuls/forgot-password-bundle": "^1.6",
"vich/uploader-bundle": "*"
},
Expand Down
48 changes: 47 additions & 1 deletion symfony/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions symfony/config/bundles.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
Vich\UploaderBundle\VichUploaderBundle::class => ['all' => true],
Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
CoopTilleuls\ForgotPasswordBundle\CoopTilleulsForgotPasswordBundle::class => ['all' => true],
SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true],
];
3 changes: 2 additions & 1 deletion symfony/config/packages/security.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ security:
check_path: /auth
username_path: email
password_path: password
success_handler: lexik_jwt_authentication.handler.authentication_success
success_handler: App\Security\Authenticator\AuthenticationSuccessHandler
failure_handler: lexik_jwt_authentication.handler.authentication_failure

# activate different ways to authenticate
Expand All @@ -43,6 +43,7 @@ security:
- { path: ^/api/docs, roles: PUBLIC_ACCESS }
- { path: ^/auth, roles: PUBLIC_ACCESS }
- { path: ^/api/users, roles: PUBLIC_ACCESS, methods: [POST]}
- { path: ^/api/users/verify_email, roles: PUBLIC_ACCESS, methods: [GET, POST]}
- { path: ^/api/actors, roles: PUBLIC_ACCESS, methods: [GET] }
- { path: ^/api/projects, roles: PUBLIC_ACCESS, methods: [GET] }
- { path: ^/api/actor_expertises, roles: PUBLIC_ACCESS, methods: [GET] }
Expand Down
2 changes: 2 additions & 0 deletions symfony/config/packages/verify_email.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
symfonycasts_verify_email:
lifetime: 172800 # 2 days
5 changes: 3 additions & 2 deletions symfony/config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ imports:
parameters:
domain: '%env(DOMAIN)%'
domain.url: 'https://%env(DOMAIN)%'
email_verifier.lifetime: 172800 # 2 days

services:
# default configuration for services in *this* file
Expand All @@ -25,9 +26,9 @@ services:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Services/State/'
- '../src/Services/Serializer/'

- '../src/Services/Service/'
- '../src/Services/State/'

# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
3 changes: 2 additions & 1 deletion symfony/config/services/State/state.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ services:
_defaults:
autowire: true
autoconfigure: true

App\Services\State\:
resource: '../../../src/Services/State/'
resource: '../../../src/Services/State/'
14 changes: 14 additions & 0 deletions symfony/config/services/service/email_verifier.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
services:
_defaults:
autowire: true
autoconfigure: true

App\Services\Service\EmailVerifier\:
resource: '../../../src/Services/Service/EmailVerifier/'

App\Services\Service\EmailVerifier\EmailVerifierSignature:
arguments:
$lifetime: '%email_verifier.lifetime%'
$userSignatureProperties: ['id', 'email']
$secret: '%env(APP_SECRET)%'

10 changes: 10 additions & 0 deletions symfony/config/services/service/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
services:
_defaults:
autowire: true
autoconfigure: true

App\Services\Service\:
resource: '../../../src/Services/Service/'
exclude:
- '../../../src/Services/Service/EmailVerifier'

8 changes: 8 additions & 0 deletions symfony/fixtures/dev/users.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
App\Entity\User\User:
user_yv:
firstName: yoh
lastName: val
email: [email protected]
plainPassword: [email protected]
roles: [ 'ROLE_ADMIN' ]
isValidated: true
32 changes: 32 additions & 0 deletions symfony/migrations/Version20241220153020.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241220153020 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}

public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE "user" ADD has_seen_requested_roles BOOLEAN NOT NULL');
}

public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE "user" DROP has_seen_requested_roles');
}
}
61 changes: 52 additions & 9 deletions symfony/src/Entity/User/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@
use App\Model\Enums\UserRoles;
use App\Repository\User\UserRepository;
use App\Security\Voter\UserVoter;
use App\Services\Service\EmailVerifier\Dto\EmailVerifierSendDto;
use App\Services\Service\EmailVerifier\Dto\EmailVerifierVerifyDto;
use App\Services\Service\EmailVerifier\Exception\SignatureParamsException;
use App\Services\State\Processor\User\UserVerifyEmailProcessor;
use App\Services\State\Provider\CurrentUserProvider;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\Signature\Exception\ExpiredSignatureException;
use Symfony\Component\Security\Core\Signature\Exception\InvalidSignatureException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Serializer\Attribute\Groups;
Expand All @@ -33,6 +39,27 @@
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
#[UniqueEntity('email')]
#[ApiResource(
operations: [
new Post(
uriTemplate: '/users/verify_email/send',
processor: UserVerifyEmailProcessor::class,
input: EmailVerifierSendDto::class,
status: 204
),
new Post(
uriTemplate: '/users/verify_email/verify',
input: EmailVerifierVerifyDto::class,
processor: UserVerifyEmailProcessor::class,
status: 204,
exceptionToStatus: [
ExpiredSignatureException::class => 410,
InvalidSignatureException::class => 400,
SignatureParamsException::class => 400,
]
),
]
)]
#[ApiResource(
operations: [
new Get(
Expand Down Expand Up @@ -109,17 +136,15 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[SerializedName('password')]
private ?string $plainPassword = null;

/**
* @var Collection<int, Actor>
*/
#[ORM\OneToMany(targetEntity: Actor::class, mappedBy: 'createdBy', orphanRemoval: true)]
private Collection $actorsCreated;

#[ORM\Column(nullable: true)]
#[Assert\Choice(choices: self::ACCEPTED_ROLES, multiple: true)]
#[Groups([self::GROUP_READ, self::GROUP_GETME, self::GROUP_WRITE])]
private ?array $requestedRoles = null;

#[ORM\Column(type: Types::BOOLEAN)]
#[Groups([self::GROUP_GETME, self::GROUP_WRITE])]
private bool $hasSeenRequestedRoles = false;

#[ORM\Column(length: 255, nullable: true)]
#[Groups([self::GROUP_READ, self::GROUP_GETME, self::GROUP_WRITE])]
private ?string $organisation = null;
Expand All @@ -144,6 +169,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
#[Groups([self::GROUP_READ, self::GROUP_GETME, self::GROUP_WRITE])]
private ?string $description = null;

/**
* @var Collection<int, Actor>
*/
#[ORM\OneToMany(targetEntity: Actor::class, mappedBy: 'createdBy', orphanRemoval: true)]
private Collection $actorsCreated;

/**
* @var Collection<int, UserLike>
*/
Expand Down Expand Up @@ -299,7 +330,7 @@ public function setLastName(string $lastName): static
}

#[Groups([Project::GET_FULL, Project::GET_PARTIAL, Actor::ACTOR_READ_ITEM, Resource::GET_FULL])]
public function getFullName(): ?string
public function getFullName(): string
{
return $this->firstName.' '.$this->lastName;
}
Expand All @@ -316,6 +347,18 @@ public function setRequestedRoles(?array $requestedRoles): static
return $this;
}

public function hasSeenRequestedRoles(): ?bool
{
return $this->hasSeenRequestedRoles;
}

public function setHasSeenRequestedRoles(bool $hasSeenRequestedRoles): static
{
$this->hasSeenRequestedRoles = $hasSeenRequestedRoles;

return $this;
}

public function getOrganisation(): ?string
{
return $this->organisation;
Expand Down Expand Up @@ -352,12 +395,12 @@ public function setPhone(?string $phone): static
return $this;
}

public function getsignUpMessage(): ?string
public function getSignUpMessage(): ?string
{
return $this->signUpMessage;
}

public function setsignUpMessage(?string $signUpMessage): static
public function setSignUpMessage(?string $signUpMessage): static
{
$this->signUpMessage = $signUpMessage;

Expand Down
2 changes: 1 addition & 1 deletion symfony/src/Entity/User/UserLike.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use App\Repository\User\UserLikeRepository;
use App\Services\State\Provider\UserLikeProvider;
use App\Services\State\Provider\User\UserLikeProvider;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Uid\Uuid;

Expand Down
12 changes: 9 additions & 3 deletions symfony/src/Event/EventListener/UserListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@

use App\Entity\User\User;
use App\Security\PasswordHasher;
use App\Services\Mailer\User\UserEmailVerifierMailer;
use Doctrine\Bundle\DoctrineBundle\Attribute\AsEntityListener;
use Doctrine\ORM\Events;
use Doctrine\Persistence\Event\LifecycleEventArgs;

#[AsEntityListener(event: Events::prePersist, method: 'hashPassword', entity: User::class)]
#[AsEntityListener(event: Events::postPersist, method: 'postPersist', entity: User::class)]
#[AsEntityListener(event: Events::preUpdate, method: 'hashPassword', entity: User::class)]
class UserListener
{
private PasswordHasher $passwordHasher;
public function __construct(
private readonly PasswordHasher $passwordHasher,
private readonly UserEmailVerifierMailer $userEmailVerifierMailer,
) {
}

public function __construct(PasswordHasher $passwordHasher)
public function postPersist(User $user, LifecycleEventArgs $event): void
{
$this->passwordHasher = $passwordHasher;
$this->userEmailVerifierMailer->send($user);
}

public function hashPassword(User $user, LifecycleEventArgs $event): void
Expand Down
5 changes: 5 additions & 0 deletions symfony/src/Repository/User/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public function upgradePassword(PasswordAuthenticatedUserInterface $user, string
}

$user->setPassword($newHashedPassword);
$this->save($user);
}

public function save(User $user): void
{
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
}
Expand Down
Loading
Loading