--- /dev/null
+<?php
+
+namespace wcf\http\middleware;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\MiddlewareInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+use wcf\data\user\User;
+use wcf\http\attribute\AllowAccessToken;
+use wcf\http\error\NotFoundHandler;
+use wcf\system\request\RequestHandler;
+use wcf\system\session\SessionHandler;
+use wcf\system\WCF;
+
+/**
+ * Handles a given access-token, that allow the user to be authed for the current request.
+ * A missing token will be ignored, an invalid token results in a throw of a IllegalLinkException.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+final class HandleAccessToken implements MiddlewareInterface
+{
+ /**
+ * @inheritDoc
+ */
+ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
+ {
+ if (!$this->handleAccessToken($request->getQueryParams()['at'] ?? '')) {
+ (new NotFoundHandler())->handle($request);
+ }
+
+ return $handler->handle($request);
+ }
+
+ private function handleAccessToken(string $accessToken): bool
+ {
+ if (!$accessToken) {
+ return true;
+ }
+
+ $activeRequest = RequestHandler::getInstance()->getActiveRequest();
+ if (!$activeRequest) {
+ return true;
+ }
+
+ $reflectionClass = new \ReflectionClass($activeRequest->getClassName());
+ if (!$this->hasAttribute($reflectionClass)) {
+ return true;
+ }
+
+ return $this->checkAccessToken($accessToken);
+ }
+
+ private function hasAttribute(\ReflectionClass $class): bool
+ {
+ if ($class->getAttributes(AllowAccessToken::class) !== []) {
+ return true;
+ }
+
+ $parentClass = $class->getParentClass();
+ if ($parentClass === false) {
+ return false;
+ }
+
+ return $this->hasAttribute($parentClass);
+ }
+
+ private function checkAccessToken(string $accessToken): bool
+ {
+ if (!\preg_match('~^(?P<userID>\d{1,10})-(?P<token>[a-f0-9]{40})$~', $accessToken, $matches)) {
+ return false;
+ }
+
+ $userID = $matches['userID'];
+ $token = $matches['token'];
+
+ if (WCF::getUser()->userID) {
+ if ($userID == WCF::getUser()->userID && \hash_equals(WCF::getUser()->accessToken, $token)) {
+ // Everything is fine, but the user is already logged in.
+ return true;
+ }
+ } else {
+ $user = new User($userID);
+ if (
+ $user->userID && $user->accessToken && \hash_equals(
+ $user->accessToken,
+ $token
+ )
+ ) {
+ // Token is valid so we log in the user for the current request.
+ SessionHandler::getInstance()->changeUser($user, true);
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
use wcf\http\middleware\EnforceCacheControlPrivate;
use wcf\http\middleware\EnforceFrameOptions;
use wcf\http\middleware\EnforceNoCacheForTemporaryRedirects;
+use wcf\http\middleware\HandleAccessToken;
use wcf\http\middleware\HandleExceptions;
use wcf\http\middleware\HandleStartupErrors;
use wcf\http\middleware\HandleValinorMappingErrors;
new CheckHttpMethod(),
new Xsrf(),
new CheckSystemEnvironment(),
+ new HandleAccessToken(),
new CheckUserBan(),
new EnforceAcpAuthentication(),
new CheckForEnterpriseNonOwnerAccess(),