Added new token/login endpoints for chat.

This commit is contained in:
flash 2025-04-22 20:16:55 +00:00
parent 85c71f604a
commit ca2c192330
Signed by: flash
GPG key ID: 2C9C2C574D47FE3E
6 changed files with 128 additions and 16 deletions

View file

@ -1 +1 @@
20250421
20250422

View file

@ -0,0 +1,70 @@
<?php
namespace Misuzu\Chat;
use InvalidArgumentException;
use Index\Config\Config;
use Index\Http\{HttpRequest,HttpResponseBuilder,HttpUri};
use Index\Http\Routing\AccessControl\AccessControl;
use Index\Http\Routing\Processors\Before;
use Index\Http\Routing\Routes\ExactRoute;
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\Auth\{AuthContext,AuthInfo};
use Misuzu\Routing\{HandlerRoles,RouteHandler,RouteHandlerCommon};
#[HandlerRoles('main')]
final class ChatApiRoutes implements RouteHandler, UrlSource {
use RouteHandlerCommon, UrlSourceCommon;
public function __construct(
private ChatContext $chatCtx,
private UrlRegistry $urls,
private AuthContext $authCtx,
private AuthInfo $authInfo,
) {}
/** @return int|void */
#[ExactRoute('GET', '/api/v1/chat/login')]
#[Before('authz:cookie')]
#[UrlFormat('api-v1-chat-login', '/api/v1/chat/login', ['redirect' => '<redirect>'])]
public function getLogin(HttpResponseBuilder $response, HttpRequest $request) {
if(!$request->hasParam('redirect'))
return 400;
$redirect = $request->getFilteredParam('redirect', FILTER_VALIDATE_URL);
if(!is_string($redirect))
return 400;
try {
$redirect = HttpUri::createUri($redirect);
} catch(InvalidArgumentException $ex) {
return 400;
}
if(!$this->chatCtx->isTrustedHost($redirect))
return 403;
if(!$this->authInfo->loggedIn)
$redirect = $this->urls->format('auth-login', [
'redirect' => $this->urls->format('api-v1-chat-login', ['redirect' => $redirect]),
]);
$response->redirect($redirect);
}
/** @return int|array{token_type: 'Misuzu', access_token: string} */
#[AccessControl(credentials: true)]
#[ExactRoute('GET', '/api/v1/chat/token')]
#[Before('authz:cookie')]
public function getToken(HttpResponseBuilder $response, HttpRequest $request): int|array {
$response->setCacheControl('no-store');
if(!$this->authInfo->tokenInfo->hasSessionToken
|| $this->authInfo->sessionInfo?->expired !== false)
return 403;
return [
'token_type' => 'Misuzu',
'access_token' => $this->authCtx->createAuthTokenPacker()->pack($this->authInfo->tokenInfo),
];
}
}

27
src/Chat/ChatContext.php Normal file
View file

@ -0,0 +1,27 @@
<?php
namespace Misuzu\Chat;
use Index\Config\Config;
use Index\Http\HttpUri;
class ChatContext {
/** @var string[] */
public array $origins {
get => $this->config->getArray('origins');
}
public string $storagePoolName {
get => $this->config->getString('storage_pool');
}
public function __construct(
public private(set) Config $config,
) {}
public function isTrustedHost(HttpUri|string $host): bool {
if($host instanceof HttpUri)
$host = $host->host;
return in_array($host, $this->origins);
}
}

View file

@ -38,6 +38,7 @@ class MisuzuContext implements RouteHandler {
public private(set) DatabaseContext $dbCtx;
public private(set) Apps\AppsContext $appsCtx;
public private(set) Auth\AuthContext $authCtx;
public private(set) Chat\ChatContext $chatCtx;
public private(set) Colours\ColoursContext $coloursCtx;
public private(set) Comments\CommentsContext $commentsCtx;
public private(set) Emoticons\EmotesContext $emotesCtx;
@ -95,6 +96,10 @@ class MisuzuContext implements RouteHandler {
Auth\AuthContext::class,
config: $this->config->scopeTo('auth'),
));
$this->deps->register($this->chatCtx = $this->deps->constructLazy(
Chat\ChatContext::class,
config: $this->config->scopeTo('chat'),
));
$this->deps->register($this->coloursCtx = $this->deps->constructLazy(Colours\ColoursContext::class));
$this->deps->register($this->commentsCtx = $this->deps->constructLazy(Comments\CommentsContext::class));
$this->deps->register($this->csrfCtx = $this->deps->constructLazy(
@ -185,6 +190,7 @@ class MisuzuContext implements RouteHandler {
$this->routingCtx->register($this->deps->constructLazy(WebFinger\WebFingerRoutes::class), $roles);
$this->routingCtx->register($this->deps->constructLazy(Chat\ChatApiRoutes::class), $roles);
$this->routingCtx->register($this->deps->constructLazy(Colours\ColoursApiRoutes::class), $roles);
$this->routingCtx->register($this->deps->constructLazy(Emoticons\EmotesApiRoutes::class), $roles);
$this->routingCtx->register($this->deps->constructLazy(Kaomoji\KaomojiApiRoutes::class), $roles);

View file

@ -31,7 +31,10 @@ class RoutingAccessControlHandler extends SimpleAccessControlHandler {
): string|bool {
if($accessControl->credentials) {
$result = null;
if($context->request->requestTarget === '/_sockchat'
if($context->request->requestTarget === '/api/v1/chat'
|| str_starts_with($context->request->requestTarget, '/api/v1/chat/')) {
$result = self::filterOrigin($this->config->getArray('chat.origins'), $origin);
} elseif($context->request->requestTarget === '/_sockchat'
|| str_starts_with($context->request->requestTarget, '/_sockchat/')) {
$result = self::filterOrigin($this->config->getArray('sockChat.origins'), $origin);
} elseif($context->request->requestTarget === '/uploads'

View file

@ -8,25 +8,31 @@ use Index\Http\Streams\Stream;
use Misuzu\Misuzu;
class RoutingErrorHandler extends HtmlErrorHandler {
public const array FORCE_HTML = [
'/api/v1/chat/login',
];
public function handle(HandlerContext $context): void {
if(!$context->response->needsBody)
return;
if(str_starts_with($context->request->requestTarget, '/_')) {
$context->response->setTypePlain();
$context->response->body = Stream::createStream(sprintf('HTTP %03d', $context->response->statusCode));
return;
}
if(!in_array($context->request->requestTarget, self::FORCE_HTML)) {
if(str_starts_with($context->request->requestTarget, '/_')) {
$context->response->setTypePlain();
$context->response->body = Stream::createStream(sprintf('HTTP %03d', $context->response->statusCode));
return;
}
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;
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, $context->response->statusCode);