Replaced internal Flashii ID routes with RPC library.

This commit is contained in:
flash 2024-08-16 19:29:57 +00:00
parent cc9fccdf18
commit 0bf7ca0d52
4 changed files with 78 additions and 81 deletions

View file

@ -8,7 +8,8 @@
"symfony/mailer": "^6.0", "symfony/mailer": "^6.0",
"matomo/device-detector": "^6.1", "matomo/device-detector": "^6.1",
"sentry/sdk": "^4.0", "sentry/sdk": "^4.0",
"nesbot/carbon": "^3.7" "nesbot/carbon": "^3.7",
"flashwave/aiwass": "^1.0"
}, },
"autoload": { "autoload": {
"classmap": [ "classmap": [

41
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "cec099ea3b4fce65aa1b1ff6b0b40f24", "content-hash": "c8edfa21e13dedc126a6351b7d367559",
"packages": [ "packages": [
{ {
"name": "carbonphp/carbon-doctrine-types", "name": "carbonphp/carbon-doctrine-types",
@ -416,6 +416,45 @@
}, },
"time": "2019-12-30T22:54:17+00:00" "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", "name": "flashwave/index",
"version": "v0.2408.40014", "version": "v0.2408.40014",

View file

@ -1,42 +1,30 @@
<?php <?php
namespace Misuzu\Hanyuu; namespace Misuzu\Hanyuu;
use stdClass;
use RuntimeException; use RuntimeException;
use Index\UriBase64;
use Index\Colour\Colour;
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler};
use Syokuhou\IConfig;
use Misuzu\CSRF; use Misuzu\CSRF;
use Misuzu\Auth\{AuthContext,AuthInfo,Sessions}; use Misuzu\Auth\AuthContext;
use Misuzu\URLs\URLRegistry; use Misuzu\URLs\URLRegistry;
use Misuzu\Users\{UsersContext,UserInfo}; use Misuzu\Users\{UsersContext,UserInfo};
use Aiwass\Server\{RpcActionHandler,RpcProcedure};
use Index\Colour\Colour;
use Syokuhou\IConfig;
final class HanyuuRoutes extends RouteHandler { final class HanyuuRpcActions extends RpcActionHandler {
public function __construct( public function __construct(
private IConfig $config, private $getBaseUrl,
private IConfig $impersonateConfig, // this sucks lol private IConfig $impersonateConfig,
private URLRegistry $urls, private URLRegistry $urls,
private UsersContext $usersCtx, private UsersContext $usersCtx,
private AuthContext $authCtx private AuthContext $authCtx
) {} ) {}
private function getEndpoint(): string { private static function createPayload(string $name, array $attrs = []): array {
return $this->config->getString('endpoint'); $payload = ['name' => $name, 'attrs' => []];
}
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 = [];
foreach($attrs as $name => $value) { foreach($attrs as $name => $value) {
if($value === null) if($value === null)
continue; continue;
$payload->attrs[(string)$name] = $value; $payload['attrs'][(string)$name] = $value;
} }
return $payload; return $payload;
@ -50,42 +38,6 @@ final class HanyuuRoutes extends RouteHandler {
return self::createPayload('error', $attrs); 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 { private function canImpersonateUserId(UserInfo $impersonator, string $targetId): bool {
if($impersonator->isSuperUser()) if($impersonator->isSuperUser())
return true; return true;
@ -97,18 +49,15 @@ final class HanyuuRoutes extends RouteHandler {
); );
} }
#[HttpPost('/_hanyuu/auth-check')] #[RpcProcedure('mszhau:authCheck')]
public function postCheckAuth($response, $request) { public function procAuthCheck(string $method, string $remoteAddr, string $token, string $avatars = '') {
$content = $request->getContent();
$method = (string)$content->getParam('method');
if($method !== 'Misuzu') if($method !== 'Misuzu')
return self::createErrorPayload('auth:check:method', 'Requested auth method is not supported.'); 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) if(filter_var($remoteAddr, FILTER_VALIDATE_IP) === false)
return self::createErrorPayload('auth:check:remote_addr', 'Provided remote address is not in a valid format.'); 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 === '') { if($avatarResolutions === '') {
$avatarResolutions = []; $avatarResolutions = [];
} else { } else {
@ -121,11 +70,12 @@ final class HanyuuRoutes extends RouteHandler {
$avatarResolutions = array_unique($avatarResolutions); $avatarResolutions = array_unique($avatarResolutions);
} }
$loginUrl = $this->getEndpoint() . $this->urls->format('auth-login'); $baseUrl = ($this->getBaseUrl)();
$registerUrl = $this->getEndpoint() . $this->urls->format('auth-register'); $loginUrl = $baseUrl . $this->urls->format('auth-login');
$registerUrl = $baseUrl . $this->urls->format('auth-register');
$tokenPacker = $this->authCtx->createAuthTokenPacker(); $tokenPacker = $this->authCtx->createAuthTokenPacker();
$tokenInfo = $tokenPacker->unpack(trim((string)$content->getParam('token'))); $tokenInfo = $tokenPacker->unpack(trim($token));
if($tokenInfo->isEmpty()) if($tokenInfo->isEmpty())
return self::createPayload('auth:check:fail', ['reason' => 'empty', 'login_url' => $loginUrl, 'register_url' => $registerUrl]); 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(), 'remaining_str' => $banInfo->getRemainingString(),
]; ];
$gatherRequestedAvatars = function($userInfo) use ($avatarResolutions) { $gatherRequestedAvatars = function($userInfo) use ($avatarResolutions, $baseUrl) {
$formatAvatarUrl = fn($res = 0) => ( $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()]; $avatars = ['original' => $formatAvatarUrl()];
@ -195,7 +145,7 @@ final class HanyuuRoutes extends RouteHandler {
'country_code' => $userInfo->getCountryCode(), 'country_code' => $userInfo->getCountryCode(),
'is_deleted' => $userInfo->isDeleted(), 'is_deleted' => $userInfo->isDeleted(),
'has_totp' => $userInfo->hasTOTPKey(), '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), 'avatars' => $gatherRequestedAvatars($userInfo),
]; ];
@ -204,7 +154,7 @@ final class HanyuuRoutes extends RouteHandler {
$response['guise'] = $extractUserInfo($userInfoReal); $response['guise'] = $extractUserInfo($userInfoReal);
$csrfp = CSRF::create($sessionInfo->getToken()); $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); return self::createPayload('auth:check:success', $response);

View file

@ -1,10 +1,6 @@
<?php <?php
namespace Misuzu; namespace Misuzu;
use Index\Data\IDbConnection;
use Index\Data\Migration\{IDbMigrationRepo,DbMigrationManager,FsDbMigrationRepo};
use Sasae\SasaeEnvironment;
use Syokuhou\IConfig;
use Misuzu\Template; use Misuzu\Template;
use Misuzu\Auth\{AuthContext,AuthInfo}; use Misuzu\Auth\{AuthContext,AuthInfo};
use Misuzu\AuditLog\AuditLog; use Misuzu\AuditLog\AuditLog;
@ -19,6 +15,12 @@ use Misuzu\Perms\Permissions;
use Misuzu\Profile\ProfileFields; use Misuzu\Profile\ProfileFields;
use Misuzu\URLs\URLRegistry; use Misuzu\URLs\URLRegistry;
use Misuzu\Users\{UsersContext,UserInfo}; use Misuzu\Users\{UsersContext,UserInfo};
use Aiwass\HmacVerificationProvider;
use Aiwass\Server\RpcServer;
use Index\Data\IDbConnection;
use Index\Data\Migration\{IDbMigrationRepo,DbMigrationManager,FsDbMigrationRepo};
use Sasae\SasaeEnvironment;
use Syokuhou\IConfig;
// this class should function as the root for everything going forward // this class should function as the root for everything going forward
// no more magical static classes that are just kind of assumed to exist // no more magical static classes that are just kind of assumed to exist
@ -277,16 +279,21 @@ class MisuzuContext {
$this->profileFields $this->profileFields
)); ));
$routingCtx->register(new \Misuzu\Hanyuu\HanyuuRoutes( $routingCtx->register(new LegacyRoutes($this->urls));
$this->config->scopeTo('hanyuu'),
$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->config->scopeTo('impersonate'),
$this->urls, $this->urls,
$this->usersCtx, $this->usersCtx,
$this->authCtx $this->authCtx
)); ));
$routingCtx->register(new LegacyRoutes($this->urls));
return $routingCtx; return $routingCtx;
} }
} }