From 4576a66c1699ef8acf7f28fb2f167cbaf49b7ccd Mon Sep 17 00:00:00 2001
From: Mateusz Charytoniuk <mateusz.charytoniuk@protonmail.com>
Date: Mon, 22 Apr 2024 21:54:41 +0200
Subject: [PATCH] chore: oauth2 session authentication hook

---
 .../security/oauth2/installation/index.md     | 28 +++++++++++++++
 resources/css/docs-hljs.css                   |  1 +
 ...h2UserSessionAuthenticatedInterceptor.php} | 22 ++++++++----
 src/OAuth2UserSessionAuthenticated.php        | 36 +++++++++++++++++++
 4 files changed, 81 insertions(+), 6 deletions(-)
 rename src/{HttpResponder/OAuth2/PostSessionAuthentication.php => HttpInterceptor/OAuth2UserSessionAuthenticatedInterceptor.php} (69%)
 create mode 100644 src/OAuth2UserSessionAuthenticated.php

diff --git a/docs/pages/docs/features/security/oauth2/installation/index.md b/docs/pages/docs/features/security/oauth2/installation/index.md
index 7b072a25..5ad28745 100644
--- a/docs/pages/docs/features/security/oauth2/installation/index.md
+++ b/docs/pages/docs/features/security/oauth2/installation/index.md
@@ -103,3 +103,31 @@ $ php ./bin/resonance.php generate:defuse-key > oauth2/defuse.key
 ```
 
 Then, change the CHMOD permissions for that key to `0600`.
+
+## Post Session Authentication Hook
+
+If you are using {{tutorials/session-based-authentication/index}} you need
+to return `OAuth2UserSessionAuthenticated` instance
+from your authentication {{docs/features/http/responders}} (see also:
+{{docs/features/http/interceptors}}).
+
+It allows OAuth2 to know that the user is authenticated and that it should
+check if user is in the middle of OAuth2 flow.
+
+```php
+<?php
+
+use Distantmagic\Resonance\OAuth2UserSessionAuthenticated;
+
+final readonly class LoginValidation extends HttpController
+{
+    public function createResponse(): HttpInterceptableInterface 
+    {
+        // ...
+        // perform session authentication somehow
+        // ...
+
+        return new OAuth2UserSessionAuthenticated();
+    }
+}
+```
diff --git a/resources/css/docs-hljs.css b/resources/css/docs-hljs.css
index f9fafeb9..ff3a8f72 100644
--- a/resources/css/docs-hljs.css
+++ b/resources/css/docs-hljs.css
@@ -85,6 +85,7 @@ code[class] {
 .fenced-code {
   background-color: var(--color-block-background);
   box-shadow: 8px 8px #00000033;
+  margin: 20px 0;
 
   @media screen and (min-width: 1024px) {
     position: relative;
diff --git a/src/HttpResponder/OAuth2/PostSessionAuthentication.php b/src/HttpInterceptor/OAuth2UserSessionAuthenticatedInterceptor.php
similarity index 69%
rename from src/HttpResponder/OAuth2/PostSessionAuthentication.php
rename to src/HttpInterceptor/OAuth2UserSessionAuthenticatedInterceptor.php
index 5579045b..8db35697 100644
--- a/src/HttpResponder/OAuth2/PostSessionAuthentication.php
+++ b/src/HttpInterceptor/OAuth2UserSessionAuthenticatedInterceptor.php
@@ -2,25 +2,32 @@
 
 declare(strict_types=1);
 
-namespace Distantmagic\Resonance\HttpResponder\OAuth2;
+namespace Distantmagic\Resonance\HttpInterceptor;
 
 use Distantmagic\Resonance\Attribute\GrantsFeature;
+use Distantmagic\Resonance\Attribute\Intercepts;
 use Distantmagic\Resonance\Attribute\Singleton;
 use Distantmagic\Resonance\Feature;
 use Distantmagic\Resonance\HttpInterceptableInterface;
-use Distantmagic\Resonance\HttpResponder;
+use Distantmagic\Resonance\HttpInterceptor;
 use Distantmagic\Resonance\HttpResponderInterface;
 use Distantmagic\Resonance\OAuth2AuthorizationCodeFlowControllerInterface;
 use Distantmagic\Resonance\OAuth2AuthorizationRequestSessionStore;
 use Distantmagic\Resonance\OAuth2AuthorizedUser;
+use Distantmagic\Resonance\OAuth2UserSessionAuthenticated;
 use Distantmagic\Resonance\SessionAuthentication;
+use Distantmagic\Resonance\SingletonCollection;
 use Psr\Http\Message\ResponseInterface;
 use Psr\Http\Message\ServerRequestInterface;
 use RuntimeException;
 
+/**
+ * @template-extends HttpInterceptor<OAuth2UserSessionAuthenticated>
+ */
 #[GrantsFeature(Feature::OAuth2)]
-#[Singleton]
-final readonly class PostSessionAuthentication extends HttpResponder
+#[Intercepts(OAuth2UserSessionAuthenticated::class)]
+#[Singleton(collection: SingletonCollection::HttpInterceptor)]
+final readonly class OAuth2UserSessionAuthenticatedInterceptor extends HttpInterceptor
 {
     public function __construct(
         private OAuth2AuthorizationCodeFlowControllerInterface $authorizationCodeFlowController,
@@ -28,8 +35,11 @@ final readonly class PostSessionAuthentication extends HttpResponder
         private SessionAuthentication $sessionAuthentication,
     ) {}
 
-    public function respond(ServerRequestInterface $request, ResponseInterface $response): HttpInterceptableInterface|HttpResponderInterface|ResponseInterface
-    {
+    public function intercept(
+        ServerRequestInterface $request,
+        ResponseInterface $response,
+        object $intercepted,
+    ): HttpInterceptableInterface|HttpResponderInterface|ResponseInterface {
         if (!$this->authorizationRequestSessionStore->has($request)) {
             return $this->authorizationCodeFlowController->redirectToAuthenticatedPage($request, $response);
         }
diff --git a/src/OAuth2UserSessionAuthenticated.php b/src/OAuth2UserSessionAuthenticated.php
new file mode 100644
index 00000000..998d33b8
--- /dev/null
+++ b/src/OAuth2UserSessionAuthenticated.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Distantmagic\Resonance;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+
+final readonly class OAuth2UserSessionAuthenticated implements HttpInterceptableInterface
+{
+    private SwooleContextRequestResponseReader $swooleContextRequestResponseReader;
+
+    /**
+     * @psalm-taint-source file $templatePath
+     */
+    public function __construct(
+        ?ServerRequestInterface $request = null,
+        ?ResponseInterface $response = null,
+    ) {
+        $this->swooleContextRequestResponseReader = new SwooleContextRequestResponseReader(
+            request: $request,
+            response: $response,
+        );
+    }
+
+    public function getResponse(): ResponseInterface
+    {
+        return $this->swooleContextRequestResponseReader->getResponse();
+    }
+
+    public function getServerRequest(): ServerRequestInterface
+    {
+        return $this->swooleContextRequestResponseReader->getServerRequest();
+    }
+}
-- 
GitLab