Kaynağa Gözat

refactore Guard and Auth logic

michelphp 1 ay önce
ebeveyn
işleme
1513d6b238

+ 4 - 6
README.md

@@ -33,13 +33,12 @@ Then, create a provider implementing `Michel\Auth\UserProviderInterface` to fetc
 Set up form authentication for your web application.
 
 ```php
-use Michel\Auth\Handler\FormAuthHandler;
-use Michel\Auth\Middlewares\AuthMiddleware;
+use Michel\Auth\Handler\Authentication\UserFormAuthHandler;use Michel\Auth\Middlewares\Authentication\AuthMiddleware;
 
 // $userProvider = new YourUserProvider();
 // $sessionStorage = new YourSessionStorage();
 
-$formHandler = new FormAuthHandler($userProvider, $sessionStorage, [
+$formHandler = new UserFormAuthHandler($userProvider, $sessionStorage, [
     'login_path' => '/login',
     'login_key' => 'email',
     'password_key' => 'password',
@@ -54,10 +53,9 @@ $authMiddleware = new AuthMiddleware($formHandler, $responseFactory, $logger);
 Ideal for stateless APIs using header tokens.
 
 ```php
-use Michel\Auth\Handler\TokenAuthHandler;
-use Michel\Auth\Middlewares\AuthMiddleware;
+use Michel\Auth\Handler\Authentication\UserTokenAuthHandler;use Michel\Auth\Middlewares\Authentication\AuthMiddleware;
 
-$tokenHandler = new TokenAuthHandler($userProvider, 'Authorization');
+$tokenHandler = new UserTokenAuthHandler($userProvider, 'Authorization');
 
 $authMiddleware = new AuthMiddleware($tokenHandler, $responseFactory, $logger);
 // Add $authMiddleware to your API routes

+ 1 - 1
src/Handler/AuthHandlerInterface.php → src/Handler/Authentication/AuthHandlerInterface.php

@@ -1,6 +1,6 @@
 <?php
 
-namespace Michel\Auth\Handler;
+namespace Michel\Auth\Handler\Authentication;
 
 use Michel\Auth\AuthIdentity;
 use Michel\Auth\Exception\AuthenticationException;

+ 1 - 2
src/Handler/StatefulAuthHandlerInterface.php → src/Handler/Authentication/StatefulAuthHandlerInterface.php

@@ -1,8 +1,7 @@
 <?php
 
-namespace Michel\Auth\Handler;
+namespace Michel\Auth\Handler\Authentication;
 
-use Michel\Auth\Exception\AuthenticationException;
 use Psr\Http\Message\ResponseFactoryInterface;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;

+ 2 - 2
src/Handler/FormAuthHandler.php → src/Handler/Authentication/UserFormAuthHandler.php

@@ -1,6 +1,6 @@
 <?php
 
-namespace Michel\Auth\Handler;
+namespace Michel\Auth\Handler\Authentication;
 
 use Michel\Auth\AuthIdentity;
 use Michel\Auth\Exception\AuthenticationException;
@@ -17,7 +17,7 @@ use Psr\Http\Message\ResponseFactoryInterface;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 
-class FormAuthHandler implements AuthHandlerInterface, StatefulAuthHandlerInterface
+final class UserFormAuthHandler implements AuthHandlerInterface, StatefulAuthHandlerInterface
 {
     public const AUTHENTICATION_ERROR = '_form.last_error';
     public const LAST_USERNAME = '_form.last_username';

+ 2 - 2
src/Handler/TokenAuthHandler.php → src/Handler/Authentication/UserTokenAuthHandler.php

@@ -1,6 +1,6 @@
 <?php
 
-namespace Michel\Auth\Handler;
+namespace Michel\Auth\Handler\Authentication;
 
 use Michel\Auth\AuthIdentity;
 use Michel\Auth\Exception\AuthenticationException;
@@ -12,7 +12,7 @@ use Psr\Http\Message\ResponseFactoryInterface;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 
-class TokenAuthHandler implements AuthHandlerInterface
+final class UserTokenAuthHandler implements AuthHandlerInterface
 {
     private UserProviderInterface $userProvider;
 

+ 25 - 0
src/Handler/Guard/GuardHandlerInterface.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace Michel\Auth\Handler\Guard;
+
+use Michel\Auth\AuthIdentity;
+use Michel\Auth\Exception\AuthenticationException;
+use Psr\Http\Message\ResponseFactoryInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+
+interface GuardHandlerInterface
+{
+
+    /**
+     * @throws AuthenticationException
+     */
+    public function check(ServerRequestInterface $request):  void;
+
+    public function onFailure(
+        ServerRequestInterface $request,
+        ResponseFactoryInterface $responseFactory,
+        ?AuthenticationException $exception = null
+    ): ResponseInterface;
+
+}

+ 3 - 7
src/Handler/HttpBasicAuthHandler.php → src/Handler/Guard/HttpBasicGuardHandler.php

@@ -1,19 +1,16 @@
 <?php
 
-namespace Michel\Auth\Handler;
+namespace Michel\Auth\Handler\Guard;
 
 use Michel\Auth\AuthIdentity;
 use Michel\Auth\Exception\AuthenticationException;
 use Michel\Auth\Exception\InvalidCredentialsException;
 use Michel\Auth\Exception\UserNotFoundException;
-use Michel\Auth\PasswordAuthenticatedUserInterface;
-use Michel\Auth\UserInterface;
-use Michel\Auth\UserProviderInterface;
 use Psr\Http\Message\ResponseFactoryInterface;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 
-class HttpBasicAuthHandler implements AuthHandlerInterface
+class HttpBasicGuardHandler implements GuardHandlerInterface
 {
     private string $user;
     private string $password;
@@ -42,7 +39,7 @@ class HttpBasicAuthHandler implements AuthHandlerInterface
      * @throws UserNotFoundException
      * @throws InvalidCredentialsException
      */
-    public function authenticate(ServerRequestInterface $request): ?AuthIdentity
+    public function check(ServerRequestInterface $request): void
     {
         $authHeader = $request->getHeaderLine('Authorization');
         if (empty($authHeader)) {
@@ -66,7 +63,6 @@ class HttpBasicAuthHandler implements AuthHandlerInterface
             throw new InvalidCredentialsException("Access denied.");
         }
 
-        return null;
     }
 
     public function onFailure(ServerRequestInterface $request, ResponseFactoryInterface $responseFactory, ?AuthenticationException $exception = null): ResponseInterface

+ 74 - 0
src/Handler/Guard/TokenGuardHandler.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace Michel\Auth\Handler\Guard;
+
+use Michel\Auth\Exception\AuthenticationException;
+use Michel\Auth\Exception\InvalidCredentialsException;
+use Michel\Auth\Exception\UserNotFoundException;
+use Psr\Http\Message\ResponseFactoryInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+
+class TokenGuardHandler implements GuardHandlerInterface
+{
+    private string $apiKey;
+    private string $headerName;
+    /**
+     * @var callable|null
+     */
+    private $onFailure;
+
+    public function __construct(
+        string $apiKey,
+        string                $headerName,
+        callable              $onFailure = null
+    )
+    {
+        $this->apiKey = $apiKey;
+        $this->headerName = $headerName;
+        $this->onFailure = $onFailure;
+    }
+
+
+    /**
+     * @throws AuthenticationException
+     * @throws UserNotFoundException
+     * @throws InvalidCredentialsException
+     */
+    public function check(ServerRequestInterface $request): void
+    {
+        if (empty($this->apiKey)) {
+            throw new InvalidCredentialsException("Invalid or expired token.");
+        }
+
+        $token = $request->getHeaderLine($this->headerName);
+        if (empty($token)) {
+            throw new AuthenticationException("Authentication token is required.");
+        }
+
+        if (!hash_equals($this->apiKey, $token)) {
+            throw new InvalidCredentialsException("Invalid or expired token.");
+        }
+    }
+
+    public function onFailure(ServerRequestInterface $request, ResponseFactoryInterface $responseFactory, ?AuthenticationException $exception = null): ResponseInterface
+    {
+        if (!is_callable($this->onFailure)) {
+            $status = 401;
+            $message = $exception ? $exception->getMessage() : "Invalid API key.";
+            $payload = [
+                'status' => $status,
+                'title'  => 'Authentication Failed',
+                'detail' => $message,
+            ];
+
+            $response = $responseFactory->createResponse($status);
+            $response->getBody()->write(json_encode($payload, JSON_UNESCAPED_SLASHES ));
+            return $response
+                ->withHeader('Content-Type', 'application/json')
+                ->withHeader('Cache-Control', 'no-store');
+
+        }
+        return ($this->onFailure)($request, $responseFactory, $exception);
+    }
+}

+ 42 - 30
src/MichelPackage/MichelAuthPackage.php

@@ -3,12 +3,13 @@
 namespace Michel\Auth\MichelPackage;
 
 use Michel\Auth\Command\AuthPasswordHashCommand;
-use Michel\Auth\Handler\FormAuthHandler;
-use Michel\Auth\Handler\HttpBasicAuthHandler;
-use Michel\Auth\Handler\TokenAuthHandler;
-use Michel\Auth\Middlewares\FormAuthMiddleware;
-use Michel\Auth\Middlewares\HttpBasicAuthMiddleware;
-use Michel\Auth\Middlewares\TokenAuthMiddleware;
+use Michel\Auth\Handler\Authentication\UserFormAuthHandler;
+use Michel\Auth\Handler\Authentication\UserTokenAuthHandler;
+use Michel\Auth\Handler\Guard\TokenGuardHandler;
+use Michel\Auth\Handler\Guard\HttpBasicGuardHandler;
+use Michel\Auth\Middlewares\Authentication\UserFormAuthMiddleware;
+use Michel\Auth\Middlewares\Authentication\UserTokenAuthMiddleware;
+use Michel\Auth\Middlewares\Guard\HttpBasicGuardMiddleware;
 use Michel\Auth\UserProviderInterface;
 use Michel\Package\PackageInterface;
 use Michel\Session\Storage\SessionStorageInterface;
@@ -22,29 +23,29 @@ class MichelAuthPackage implements PackageInterface
     public function getDefinitions(): array
     {
         return [
-            FormAuthMiddleware::class => static function (ContainerInterface $container) {
-                return new FormAuthMiddleware(
-                    $container->get(FormAuthHandler::class),
+            UserFormAuthMiddleware::class => static function (ContainerInterface $container) {
+                return new UserFormAuthMiddleware(
+                    $container->get(UserFormAuthHandler::class),
                     $container->get(ResponseFactoryInterface::class),
                     $container->get(LoggerInterface::class)
                 );
             },
-            TokenAuthMiddleware::class => static function (ContainerInterface $container) {
-                return new TokenAuthMiddleware(
-                    $container->get(TokenAuthHandler::class),
+            UserTokenAuthMiddleware::class => static function (ContainerInterface $container) {
+                return new UserTokenAuthMiddleware(
+                    $container->get(UserTokenAuthHandler::class),
                     $container->get(ResponseFactoryInterface::class),
                     $container->get(LoggerInterface::class)
                 );
             },
-            HttpBasicAuthMiddleware::class => static function (ContainerInterface $container) {
-                return new HttpBasicAuthMiddleware(
-                    $container->get(HttpBasicAuthHandler::class),
+            HttpBasicGuardMiddleware::class => static function (ContainerInterface $container) {
+                return new HttpBasicGuardMiddleware(
+                    $container->get(HttpBasicGuardHandler::class),
                     $container->get(ResponseFactoryInterface::class),
                     $container->get(LoggerInterface::class)
                 );
             },
-            FormAuthHandler::class => static function (ContainerInterface $container) {
-                return new FormAuthHandler(
+            UserFormAuthHandler::class => static function (ContainerInterface $container) {
+                return new UserFormAuthHandler(
                     $container->get(UserProviderInterface::class),
                     $container->get(SessionStorageInterface::class),
                     [
@@ -56,19 +57,26 @@ class MichelAuthPackage implements PackageInterface
                     ]
                 );
             },
-            TokenAuthHandler::class => static function (ContainerInterface $container) {
-                return new TokenAuthHandler(
+            UserTokenAuthHandler::class => static function (ContainerInterface $container) {
+                return new UserTokenAuthHandler(
                     $container->get(UserProviderInterface::class),
                     $container->get('auth.token.header_name'),
                     $container->get('auth.token.on_failure')
                 );
             },
-            HttpBasicAuthHandler::class => static function (ContainerInterface $container) {
-                return new HttpBasicAuthHandler(
-                    $container->get('auth.basic.user'),
-                    $container->get('auth.basic.password'),
-                    $container->get('auth.http.basic.realm'),
-                    $container->get('auth.http.basic.on_failure')
+            HttpBasicGuardHandler::class => static function (ContainerInterface $container) {
+                return new HttpBasicGuardHandler(
+                    $container->get('guard.basic.user'),
+                    $container->get('guard.basic.password'),
+                    $container->get('guard.basic.realm'),
+                    $container->get('guard.http.basic.on_failure')
+                );
+            },
+            TokenGuardHandler::class => static function (ContainerInterface $container) {
+                return new TokenGuardHandler(
+                    $container->get('guard.token.secret'),
+                    $container->get('guard.token.header_name'),
+                    $container->get('guard.token.on_failure')
                 );
             }
         ];
@@ -83,13 +91,17 @@ class MichelAuthPackage implements PackageInterface
             'auth.form.password_key' => '_password',
             'auth.form.on_failure' => null,
 
-            'auth.token.header_name' => 'X-Api-Key',
+            'auth.token.header_name' => 'X-AUTH-TOKEN',
             'auth.token.on_failure' => null,
 
-            'auth.basic.user' => '',
-            'auth.basic.password' => '',
-            'auth.http.basic.realm' => 'Restricted Area',
-            'auth.http.basic.on_failure' => null
+            'guard.basic.user' => '',
+            'guard.basic.password' => '',
+            'guard.basic.realm' => 'Restricted Area',
+            'guard.basic.on_failure' => null,
+
+            'guard.token.secret'      => '',
+            'guard.token.header_name' => 'X-API-KEY',
+            'guard.token.on_failure'  => null,
         ];
     }
 

+ 6 - 6
src/Middlewares/AuthMiddleware.php → src/Middlewares/Authentication/AuthMiddleware.php

@@ -1,11 +1,11 @@
 <?php
 
-namespace Michel\Auth\Middlewares;
+namespace Michel\Auth\Middlewares\Authentication;
 
 use Michel\Auth\AuthIdentity;
 use Michel\Auth\Exception\AuthenticationException;
-use Michel\Auth\Handler\AuthHandlerInterface;
-use Michel\Auth\Handler\StatefulAuthHandlerInterface;
+use Michel\Auth\Handler\Authentication\AuthHandlerInterface;
+use Michel\Auth\Handler\Authentication\StatefulAuthHandlerInterface;
 use Michel\Auth\Helper\IpHelper;
 use Michel\Auth\UserInterface;
 use Psr\Http\Message\ResponseFactoryInterface;
@@ -22,9 +22,9 @@ abstract class AuthMiddleware implements MiddlewareInterface
     private ?LoggerInterface $logger;
 
     public function __construct(
-        AuthHandlerInterface $authHandler,
+        AuthHandlerInterface     $authHandler,
         ResponseFactoryInterface $responseFactory,
-        LoggerInterface $logger = null
+        LoggerInterface          $logger = null
     )
     {
         $this->authHandler = $authHandler;
@@ -34,11 +34,11 @@ abstract class AuthMiddleware implements MiddlewareInterface
 
     public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
     {
-        $handlerName = get_class($this->authHandler);
         if ($request->getAttribute('user') instanceof UserInterface) {
             return $handler->handle($request);
         }
 
+        $handlerName = get_class($this->authHandler);
         try {
             $authIdentity = $this->authHandler->authenticate($request);
             if (!$authIdentity instanceof AuthIdentity) {

+ 7 - 0
src/Middlewares/Authentication/UserFormAuthMiddleware.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Michel\Auth\Middlewares\Authentication;
+
+final class UserFormAuthMiddleware extends AuthMiddleware
+{
+}

+ 7 - 0
src/Middlewares/Authentication/UserTokenAuthMiddleware.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Michel\Auth\Middlewares\Authentication;
+
+final class UserTokenAuthMiddleware extends AuthMiddleware
+{
+}

+ 0 - 7
src/Middlewares/FormAuthMiddleware.php

@@ -1,7 +0,0 @@
-<?php
-
-namespace Michel\Auth\Middlewares;
-
-final class FormAuthMiddleware extends AuthMiddleware
-{
-}

+ 63 - 0
src/Middlewares/Guard/GuardMiddleware.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace Michel\Auth\Middlewares\Guard;
+
+use Michel\Auth\AuthIdentity;
+use Michel\Auth\Exception\AuthenticationException;
+use Michel\Auth\Handler\Authentication\AuthHandlerInterface;
+use Michel\Auth\Handler\Authentication\StatefulAuthHandlerInterface;
+use Michel\Auth\Handler\Guard\GuardHandlerInterface;
+use Michel\Auth\Helper\IpHelper;
+use Michel\Auth\UserInterface;
+use Psr\Http\Message\ResponseFactoryInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use Psr\Log\LoggerInterface;
+
+abstract class GuardMiddleware implements MiddlewareInterface
+{
+    private GuardHandlerInterface $guardHandler;
+    private ResponseFactoryInterface $responseFactory;
+    private ?LoggerInterface $logger;
+
+    public function __construct(
+        GuardHandlerInterface     $guardHandler,
+        ResponseFactoryInterface $responseFactory,
+        LoggerInterface          $logger = null
+    )
+    {
+        $this->guardHandler = $guardHandler;
+        $this->responseFactory = $responseFactory;
+        $this->logger = $logger;
+    }
+
+    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+    {
+        $handlerName = get_class($this->guardHandler);
+        if ($request->getAttribute('user') instanceof UserInterface) {
+            return $handler->handle($request);
+        }
+
+        try {
+            $this->guardHandler->check($request);
+            return $handler->handle($request);
+        }catch (AuthenticationException $exception) {
+            if ($this->logger) {
+                $this->logger->log(
+                    'warning',
+                    '[{handler}] Authentication failed (ip: {ip}, path: {path}) : {message}',
+                    [
+                        'handler' => $handlerName,
+                        'message' => $exception->getMessage(),
+                        'ip'      => IpHelper::getIpFromRequest($request),
+                        'path'    => $request->getUri()->getPath(),
+                    ]
+                );
+            }
+            return $this->guardHandler->onFailure($request, $this->responseFactory, $exception);
+        }
+    }
+
+}

+ 7 - 0
src/Middlewares/Guard/HttpBasicGuardMiddleware.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Michel\Auth\Middlewares\Guard;
+
+final class HttpBasicGuardMiddleware extends GuardMiddleware
+{
+}

+ 7 - 0
src/Middlewares/Guard/TokenGuardMiddleware.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace Michel\Auth\Middlewares\Guard;
+
+final class TokenGuardMiddleware extends GuardMiddleware
+{
+}

+ 0 - 7
src/Middlewares/HttpBasicAuthMiddleware.php

@@ -1,7 +0,0 @@
-<?php
-
-namespace Michel\Auth\Middlewares;
-
-final class HttpBasicAuthMiddleware extends AuthMiddleware
-{
-}

+ 0 - 7
src/Middlewares/TokenAuthMiddleware.php

@@ -1,7 +0,0 @@
-<?php
-
-namespace Michel\Auth\Middlewares;
-
-final class TokenAuthMiddleware extends AuthMiddleware
-{
-}