From 9049f1581b1fe5dee2927570f93d8028fc464252 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Wed, 6 May 2026 22:12:51 +0300 Subject: [PATCH] implement auth controller and routes Wires AuthController (signup, confirmEmail, login, me, logout) to the existing auth use cases. Routes mounted under /api with AuthMiddleware on logout/me. RepositoryServiceProvider gains EmailConfirmationToken and Post bindings; AppServiceProvider binds the Emailer/EmailFactory and constructs SignupUser with the configured from-address. --- backend/app/Controllers/AuthController.php | 159 ++++++++++++++++++ backend/app/Providers/AppServiceProvider.php | 33 ++++ .../Providers/RepositoryServiceProvider.php | 12 ++ backend/routes/api.php | 19 ++- 4 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 backend/app/Controllers/AuthController.php diff --git a/backend/app/Controllers/AuthController.php b/backend/app/Controllers/AuthController.php new file mode 100644 index 0000000..f7cac97 --- /dev/null +++ b/backend/app/Controllers/AuthController.php @@ -0,0 +1,159 @@ +signupUser->execute(new SignupUserRequest( + email: $request->input('email'), + displayName: $request->input('displayName'), + )); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } catch (DomainException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 409, + ); + } + + return new JsonResponse(null, 201); + } + + public function confirmEmail(Request $request): JsonResponse + { + try { + $this->confirmUserEmail->execute(new ConfirmUserEmailRequest( + token: $request->input('token'), + password: $request->input('password'), + )); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } catch (DomainException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 409, + ); + } + + return new JsonResponse(null, 200); + } + + public function login(Request $request): JsonResponse + { + try { + $user = $this->authenticateUser->execute( + new AuthenticateUserRequest( + email: $request->input('email'), + password: $request->input('password'), + ), + ); + } catch (BadRequestException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 400, + ); + } catch (UnauthorizedException $exception) { + return new JsonResponse( + ['error' => $exception->getMessage()], 401, + ); + } + + $session = $this->createSession->execute($user); + + $response = new JsonResponse([ + 'user' => $this->buildUserPayload($user), + ], 200); + + return $response->withCookie(Cookie::create( + name: AuthMiddleware::COOKIE_NAME, + value: $session->getToken(), + expire: $session->getExpiresAt()->getTimestamp(), + path: '/', + domain: null, + secure: false, + httpOnly: true, + raw: false, + sameSite: Cookie::SAMESITE_LAX, + )); + } + + public function me(Request $request): JsonResponse + { + /** @var User $user */ + $user = $request->attributes->get('user'); + + return new JsonResponse([ + 'user' => $this->buildUserPayload($user), + ], 200); + } + + public function logout(Request $request): JsonResponse + { + $token = $request->cookie(AuthMiddleware::COOKIE_NAME); + if (is_string($token) && $token !== '') { + $this->logoutUseCase->execute($token); + } + + $response = new JsonResponse(null, 204); + + return $response->withCookie(Cookie::create( + name: AuthMiddleware::COOKIE_NAME, + value: '', + expire: 1, + path: '/', + domain: null, + secure: false, + httpOnly: true, + raw: false, + sameSite: Cookie::SAMESITE_LAX, + )); + } + + /** + * @return array{ + * id: int, + * email: string, + * displayName: string, + * isAdmin: bool + * } + */ + private function buildUserPayload(User $user): array + { + return [ + 'id' => $user->getId(), + 'email' => $user->getEmail()->value(), + 'displayName' => $user->getDisplayName(), + 'isAdmin' => $user->isAdmin(), + ]; + } +} diff --git a/backend/app/Providers/AppServiceProvider.php b/backend/app/Providers/AppServiceProvider.php index 5b73916..d17a4c9 100644 --- a/backend/app/Providers/AppServiceProvider.php +++ b/backend/app/Providers/AppServiceProvider.php @@ -8,6 +8,14 @@ use App\Auth\PasswordHasher; use App\Auth\RandomTokenGenerator; use App\Auth\SystemClock; use App\Auth\TokenGenerator; +use App\Email\EmailConfirmationToken\EmailConfirmationTokenRepository; +use App\Email\Emailer; +use App\Email\EmailFactory; +use App\Email\LaravelEmailFactory; +use App\Email\LaravelMailer; +use App\User\UseCases\SignupUser\SignupUser; +use App\User\UserRepository; +use Illuminate\Contracts\Foundation\Application; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider @@ -17,6 +25,31 @@ class AppServiceProvider extends ServiceProvider $this->app->bind(Clock::class, SystemClock::class); $this->app->bind(TokenGenerator::class, RandomTokenGenerator::class); $this->app->bind(PasswordHasher::class, BcryptPasswordHasher::class); + $this->app->bind(Emailer::class, LaravelMailer::class); + $this->app->bind( + EmailFactory::class, + function () { + return new LaravelEmailFactory( + confirmationUrlPrefix: config('app.frontend_url') + .'/confirm-email?token=', + ); + }, + ); + $this->app->bind( + SignupUser::class, + function (Application $app) { + return new SignupUser( + userRepo: $app->make(UserRepository::class), + tokenRepo: $app->make( + EmailConfirmationTokenRepository::class, + ), + emailer: $app->make(Emailer::class), + emailFactory: $app->make(EmailFactory::class), + clock: $app->make(Clock::class), + fromAddress: config('mail.from.address'), + ); + }, + ); } public function boot(): void diff --git a/backend/app/Providers/RepositoryServiceProvider.php b/backend/app/Providers/RepositoryServiceProvider.php index 0671b59..1532e12 100644 --- a/backend/app/Providers/RepositoryServiceProvider.php +++ b/backend/app/Providers/RepositoryServiceProvider.php @@ -4,6 +4,10 @@ namespace App\Providers; use App\Auth\EloquentSessionRepository; use App\Auth\SessionRepository; +use App\Email\EmailConfirmationToken\EloquentEmailConfirmationTokenRepository; +use App\Email\EmailConfirmationToken\EmailConfirmationTokenRepository; +use App\Post\EloquentPostRepository; +use App\Post\PostRepository; use App\User\EloquentUserRepository; use App\User\UserRepository; use Illuminate\Support\ServiceProvider; @@ -20,5 +24,13 @@ class RepositoryServiceProvider extends ServiceProvider SessionRepository::class, EloquentSessionRepository::class, ); + $this->app->bind( + EmailConfirmationTokenRepository::class, + EloquentEmailConfirmationTokenRepository::class, + ); + $this->app->bind( + PostRepository::class, + EloquentPostRepository::class, + ); } } diff --git a/backend/routes/api.php b/backend/routes/api.php index f4f638e..45699fb 100644 --- a/backend/routes/api.php +++ b/backend/routes/api.php @@ -1,6 +1,17 @@ middleware(AuthMiddleware::class); +Route::get('/me', [AuthController::class, 'me']) + ->middleware(AuthMiddleware::class);