full working version

This commit is contained in:
flo 2025-01-04 04:01:24 +01:00
parent 9565270c19
commit 9e493dcc33
27 changed files with 276 additions and 52 deletions

View File

@ -8,8 +8,12 @@ return [
],
'permissions' => [
'user' => [
'user.change-password',
'user.change-username',
],
'admin' => [
'user.change-password',
'user.change-username',
'user.create',
'user.read-list',
],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 946 B

View File

@ -51,16 +51,10 @@
<div id="message-block" class="message-block">
<p>Hallo {$username},</p>
<br />
<p>Herzlich willkommen beim Template!</p>
<p>Herzlich willkommen bei Template!</p>
<br />
<p>Ich konnte deine Anmeldung nun bestätigen.</p>
<p>Bitte klicke auf <a href="{$confirmationLink}">diesen Link</a> um dein Passwort festzulegen.</p>
<p>Danach ist deine Registierung abgeschlossen und du kannst loslegen!</p>
<br />
<p>Mit grünen Grüßen,</p>
<p>{$growerName}</p>
<br />
<p class="quote">"Smoke weed everyday" - Snoop Dogg</p>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 946 B

View File

@ -55,9 +55,6 @@
<p>Bitte klicke auf <a href="{$passwordResetLink}">diesen Link</a> um ein neues Passwort zu vergeben.</p>
<br />
<p>Wenn du dein Passwort nicht zurückgesetzt hast, kannst du diese Nachricht ignorieren!</p>
<br />
<p>Mit fleißigen Grüßen,</p>
<p>Der Template</p>
</div>
</body>
</html>

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Template\Migrations\Bee;
namespace Template\Migrations\Template;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240827155813 extends AbstractMigration
final class Version20250104024640 extends AbstractMigration
{
public function getDescription(): string
{

View File

@ -2,7 +2,7 @@
declare(strict_types=1);
namespace Bee\Migrations\Bee;
namespace Template\Migrations\Template;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240911191301 extends AbstractMigration
final class Version20250104024641 extends AbstractMigration
{
public function getDescription(): string
{

View File

@ -22,10 +22,8 @@ class ConfirmRegistrationResponseFormatter
return [
'id' => $user->getId()->toString(),
'username' => $user->getUsername(),
'role' => $user->getRole()->getIdentifier(),
'permissions' => $permissions,
'created' => $user->getCreatedAt()->format(DateTimeInterface::RFC3339),
'updated' => $user->getUpdatedAt()->format(DateTimeInterface::RFC3339)
'roleIdentifier' => $user->getRole()->getIdentifier(),
'permissions' => $permissions
];
}
}

View File

@ -7,6 +7,8 @@ namespace Template\API\External\Authentication\ForgotPassword;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Template\Handling\User\Exception\UserNotFoundByMailException;
use Template\Infrastructure\Request\Middleware\AnalyzeHeaderMiddleware;
use Template\Infrastructure\Response\SuccessResponse;
use Template\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use Template\Handling\Authentication\UseCase\ForgotPassword\ForgotPasswordUseCaseHandler;
@ -22,11 +24,16 @@ class ForgotPasswordRequestHandler implements RequestHandlerInterface
) {
}
/**
* @throws UserNotFoundByMailException
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$host = $request->getAttribute(AnalyzeHeaderMiddleware::HOST_ATTRIBUTE);
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
$useCase = $this->builder->build(
$host,
$data['mail'],
);
$result = $this->handler->handle($useCase);

View File

@ -7,6 +7,7 @@ namespace Template\API\External\Authentication\RegisterUser;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Template\Infrastructure\Request\Middleware\AnalyzeHeaderMiddleware;
use Template\Infrastructure\Response\SuccessResponse;
use Template\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use Template\Handling\Registration\UseCase\RegisterUser\RegisterUserUseCaseHandler;
@ -24,9 +25,11 @@ class RegisterUserRequestHandler implements RequestHandlerInterface
public function handle(ServerRequestInterface $request): ResponseInterface
{
$host = $request->getAttribute(AnalyzeHeaderMiddleware::HOST_ATTRIBUTE);
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);
$useCase = $this->builder->build(
$host,
$data['username'],
$data['mail'],
);

View File

@ -8,6 +8,8 @@ use Ramsey\Uuid\Uuid;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Template\Handling\User\Exception\UserPasswordConfirmationMismatchException;
use Template\Handling\User\Exception\UserPasswordTokenNotFoundByIdException;
use Template\Infrastructure\Response\SuccessResponse;
use Template\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use Template\Handling\Authentication\UseCase\ResetPassword\ResetPasswordUseCaseHandler;
@ -23,6 +25,10 @@ class ResetPasswordRequestHandler implements RequestHandlerInterface
) {
}
/**
* @throws UserPasswordTokenNotFoundByIdException
* @throws UserPasswordConfirmationMismatchException
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
$data = $request->getAttribute(AnalyzeBodyMiddleware::JSON_DATA);

View File

@ -8,6 +8,7 @@ use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Template\Data\Business\Entity\User;
use Template\Handling\User\Exception\UserWrongPasswordException;
use Template\Infrastructure\Response\SuccessResponse;
use Template\Infrastructure\Request\Middleware\AnalyzeBodyMiddleware;
use Template\Handling\User\UseCase\ChangePassword\ChangePasswordUseCaseHandler;
@ -24,6 +25,9 @@ class ChangePasswordRequestHandler implements RequestHandlerInterface
) {
}
/**
* @throws UserWrongPasswordException
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
/** @var User $user */

View File

@ -32,10 +32,10 @@ return [
LogoutUserUseCaseHandler::class => AutoWiringFactory::class,
LogoutUserUseCaseBuilder::class => AutoWiringFactory::class,
/// ForgotPassword
ForgotPasswordUseCaseHandler::class => AutoWiringFactory::class,
ForgotPasswordUseCaseHandler::class => InjectionFactory::class,
ForgotPasswordUseCaseBuilder::class => AutoWiringFactory::class,
/// ResetPassword
ResetPasswordUseCaseHandler::class => AutoWiringFactory::class,
ResetPasswordUseCaseHandler::class => InjectionFactory::class,
ResetPasswordUseCaseBuilder::class => AutoWiringFactory::class,
]
];

View File

@ -7,10 +7,16 @@ namespace Template\Handling\Authentication\UseCase\ForgotPassword;
class ForgotPasswordUseCase
{
public function __construct(
private readonly string $domain,
private readonly string $mail,
) {
}
public function getDomain(): string
{
return $this->domain;
}
public function getMail(): string
{
return $this->mail;

View File

@ -7,9 +7,11 @@ namespace Template\Handling\Authentication\UseCase\ForgotPassword;
class ForgotPasswordUseCaseBuilder
{
public function build(
string $domain,
string $mail,
): ForgotPasswordUseCase {
return new ForgotPasswordUseCase(
$domain,
$mail,
);
}

View File

@ -4,14 +4,68 @@ declare(strict_types=1);
namespace Template\Handling\Authentication\UseCase\ForgotPassword;
use Reinfi\DependencyInjection\Annotation\Inject;
use Reinfi\DependencyInjection\Annotation\InjectDoctrineRepository;
use Template\Data\Business\Entity\User;
use Template\Data\Business\Manager\EntityManager;
use Template\Data\Business\Repository\UserRepository;
use Template\Handling\User\Builder\UserPasswordTokenBuilder;
use Template\Handling\User\Exception\UserNotFoundByMailException;
use Template\Infrastructure\Mail\Service\MailService;
class ForgotPasswordUseCaseHandler
{
private const RESET_PASSWORD_LINK = "https://%s/auth/reset-password/%s";
/**
* @InjectDoctrineRepository(
* entityManager="Template\Data\Business\Manager\EntityManager",
* entity="Template\Data\Business\Entity\User"
* )
* @Inject("Template\Handling\User\Builder\UserPasswordTokenBuilder")
* @Inject("Template\Infrastructure\Mail\Service\MailService")
* @Inject("Template\Data\Business\Manager\EntityManager")
*/
public function __construct(
private readonly UserRepository $userRepository,
private readonly UserPasswordTokenBuilder $passwordTokenBuilder,
private readonly MailService $mailService,
private readonly EntityManager $entityManager,
) {
}
/**
* @throws UserNotFoundByMailException
*/
public function handle(ForgotPasswordUseCase $useCase): ForgotPasswordUseCaseResult
{
$mail = $useCase->getMail();
/** @var User $user */
$user = $this->userRepository->findOneBy(['mail' => $mail]);
if ($user === null) {
throw new UserNotFoundByMailException($mail);
}
$passwordToken = $this->passwordTokenBuilder->build($user);
$this->entityManager->persist($passwordToken);
$this->entityManager->flush();
$this->mailService->enqueue(
'reset-password',
templateData: [
'username' => $user->getUsername(),
'passwordResetLink' => sprintf(
self::RESET_PASSWORD_LINK,
$useCase->getDomain(),
$passwordToken->getId()->toString()
)
],
recipient: $mail,
);
return new ForgotPasswordUseCaseResult();
}
}

View File

@ -4,14 +4,64 @@ declare(strict_types=1);
namespace Template\Handling\Authentication\UseCase\ResetPassword;
use Reinfi\DependencyInjection\Annotation\Inject;
use Reinfi\DependencyInjection\Annotation\InjectDoctrineRepository;
use Template\Data\Business\Entity\UserPasswordToken;
use Template\Data\Business\Manager\EntityManager;
use Template\Data\Business\Repository\UserPasswordTokenRepository;
use Template\Handling\User\Exception\UserPasswordConfirmationMismatchException;
use Template\Handling\User\Exception\UserPasswordTokenNotFoundByIdException;
use Template\Infrastructure\Encryption\Client\EncryptionClient;
class ResetPasswordUseCaseHandler
{
/**
* @InjectDoctrineRepository(
* entityManager="Template\Data\Business\Manager\EntityManager",
* entity="Template\Data\Business\Entity\UserPasswordToken"
* )
* @Inject("Template\Infrastructure\Encryption\Client\EncryptionClient")
* @Inject("Template\Data\Business\Manager\EntityManager")
*/
public function __construct(
private readonly UserPasswordTokenRepository $repository,
private readonly EncryptionClient $encryptionClient,
private readonly EntityManager $entityManager,
) {
}
/**
* @throws UserPasswordTokenNotFoundByIdException
* @throws UserPasswordConfirmationMismatchException
*/
public function handle(ResetPasswordUseCase $useCase): ResetPasswordUseCaseResult
{
$passwordTokenId = $useCase->getPasswordToken();
// Load and check password token
/** @var ?UserPasswordToken $passwordToken */
$passwordToken = $this->repository->findOneBy(['id' => $passwordTokenId]);
if ($passwordToken === null) {
throw new UserPasswordTokenNotFoundByIdException($passwordTokenId->toString());
}
if ($useCase->getNewPassword() !== $useCase->getPasswordConfirmation()) {
throw new UserPasswordConfirmationMismatchException();
}
// Update Password
$user = $passwordToken->getUser();
$user->setPassword($this->encryptionClient->encrypt($useCase->getNewPassword()));
// Save to DB
$this->entityManager->remove($passwordToken);
$this->entityManager->persist($user);
if ($user->getSession() !== null) {
$this->entityManager->remove($user->getSession());
}
$this->entityManager->flush();
return new ResetPasswordUseCaseResult();
}
}

View File

@ -7,6 +7,8 @@ namespace Template\Handling\Registration\Pipeline\ConfirmRegistration\Step;
use Template\Handling\Registration\Exception\RegistrationWithIdentifierAlreadyExistsException;
use Template\Handling\Registration\Handler\Command\ConfirmRegistration\ConfirmRegistrationCommand;
use Template\Handling\Registration\Rule\RegistrationWithIdentifierAlreadyExistsRule;
use Template\Handling\Registration\UseCase\ConfirmRegistration\ConfirmRegistrationUseCase;
use Template\Handling\User\Builder\UserBuilder;
use Template\Handling\User\Exception\UserPasswordMismatchException;
use Template\Handling\User\Exception\UserWithIdentifierAlreadyExistsException;
use Template\Handling\User\Handler\Command\CreateUser\CreateUserCommandBuilder;
@ -15,25 +17,26 @@ use Template\Handling\User\Rule\UserPasswordMatchRule;
use Template\Handling\User\Rule\UserWithIdentifierAlreadyExistsRule;
use teewurst\Pipeline\PipelineInterface;
use teewurst\Pipeline\TaskInterface;
use Template\Infrastructure\Rbac\Exception\RoleNotFoundByIdentifierException;
class CreateUserStep implements TaskInterface
{
public function __construct(
private readonly CreateUserCommandBuilder $createUserCommandBuilder,
private readonly CreateUserCommandHandler $createUserCommandHandler,
private readonly UserBuilder $builder,
private readonly UserPasswordMatchRule $passwordMatchRule,
) {
}
/**
* @throws UserPasswordMismatchException
* @throws RoleNotFoundByIdentifierException
*/
public function __invoke(
$payload,
PipelineInterface $pipeline
): void
{
/** @var ConfirmRegistrationCommand $useCase */
/** @var ConfirmRegistrationUseCase $useCase */
$useCase = $payload->getUseCase();
$registration = $payload->getRegistration();
@ -46,12 +49,12 @@ class CreateUserStep implements TaskInterface
throw new UserPasswordMismatchException();
}
$createUserCommand = $this->createUserCommandBuilder->build(
$user = $this->builder->build(
username: $registration->getUsername(),
roleIdentifier: 'user',
mail: $registration->getMail(),
password: $useCase->getPassword(),
password: $useCase->getPassword()
);
$user = $this->createUserCommandHandler->execute($createUserCommand);
$payload->setUser($user);

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Template\Handling\Registration\Pipeline\RegisterUser\Step;
use Template\Infrastructure\Request\Service\RequestService;
use Template\Infrastructure\Mail\Service\MailService;
use teewurst\Pipeline\PipelineInterface;
use teewurst\Pipeline\TaskInterface;
@ -13,7 +13,7 @@ class SendMailStep implements TaskInterface
private const CONFIRM_LINK = 'https://%s/auth/registration/%s';
public function __construct(
private RequestService $requestService
private readonly MailService $mailService,
) {
}
@ -25,23 +25,17 @@ class SendMailStep implements TaskInterface
$useCase = $payload->getUseCase();
$registration = $payload->getRegistration();
$this->requestService->request(
'notification',
'send-mail',
[
'template-identifier' => 'new-account',
'sender' => 'info@stack-up.de',
'recipient' => $useCase->getMail(),
'data' => [
$this->mailService->enqueue(
'registration',
templateData: [
'username' => $useCase->getUsername(),
'confirmationLink' =>
sprintf(
'confirmationLink' => sprintf(
self::CONFIRM_LINK,
$useCase->getHost(),
$registration->getId()->toString()
)
]
]
],
recipient: $useCase->getMail(),
);
$pipeline->next()($payload, $pipeline);

View File

@ -7,11 +7,17 @@ namespace Template\Handling\Registration\UseCase\RegisterUser;
class RegisterUserUseCase
{
public function __construct(
private readonly string $host,
private readonly string $username,
private readonly string $mail,
) {
}
public function getHost(): string
{
return $this->host;
}
public function getUsername(): string
{
return $this->username;

View File

@ -7,10 +7,12 @@ namespace Template\Handling\Registration\UseCase\RegisterUser;
class RegisterUserUseCaseBuilder
{
public function build(
string $host,
string $username,
string $mail,
): RegisterUserUseCase {
return new RegisterUserUseCase(
$host,
$username,
$mail,
);

View File

@ -1,6 +1,7 @@
<?php
use Template\Handling\User\Builder\UserBuilder;
use Template\Handling\User\Builder\UserPasswordTokenBuilder;
use Template\Handling\User\UseCase\Create\CreateUseCaseHandler;
use Template\Handling\User\UseCase\Create\CreateUseCaseBuilder;
use Template\Handling\User\UseCase\ChangePassword\ChangePasswordUseCaseHandler;
@ -20,6 +21,7 @@ return [
/// Builder
UserBuilder::class => InjectionFactory::class,
UserPasswordTokenBuilder::class => InjectionFactory::class,
/// Rule
UserWithIdentifierAlreadyExistsRule::class => InjectionFactory::class,

View File

@ -0,0 +1,19 @@
<?php
namespace Template\Handling\User\Builder;
use Template\Data\Business\Entity\User;
use Template\Data\Business\Entity\UserPasswordToken;
class UserPasswordTokenBuilder
{
public function build(
User $user
): UserPasswordToken
{
$userPasswordToken = new UserPasswordToken();
$userPasswordToken->setUser($user);
return $userPasswordToken;
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Template\Handling\User\Exception;
use Template\Infrastructure\Exception\ErrorCode;
use Template\Infrastructure\Exception\ErrorDomain;
use Template\Infrastructure\Exception\Exception\TemplateException;
class UserNotFoundByMailException extends TemplateException {
private const MESSAGE = 'The user with the mail %s was not found!';
public function __construct(string $identifier)
{
parent::__construct(
sprintf(
self::MESSAGE,
$identifier
),
ErrorDomain::User,
ErrorCode::NotFound
);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Template\Handling\User\Exception;
use Template\Infrastructure\Exception\ErrorCode;
use Template\Infrastructure\Exception\ErrorDomain;
use Template\Infrastructure\Exception\Exception\Exception;
use Template\Infrastructure\Exception\Exception\TemplateException;
class UserPasswordConfirmationMismatchException extends TemplateException {
private const MESSAGE = 'The two passwordConfirmation does not match the newPassword';
public function __construct()
{
parent::__construct(
self::MESSAGE,
ErrorDomain::UserPassword,
ErrorCode::Mismatch,
);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace Template\Handling\User\Exception;
use Template\Infrastructure\Exception\ErrorCode;
use Template\Infrastructure\Exception\ErrorDomain;
use Template\Infrastructure\Exception\Exception\Exception;
use Template\Infrastructure\Exception\Exception\TemplateException;
class UserPasswordTokenNotFoundByIdException extends TemplateException {
private const MESSAGE = 'User password token with the id %s was not found!';
public function __construct(
string $identification
)
{
parent::__construct(
sprintf(
self::MESSAGE,
$identification
),
ErrorDomain::UserPassword,
ErrorCode::Mismatch,
);
}
}