misuzu/src/MisuzuContext.php
flash 00d1d2922d 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.
2023-08-03 01:35:08 +00:00

520 lines
18 KiB
PHP

<?php
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;
use Misuzu\Auth\TwoFactorAuthSessions;
use Misuzu\AuditLog\AuditLog;
use Misuzu\Changelog\Changelog;
use Misuzu\Comments\Comments;
use Misuzu\Config\IConfig;
use Misuzu\Counters\Counters;
use Misuzu\Emoticons\Emotes;
use Misuzu\News\News;
use Misuzu\Profile\ProfileFields;
use Misuzu\Satori\SatoriRoutes;
use Misuzu\SharpChat\SharpChatRoutes;
use Misuzu\Users\Bans;
use Misuzu\Users\BanInfo;
use Misuzu\Users\ModNotes;
use Misuzu\Users\Roles;
use Misuzu\Users\Users;
use Misuzu\Users\UserInfo;
use Misuzu\Users\Warnings;
use Index\Data\IDbConnection;
use Index\Data\Migration\IDbMigrationRepo;
use Index\Data\Migration\DbMigrationManager;
use Index\Data\Migration\FsDbMigrationRepo;
use Index\Http\HttpFx;
use Index\Http\HttpRequest;
use Index\Routing\Router;
// this class should function as the root for everything going forward
// no more magical static classes that are just kind of assumed to exist
// it currently looks Pretty Messy, but most everything else will be holding instances of other classes
// instances of certain classes should only be made as needed,
// dunno if i want null checks some maybe some kind of init func should be called first like is the case
// with the http shit
class MisuzuContext {
private IDbConnection $dbConn;
private IConfig $config;
private HttpFx $router;
private AuditLog $auditLog;
private Emotes $emotes;
private Changelog $changelog;
private News $news;
private Comments $comments;
private LoginAttempts $loginAttempts;
private RecoveryTokens $recoveryTokens;
private ModNotes $modNotes;
private Bans $bans;
private Warnings $warnings;
private TwoFactorAuthSessions $tfaSessions;
private Roles $roles;
private Users $users;
private Sessions $sessions;
private Counters $counters;
private ProfileFields $profileFields;
private AuthInfo $authInfo;
public function __construct(IDbConnection $dbConn, IConfig $config) {
$this->dbConn = $dbConn;
$this->config = $config;
$this->auditLog = new AuditLog($this->dbConn);
$this->emotes = new Emotes($this->dbConn);
$this->changelog = new Changelog($this->dbConn);
$this->news = new News($this->dbConn);
$this->comments = new Comments($this->dbConn);
$this->loginAttempts = new LoginAttempts($this->dbConn);
$this->recoveryTokens = new RecoveryTokens($this->dbConn);
$this->modNotes = new ModNotes($this->dbConn);
$this->bans = new Bans($this->dbConn);
$this->warnings = new Warnings($this->dbConn);
$this->tfaSessions = new TwoFactorAuthSessions($this->dbConn);
$this->roles = new Roles($this->dbConn);
$this->users = new Users($this->dbConn);
$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 {
return $this->dbConn;
}
public function getDbQueryCount(): int {
$result = $this->dbConn->query('SHOW SESSION STATUS LIKE "Questions"');
return $result->next() ? $result->getInteger(1) : 0;
}
public function createMigrationManager(): DbMigrationManager {
return new DbMigrationManager($this->dbConn, 'msz_' . DbMigrationManager::DEFAULT_TABLE);
}
public function createMigrationRepo(): IDbMigrationRepo {
return new FsDbMigrationRepo(MSZ_MIGRATIONS);
}
public function getConfig(): IConfig {
return $this->config;
}
public function getRouter(): Router {
return $this->router->getRouter();
}
public function getEmotes(): Emotes {
return $this->emotes;
}
public function getChangelog(): Changelog {
return $this->changelog;
}
public function getNews(): News {
return $this->news;
}
public function getComments(): Comments {
return $this->comments;
}
public function getAuditLog(): AuditLog {
return $this->auditLog;
}
public function getLoginAttempts(): LoginAttempts {
return $this->loginAttempts;
}
public function getRecoveryTokens(): RecoveryTokens {
return $this->recoveryTokens;
}
public function getModNotes(): ModNotes {
return $this->modNotes;
}
public function getBans(): Bans {
return $this->bans;
}
public function getWarnings(): Warnings {
return $this->warnings;
}
public function getTFASessions(): TwoFactorAuthSessions {
return $this->tfaSessions;
}
public function getRoles(): Roles {
return $this->roles;
}
public function getUsers(): Users {
return $this->users;
}
public function getSessions(): Sessions {
return $this->sessions;
}
public function getCounters(): Counters {
return $this->counters;
}
public function getProfileFields(): ProfileFields {
return $this->profileFields;
}
public function createAuthTokenPacker(): AuthTokenPacker {
return new AuthTokenPacker($this->config->getString('auth.secret', 'meow'));
}
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->authInfo->isLoggedIn();
}
public function getActiveUser(): ?UserInfo {
return $this->authInfo->getUserInfo();
}
private array $activeBansCache = [];
public function tryGetActiveBan(UserInfo|string|null $userInfo = null): ?BanInfo {
if($userInfo === null) {
if($this->isLoggedIn())
$userInfo = $this->getActiveUser();
else return null;
}
$userId = (string)$userInfo->getId();
if(array_key_exists($userId, $this->activeBansCache))
return $this->activeBansCache[$userId];
return $this->activeBansCache[$userId] = $this->bans->tryGetActiveBan($userId);
}
public function hasActiveBan(UserInfo|string|null $userInfo = null): bool {
return $this->tryGetActiveBan($userInfo) !== null;
}
public function createAuditLog(string $action, array $params = [], UserInfo|string|null $userInfo = null): void {
if($userInfo === null && $this->isLoggedIn())
$userInfo = $this->getActiveUser();
$this->auditLog->createLog(
$userInfo,
$action,
$params,
$_SERVER['REMOTE_ADDR'] ?? '::1',
$_SERVER['COUNTRY_CODE'] ?? 'XX'
);
}
public function getHeaderMenu(?UserInfo $userInfo): array {
$hasUserInfo = $userInfo?->isDeleted() === false;
$menu = [];
$home = [
'title' => 'Home',
'url' => url('index'),
'menu' => [],
];
if($hasUserInfo)
$home['menu'][] = [
'title' => 'Members',
'url' => url('user-list'),
];
$home['menu'][] = [
'title' => 'Changelog',
'url' => url('changelog-index'),
];
$home['menu'][] = [
'title' => 'Contact',
'url' => url('info', ['title' => 'contact']),
];
$home['menu'][] = [
'title' => 'Rules',
'url' => url('info', ['title' => 'rules']),
];
$menu[] = $home;
$menu[] = [
'title' => 'News',
'url' => url('news-index'),
];
$forum = [
'title' => 'Forum',
'url' => url('forum-index'),
'menu' => [],
];
if($hasUserInfo && perms_check_user(MSZ_PERMS_GENERAL, $userInfo->getId(), MSZ_PERM_FORUM_VIEW_LEADERBOARD))
$forum['menu'][] = [
'title' => 'Leaderboard',
'url' => url('forum-leaderboard'),
];
$menu[] = $forum;
$chatPath = $this->config->getString('sockChat.chatPath.normal');
if(!empty($chatPath))
$menu[] = [
'title' => 'Chat',
'url' => $chatPath,
];
return $menu;
}
public function getUserMenu(?UserInfo $userInfo, bool $inBroomCloset): array {
$menu = [];
if($userInfo === null) {
$menu[] = [
'title' => 'Register',
'url' => url('auth-register'),
'icon' => 'fas fa-user-plus fa-fw',
];
$menu[] = [
'title' => 'Log in',
'url' => url('auth-login'),
'icon' => 'fas fa-sign-in-alt fa-fw',
];
} else {
$menu[] = [
'title' => 'Profile',
'url' => url('user-profile', ['user' => $userInfo->getId()]),
'icon' => 'fas fa-user fa-fw',
];
$menu[] = [
'title' => 'Settings',
'url' => url('settings-index'),
'icon' => 'fas fa-cog fa-fw',
];
$menu[] = [
'title' => 'Search',
'url' => url('search-index'),
'icon' => 'fas fa-search fa-fw',
];
if(!$this->hasActiveBan($userInfo) && perms_check_user(MSZ_PERMS_GENERAL, $userInfo->getId(), MSZ_PERM_GENERAL_CAN_MANAGE)) {
// restore behaviour where clicking this button switches between
// site version and broom version
if($inBroomCloset)
$menu[] = [
'title' => 'Exit Broom Closet',
'url' => url('index'),
'icon' => 'fas fa-door-open fa-fw',
];
else
$menu[] = [
'title' => 'Enter Broom Closet',
'url' => url('manage-index'),
'icon' => 'fas fa-door-closed fa-fw',
];
}
$menu[] = [
'title' => 'Log out',
'url' => url('auth-logout'),
'icon' => 'fas fa-sign-out-alt fa-fw',
];
}
return $menu;
}
public function setUpHttp(bool $legacy = false): void {
$this->router = new HttpFx;
$this->router->use('/', function($response) {
$response->setPoweredBy('Misuzu');
});
$this->registerErrorPages();
if($legacy)
$this->registerLegacyRedirects();
else
$this->registerHttpRoutes();
}
public function dispatchHttp(?HttpRequest $request = null): void {
$this->router->dispatch($request);
}
private function registerErrorPages(): void {
$this->router->addErrorHandler(400, function($response) {
$response->setContent(Template::renderRaw('errors.400'));
});
$this->router->addErrorHandler(403, function($response) {
$response->setContent(Template::renderRaw('errors.403'));
});
$this->router->addErrorHandler(404, function($response) {
$response->setContent(Template::renderRaw('errors.404'));
});
$this->router->addErrorHandler(500, function($response) {
$response->setContent(file_get_contents(MSZ_TEMPLATES . '/500.html'));
});
$this->router->addErrorHandler(503, function($response) {
$response->setContent(file_get_contents(MSZ_TEMPLATES . '/503.html'));
});
}
private function registerHttpRoutes(): void {
$mszCompatHandler = fn($className, $method) => fn(...$args) => (new ("\\Misuzu\\Http\\Handlers\\{$className}Handler")($this))->{$method}(...$args);
$this->router->get('/', $mszCompatHandler('Home', 'index'));
$this->router->get('/assets/avatar/:filename', $mszCompatHandler('Assets', 'serveAvatar'));
$this->router->get('/assets/profile-background/:filename', $mszCompatHandler('Assets', 'serveProfileBackground'));
$this->router->get('/info', $mszCompatHandler('Info', 'index'));
$this->router->get('/info/:name', $mszCompatHandler('Info', 'page'));
$this->router->get('/info/:project/:name', $mszCompatHandler('Info', 'page'));
$this->router->get('/changelog', $mszCompatHandler('Changelog', 'index'));
$this->router->get('/changelog.rss', $mszCompatHandler('Changelog', 'feedRss'));
$this->router->get('/changelog.atom', $mszCompatHandler('Changelog', 'feedAtom'));
$this->router->get('/changelog/change/:id', $mszCompatHandler('Changelog', 'change'));
$this->router->get('/news', $mszCompatHandler('News', 'index'));
$this->router->get('/news.rss', $mszCompatHandler('News', 'feedIndexRss'));
$this->router->get('/news.atom', $mszCompatHandler('News', 'feedIndexAtom'));
$this->router->get('/news/:category', $mszCompatHandler('News', 'viewCategory'));
$this->router->get('/news/post/:id', $mszCompatHandler('News', 'viewPost'));
$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->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);
}
private function registerLegacyRedirects(): void {
$this->router->get('/index.php', function($response) {
$response->redirect(url('index'), true);
});
$this->router->get('/info.php', function($response) {
$response->redirect(url('info'), true);
});
$this->router->get('/settings.php', function($response) {
$response->redirect(url('settings-index'), true);
});
$this->router->get('/changelog.php', function($response, $request) {
$changeId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
if($changeId) {
$response->redirect(url('changelog-change', ['change' => $changeId]), true);
return;
}
$response->redirect(url('changelog-index', [
'date' => $request->getParam('d'),
'user' => $request->getParam('u', FILTER_SANITIZE_NUMBER_INT),
]), true);
});
$infoRedirect = function($response, $request, string ...$parts) {
$response->redirect(url('info', ['title' => implode('/', $parts)]), true);
};
$this->router->get('/info.php/:name', $infoRedirect);
$this->router->get('/info.php/:project/:name', $infoRedirect);
$this->router->get('/auth.php', function($response, $request) {
$response->redirect(url([
'logout' => 'auth-logout',
'reset' => 'auth-reset',
'forgot' => 'auth-forgot',
'register' => 'auth-register',
][$request->getParam('m')] ?? 'auth-login'), true);
});
$this->router->get('/news.php', function($response, $request) {
$postId = $request->getParam('n', FILTER_SANITIZE_NUMBER_INT) ?? $request->getParam('p', FILTER_SANITIZE_NUMBER_INT);
if($postId > 0)
$location = url('news-post', ['post' => $postId]);
else {
$catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$pageId = $request->getParam('page', FILTER_SANITIZE_NUMBER_INT);
$location = url($catId > 0 ? 'news-category' : 'news-index', ['category' => $catId, 'page' => $pageId]);
}
$response->redirect($location, true);
});
$this->router->get('/news.php/rss', function($response, $request) {
$catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$location = url($catId > 0 ? 'news-category-feed-rss' : 'news-feed-rss', ['category' => $catId]);
$response->redirect($location, true);
});
$this->router->get('/news.php/atom', function($response, $request) {
$catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$location = url($catId > 0 ? 'news-category-feed-atom' : 'news-feed-atom', ['category' => $catId]);
$response->redirect($location, true);
});
$this->router->get('/news/index.php', function($response, $request) {
$response->redirect(url('news-index', [
'page' => $request->getParam('page', FILTER_SANITIZE_NUMBER_INT),
]), true);
});
$this->router->get('/news/category.php', function($response, $request) {
$response->redirect(url('news-category', [
'category' => $request->getParam('c', FILTER_SANITIZE_NUMBER_INT),
'page' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
]), true);
});
$this->router->get('/news/post.php', function($response, $request) {
$response->redirect(url('news-post', [
'post' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT),
]), true);
});
$this->router->get('/news/feed.php', function() {
return 400;
});
$this->router->get('/news/feed.php/rss', function($response, $request) {
$catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$response->redirect(url(
$catId > 0 ? 'news-category-feed-rss' : 'news-feed-rss',
['category' => $catId]
), true);
});
$this->router->get('/news/feed.php/atom', function($response, $request) {
$catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT);
$response->redirect(url(
$catId > 0 ? 'news-category-feed-atom' : 'news-feed-atom',
['category' => $catId]
), true);
});
$this->router->get('/user-assets.php', function($response, $request) {
return (new \Misuzu\Http\Handlers\AssetsHandler($this))->serveLegacy($response, $request);
});
}
}