Skip to content
Merged
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
5 changes: 5 additions & 0 deletions bin/hackathon
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#!/usr/bin/env bash

# --- Path Initialization ---
# Get the directory of the script and switch to the project root (one level up)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR/.." || exit 1

# --- Configuration ---
CONTAINER_PHP="php"
DOCKER_COMPOSE="docker compose"
Expand Down
2 changes: 1 addition & 1 deletion config/autoload/project.develope.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

return [
'project' => [
'uri' => 'http://localhost',
'uri' => 'http://localhost:5173',
],
'api' => [
'access' => [
Expand Down
3 changes: 2 additions & 1 deletion config/autoload/project.global.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

return [
'project' => [
'uri' => 'dev.ownhackathon.de',
'uri' => 'https:\\dev.ownhackathon.de',
'senderEmail' => 'no-replay@ownhackathon.de',
],
'api' => [
'access' => [
Expand Down
2 changes: 1 addition & 1 deletion config/autoload/token.action.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
'access' => [
'key' => 'b:?Y@5JCWF:yi{o>irc(3$HFcR-#b\SA',
'algorithmus' => 'HS512',
'duration' => 60 * 15,
'duration' => 60 * 5,
'iss' => 'localhost',
'aud' => 'localhost',
],
Expand Down
2 changes: 1 addition & 1 deletion config/autoload/token.develope.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
'access' => [
'key' => 'b:?Y@5JCWF:yi{o>irc(3$HFcR-#b\SA',
'algorithmus' => 'HS512',
'duration' => 60 * 15,
'duration' => 60 * 5,
'iss' => 'localhost',
'aud' => 'localhost',
],
Expand Down
2 changes: 1 addition & 1 deletion config/autoload/token.testing.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
'access' => [
'key' => 'b:?Y@5JCWF:yi{o>irc(3$HFcR-#b\SA',
'algorithmus' => 'HS512',
'duration' => 60 * 15,
'duration' => 60 * 5,
'iss' => 'localhost',
'aud' => 'localhost',
],
Expand Down
2 changes: 1 addition & 1 deletion config/pipeline.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@

return function (Application $app, MiddlewareFactory $factory, ContainerInterface $container): void {
$app->pipe([
CorsMiddleware::class,
ApiErrorHandlerMiddleware::class,
ServerUrlMiddleware::class,
BodyParamsMiddleware::class,

CorsMiddleware::class,
RouteMiddleware::class,

ImplicitHeadMiddleware::class,
Expand Down
32 changes: 11 additions & 21 deletions public/api/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"Account"
],
"summary": "Return of a new access token",
"operationId": "1ee7f36021506322a0e202b1fb32f278",
"operationId": "8153437a40e6c68ceb15c851fe31e2bc",
"responses": {
"200": {
"description": "Success",
Expand Down Expand Up @@ -47,7 +47,7 @@
"Account"
],
"summary": "Activate the account",
"operationId": "db81d65a9e026816e8f2ea40f73a394b",
"operationId": "df8fdb2be361e5167853062f340f4425",
"parameters": [
{
"name": "token",
Expand Down Expand Up @@ -93,7 +93,7 @@
"Account"
],
"summary": "Creates a token for password reset. Sending via E-Mail",
"operationId": "faec81c539d68fbab3b442b162ad7c84",
"operationId": "1ab5a49dfe70ff9e4be96e3b4fd58802",
"requestBody": {
"description": "Set Password for a Account",
"required": true,
Expand All @@ -118,7 +118,7 @@
"Account"
],
"summary": "Set Password to the account",
"operationId": "c5f1c414d69d6f318ab519d1746570e4",
"operationId": "3dfa8cc9f974f6db93df1a49b170fc75",
"parameters": [
{
"name": "token",
Expand Down Expand Up @@ -163,9 +163,9 @@
"tags": [
"Account"
],
"summary": "Endpoint to register a new user account.",
"description": "Create Account",
"operationId": "8a212b9a005ff2dd5aeea1756d25bd05",
"summary": "Endpoint to register a new user account or request a password reset.",
"description": "Starts the account process for the given email address. \n\n**Security Note:** This endpoint always returns a 200 OK status to prevent user enumeration. The user will not know if an account already exists based on the API response.",
"operationId": "969568dfb3cf23c770ed1a16782476cd",
"requestBody": {
"description": "The email address for the new account",
"required": true,
Expand All @@ -179,17 +179,7 @@
},
"responses": {
"200": {
"description": "Success"
},
"400": {
"description": "Bad request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HttpResponseMessage"
}
}
}
"description": "The request was processed successfully. Depending on the state of the account, one of the following actions will occur:\n1. **Account does not exist:** A token to activate the new account will be sent to the email address.\n2. **Account already exists:** A token to reset the password will be sent to the email address."
}
}
}
Expand All @@ -200,7 +190,7 @@
"Account"
],
"summary": "Attempts to log in an account using transferred data",
"operationId": "4573a8211c6a27b677250eb46ca168ee",
"operationId": "9b6381b1663be08be716cc9ca3f04d2b",
"requestBody": {
"description": "Account data for authentication",
"required": true,
Expand Down Expand Up @@ -252,7 +242,7 @@
"Account"
],
"summary": "Attempts to log out an account",
"operationId": "1154528e1da5f90aa3b356c63502b533",
"operationId": "fd1f895986002b7c0d208a40b3cf9dd5",
"responses": {
"200": {
"description": "Success",
Expand Down Expand Up @@ -283,7 +273,7 @@
"System Information"
],
"summary": "Returns the current time in Unix format",
"operationId": "ffea4e020f3a4868f9f018e280509330",
"operationId": "8756d19e2e4758137f374e8015adad45",
"responses": {
"200": {
"description": "Success",
Expand Down
22 changes: 10 additions & 12 deletions src/App/ConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@

namespace App;

use Envms\FluentPDO\Query;
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Laminas\ServiceManager\Factory\InvokableFactory;
use App\Hydrator\AccountAccessAuthHydratorInterface;
use App\Hydrator\AccountActivationHydratorInterface;
use App\Hydrator\AccountHydratorInterface;
use App\Hydrator\TokenHydratorInterface;
use App\Service\Account\AccountService;
use App\Service\Authentication\AuthenticationService;
use App\Service\ClientIdentification\ClientIdentificationService;
use App\Service\Email\EmailService;
use App\Service\Email\EmailServiceFactory;
use App\Service\Token\AccessTokenService;
use App\Service\Token\ActivationTokenService;
use App\Service\Token\ActivationTokenServiceFactory;
use App\Service\Token\PasswordTokenService;
use App\Service\Token\PasswordTokenServiceFactory;
use App\Service\Token\RefreshTokenService;
use App\Table\AccountAccessAuthTable;
use App\Table\AccountActivationTable;
Expand All @@ -33,8 +34,10 @@
use Core\Repository\TokenRepositoryInterface;
use Core\Store;
use Core\Utils\UuidFactoryInterface;
use Envms\FluentPDO\Query;
use Laminas\ServiceManager\AbstractFactory\ConfigAbstractFactory;
use Laminas\ServiceManager\Factory\InvokableFactory;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\MailerInterface;

class ConfigProvider
{
Expand Down Expand Up @@ -102,9 +105,10 @@ public function getDependencies(): array
Service\Account\AccountService::class => ConfigAbstractFactory::class,
Service\Authentication\AuthenticationService::class => InvokableFactory::class,
Service\ClientIdentification\ClientIdentificationService::class => InvokableFactory::class,
EmailService::class => EmailServiceFactory::class,
Service\Token\AccessTokenService::class => Service\Token\AccessTokenServiceFactory::class,
Service\Token\ActivationTokenService::class => ConfigAbstractFactory::class,
Service\Token\PasswordTokenService::class => ConfigAbstractFactory::class,
Service\Token\ActivationTokenService::class => ActivationTokenServiceFactory::class,
Service\Token\PasswordTokenService::class => PasswordTokenServiceFactory::class,
Service\Token\RefreshTokenService::class => Service\Token\RefreshTokenServiceFactory::class,
Table\AccountAccessAuthTable::class => ConfigAbstractFactory::class,
Table\AccountActivationTable::class => ConfigAbstractFactory::class,
Expand Down Expand Up @@ -227,12 +231,6 @@ public function getAbstractFactoryConfig(): array
PasswordTokenService::class,
UuidFactoryInterface::class,
],
Service\Token\ActivationTokenService::class => [
MailerInterface::class,
],
Service\Token\PasswordTokenService::class => [
MailerInterface::class,
],
Table\AccountAccessAuthTable::class => [
Query::class,
AccountAccessAuthHydratorInterface::class,
Expand Down
17 changes: 7 additions & 10 deletions src/App/Handler/Account/AccountRegisterHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
use Laminas\Diactoros\Response\JsonResponse;
use OpenApi\Attributes as OA;
use App\DTO\EMail\EMail;
use App\DTO\Response\HttpResponseMessage;
use Core\Enum\Message\StatusMessage;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
Expand All @@ -16,8 +14,10 @@
{
#[OA\Post(
path: '/account',
description: 'Create Account',
summary: 'Endpoint to register a new user account.',
description: "Starts the account process for the given email address. \n\n" .
"**Security Note:** This endpoint always returns a 200 OK status to prevent user enumeration. " .
"The user will not know if an account already exists based on the API response.",
summary: 'Endpoint to register a new user account or request a password reset.',
tags: ['Account'],
)]
#[OA\RequestBody(
Expand All @@ -27,12 +27,9 @@
)]
#[OA\Response(
response: HTTP::STATUS_OK,
description: StatusMessage::SUCCESS->value,
)]
#[OA\Response(
response: HTTP::STATUS_BAD_REQUEST,
description: StatusMessage::BAD_REQUEST->value,
content: [new OA\JsonContent(ref: HttpResponseMessage::class)]
description: "The request was processed successfully. Depending on the state of the account, one of the following actions will occur:\n" .
"1. **Account does not exist:** A token to activate the new account will be sent to the email address.\n" .
'2. **Account already exists:** A token to reset the password will be sent to the email address.',
)]
public function handle(ServerRequestInterface $request): ResponseInterface
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
}

$accountAccessAuth = new AccountAccessAuth(
1,
null,
$account->id,
'default',
$refreshToken->refreshToken,
Expand Down
28 changes: 28 additions & 0 deletions src/App/Service/Email/EmailService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php declare(strict_types=1);

namespace App\Service\Email;

use Core\Type\Email as EmailType;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

readonly class EmailService
{
public function __construct(
private MailerInterface $mailer,
private EmailType $senderEmail,
) {
}

public function send(EmailType $email, string $plainText, string $html, string $subject): void
{
$email = new Email()
->from($this->senderEmail->toString())
->to($email->toString())
->subject($subject)
->text($plainText)
->html($html);

$this->mailer->send($email);
}
}
18 changes: 18 additions & 0 deletions src/App/Service/Email/EmailServiceFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php declare(strict_types=1);

namespace App\Service\Email;

use Core\Type\Email;
use Psr\Container\ContainerInterface;
use Symfony\Component\Mailer\MailerInterface;

readonly class EmailServiceFactory
{
public function __invoke(ContainerInterface $container): EmailService
{
$mailer = $container->get(MailerInterface::class);
$senderEmail = new Email($container->get('config')['project']['senderEmail']);

return new EmailService($mailer, $senderEmail);
}
}
50 changes: 40 additions & 10 deletions src/App/Service/Token/ActivationTokenService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,59 @@

namespace App\Service\Token;

use App\Service\Email\EmailService;
use Core\Entity\Account\AccountActivationInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;

use function sprintf;

readonly class ActivationTokenService
{
public function __construct(
private MailerInterface $mailer,
private EmailService $emailService,
private string $projectUri,
) {
}

public function sendEmail(AccountActivationInterface $activation): void
{
$text = sprintf('Your token to activate your Account: %s', $activation->token->getHex()->toString());
$text = sprintf(
'Hallo!
Vielen Dank für Ihr Interesse an ownHackathon.

$email = new Email()
->from('no-reply@stormannsgal.de')
->to($activation->email->toString())
->subject('Account Activation Code')
->text($text);
Sie haben eine Registrierung mit dieser E-Mail-Adresse angefordert.
Um den Vorgang abzuschließen und Ihr Benutzerkonto zu erstellen,
bestätigen Sie bitte Ihre E-Mail-Adresse über den folgenden Link:

$this->mailer->send($email);
%s/app/account/activation/%s

Hinweis: Erst wenn Sie diesen Link anklicken, wird Ihr Account verbindlich angelegt. Der Link ist zeitlich begrenzt gültig.

Falls Sie diese Anfrage nicht selbst gestellt haben, können Sie diese Nachricht einfach ignorieren.
Es wird in diesem Fall kein Konto erstellt und Ihre E-Mail-Adresse wird nicht bei uns gespeichert.

Mit freundlichen Grüßen,
Ihr Team von ownHackathon',
$this->projectUri,
$activation->token->getHex()->toString()
);

$html = sprintf(
'<p>Hallo!</p>
<p>Vielen Dank für Ihr Interesse an ownHackathon.</p>
<p>Sie haben eine Registrierung mit dieser E-Mail-Adresse angefordert.
Um den Vorgang abzuschließen und Ihr Benutzerkonto zu erstellen,
bestätigen Sie bitte Ihre E-Mail-Adresse über den folgenden Link:</p>
<p><a href=\'%s/app/account/activation/%s\'>Account erstellen</a></p>
<p><strong>Hinweis:</strong> Erst wenn Sie diesen Link anklicken, wird Ihr Account verbindlich angelegt.
Der Link ist nur für begrenzte Zeit gültig.</p>
<p>Falls Sie diese Anfrage nicht selbst gestellt haben, können Sie diese Nachricht einfach ignorieren.
Es wird in diesem Fall kein Konto erstellt und Ihre E-Mail-Adresse wird nicht dauerhaft bei uns gespeichert.</p>
<p>Mit freundlichen Grüßen,<br>
Ihr Team von ownHackathon</p>',
$this->projectUri,
$activation->token->getHex()->toString()
);

$this->emailService->send($activation->email, $text, $html, 'Account Aktivierung');
}
}
Loading