Changed the way msz_auth is handled.
Going forward msz_auth is always assumed to be present, even while the user is not logged in. If the cookie is not present a default, empty value will be used. The msz_uid and msz_sid cookies are also still upconverted for some reason but are no longer removed even though there's no active sessions that can possibly have those anymore. As with the previous change, shit may be broken so report any Anomalies you come across, through flashii-issues@flash.moe if necessary.
This commit is contained in:
parent
383e2ed0e0
commit
00d1d2922d
17 changed files with 618 additions and 372 deletions
126
composer.lock
generated
126
composer.lock
generated
|
@ -348,7 +348,7 @@
|
|||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://git.flash.moe/flash/index.git",
|
||||
"reference": "557f089ff79c3806f1973ee7bf82f81ab4faa5f4"
|
||||
"reference": "553b7c4a14aa7f2403c87ce474933986ac17d040"
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
|
@ -386,20 +386,20 @@
|
|||
],
|
||||
"description": "Composer package for the common library for my projects.",
|
||||
"homepage": "https://railgun.sh/index",
|
||||
"time": "2023-07-22T14:25:58+00:00"
|
||||
"time": "2023-08-03T01:29:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "matomo/device-detector",
|
||||
"version": "6.1.3",
|
||||
"version": "6.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/matomo-org/device-detector.git",
|
||||
"reference": "3e0fac7e77f3faadc3858fea9f5fa7efeb9cf239"
|
||||
"reference": "74f6c4f6732b3ad6cdf25560746841d522969112"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/3e0fac7e77f3faadc3858fea9f5fa7efeb9cf239",
|
||||
"reference": "3e0fac7e77f3faadc3858fea9f5fa7efeb9cf239",
|
||||
"url": "https://api.github.com/repos/matomo-org/device-detector/zipball/74f6c4f6732b3ad6cdf25560746841d522969112",
|
||||
"reference": "74f6c4f6732b3ad6cdf25560746841d522969112",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -455,7 +455,7 @@
|
|||
"source": "https://github.com/matomo-org/matomo",
|
||||
"wiki": "https://dev.matomo.org/"
|
||||
},
|
||||
"time": "2023-06-06T11:58:07+00:00"
|
||||
"time": "2023-08-02T08:48:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mustangostang/spyc",
|
||||
|
@ -661,17 +661,84 @@
|
|||
"time": "2021-07-14T16:46:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v6.3.0",
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v3.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa"
|
||||
"url": "https://github.com/symfony/deprecation-contracts.git",
|
||||
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa",
|
||||
"reference": "3af8ac1a3f98f6dbc55e10ae59c9e44bfc38dfaa",
|
||||
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
|
||||
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.4-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/contracts",
|
||||
"url": "https://github.com/symfony/contracts"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"function.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "A generic function and convention to trigger deprecation notices",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-05-23T14:45:45+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v6.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/adb01fe097a4ee930db9258a3cc906b5beb5cf2e",
|
||||
"reference": "adb01fe097a4ee930db9258a3cc906b5beb5cf2e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -722,7 +789,7 @@
|
|||
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v6.3.0"
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v6.3.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -738,7 +805,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-04-21T14:41:17+00:00"
|
||||
"time": "2023-07-06T06:56:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher-contracts",
|
||||
|
@ -898,20 +965,21 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/mime",
|
||||
"version": "v6.3.0",
|
||||
"version": "v6.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/mime.git",
|
||||
"reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad"
|
||||
"reference": "9a0cbd52baa5ba5a5b1f0cacc59466f194730f98"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/7b5d2121858cd6efbed778abce9cfdd7ab1f62ad",
|
||||
"reference": "7b5d2121858cd6efbed778abce9cfdd7ab1f62ad",
|
||||
"url": "https://api.github.com/repos/symfony/mime/zipball/9a0cbd52baa5ba5a5b1f0cacc59466f194730f98",
|
||||
"reference": "9a0cbd52baa5ba5a5b1f0cacc59466f194730f98",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.1",
|
||||
"symfony/deprecation-contracts": "^2.5|^3",
|
||||
"symfony/polyfill-intl-idn": "^1.10",
|
||||
"symfony/polyfill-mbstring": "^1.0"
|
||||
},
|
||||
|
@ -920,7 +988,7 @@
|
|||
"phpdocumentor/reflection-docblock": "<3.2.2",
|
||||
"phpdocumentor/type-resolver": "<1.4.0",
|
||||
"symfony/mailer": "<5.4",
|
||||
"symfony/serializer": "<6.2"
|
||||
"symfony/serializer": "<6.2.13|>=6.3,<6.3.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"egulias/email-validator": "^2.1.10|^3.1|^4",
|
||||
|
@ -929,7 +997,7 @@
|
|||
"symfony/dependency-injection": "^5.4|^6.0",
|
||||
"symfony/property-access": "^5.4|^6.0",
|
||||
"symfony/property-info": "^5.4|^6.0",
|
||||
"symfony/serializer": "^6.2"
|
||||
"symfony/serializer": "~6.2.13|^6.3.2"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
@ -961,7 +1029,7 @@
|
|||
"mime-type"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/mime/tree/v6.3.0"
|
||||
"source": "https://github.com/symfony/mime/tree/v6.3.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -977,7 +1045,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-04-28T15:57:00+00:00"
|
||||
"time": "2023-07-31T07:08:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
|
@ -1475,16 +1543,16 @@
|
|||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v3.6.1",
|
||||
"version": "v3.7.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd"
|
||||
"reference": "5cf942bbab3df42afa918caeba947f1b690af64b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
|
||||
"reference": "7e7d5839d4bec168dfeef0ac66d5c5a2edbabffd",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/5cf942bbab3df42afa918caeba947f1b690af64b",
|
||||
"reference": "5cf942bbab3df42afa918caeba947f1b690af64b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1530,7 +1598,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/twigphp/Twig/issues",
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.6.1"
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.7.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -1542,7 +1610,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-06-08T12:52:13+00:00"
|
||||
"time": "2023-07-26T07:16:09+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use Exception;
|
||||
use Misuzu\Auth\AuthTokenCookie;
|
||||
|
||||
if($msz->isLoggedIn()) {
|
||||
url_redirect('index');
|
||||
|
@ -135,8 +136,13 @@ while(!empty($_POST['login']) && is_array($_POST['login'])) {
|
|||
break;
|
||||
}
|
||||
|
||||
$authToken = AuthToken::create($userInfo, $sessionInfo);
|
||||
$authToken->applyCookie($sessionInfo->getExpiresTime());
|
||||
$tokenBuilder = $msz->getAuthInfo()->getTokenInfo()->toBuilder();
|
||||
$tokenBuilder->setUserId($userInfo);
|
||||
$tokenBuilder->setSessionToken($sessionInfo);
|
||||
$tokenBuilder->removeImpersonatedUserId();
|
||||
$tokenInfo = $tokenBuilder->toInfo();
|
||||
|
||||
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
||||
|
||||
if(!is_local_url($loginRedirect))
|
||||
$loginRedirect = url('index');
|
||||
|
|
|
@ -1,16 +1,25 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
if(!$msz->isLoggedIn()) {
|
||||
url_redirect('index');
|
||||
use Misuzu\Auth\AuthTokenCookie;
|
||||
|
||||
if($msz->isLoggedIn()) {
|
||||
if(!CSRF::validateRequest()) {
|
||||
Template::render('auth.logout');
|
||||
return;
|
||||
}
|
||||
|
||||
$tokenInfo = $msz->getAuthInfo()->getTokenInfo();
|
||||
|
||||
$msz->getSessions()->deleteSessions(sessionTokens: $tokenInfo->getSessionToken());
|
||||
|
||||
$tokenBuilder = $tokenInfo->toBuilder();
|
||||
$tokenBuilder->removeUserId();
|
||||
$tokenBuilder->removeSessionToken();
|
||||
$tokenBuilder->removeImpersonatedUserId();
|
||||
$tokenInfo = $tokenBuilder->toInfo();
|
||||
|
||||
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
||||
}
|
||||
|
||||
if(CSRF::validateRequest()) {
|
||||
$msz->getSessions()->deleteSessions(sessionTokens: $authToken->getSessionToken());
|
||||
AuthToken::nukeCookie();
|
||||
url_redirect('index');
|
||||
return;
|
||||
}
|
||||
|
||||
Template::render('auth.logout');
|
||||
url_redirect('index');
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
if(!isset($userInfoReal) || !$authToken->hasImpersonatedUserId() || !CSRF::validateRequest()) {
|
||||
url_redirect('index');
|
||||
use Misuzu\Auth\AuthTokenCookie;
|
||||
|
||||
if(CSRF::validateRequest()) {
|
||||
$tokenInfo = $msz->getAuthInfo()->getTokenInfo();
|
||||
|
||||
if($tokenInfo->hasImpersonatedUserId()) {
|
||||
$impUserId = $tokenInfo->getImpersonatedUserId();
|
||||
|
||||
$tokenBuilder = $tokenInfo->toBuilder();
|
||||
$tokenBuilder->removeImpersonatedUserId();
|
||||
$tokenInfo = $tokenBuilder->toInfo();
|
||||
|
||||
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
||||
url_redirect('manage-user', ['user' => $impUserId]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$impUserId = $authToken->getImpersonatedUserId();
|
||||
$authToken->removeImpersonatedUserId();
|
||||
$authToken->applyCookie();
|
||||
|
||||
url_redirect(
|
||||
$impUserId > 0 ? 'manage-user' : 'index',
|
||||
['user' => $impUserId]
|
||||
);
|
||||
url_redirect('index');
|
||||
|
|
|
@ -3,6 +3,7 @@ namespace Misuzu;
|
|||
|
||||
use RuntimeException;
|
||||
use Misuzu\TOTPGenerator;
|
||||
use Misuzu\Auth\AuthTokenCookie;
|
||||
|
||||
if($msz->isLoggedIn()) {
|
||||
url_redirect('index');
|
||||
|
@ -83,8 +84,13 @@ while(!empty($twofactor)) {
|
|||
break;
|
||||
}
|
||||
|
||||
$authToken = AuthToken::create($userInfo, $sessionInfo);
|
||||
$authToken->applyCookie($sessionInfo->getExpiresTime());
|
||||
$tokenBuilder = $msz->getAuthInfo()->getTokenInfo()->toBuilder();
|
||||
$tokenBuilder->setUserId($userInfo);
|
||||
$tokenBuilder->setSessionToken($sessionInfo);
|
||||
$tokenBuilder->removeImpersonatedUserId();
|
||||
$tokenInfo = $tokenBuilder->toInfo();
|
||||
|
||||
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
||||
|
||||
if(!is_local_url($redirect))
|
||||
$redirect = url('index');
|
||||
|
|
|
@ -3,6 +3,7 @@ namespace Misuzu;
|
|||
|
||||
use RuntimeException;
|
||||
use Index\Colour\Colour;
|
||||
use Misuzu\Auth\AuthTokenCookie;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
if(!$msz->isLoggedIn()) {
|
||||
|
@ -62,8 +63,12 @@ if(CSRF::validateRequest() && $canEdit) {
|
|||
|
||||
if($allowToImpersonate) {
|
||||
$msz->createAuditLog('USER_IMPERSONATE', [$userInfo->getId(), $userInfo->getName()]);
|
||||
$authToken->setImpersonatedUserId($userInfo->getId());
|
||||
$authToken->applyCookie();
|
||||
|
||||
$tokenBuilder = $msz->getAuthInfo()->getTokenInfo()->toBuilder();
|
||||
$tokenBuilder->setImpersonatedUserId($userInfo->getId());
|
||||
$tokenInfo = $tokenBuilder->toInfo();
|
||||
|
||||
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
||||
url_redirect('index');
|
||||
return;
|
||||
} else $notices[] = 'You aren\'t allowed to impersonate this user.';
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\Auth\AuthTokenBuilder;
|
||||
use Misuzu\Auth\AuthTokenCookie;
|
||||
use Misuzu\Auth\AuthTokenInfo;
|
||||
|
||||
require_once __DIR__ . '/../misuzu.php';
|
||||
|
||||
|
@ -51,7 +54,6 @@ $globals = $cfg->getValues([
|
|||
'site.url:s',
|
||||
'eeprom.path:s',
|
||||
'eeprom.app:s',
|
||||
['auth.secret:s', 'meow'],
|
||||
['csrf.secret:s', 'soup'],
|
||||
]);
|
||||
|
||||
|
@ -74,50 +76,55 @@ unset($mszAssetsInfo);
|
|||
|
||||
Template::addPath(MSZ_TEMPLATES);
|
||||
|
||||
AuthToken::setSecretKey($globals['auth.secret']);
|
||||
$tokenPacker = $msz->createAuthTokenPacker();
|
||||
|
||||
if(isset($_COOKIE['msz_uid']) && isset($_COOKIE['msz_sid'])) {
|
||||
$authToken = new AuthToken;
|
||||
$authToken->setUserId(filter_input(INPUT_COOKIE, 'msz_uid', FILTER_SANITIZE_NUMBER_INT) ?? '0');
|
||||
$authToken->setSessionToken(filter_input(INPUT_COOKIE, 'msz_sid') ?? '');
|
||||
if(filter_has_var(INPUT_COOKIE, 'msz_auth'))
|
||||
$tokenInfo = $tokenPacker->unpack(filter_input(INPUT_COOKIE, 'msz_auth'));
|
||||
elseif(filter_has_var(INPUT_COOKIE, 'msz_uid') && filter_has_var(INPUT_COOKIE, 'msz_sid')) {
|
||||
$tokenBuilder = new AuthTokenBuilder;
|
||||
$tokenBuilder->setUserId((string)filter_input(INPUT_COOKIE, 'msz_uid', FILTER_SANITIZE_NUMBER_INT));
|
||||
$tokenBuilder->setSessionToken((string)filter_input(INPUT_COOKIE, 'msz_sid'));
|
||||
$tokenInfo = $tokenBuilder->toInfo();
|
||||
$tokenBuilder = null;
|
||||
} else
|
||||
$tokenInfo = AuthTokenInfo::empty();
|
||||
|
||||
if($authToken->isValid())
|
||||
$authToken->applyCookie(strtotime('1 year'));
|
||||
$userInfo = null;
|
||||
$sessionInfo = null;
|
||||
$userInfoReal = null;
|
||||
|
||||
AuthToken::nukeCookieLegacy();
|
||||
}
|
||||
if($tokenInfo->hasUserId() && $tokenInfo->hasSessionToken()) {
|
||||
$users = $msz->getUsers();
|
||||
$sessions = $msz->getSessions();
|
||||
$tokenBuilder = new AuthTokenBuilder($tokenInfo);
|
||||
|
||||
if(!isset($authToken))
|
||||
$authToken = AuthToken::unpack(filter_input(INPUT_COOKIE, 'msz_auth') ?? '');
|
||||
|
||||
$users = $msz->getUsers();
|
||||
$sessions = $msz->getSessions();
|
||||
|
||||
if($authToken->isValid()) {
|
||||
try {
|
||||
$sessionInfo = $sessions->getSession(sessionToken: $authToken->getSessionToken());
|
||||
$sessionInfo = $sessions->getSession(sessionToken: $tokenInfo->getSessionToken());
|
||||
|
||||
if($sessionInfo->hasExpired()) {
|
||||
$sessions->deleteSessions(sessionInfos: $sessionInfo);
|
||||
} elseif($sessionInfo->getUserId() === $authToken->getUserId()) {
|
||||
$userInfo = $users->getUser($authToken->getUserId(), 'id');
|
||||
$tokenBuilder->removeUserId();
|
||||
$tokenBuilder->removeSessionToken();
|
||||
} elseif($sessionInfo->getUserId() === $tokenInfo->getUserId()) {
|
||||
$userInfo = $users->getUser($tokenInfo->getUserId(), 'id');
|
||||
|
||||
if(!$userInfo->isDeleted()) {
|
||||
if($userInfo->isDeleted()) {
|
||||
$tokenBuilder->removeUserId();
|
||||
$tokenBuilder->removeSessionToken();
|
||||
} else {
|
||||
$users->recordUserActivity($userInfo, remoteAddr: $_SERVER['REMOTE_ADDR']);
|
||||
$sessions->recordSessionActivity(sessionInfo: $sessionInfo, remoteAddr: $_SERVER['REMOTE_ADDR']);
|
||||
if($sessionInfo->shouldBumpExpires())
|
||||
$authToken->applyCookie($sessionInfo->getExpiresTime());
|
||||
$tokenBuilder->setEdited();
|
||||
|
||||
if($authToken->hasImpersonatedUserId()) {
|
||||
if($tokenInfo->hasImpersonatedUserId()) {
|
||||
$allowToImpersonate = $userInfo->isSuperUser();
|
||||
$impersonatedUserId = $authToken->getImpersonatedUserId();
|
||||
$impersonatedUserId = $tokenInfo->getImpersonatedUserId();
|
||||
|
||||
if(!$allowToImpersonate) {
|
||||
$allowImpersonateUsers = $cfg->getArray(sprintf('impersonate.allow.u%s', $userInfo->getId()));
|
||||
$allowToImpersonate = in_array((string)$impersonatedUserId, $allowImpersonateUsers, true);
|
||||
}
|
||||
|
||||
$removeImpersonationData = !$allowToImpersonate;
|
||||
if($allowToImpersonate) {
|
||||
$userInfoReal = $userInfo;
|
||||
|
||||
|
@ -125,24 +132,30 @@ if($authToken->isValid()) {
|
|||
$userInfo = $users->getUser($impersonatedUserId, 'id');
|
||||
} catch(RuntimeException $ex) {
|
||||
$userInfo = $userInfoReal;
|
||||
$removeImpersonationData = true;
|
||||
$userInfoReal = null;
|
||||
$tokenBuilder->removeImpersonatedUserId();
|
||||
}
|
||||
} else $tokenBuilder->removeImpersonatedUserId();
|
||||
}
|
||||
|
||||
if($removeImpersonationData) {
|
||||
$authToken->removeImpersonatedUserId();
|
||||
$authToken->applyCookie();
|
||||
}
|
||||
}
|
||||
|
||||
$msz->setAuthInfo($authToken, $userInfo, $userInfoReal ?? null);
|
||||
}
|
||||
}
|
||||
} catch(RuntimeException $ex) {
|
||||
AuthToken::nukeCookie();
|
||||
$tokenBuilder->removeUserId();
|
||||
$tokenBuilder->removeSessionToken();
|
||||
$tokenBuilder->removeImpersonatedUserId();
|
||||
$userInfo = null;
|
||||
$sessionInfo = null;
|
||||
$userInfoReal = null;
|
||||
}
|
||||
|
||||
if($tokenBuilder->isEdited()) {
|
||||
$tokenInfo = $tokenBuilder->toInfo();
|
||||
AuthTokenCookie::apply($tokenPacker->pack($tokenInfo));
|
||||
}
|
||||
}
|
||||
|
||||
$msz->getAuthInfo()->setInfo($tokenInfo, $userInfo, $sessionInfo, $userInfoReal);
|
||||
|
||||
if(!empty($userInfo))
|
||||
$userInfo = $users->getUser((string)$userInfo->getId(), 'id');
|
||||
if(!empty($userInfoReal))
|
||||
|
@ -150,7 +163,7 @@ if(!empty($userInfoReal))
|
|||
|
||||
CSRF::init(
|
||||
$globals['csrf.secret'],
|
||||
($msz->isLoggedIn() ? $authToken->getSessionToken() : $_SERVER['REMOTE_ADDR'])
|
||||
($msz->isLoggedIn() ? $sessionInfo->getToken() : $_SERVER['REMOTE_ADDR'])
|
||||
);
|
||||
|
||||
if(!empty($userInfo)) {
|
||||
|
|
86
src/Auth/AuthInfo.php
Normal file
86
src/Auth/AuthInfo.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
namespace Misuzu\Auth;
|
||||
|
||||
use Misuzu\Auth\SessionInfo;
|
||||
use Misuzu\Users\UserInfo;
|
||||
|
||||
class AuthInfo {
|
||||
private AuthTokenInfo $tokenInfo;
|
||||
private ?UserInfo $userInfo;
|
||||
private ?SessionInfo $sessionInfo;
|
||||
private ?UserInfo $realUserInfo;
|
||||
|
||||
public function __construct(
|
||||
?AuthTokenInfo $tokenInfo = null,
|
||||
?UserInfo $userInfo = null,
|
||||
?SessionInfo $sessionInfo = null,
|
||||
?UserInfo $realUserInfo = null
|
||||
) {
|
||||
$this->setInfo(
|
||||
$tokenInfo ?? AuthTokenInfo::empty(),
|
||||
$userInfo,
|
||||
$sessionInfo,
|
||||
$realUserInfo
|
||||
);
|
||||
}
|
||||
|
||||
public function setInfo(
|
||||
AuthTokenInfo $tokenInfo,
|
||||
?UserInfo $userInfo = null,
|
||||
?SessionInfo $sessionInfo = null,
|
||||
?UserInfo $realUserInfo = null
|
||||
): void {
|
||||
$this->tokenInfo = $tokenInfo;
|
||||
$this->userInfo = $userInfo;
|
||||
$this->sessionInfo = $sessionInfo;
|
||||
$this->realUserInfo = $realUserInfo;
|
||||
}
|
||||
|
||||
public function removeInfo(): void {
|
||||
$this->setInfo(AuthTokenInfo::empty());
|
||||
}
|
||||
|
||||
public function getTokenInfo(): AuthTokenInfo {
|
||||
return $this->tokenInfo;
|
||||
}
|
||||
|
||||
public function isLoggedIn(): bool {
|
||||
return $this->userInfo !== null;
|
||||
}
|
||||
|
||||
public function getUserId(): ?string {
|
||||
return $this->userInfo?->getId();
|
||||
}
|
||||
|
||||
public function getUserInfo(): ?UserInfo {
|
||||
return $this->userInfo;
|
||||
}
|
||||
|
||||
public function getSessionInfo(): ?SessionInfo {
|
||||
return $this->sessionInfo;
|
||||
}
|
||||
|
||||
public function isImpersonating(): bool {
|
||||
return $this->realUserInfo !== null;
|
||||
}
|
||||
|
||||
public function getRealUserId(): ?string {
|
||||
return $this->realUserInfo?->getId();
|
||||
}
|
||||
|
||||
public function getRealUserInfo(): ?UserInfo {
|
||||
return $this->realUserInfo;
|
||||
}
|
||||
|
||||
private static AuthInfo $empty;
|
||||
|
||||
public static function init(): void {
|
||||
self::$empty = new AuthInfo(AuthTokenInfo::empty());
|
||||
}
|
||||
|
||||
public static function empty(): self {
|
||||
return self::$empty;
|
||||
}
|
||||
}
|
||||
|
||||
AuthInfo::init();
|
72
src/Auth/AuthTokenBuilder.php
Normal file
72
src/Auth/AuthTokenBuilder.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
namespace Misuzu\Auth;
|
||||
|
||||
use Misuzu\Auth\SessionInfo;
|
||||
use Misuzu\Users\UserInfo;
|
||||
|
||||
class AuthTokenBuilder {
|
||||
private array $props;
|
||||
private bool $edited = false;
|
||||
|
||||
public function __construct(?AuthTokenInfo $baseTokenInfo = null) {
|
||||
$this->props = $baseTokenInfo === null ? [] : $baseTokenInfo->getProperties();
|
||||
}
|
||||
|
||||
public function getProperties(): array {
|
||||
return $this->props;
|
||||
}
|
||||
|
||||
public function setEdited(): void {
|
||||
$this->edited = true;
|
||||
}
|
||||
|
||||
public function isEdited(): bool {
|
||||
return $this->edited;
|
||||
}
|
||||
|
||||
public function setProperty(string $name, string $value): void {
|
||||
$this->props[$name] = $value;
|
||||
}
|
||||
|
||||
public function removeProperty(string $name): void {
|
||||
$this->edited = true;
|
||||
unset($this->props[$name]);
|
||||
}
|
||||
|
||||
public function setUserId(UserInfo|string $userId): void {
|
||||
if($userId instanceof UserInfo)
|
||||
$userId = $userId->getId();
|
||||
|
||||
$this->setProperty(AuthTokenInfo::USER_ID, $userId);
|
||||
}
|
||||
|
||||
public function removeUserId(): void {
|
||||
$this->removeProperty(AuthTokenInfo::USER_ID);
|
||||
}
|
||||
|
||||
public function setSessionToken(SessionInfo|string $sessionKey): void {
|
||||
if($sessionKey instanceof SessionInfo)
|
||||
$sessionKey = $sessionKey->getToken();
|
||||
|
||||
$this->setProperty(AuthTokenInfo::SESSION_TOKEN, $sessionKey);
|
||||
}
|
||||
|
||||
public function removeSessionToken(): void {
|
||||
$this->removeProperty(AuthTokenInfo::SESSION_TOKEN);
|
||||
}
|
||||
|
||||
public function setImpersonatedUserId(UserInfo|string $userId): void {
|
||||
if($userId instanceof UserInfo)
|
||||
$userId = $userId->getId();
|
||||
|
||||
$this->setProperty(AuthTokenInfo::IMPERSONATED_USER_ID, $userId);
|
||||
}
|
||||
|
||||
public function removeImpersonatedUserId(): void {
|
||||
$this->removeProperty(AuthTokenInfo::IMPERSONATED_USER_ID);
|
||||
}
|
||||
|
||||
public function toInfo(?int $timestamp = null): AuthTokenInfo {
|
||||
return new AuthTokenInfo($timestamp ?? time(), $this->props);
|
||||
}
|
||||
}
|
25
src/Auth/AuthTokenCookie.php
Normal file
25
src/Auth/AuthTokenCookie.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
namespace Misuzu\Auth;
|
||||
|
||||
// is this the right way to do this?
|
||||
|
||||
final class AuthTokenCookie {
|
||||
public static function domain(): string {
|
||||
$url = parse_url($_SERVER['HTTP_HOST'], PHP_URL_HOST);
|
||||
if(empty($url))
|
||||
$url = $_SERVER['HTTP_HOST'];
|
||||
|
||||
if(!filter_var($url, FILTER_VALIDATE_IP))
|
||||
$url = '.' . $url;
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public static function apply(string $packed): void {
|
||||
setcookie('msz_auth', $packed, strtotime('+3 months'), '/', self::domain(), !empty($_SERVER['HTTPS']), true);
|
||||
}
|
||||
|
||||
public static function nuke(): void {
|
||||
setcookie('msz_auth', '', -9001, '/', self::domain(), !empty($_SERVER['HTTPS']), true);
|
||||
}
|
||||
}
|
83
src/Auth/AuthTokenInfo.php
Normal file
83
src/Auth/AuthTokenInfo.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
namespace Misuzu\Auth;
|
||||
|
||||
class AuthTokenInfo {
|
||||
// Standard auth token properties
|
||||
// Props may be used for general purpose stuff not defined here but don't use single char names for them
|
||||
public const USER_ID = 'u'; // User ID
|
||||
public const SESSION_TOKEN = 's'; // Session token
|
||||
public const SESSION_TOKEN_HEX = 't'; // Session token that should be hex encoded
|
||||
public const IMPERSONATED_USER_ID = 'i'; // Impersonated user ID
|
||||
|
||||
public function __construct(
|
||||
private int $timestamp = 0,
|
||||
private array $props = []
|
||||
) {}
|
||||
|
||||
public function isEmpty(): bool {
|
||||
return $this->timestamp === 0 && empty($this->props);
|
||||
}
|
||||
|
||||
public function getTimestamp(): int {
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
public function getProperties(): array {
|
||||
return $this->props;
|
||||
}
|
||||
|
||||
public function toBuilder(): AuthTokenBuilder {
|
||||
return new AuthTokenBuilder($this);
|
||||
}
|
||||
|
||||
public function hasProperty(string $name): bool {
|
||||
return array_key_exists($name, $this->props);
|
||||
}
|
||||
|
||||
public function getProperty(string $name): string {
|
||||
return $this->props[$name] ?? '';
|
||||
}
|
||||
|
||||
public function hasUserId(): bool {
|
||||
return $this->hasProperty(self::USER_ID);
|
||||
}
|
||||
|
||||
public function getUserId(): string {
|
||||
return $this->getProperty(self::USER_ID);
|
||||
}
|
||||
|
||||
public function hasSessionToken(): bool {
|
||||
return $this->hasProperty(self::SESSION_TOKEN)
|
||||
|| $this->hasProperty(self::SESSION_TOKEN_HEX);
|
||||
}
|
||||
|
||||
public function getSessionToken(): string {
|
||||
if($this->hasProperty(self::SESSION_TOKEN))
|
||||
return $this->getProperty(self::SESSION_TOKEN);
|
||||
|
||||
if($this->hasProperty(self::SESSION_TOKEN_HEX))
|
||||
return bin2hex($this->getProperty(self::SESSION_TOKEN_HEX));
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function hasImpersonatedUserId(): bool {
|
||||
return $this->hasProperty(self::IMPERSONATED_USER_ID);
|
||||
}
|
||||
|
||||
public function getImpersonatedUserId(): string {
|
||||
return $this->getProperty(self::IMPERSONATED_USER_ID);
|
||||
}
|
||||
|
||||
private static AuthTokenInfo $empty;
|
||||
|
||||
public static function init(): void {
|
||||
self::$empty = new AuthTokenInfo(0);
|
||||
}
|
||||
|
||||
public static function empty(): self {
|
||||
return self::$empty;
|
||||
}
|
||||
}
|
||||
|
||||
AuthTokenInfo::init();
|
99
src/Auth/AuthTokenPacker.php
Normal file
99
src/Auth/AuthTokenPacker.php
Normal file
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
namespace Misuzu\Auth;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\IO\MemoryStream;
|
||||
use Index\Serialisation\UriBase64;
|
||||
|
||||
class AuthTokenPacker {
|
||||
private const EPOCH_V2 = 1682985600;
|
||||
|
||||
public function __construct(private string $secretKey) {}
|
||||
|
||||
public function pack(AuthTokenBuilder|AuthTokenInfo $tokenInfo): string {
|
||||
$props = $tokenInfo->getProperties();
|
||||
$timestamp = $tokenInfo instanceof AuthTokenInfo ? $tokenInfo->getTimestamp() : time();
|
||||
|
||||
$data = '';
|
||||
|
||||
foreach($props as $name => $value) {
|
||||
// very smart solution for this issue, you definitely won't be confused by this later
|
||||
// down the line when a variable suddenly despawns from the token
|
||||
$nameLength = strlen($name);
|
||||
$valueLength = strlen($value);
|
||||
if($nameLength > 255 || $valueLength > 255)
|
||||
continue;
|
||||
|
||||
$data .= chr($nameLength) . $name . chr($valueLength) . $value;
|
||||
}
|
||||
|
||||
$prefix = pack('CN', 2, $timestamp - self::EPOCH_V2);
|
||||
$data = $prefix . hash_hmac('sha3-256', $prefix . $data, $this->secretKey, true) . $data;
|
||||
|
||||
return UriBase64::encode($data);
|
||||
}
|
||||
|
||||
public function unpack(?string $token): AuthTokenInfo {
|
||||
if($token === null || $token === '')
|
||||
return AuthTokenInfo::empty();
|
||||
|
||||
$data = UriBase64::decode($token);
|
||||
if($data === false || $data === '')
|
||||
return AuthTokenInfo::empty();
|
||||
|
||||
$builder = new AuthTokenBuilder;
|
||||
$version = ord($data[0]);
|
||||
$data = str_pad(substr($data, 1), 36, "\x00");
|
||||
$timestamp = null;
|
||||
|
||||
if($version === 1) {
|
||||
$data = unpack('Nuser/H*token', $data);
|
||||
if($data === false)
|
||||
return AuthTokenInfo::empty();
|
||||
|
||||
$builder->setUserId((string)$data['user']);
|
||||
$builder->setSessionToken($data['token']);
|
||||
} elseif($version === 2) {
|
||||
$timestamp = substr($data, 0, 4);
|
||||
$userHash = substr($data, 4, 32);
|
||||
$data = substr($data, 36);
|
||||
$realHash = hash_hmac('sha3-256', chr($version) . $timestamp . $data, $this->secretKey, true);
|
||||
|
||||
if(!hash_equals($realHash, $userHash))
|
||||
return AuthTokenInfo::empty();
|
||||
|
||||
$unpackTime = unpack('Nts', $timestamp);
|
||||
if($unpackTime === false)
|
||||
throw new RuntimeException('$token does not contain a valid timestamp.');
|
||||
|
||||
$timestamp = $unpackTime['ts'] + self::EPOCH_V2;
|
||||
|
||||
$stream = MemoryStream::fromString($data);
|
||||
$stream->seek(0);
|
||||
|
||||
for(;;) {
|
||||
$length = $stream->readChar();
|
||||
if($length === null)
|
||||
break;
|
||||
|
||||
$length = ord($length);
|
||||
if($length < 1)
|
||||
break;
|
||||
|
||||
$name = $stream->read($length);
|
||||
$value = null;
|
||||
$length = $stream->readChar();
|
||||
if($length !== null) {
|
||||
$length = ord($length);
|
||||
if($length > 0)
|
||||
$value = $stream->read($length);
|
||||
}
|
||||
|
||||
$builder->setProperty($name, $value);
|
||||
}
|
||||
} else
|
||||
return AuthTokenInfo::empty();
|
||||
|
||||
return $builder->toInfo($timestamp);
|
||||
}
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Index\IO\MemoryStream;
|
||||
use Index\Serialisation\UriBase64;
|
||||
use Misuzu\Auth\SessionInfo;
|
||||
use Misuzu\Users\UserInfo;
|
||||
|
||||
/* Map of props
|
||||
* u - User ID
|
||||
* s - Plaintext token string
|
||||
* t - Old hex token string, fallback for s
|
||||
* i - Impersonation User ID
|
||||
*/
|
||||
|
||||
class AuthToken {
|
||||
private const EPOCH = 1682985600;
|
||||
|
||||
private int $timestamp = 0;
|
||||
private int $cookieExpires = 0;
|
||||
private array $props = [];
|
||||
|
||||
private static string $secretKey = '';
|
||||
|
||||
public static function setSecretKey(string $secretKey): void {
|
||||
self::$secretKey = $secretKey;
|
||||
}
|
||||
|
||||
public function getTimestamp(): int {
|
||||
return $this->timestamp;
|
||||
}
|
||||
public function updateTimestamp(): void {
|
||||
$this->timestamp = self::timestamp();
|
||||
}
|
||||
|
||||
public function hasProperty(string $name): bool {
|
||||
return isset($this->props[$name]);
|
||||
}
|
||||
public function getProperty(string $name): string {
|
||||
return $this->props[$name] ?? '';
|
||||
}
|
||||
public function setProperty(string $name, string $value): void {
|
||||
$this->props[$name] = $value;
|
||||
$this->updateTimestamp();
|
||||
}
|
||||
public function removeProperty(string $name): void {
|
||||
unset($this->props[$name]);
|
||||
$this->updateTimestamp();
|
||||
}
|
||||
|
||||
public function isValid(): bool {
|
||||
if($this->getUserId() < 1 || empty($this->getSessionToken()))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getUserId(): string {
|
||||
return $this->getProperty('u');
|
||||
}
|
||||
public function setUserId(string $userId): self {
|
||||
$this->setProperty('u', $userId);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSessionToken(): string {
|
||||
if($this->hasProperty('s'))
|
||||
return $this->getProperty('s');
|
||||
|
||||
if($this->hasProperty('t'))
|
||||
return bin2hex($this->getProperty('t'));
|
||||
|
||||
return '';
|
||||
}
|
||||
public function setSessionToken(string $token): self {
|
||||
$this->setProperty('s', $token);
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasImpersonatedUserId(): bool {
|
||||
return $this->hasProperty('i');
|
||||
}
|
||||
public function getImpersonatedUserId(): int {
|
||||
$value = (int)$this->getProperty('i');
|
||||
return $value < 1 ? -1 : $value;
|
||||
}
|
||||
public function setImpersonatedUserId(int $userId): void {
|
||||
$this->setProperty('i', (string)$userId);
|
||||
}
|
||||
public function removeImpersonatedUserId(): void {
|
||||
$this->removeProperty('i');
|
||||
}
|
||||
|
||||
public function pack(bool $base64 = true): string {
|
||||
$data = '';
|
||||
|
||||
foreach($this->props as $name => $value) {
|
||||
// very smart solution for this issue, you definitely won't be confused by this later
|
||||
// down the line when a variable suddenly despawns from the token
|
||||
$nameLength = strlen($name);
|
||||
$valueLength = strlen($value);
|
||||
if($nameLength > 255 || $valueLength > 255)
|
||||
continue;
|
||||
|
||||
$data .= chr($nameLength) . $name . chr($valueLength) . $value;
|
||||
}
|
||||
|
||||
$prefix = pack('CN', 2, $this->getTimestamp());
|
||||
$data = $prefix . hash_hmac('sha3-256', $prefix . $data, self::$secretKey, true) . $data;
|
||||
if($base64)
|
||||
$data = UriBase64::encode($data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function unpack(string $data, bool $base64 = true): self {
|
||||
$obj = new AuthToken;
|
||||
|
||||
if(empty($data))
|
||||
return $obj;
|
||||
if($base64)
|
||||
$data = UriBase64::decode($data);
|
||||
if(empty($data))
|
||||
return $obj;
|
||||
|
||||
$version = ord($data[0]);
|
||||
$data = substr($data, 1);
|
||||
|
||||
if($version === 1) {
|
||||
$data = str_pad($data, 36, "\x00");
|
||||
$data = unpack('Nuser/H*token', $data);
|
||||
|
||||
$obj->props['u'] = (string)$data['user'];
|
||||
$obj->props['s'] = $data['token'];
|
||||
$obj->updateTimestamp();
|
||||
} elseif($version === 2) {
|
||||
$timestamp = substr($data, 0, 4);
|
||||
$userHash = substr($data, 4, 32);
|
||||
$data = substr($data, 36);
|
||||
$realHash = hash_hmac('sha3-256', chr($version) . $timestamp . $data, self::$secretKey, true);
|
||||
|
||||
if(!hash_equals($realHash, $userHash))
|
||||
return $obj;
|
||||
|
||||
$unpacked = unpack('Nts', $timestamp);
|
||||
$obj->timestamp = (int)$unpacked['ts'];
|
||||
|
||||
$stream = MemoryStream::fromString($data);
|
||||
$stream->seek(0);
|
||||
|
||||
for(;;) {
|
||||
$length = $stream->readChar();
|
||||
if($length === null)
|
||||
break;
|
||||
|
||||
$length = ord($length);
|
||||
if($length < 1)
|
||||
break;
|
||||
|
||||
$name = $stream->read($length);
|
||||
$value = null;
|
||||
$length = $stream->readChar();
|
||||
if($length !== null) {
|
||||
$length = ord($length);
|
||||
if($length > 0)
|
||||
$value = $stream->read($length);
|
||||
}
|
||||
|
||||
$obj->props[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function timestamp(): int {
|
||||
return time() - self::EPOCH;
|
||||
}
|
||||
|
||||
public static function create(UserInfo $userInfo, SessionInfo $sessionInfo): self {
|
||||
$token = new AuthToken;
|
||||
$token->setUserId($userInfo->getId());
|
||||
$token->setSessionToken($sessionInfo->getToken());
|
||||
return $token;
|
||||
}
|
||||
|
||||
public static function cookieDomain(bool $compatible = true): string {
|
||||
$url = parse_url($_SERVER['HTTP_HOST'], PHP_URL_HOST);
|
||||
if(empty($url))
|
||||
$url = $_SERVER['HTTP_HOST'];
|
||||
|
||||
if(!filter_var($url, FILTER_VALIDATE_IP) && $compatible)
|
||||
$url = '.' . $url;
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function applyCookie(int $expires = 0): void {
|
||||
if($expires > 0)
|
||||
$this->cookieExpires = $expires;
|
||||
else
|
||||
$expires = $this->cookieExpires;
|
||||
|
||||
setcookie('msz_auth', $this->pack(), $expires, '/', self::cookieDomain(), !empty($_SERVER['HTTPS']), true);
|
||||
}
|
||||
|
||||
public static function nukeCookie(): void {
|
||||
setcookie('msz_auth', '', -9001, '/', self::cookieDomain(), !empty($_SERVER['HTTPS']), true);
|
||||
setcookie('msz_auth', '', -9001, '/', '', !empty($_SERVER['HTTPS']), true);
|
||||
}
|
||||
|
||||
public static function nukeCookieLegacy(): void {
|
||||
setcookie('msz_uid', '', -3600, '/', '', !empty($_SERVER['HTTPS']), true);
|
||||
setcookie('msz_sid', '', -3600, '/', '', !empty($_SERVER['HTTPS']), true);
|
||||
}
|
||||
}
|
|
@ -41,7 +41,7 @@ final class AssetsHandler extends Handler {
|
|||
}
|
||||
|
||||
public function serveAvatar($response, $request, string $fileName) {
|
||||
$userId = (int)pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$userId = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$type = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
|
||||
if($type !== '' && $type !== 'png')
|
||||
|
@ -65,7 +65,7 @@ final class AssetsHandler extends Handler {
|
|||
}
|
||||
|
||||
public function serveProfileBackground($response, $request, string $fileName) {
|
||||
$userId = (int)pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$userId = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$type = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
|
||||
if($type !== '' && $type !== 'png')
|
||||
|
|
|
@ -32,7 +32,7 @@ final class ForumHandler extends Handler {
|
|||
return 400;
|
||||
|
||||
$forumId = (int)$request->getContent()->getParam('forum', FILTER_SANITIZE_NUMBER_INT);
|
||||
forum_mark_read($forumId, $this->context->getActiveUser()->getId());
|
||||
forum_mark_read($forumId, (int)$this->context->getActiveUser()->getId());
|
||||
$redirect = url($forumId ? 'forum-category' : 'forum-index', ['forum' => $forumId]);
|
||||
|
||||
$response->redirect($redirect, false);
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Template;
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Auth\AuthTokenPacker;
|
||||
use Misuzu\Auth\LoginAttempts;
|
||||
use Misuzu\Auth\RecoveryTokens;
|
||||
use Misuzu\Auth\Sessions;
|
||||
|
@ -57,6 +59,7 @@ class MisuzuContext {
|
|||
private Sessions $sessions;
|
||||
private Counters $counters;
|
||||
private ProfileFields $profileFields;
|
||||
private AuthInfo $authInfo;
|
||||
|
||||
public function __construct(IDbConnection $dbConn, IConfig $config) {
|
||||
$this->dbConn = $dbConn;
|
||||
|
@ -77,6 +80,7 @@ class MisuzuContext {
|
|||
$this->sessions = new Sessions($this->dbConn);
|
||||
$this->counters = new Counters($this->dbConn);
|
||||
$this->profileFields = new ProfileFields($this->dbConn);
|
||||
$this->authInfo = new AuthInfo;
|
||||
}
|
||||
|
||||
public function getDbConn(): IDbConnection {
|
||||
|
@ -168,53 +172,21 @@ class MisuzuContext {
|
|||
return $this->profileFields;
|
||||
}
|
||||
|
||||
private ?AuthToken $authToken = null;
|
||||
private ?UserInfo $activeUser = null;
|
||||
private ?UserInfo $activeUserReal = null;
|
||||
|
||||
public function setAuthInfo(AuthToken $authToken, ?UserInfo $userInfo, ?UserInfo $realUserInfo): void {
|
||||
$this->authToken = $authToken;
|
||||
$this->activeUser = $userInfo;
|
||||
$this->activeUserReal = $realUserInfo;
|
||||
public function createAuthTokenPacker(): AuthTokenPacker {
|
||||
return new AuthTokenPacker($this->config->getString('auth.secret', 'meow'));
|
||||
}
|
||||
|
||||
public function removeAuthInfo(): void {
|
||||
$this->authToken = null;
|
||||
$this->activeUser = null;
|
||||
$this->activeUserReal = null;
|
||||
}
|
||||
|
||||
public function hasAuthToken(): bool {
|
||||
return $this->authToken !== null;
|
||||
}
|
||||
|
||||
public function getAuthToken(): ?AuthToken {
|
||||
return $this->authToken;
|
||||
public function getAuthInfo(): AuthInfo {
|
||||
return $this->authInfo;
|
||||
}
|
||||
|
||||
// isLoggedIn and getActiveUser are proxied for convenience, supply authInfo to things in the future
|
||||
public function isLoggedIn(): bool {
|
||||
return $this->authToken !== null && $this->activeUser !== null;
|
||||
}
|
||||
|
||||
public function isImpersonating(): bool {
|
||||
return $this->activeUser !== null && $this->activeUserReal !== null
|
||||
&& $this->activeUser->getId() !== $this->activeUserReal->getId();
|
||||
}
|
||||
|
||||
public function hasActiveUser(): bool {
|
||||
return $this->activeUser !== null;
|
||||
return $this->authInfo->isLoggedIn();
|
||||
}
|
||||
|
||||
public function getActiveUser(): ?UserInfo {
|
||||
return $this->activeUser;
|
||||
}
|
||||
|
||||
public function hasRealActiveUser(): bool {
|
||||
return $this->activeUserReal !== null;
|
||||
}
|
||||
|
||||
public function getRealActiveUser(): ?UserInfo {
|
||||
return $this->activeUserReal;
|
||||
return $this->authInfo->getUserInfo();
|
||||
}
|
||||
|
||||
private array $activeBansCache = [];
|
||||
|
@ -430,7 +402,7 @@ class MisuzuContext {
|
|||
$this->router->get('/forum/mark-as-read', $mszCompatHandler('Forum', 'markAsReadGET'));
|
||||
$this->router->post('/forum/mark-as-read', $mszCompatHandler('Forum', 'markAsReadPOST'));
|
||||
|
||||
new SharpChatRoutes($this->router, $this->config->scopeTo('sockChat'), $this, $this->bans, $this->emotes, $this->users, $this->sessions);
|
||||
new SharpChatRoutes($this->router, $this->config->scopeTo('sockChat'), $this->bans, $this->emotes, $this->users, $this->sessions, $this->authInfo, $this->createAuthTokenPacker(...));
|
||||
new SatoriRoutes($this->dbConn, $this->config->scopeTo('satori'), $this->router, $this->users, $this->profileFields);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<?php
|
||||
namespace Misuzu\SharpChat;
|
||||
|
||||
use Closure;
|
||||
use RuntimeException;
|
||||
use Index\Colour\Colour;
|
||||
use Index\Routing\IRouter;
|
||||
use Index\Http\HttpFx;
|
||||
use Misuzu\AuthToken;
|
||||
use Misuzu\MisuzuContext;
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Auth\Sessions;
|
||||
use Misuzu\Config\IConfig;
|
||||
use Misuzu\Emoticons\Emotes;
|
||||
|
@ -15,28 +15,31 @@ use Misuzu\Users\Users;
|
|||
|
||||
final class SharpChatRoutes {
|
||||
private IConfig $config;
|
||||
private MisuzuContext $context;
|
||||
private Bans $bans;
|
||||
private Emotes $emotes;
|
||||
private Users $users;
|
||||
private Sessions $sessions;
|
||||
private AuthInfo $authInfo;
|
||||
private Closure $createAuthTokenPacker;
|
||||
private string $hashKey;
|
||||
|
||||
public function __construct(
|
||||
IRouter $router,
|
||||
IConfig $config,
|
||||
MisuzuContext $context,
|
||||
Bans $bans,
|
||||
Emotes $emotes,
|
||||
Users $users,
|
||||
Sessions $sessions
|
||||
Sessions $sessions,
|
||||
AuthInfo $authInfo,
|
||||
Closure $createAuthTokenPacker // this sucks lol
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->context = $context;
|
||||
$this->bans = $bans;
|
||||
$this->emotes = $emotes;
|
||||
$this->users = $users;
|
||||
$this->sessions = $sessions;
|
||||
$this->authInfo = $authInfo;
|
||||
$this->createAuthTokenPacker = $createAuthTokenPacker;
|
||||
$this->hashKey = $this->config->getString('hashKey', 'woomy');
|
||||
|
||||
// Simplify default error pages
|
||||
|
@ -98,7 +101,7 @@ final class SharpChatRoutes {
|
|||
}
|
||||
|
||||
public function getLogin($response, $request): void {
|
||||
if(!$this->context->isLoggedIn()) {
|
||||
if(!$this->authInfo->isLoggedIn()) {
|
||||
$response->redirect(url('auth-login'));
|
||||
return;
|
||||
}
|
||||
|
@ -132,10 +135,10 @@ final class SharpChatRoutes {
|
|||
if($request->getMethod() === 'OPTIONS')
|
||||
return 204;
|
||||
|
||||
if(!$this->context->hasAuthToken())
|
||||
return ['ok' => false, 'err' => 'token'];
|
||||
$tokenInfo = $this->authInfo->getTokenInfo();
|
||||
|
||||
$tokenInfo = $this->context->getAuthToken();
|
||||
if(!$tokenInfo->hasSessionToken())
|
||||
return ['ok' => false, 'err' => 'token'];
|
||||
|
||||
try {
|
||||
$sessionInfo = $this->sessions->getSession(sessionToken: $tokenInfo->getSessionToken());
|
||||
|
@ -145,16 +148,20 @@ final class SharpChatRoutes {
|
|||
|
||||
if($sessionInfo->hasExpired())
|
||||
return ['ok' => false, 'err' => 'expired'];
|
||||
if($sessionInfo->getUserId() !== $tokenInfo->getUserId())
|
||||
return ['ok' => false, 'err' => 'user'];
|
||||
|
||||
$userInfo = $this->users->getUser($sessionInfo->getUserId(), 'id');
|
||||
$userId = $tokenInfo->hasImpersonatedUserId() && $userInfo->isSuperUser()
|
||||
? $tokenInfo->getImpersonatedUserId()
|
||||
: $userInfo->getId();
|
||||
|
||||
$tokenPacker = ($this->createAuthTokenPacker)();
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'usr' => (int)$userId,
|
||||
'tkn' => $tokenInfo->pack(),
|
||||
'tkn' => $tokenPacker->pack($tokenInfo),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -212,12 +219,19 @@ final class SharpChatRoutes {
|
|||
return ['success' => false, 'reason' => 'hash'];
|
||||
|
||||
if($authMethod === 'SESS' || $authMethod === 'Misuzu') {
|
||||
$authTokenInfo = AuthToken::unpack($authToken);
|
||||
if($authTokenInfo->isValid())
|
||||
$authToken = $authTokenInfo->getSessionToken();
|
||||
$tokenPacker = ($this->createAuthTokenPacker)();
|
||||
$tokenInfo = $tokenPacker->unpack($authToken);
|
||||
if($tokenInfo->isEmpty()) {
|
||||
// don't support using the raw session key for Misuzu format
|
||||
if($authMethod !== 'SESS')
|
||||
return ['success' => false, 'reason' => 'format'];
|
||||
|
||||
$sessionToken = $authToken;
|
||||
} else
|
||||
$sessionToken = $tokenInfo->getSessionToken();
|
||||
|
||||
try {
|
||||
$sessionInfo = $this->sessions->getSession(sessionToken: $authToken);
|
||||
$sessionInfo = $this->sessions->getSession(sessionToken: $sessionToken);
|
||||
} catch(RuntimeException $ex) {
|
||||
return ['success' => false, 'reason' => 'token'];
|
||||
}
|
||||
|
@ -230,11 +244,11 @@ final class SharpChatRoutes {
|
|||
$this->sessions->recordSessionActivity(sessionInfo: $sessionInfo, remoteAddr: $ipAddress);
|
||||
|
||||
$userInfo = $this->users->getUser($sessionInfo->getUserId(), 'id');
|
||||
if($authTokenInfo->hasImpersonatedUserId() && $userInfo->isSuperUser()) {
|
||||
if($tokenInfo->hasImpersonatedUserId() && $userInfo->isSuperUser()) {
|
||||
$userInfoReal = $userInfo;
|
||||
|
||||
try {
|
||||
$userInfo = $this->users->getUser($authTokenInfo->getImpersonatedUserId(), 'id');
|
||||
$userInfo = $this->users->getUser($tokenInfo->getImpersonatedUserId(), 'id');
|
||||
} catch(RuntimeException $ex) {
|
||||
$userInfo = $userInfoReal;
|
||||
}
|
||||
|
@ -243,9 +257,6 @@ final class SharpChatRoutes {
|
|||
return ['success' => false, 'reason' => 'unsupported'];
|
||||
}
|
||||
|
||||
if(empty($userInfo))
|
||||
return ['success' => false, 'reason' => 'user'];
|
||||
|
||||
$this->users->recordUserActivity($userInfo, remoteAddr: $ipAddress);
|
||||
$userColour = $this->users->getUserColour($userInfo);
|
||||
$userRank = $this->users->getUserRank($userInfo);
|
||||
|
|
Loading…
Reference in a new issue