akanyan/app/App.php

201 lines
5.7 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace MingTsay\Akanyan;
use Exception;
use JetBrains\PhpStorm\NoReturn;
use League\Plates\Engine;
use MingTsay\Akanyan\Discord\Me;
use MingTsay\Akanyan\Discord\Token;
require_once __DIR__ . '/../env.php';
class App
{
private const allowedUsers = [
305331852225544193, // 小喵#3521
307567957863694348, // Aᴋᴀɴʏᴀɴ#2870
559026859547951104, // 2072#7474
];
public static function getTemplate(): Engine
{
static $templates = new Engine(__DIR__ . '/../templates');
$me = static::me();
$isLogin = static::checkU();
$templates->addData([
'me' => $me,
'whoami' => "$me->username#$me->userDiscriminator",
'noStatus' => false,
'isLogin' => $isLogin,
'isAllowed' => $isLogin && $me !== null && static::isAllowed($me->userId),
]);
return $templates;
}
public static function getU(): ?Token
{
return Auth::decrypt($_COOKIE['u'] ?? '');
}
public static function checkU(): bool
{
$token = static::getU();
if ($token === null) return false;
if ($token->timestamp + $token->expires_in < time()) return false;
return static::me() !== null;
}
public static function setU(Token $token): void
{
try {
setcookie('u', Auth::encrypt($token), [
'expires' => $token->timestamp + $token->expires_in,
'path' => '/',
'domain' => 'akanyan.oho.tw',
'samesite' => 'None',
'secure' => true,
'httponly' => true,
]);
} catch (Exception) {
error_log('Failed to setU.');
}
}
public static function unsetU(): void
{
Redis::unsetMe($_COOKIE['u'] ?? '');
try {
setcookie('u', null, [
'expires' => time() - 3600,
'path' => '/',
'domain' => 'akanyan.oho.tw',
'samesite' => 'None',
'secure' => true,
'httponly' => true,
]);
} catch (Exception) {
error_log('Failed to unsetU.');
}
}
public static function requireAuth(): void
{
if (!static::checkU()) {
header('location: /login.php');
http_response_code(302);
exit;
}
}
public static function requireNonAuth(): void
{
if (static::checkU()) {
header('location: /');
http_response_code(302);
exit;
}
}
public static function requireAllowed(): void
{
static::requireAuth();
$me = static::me();
if ($me === null || !static::isAllowed($me->userId)) {
static::template([
'title' => '您無權限使用本系統',
'body' => <<<HTML
<div class="mt-5">
<p>您的 Discord 帳號不在白名單中。</p>
<p>若您認為這是個錯誤,請聯絡 <a href="https://discordapp.com/users/305331852225544193" target="_blank">小喵#3521</a> 並提供您的使用者編號 <code>$me->userId</code>。</p>
</div>
HTML,
]);
}
}
public static function auth(string $code): void
{
try {
$token = Auth::getTokenByCode($code);
if ($token !== null) static::setU($token);
} catch (Exception) {
error_log('Failed to getTokenByCode.');
}
}
public static function me(): ?Me
{
try {
$u = static::getU();
if ($u === null) return null;
$cachedMe = Redis::getCachedMe($_COOKIE['u'] ?? '');
if ($cachedMe === null) return Redis::cacheMe($_COOKIE['u'] ?? '', Auth::getMe($u));
return $cachedMe;
} catch (Exception) {
error_log('Failed to getMe.');
return null;
}
}
#[NoReturn]
public static function template(array $params): void
{
$status = '';
if (static::checkU() && !$params['no_status']) {
$me = static::me();
$status = <<<HTML
<div>您已使用 <code>$me->username#$me->userDiscriminator</code> 登入,點選此處以<a href="/logout.php">登出系統</a>。</div>
HTML;
}
$html = <<<HTML
<!DOCTYPE html>
<html lang="zh-Hant-TW">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>$params[title]</title>
<link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css"/>
<link rel="stylesheet" href="/style.css"/>
</head>
<body class="text-center">
<h1 class="h3 mb-3 font-weight-normal">$params[title]</h1>
$status
<main>$params[body]</main>
<footer class="mt-5 mb-3 text-muted">Copyright &copy; 2022 Ming Tsay. All rights reserved.</footer>
<script src="/bootstrap/js/bootstrap.bundle.min.js"></script>
</body>
</html>
HTML;
header('Content-Type: text/html; charset=utf-8');
header('Content-Length: ' . strlen($html));
http_response_code(200);
echo($html);
exit;
}
public static function authUrl(): string
{
return Auth::authorize();
}
public static function isAllowed(string $userId): bool
{
return in_array($userId, static::allowedUsers);
}
#[NoReturn]
public static function render(string $name, array $data = []): void
{
$html = static::getTemplate()->render($name, $data);
header('Content-Type: text/html; charset=utf-8');
header('Content-Length: ' . strlen($html));
http_response_code(200);
echo($html);
exit;
}
}