Compare commits

..

5 Commits

15 changed files with 155 additions and 30 deletions

6
.gitignore vendored
View File

@ -1,4 +1,4 @@
server/database/dragon.db server/database/*.db
server/database/dragon.db-shm server/database/*.db-shm
server/database/dragon.db-wal server/database/*.db-wal
server/.installed server/.installed

View File

@ -22,6 +22,36 @@ body {
justify-content: space-between; justify-content: space-between;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
footer {
margin-top: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
background-color: rgba(0, 0, 0, 0.25);
padding: 0.5rem;
color: white;
}
div#content {
display: flex;
flex-direction: row;
gap: 1rem;
aside#left {
flex-grow: 1;
max-width: 250px;
}
aside#right {
flex-grow: 1;
max-width: 250px;
}
main {
flex-grow: 1;
}
}
} }
.form-group { .form-group {

View File

@ -19,3 +19,6 @@ if ($route == 'gate') return GateModule::handle();
// 404 // 404
http_response_code(404); http_response_code(404);
echo '404: ' . $route; echo '404: ' . $route;
// cleanup
$app->cleanup();

View File

@ -11,19 +11,26 @@ class App
public static Request $req; public static Request $req;
public static Auth $auth; public static Auth $auth;
public static array $s = []; // game settings public static array $s = []; // game settings
public static array $flashes = []; // flash messages
public function __construct(string $dbPath) public function __construct(string $dbPath)
{ {
self::$req = new Request(); // the current request self::$req = new Request(); // the current request
self::$db = new Database($dbPath); // the database self::$db = new Database($dbPath); // the database
self::$dbPath = $dbPath; // the database path self::$dbPath = $dbPath; // the database path
self::$auth = new Auth();
// load game settings // load game settings
$s = self::$db->q('SELECT * FROM settings WHERE id = 1;'); $s = self::$db->q('SELECT * FROM settings WHERE id = 1;');
self::$s = $s ? $s->fetch() : []; self::$s = $s ? $s->fetch() : [];
// init authentication if (INSTALLED) {
self::$auth = new Auth(); // load the player's auth
self::$auth->good();
}
// load flash messages
self::$flashes = $_SESSION['flash'] ?? [];
} }
public static function performDatabaseReset(): void public static function performDatabaseReset(): void
@ -38,4 +45,21 @@ class App
{ {
return self::$auth->good(); return self::$auth->good();
} }
public static function flash(string $key, mixed $value = null): mixed
{
// get a flash message
if ($value === null) return self::$flashes[$key] ?? null;
// set a flash message
$_SESSION['flash'][$key] = $value;
self::$flashes[$key] = $value;
}
public function cleanup()
{
// clean up flash messages
$_SESSION['flash'] = [];
unset($_SESSION['flash']);
}
} }

View File

@ -15,7 +15,7 @@ class Auth
public function __construct() public function __construct()
{ {
$this->good();
} }
public function login(string $identifier, string $password, bool $remember = false): bool public function login(string $identifier, string $password, bool $remember = false): bool
@ -37,20 +37,25 @@ class Auth
return true; return true;
} }
private function remember(int $id): array|false private function remember(int $id): array
{ {
$data = ['player_id' => $id, 'token' => token()]; $data = ['player_id' => $id, 'token' => token()];
Session::createOrUpdate($data); Session::createOrUpdate($data); // save the token in the database, overwriting the old one if it exists
setcookie(self::COOKIE_NAME, implode('::', $data), strtotime('+30 days'), '/', '', true, true); setcookie(self::COOKIE_NAME, implode('::', $data), strtotime('+30 days'), '/', '', true, true);
return $data; return $data;
} }
private function logout(): void public function logout(): void
{ {
if (isset($_SESSION['player_id'])) unset($_SESSION['player_id']); if (isset($_SESSION['player_id'])) unset($_SESSION['player_id']);
if (isset($_COOKIE[self::COOKIE_NAME])) setcookie(self::COOKIE_NAME, '', time() - 86400, '/', '', true, true); if (isset($_COOKIE[self::COOKIE_NAME])) setcookie(self::COOKIE_NAME, '', time() - 86400, '/', '', true, true);
if (ini_get("session.use_cookies")) {
$params = session_get_cookie_params();
setcookie(session_name(), '', time() - 42000, $params["path"], $params["domain"], $params["secure"], $params["httponly"]);
}
session_destroy();
} }
public function good(): bool public function good(): bool

View File

@ -40,7 +40,7 @@ class Player
public static function validateCredentials(string $identifier, string $password, bool $fetch = false): int|false public static function validateCredentials(string $identifier, string $password, bool $fetch = false): int|false
{ {
// get the player from their username or email // get the player from their username or email
$player = App::$db->do("SELECT " . ($fetch ? '*' : 'id, password') . " FROM players WHERE username = :i OR email = :i LIMIT 1;", ['i' => $identifier]); $player = App::$db->do("SELECT " . ($fetch ? '*' : 'id, password') . " FROM players WHERE LOWER(username) = :i OR LOWER(email) = :i LIMIT 1;", ['i' => strtolower($identifier)]);
if ($player == false) return false; if ($player == false) return false;
$player = $player->fetch(); $player = $player->fetch();

View File

@ -9,11 +9,47 @@ class GateModule
$s = App::$req->uri(1) ?? ''; // second segment $s = App::$req->uri(1) ?? ''; // second segment
$m = App::$req->method; // request method $m = App::$req->method; // request method
if ($s == '' || $s == 'login') return self::login(); if (App::$auth->good() && in_array($s, self::GUEST)) redirect('/');
if ($s == '' || $s == 'login') return self::login($m);
if ($s == 'logout' && $m == 'POST') return self::logout();
} }
public static function login() public static function login(string $method)
{ {
echo render('layout', ['title' => 'Login']); // just display the login page
if ($method == 'GET') {
echo render('layout', ['title' => 'Login', 'content' => 'gate/login']);
return;
}
// handle the login form
$id = trim($_POST['id'] ?? ''); // identifier; let a user log in with email or username
$pw = $_POST['pw'] ?? ''; // password
// fields are required
if (empty($id) || empty($pw)) {
App::flash('error', 'Please fill out all fields.');
redirect('/gate/login');
}
// find the user, login if valid
$found = App::$auth->login($id, $pw, isset($_POST['remember']));
// Login is valid!
if ($found) {
App::flash('success', 'Welcome back!');
redirect('/');
} else {
App::flash('error', 'Player account not found.');
redirect('/gate/login');
}
}
private static function logout()
{
App::$auth->logout();
App::flash('success', 'You have been logged out.');
redirect('/');
} }
} }

View File

@ -4,8 +4,13 @@ class HomeModule
{ {
public static function home() public static function home()
{ {
foreach ($_SESSION['flash'] as $key => $value) {
echo '<div class="alert ' . $key . '">- ' . $value . '</div>';
}
if (App::auth()) { if (App::auth()) {
echo 'You are already logged in!<br>'; echo 'You are already logged in!<br>';
echo '<form action="/gate/logout" method="post"><button>Logout</button></form>';
} else { } else {
echo 'You are not logged in!<br>'; echo 'You are not logged in!<br>';
} }

View File

@ -294,15 +294,14 @@ class InstallModule
'level' => $_POST['level'] ?? 1 'level' => $_POST['level'] ?? 1
]); ]);
// Create the .installed file in the server folder
file_put_contents(SERVER.'/.installed', 'Installed on '.date('Y-m-d H:i:s'));
// login the admin // login the admin
App::$auth->login($_POST['username'], $_POST['password']); App::$auth->login($_POST['username'], $_POST['password']);
// Create the .installed file in the server folder
file_put_contents(SERVER.'/.installed', 'Installed on '.date('Y-m-d H:i:s'));
// Render the finished page! // Render the finished page!
echo render('install/layout', ['title' => 'Finished!', 'step' => 'done', 'name' => $_POST['username'], 'complete' => $_POST['complete'] ?? false]); echo render('install/layout', ['title' => 'Finished!', 'step' => 'done', 'name' => $_POST['username'], 'complete' => $_POST['complete'] ?? false]);
} }
private static function fourOhFour() private static function fourOhFour()

View File

@ -0,0 +1,7 @@
<form action="/gate/login" method="post">
<input type="text" name="id" placeholder="Email or Username">
<input type="password" name="pw" placeholder="Password">
<input type="checkbox" name="remember" id="remember">
<label for="remember">Remember me</label>
<button>Login</button>
</form>

View File

@ -14,15 +14,19 @@
<div id="content"> <div id="content">
<aside id="left"> <aside id="left">
left <?php if (App::$auth->good()) {
echo render('partials/left', $data);
} ?>
</aside> </aside>
<main> <main>
<?= render($content, $data) ?>
</main> </main>
<aside id="right"> <aside id="right">
right <?php if (App::$auth->good()) {
echo render('partials/right', $data);
} ?>
</aside> </aside>
</div> </div>

View File

@ -1 +1,11 @@
footer <div>
&copy; <?= date('Y') ?> <?= App::$s['game_dev'] ?>
</div>
<div>
<?= App::$db->q("SELECT COUNT(id) FROM players WHERE last_online > ?;", [time() - 300])->fetchColumn() ?> players online
</div>
<div>
<?= App::$db->queries() ?> queries in <?= round(App::$db->time(), 2) ?> seconds
</div>

View File

@ -0,0 +1 @@
Left

View File

@ -0,0 +1 @@
Right