From 238bb3f48e8177a4176d61e62ef110061829ded6 Mon Sep 17 00:00:00 2001
From: flashwave <me@flash.moe>
Date: Tue, 25 Mar 2025 13:51:39 +0000
Subject: [PATCH] Updated leftovers from rebase.

---
 VERSION                             |  2 +-
 composer.lock                       | 10 +++++-----
 src/Auth/AuthApiRoutes.php          |  9 +++++----
 src/Auth/AuthInfo.php               |  1 +
 src/Emoticons/EmotesApiRoutes.php   | 20 +++++++-------------
 src/OAuth2/OAuth2ApiRoutes.php      | 17 ++++++-----------
 src/Routing/RoutingErrorHandler.php | 17 ++++++++++-------
 src/Users/UsersApiRoutes.php        | 18 +++++++-----------
 8 files changed, 42 insertions(+), 52 deletions(-)

diff --git a/VERSION b/VERSION
index 5daf0b7b..11cd15cc 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-20250325
+20250325.1
diff --git a/composer.lock b/composer.lock
index 87e16339..3299ca9d 100644
--- a/composer.lock
+++ b/composer.lock
@@ -3679,16 +3679,16 @@
     "packages-dev": [
         {
             "name": "phpstan/phpstan",
-            "version": "2.1.10",
+            "version": "2.1.11",
             "source": {
                 "type": "git",
                 "url": "https://github.com/phpstan/phpstan.git",
-                "reference": "051a3b6b9b80df4ba3a7f801a8b53ad7d8f1c15f"
+                "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/phpstan/phpstan/zipball/051a3b6b9b80df4ba3a7f801a8b53ad7d8f1c15f",
-                "reference": "051a3b6b9b80df4ba3a7f801a8b53ad7d8f1c15f",
+                "url": "https://api.github.com/repos/phpstan/phpstan/zipball/8ca5f79a8f63c49b2359065832a654e1ec70ac30",
+                "reference": "8ca5f79a8f63c49b2359065832a654e1ec70ac30",
                 "shasum": ""
             },
             "require": {
@@ -3733,7 +3733,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2025-03-23T14:57:55+00:00"
+            "time": "2025-03-24T13:45:00+00:00"
         }
     ],
     "aliases": [],
diff --git a/src/Auth/AuthApiRoutes.php b/src/Auth/AuthApiRoutes.php
index b5d9a11a..a10c3cc9 100644
--- a/src/Auth/AuthApiRoutes.php
+++ b/src/Auth/AuthApiRoutes.php
@@ -6,7 +6,8 @@ use Misuzu\OAuth2\{OAuth2AccessInfoGetField,OAuth2Context};
 use Misuzu\Users\{UsersContext,UserInfo};
 use Index\Config\Config;
 use Index\Http\{HttpRequest,HttpResponseBuilder};
-use Index\Http\Routing\{HttpMiddleware,RouteHandler,RouteHandlerCommon};
+use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
+use Index\Http\Routing\Filters\PrefixFilter;
 
 final class AuthApiRoutes implements RouteHandler {
     use RouteHandlerCommon;
@@ -27,9 +28,9 @@ final class AuthApiRoutes implements RouteHandler {
         return in_array($targetId, $whitelist, true);
     }
 
-    /** @return void|int */
-    #[HttpMiddleware('/api/v1')]
-    #[HttpMiddleware('/oauth2')]
+    /** @return void|array{error: string, error_description?: string} */
+    #[PrefixFilter('/api/v1')]
+    #[PrefixFilter('/oauth2')]
     public function handleAuthorization(HttpResponseBuilder $response, HttpRequest $request) {
         if($this->authInfo->loggedIn)
             return;
diff --git a/src/Auth/AuthInfo.php b/src/Auth/AuthInfo.php
index 3d0602fa..6c1de7c9 100644
--- a/src/Auth/AuthInfo.php
+++ b/src/Auth/AuthInfo.php
@@ -86,6 +86,7 @@ class AuthInfo {
         get => $this->appInfo?->id;
     }
 
+    /** @var string[]|null */
     public ?array $scopes {
         get {
             if($this->appInfo !== null)
diff --git a/src/Emoticons/EmotesApiRoutes.php b/src/Emoticons/EmotesApiRoutes.php
index 58d52550..a713ae54 100644
--- a/src/Emoticons/EmotesApiRoutes.php
+++ b/src/Emoticons/EmotesApiRoutes.php
@@ -3,7 +3,9 @@ namespace Misuzu\Emoticons;
 
 use Index\XArray;
 use Index\Http\{HttpRequest,HttpResponseBuilder};
-use Index\Http\Routing\{HttpGet,HttpOptions,RouteHandler,RouteHandlerCommon};
+use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
+use Index\Http\Routing\AccessControl\AccessControl;
+use Index\Http\Routing\Routes\ExactRoute;
 
 final class EmotesApiRoutes implements RouteHandler {
     use RouteHandlerCommon;
@@ -12,18 +14,10 @@ final class EmotesApiRoutes implements RouteHandler {
         private EmotesData $emotes
     ) {}
 
-    /** @return int|mixed[] */
-    #[HttpOptions('/api/v1/emotes')]
-    #[HttpGet('/api/v1/emotes')]
-    public function getEmotes(HttpResponseBuilder $response, HttpRequest $request): int|array {
-        $response->setHeader('Access-Control-Allow-Origin', '*');
-        $response->setHeader('Access-Control-Allow-Methods', 'GET');
-        $response->addHeader('Access-Control-Allow-Headers', 'Cache-Control');
-        $response->setHeader('Cache-Control', 'public, max-age=3600');
-
-        if($request->method === 'OPTIONS')
-            return 204;
-
+    /** @return mixed[] */
+    #[AccessControl]
+    #[ExactRoute('GET', '/api/v1/emotes')]
+    public function getEmotes(HttpResponseBuilder $response, HttpRequest $request): array {
         $includeId = !empty($request->getParam('include_id'));
         $includeOrder = !empty($request->getParam('include_order'));
 
diff --git a/src/OAuth2/OAuth2ApiRoutes.php b/src/OAuth2/OAuth2ApiRoutes.php
index 3d10765e..da32e3a6 100644
--- a/src/OAuth2/OAuth2ApiRoutes.php
+++ b/src/OAuth2/OAuth2ApiRoutes.php
@@ -173,9 +173,9 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
         ];
     }
 
-    /** @return int|array{keys?: array<string, string>} */
-    #[HttpOptions('/oauth2/jwks.json')]
-    #[HttpGet('/oauth2/jwks.json')]
+    /** @return array{keys?: array<string, string>} */
+    #[AccessControl]
+    #[ExactRoute('GET', '/oauth2/jwks.json')]
     #[UrlFormat('oauth2-jwks', '/oauth2/jwks.json')]
     public function getJwks(HttpResponseBuilder $response, HttpRequest $request): array {
         return $this->oauth2Ctx->keys->getPublicKeysForJson();
@@ -360,15 +360,10 @@ final class OAuth2ApiRoutes implements RouteHandler, UrlSource {
      *  email_verified?: bool,
      * }|array{ error: string, error_description: string }
      */
-    #[HttpOptions('/oauth2/userinfo')]
-    #[HttpGet('/oauth2/userinfo')]
+    #[AccessControl(allowHeaders: ['Authorization'], exposeHeaders: ['WWW-Authenticate'])]
+    #[ExactRoute('GET', '/oauth2/userinfo')]
     #[UrlFormat('oauth2-openid-userinfo', '/oauth2/userinfo')]
-    public function getUserInfo(HttpResponseBuilder $response, HttpRequest $request): int|array {
-        $response->setHeader('Access-Control-Allow-Origin', '*');
-        $response->setHeader('Access-Control-Allow-Headers', 'Authorization');
-        if($request->method === 'OPTIONS')
-            return 204;
-
+    public function getUserInfo(HttpResponseBuilder $response, HttpRequest $request): array {
         if(!$this->authInfo->loggedInBearer) {
             $response->statusCode = 401;
             $response->setHeader('WWW-Authenticate', 'Bearer error="invalid_token", error_description="Bearer authentication must be used."');
diff --git a/src/Routing/RoutingErrorHandler.php b/src/Routing/RoutingErrorHandler.php
index a01cf401..ad1ec0d9 100644
--- a/src/Routing/RoutingErrorHandler.php
+++ b/src/Routing/RoutingErrorHandler.php
@@ -1,6 +1,7 @@
 <?php
 namespace Misuzu\Routing;
 
+use Index\Http\HttpResponse;
 use Index\Http\Routing\HandlerContext;
 use Index\Http\Routing\ErrorHandling\HtmlErrorHandler;
 use Index\Http\Streams\Stream;
@@ -17,16 +18,18 @@ class RoutingErrorHandler extends HtmlErrorHandler {
             return;
         }
 
-        if(str_starts_with($request->path, '/api')) {
-            $response->setTypeJson();
-            $response->content = json_encode([
-                'error' => sprintf('http:%s', $code),
-                'message' => $message,
-            ], JSON_UNESCAPED_SLASHES);
+        if(str_starts_with($context->request->requestTarget, '/api')) {
+            $context->response->setTypeJson();
+            $context->response->body = Stream::createStream(json_encode([
+                'error' => sprintf('http:%s', $context->response->statusCode),
+                'message' => $context->response->reasonPhrase === ''
+                    ? HttpResponse::defaultReasonPhase($context->response->statusCode)
+                    : $context->response->reasonPhrase,
+            ], JSON_UNESCAPED_SLASHES));
             return;
         }
 
-        $path = sprintf('%s/error-%03d.html', Misuzu::PATH_PUBLIC, $code);
+        $path = sprintf('%s/error-%03d.html', Misuzu::PATH_PUBLIC, $context->response->statusCode);
         if(is_file($path)) {
             $context->response->setTypeHTML();
             $context->response->body = Stream::createStreamFromFile($path, 'rb');
diff --git a/src/Users/UsersApiRoutes.php b/src/Users/UsersApiRoutes.php
index ab2e2cc5..74ed30e0 100644
--- a/src/Users/UsersApiRoutes.php
+++ b/src/Users/UsersApiRoutes.php
@@ -8,7 +8,9 @@ use Misuzu\Users\Assets\UserAvatarAsset;
 use Index\XArray;
 use Index\Colour\{Colour,ColourRgb};
 use Index\Http\{HttpRequest,HttpResponseBuilder};
-use Index\Http\Routing\{HttpGet,HttpOptions,RouteHandler,RouteHandlerCommon};
+use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
+use Index\Http\Routing\AccessControl\AccessControl;
+use Index\Http\Routing\Routes\ExactRoute;
 use Index\Urls\UrlRegistry;
 
 final class UsersApiRoutes implements RouteHandler {
@@ -22,16 +24,10 @@ final class UsersApiRoutes implements RouteHandler {
     ) {}
 
     /** @return int|mixed[] */
-    #[HttpOptions('/api/v1/me')]
-    #[HttpGet('/api/v1/me')]
-    public function getEmotes(HttpResponseBuilder $response, HttpRequest $request): int|array {
-        $response->setHeader('Access-Control-Allow-Origin', '*');
-        $response->setHeader('Access-Control-Allow-Methods', 'GET');
-        $response->addHeader('Access-Control-Allow-Headers', 'Cache-Control');
-        $response->setHeader('Cache-Control', 'public, max-age=3600');
-
-        if($request->method === 'OPTIONS')
-            return 204;
+    #[AccessControl]
+    #[ExactRoute('GET', '/api/v1/me')]
+    public function getMe(HttpResponseBuilder $response, HttpRequest $request): int|array {
+        $response->setHeader('Cache-Control', 'no-store');
 
         $openid = $this->authInfo->hasScope('openid');
         if(!$openid