198 lines
5.4 KiB
PHP
198 lines
5.4 KiB
PHP
<?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 = self::me();
|
||
$isLogin = self::checkU();
|
||
$templates->addData([
|
||
'me' => $me,
|
||
'whoami' => "$me->username#$me->userDiscriminator",
|
||
'noStatus' => false,
|
||
'isLogin' => $isLogin,
|
||
'isAllowed' => $isLogin && $me !== null && self::isAllowed($me->userId),
|
||
]);
|
||
return $templates;
|
||
}
|
||
|
||
public static function getU(): ?Token
|
||
{
|
||
return Auth::decrypt($_COOKIE['u'] ?? '');
|
||
}
|
||
|
||
public static function checkU(): bool
|
||
{
|
||
$token = self::getU();
|
||
if ($token === null) return false;
|
||
if ($token->timestamp + $token->expires_in < time()) return false;
|
||
return self::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
|
||
{
|
||
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 (!self::checkU()) {
|
||
header('location: /login.php');
|
||
http_response_code(302);
|
||
exit;
|
||
}
|
||
}
|
||
|
||
public static function requireNonAuth(): void
|
||
{
|
||
if (self::checkU()) {
|
||
header('location: /');
|
||
http_response_code(302);
|
||
exit;
|
||
}
|
||
}
|
||
|
||
public static function requireAllowed(): void
|
||
{
|
||
self::requireAuth();
|
||
$me = self::me();
|
||
if ($me === null || !self::isAllowed($me->userId)) {
|
||
self::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) self::setU($token);
|
||
} catch (Exception) {
|
||
error_log('Failed to getTokenByCode.');
|
||
}
|
||
}
|
||
|
||
public static function me(): ?Me
|
||
{
|
||
try {
|
||
$u = self::getU();
|
||
if ($u === null) return null;
|
||
return Auth::getMe($u);
|
||
} catch (Exception) {
|
||
error_log('Failed to getMe.');
|
||
return null;
|
||
}
|
||
}
|
||
|
||
#[NoReturn]
|
||
public static function template(array $params): void
|
||
{
|
||
$status = '';
|
||
if (self::checkU() && !$params['no_status']) {
|
||
$me = self::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 © 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, self::allowedUsers);
|
||
}
|
||
|
||
#[NoReturn]
|
||
public static function render(string $name, array $data = []): void
|
||
{
|
||
$html = self::getTemplate()->render($name, $data);
|
||
|
||
header('Content-Type: text/html; charset=utf-8');
|
||
header('Content-Length: ' . strlen($html));
|
||
http_response_code(200);
|
||
echo($html);
|
||
exit;
|
||
}
|
||
}
|