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