diff --git a/composer.json b/composer.json index d90bf27..fe0c0d0 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,8 @@ "symfony/mailer": "^6.0", "matomo/device-detector": "^6.1", "sentry/sdk": "^4.0", - "nesbot/carbon": "^3.7" + "nesbot/carbon": "^3.7", + "flashwave/aiwass": "^1.0" }, "autoload": { "classmap": [ diff --git a/composer.lock b/composer.lock index 4cd2353..b12b828 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "cec099ea3b4fce65aa1b1ff6b0b40f24", + "content-hash": "c8edfa21e13dedc126a6351b7d367559", "packages": [ { "name": "carbonphp/carbon-doctrine-types", @@ -416,6 +416,45 @@ }, "time": "2019-12-30T22:54:17+00:00" }, + { + "name": "flashwave/aiwass", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://patchii.net/flashii/aiwass.git", + "reference": "de27da54b603f0fdc06dab89341908e73ea7a880" + }, + "require": { + "ext-msgpack": ">=2.2", + "flashwave/index": "^0.2408.40014", + "php": ">=8.3" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "phpunit/phpunit": "^11.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Aiwass\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "bsd-3-clause-clear" + ], + "authors": [ + { + "name": "flashwave", + "email": "packagist@flash.moe", + "homepage": "https://flash.moe", + "role": "mom" + } + ], + "description": "Shared HTTP RPC client/server library.", + "homepage": "https://railgun.sh/aiwass", + "time": "2024-08-16T15:59:19+00:00" + }, { "name": "flashwave/index", "version": "v0.2408.40014", diff --git a/src/Hanyuu/HanyuuRoutes.php b/src/Hanyuu/HanyuuRpcActions.php similarity index 64% rename from src/Hanyuu/HanyuuRoutes.php rename to src/Hanyuu/HanyuuRpcActions.php index af51134..4d897de 100644 --- a/src/Hanyuu/HanyuuRoutes.php +++ b/src/Hanyuu/HanyuuRpcActions.php @@ -1,42 +1,30 @@ config->getString('endpoint'); - } - - private function getSecret(): string { - return $this->config->getString('secret'); - } - - private static function createPayload(string $name, array $attrs = []): object { - $payload = new stdClass; - $payload->name = $name; - $payload->attrs = []; + private static function createPayload(string $name, array $attrs = []): array { + $payload = ['name' => $name, 'attrs' => []]; foreach($attrs as $name => $value) { if($value === null) continue; - $payload->attrs[(string)$name] = $value; + $payload['attrs'][(string)$name] = $value; } return $payload; @@ -50,42 +38,6 @@ final class HanyuuRoutes extends RouteHandler { return self::createPayload('error', $attrs); } - #[HttpMiddleware('/_hanyuu/(.*)')] - public function verifyRequest($response, $request, string $action) { - $userTime = (int)$request->getHeaderLine('X-Hanyuu-Timestamp'); - $userHash = UriBase64::decode((string)$request->getHeaderLine('X-Hanyuu-Signature')); - - $currentTime = time(); - if(empty($userHash) || $userTime < $currentTime - 60 || $userTime > $currentTime + 60) - return self::createErrorPayload('verification', 'Request verification failed.'); - - $url = sprintf('%s/_hanyuu/%s', $this->getEndpoint(), $action); - $params = $request->getParamString(); - if($params !== '') - $url .= sprintf('?%s', $params); - - if($request->getMethod() === 'POST') { - if(!$request->isFormContent()) - return self::createErrorPayload('request', 'Request body is not in expect format.'); - - $body = $request->getContent()->getParamString(); - } elseif($request->getMethod() !== 'GET') { - return self::createErrorPayload('request', 'Only GET and POST methods are allowed.'); - } else { - $body = ''; - } - - $verifyHash = hash_hmac( - 'sha256', - sprintf('[%s|%s|%s]', $userTime, $url, $body), - $this->getSecret(), - true - ); - - if(!hash_equals($verifyHash, $userHash)) - return self::createErrorPayload('verification', 'Request verification failed.'); - } - private function canImpersonateUserId(UserInfo $impersonator, string $targetId): bool { if($impersonator->isSuperUser()) return true; @@ -97,18 +49,15 @@ final class HanyuuRoutes extends RouteHandler { ); } - #[HttpPost('/_hanyuu/auth-check')] - public function postCheckAuth($response, $request) { - $content = $request->getContent(); - $method = (string)$content->getParam('method'); + #[RpcProcedure('mszhau:authCheck')] + public function procAuthCheck(string $method, string $remoteAddr, string $token, string $avatars = '') { if($method !== 'Misuzu') return self::createErrorPayload('auth:check:method', 'Requested auth method is not supported.'); - $remoteAddr = (string)$content->getParam('remote_addr'); if(filter_var($remoteAddr, FILTER_VALIDATE_IP) === false) return self::createErrorPayload('auth:check:remote_addr', 'Provided remote address is not in a valid format.'); - $avatarResolutions = trim((string)$content->getParam('avatars')); + $avatarResolutions = trim($avatars); if($avatarResolutions === '') { $avatarResolutions = []; } else { @@ -121,11 +70,12 @@ final class HanyuuRoutes extends RouteHandler { $avatarResolutions = array_unique($avatarResolutions); } - $loginUrl = $this->getEndpoint() . $this->urls->format('auth-login'); - $registerUrl = $this->getEndpoint() . $this->urls->format('auth-register'); + $baseUrl = ($this->getBaseUrl)(); + $loginUrl = $baseUrl . $this->urls->format('auth-login'); + $registerUrl = $baseUrl . $this->urls->format('auth-register'); $tokenPacker = $this->authCtx->createAuthTokenPacker(); - $tokenInfo = $tokenPacker->unpack(trim((string)$content->getParam('token'))); + $tokenInfo = $tokenPacker->unpack(trim($token)); if($tokenInfo->isEmpty()) return self::createPayload('auth:check:fail', ['reason' => 'empty', 'login_url' => $loginUrl, 'register_url' => $registerUrl]); @@ -173,9 +123,9 @@ final class HanyuuRoutes extends RouteHandler { 'remaining_str' => $banInfo->getRemainingString(), ]; - $gatherRequestedAvatars = function($userInfo) use ($avatarResolutions) { + $gatherRequestedAvatars = function($userInfo) use ($avatarResolutions, $baseUrl) { $formatAvatarUrl = fn($res = 0) => ( - $this->getEndpoint() . $this->urls->format('user-avatar', ['user' => $userInfo->getId(), 'res' => $res]) + $baseUrl . $this->urls->format('user-avatar', ['user' => $userInfo->getId(), 'res' => $res]) ); $avatars = ['original' => $formatAvatarUrl()]; @@ -195,7 +145,7 @@ final class HanyuuRoutes extends RouteHandler { 'country_code' => $userInfo->getCountryCode(), 'is_deleted' => $userInfo->isDeleted(), 'has_totp' => $userInfo->hasTOTPKey(), - 'profile_url' => $this->getEndpoint() . $this->urls->format('user-profile', ['user' => $userInfo->getId()]), + 'profile_url' => $baseUrl . $this->urls->format('user-profile', ['user' => $userInfo->getId()]), 'avatars' => $gatherRequestedAvatars($userInfo), ]; @@ -204,7 +154,7 @@ final class HanyuuRoutes extends RouteHandler { $response['guise'] = $extractUserInfo($userInfoReal); $csrfp = CSRF::create($sessionInfo->getToken()); - $response['guise']['revert_url'] = $this->getEndpoint() . $this->urls->format('auth-revert', ['csrf' => $csrfp->createToken()]); + $response['guise']['revert_url'] = $baseUrl . $this->urls->format('auth-revert', ['csrf' => $csrfp->createToken()]); } return self::createPayload('auth:check:success', $response); diff --git a/src/MisuzuContext.php b/src/MisuzuContext.php index 6b887f3..82df4b4 100644 --- a/src/MisuzuContext.php +++ b/src/MisuzuContext.php @@ -1,10 +1,6 @@ profileFields )); - $routingCtx->register(new \Misuzu\Hanyuu\HanyuuRoutes( - $this->config->scopeTo('hanyuu'), + $routingCtx->register(new LegacyRoutes($this->urls)); + + $rpcServer = new RpcServer; + $routingCtx->getRouter()->scopeTo('/_hanyuu')->register($rpcServer->createRouteHandler( + new HmacVerificationProvider(fn() => $this->config->getString('hanyuu.secret')) + )); + + $rpcServer->register(new Hanyuu\HanyuuRpcActions( + fn() => $this->config->getString('hanyuu.endpoint'), $this->config->scopeTo('impersonate'), $this->urls, $this->usersCtx, $this->authCtx )); - $routingCtx->register(new LegacyRoutes($this->urls)); - return $routingCtx; } }