diff --git a/public/css/dragon.css b/public/css/dragon.css index a3b43c5..3788ad5 100644 --- a/public/css/dragon.css +++ b/public/css/dragon.css @@ -30,7 +30,8 @@ body { align-items: center; background-color: rgba(0, 0, 0, 0.25); padding: 0.5rem; - color: white; + color: rgba(0, 0, 0, 0.75); + font-size: 0.75rem; } div#content { diff --git a/server/models/Player.php b/server/models/Player.php index b2b366a..d07bd5d 100644 --- a/server/models/Player.php +++ b/server/models/Player.php @@ -52,4 +52,22 @@ class Player return false; } + + public static function goodUsername(string $username): bool + { + // username must be alphanumeric and between 2 and 20 characters, allow single spaces + return preg_match('/^(?!.* )[a-zA-Z0-9 ]{2,20}$/', $username); + } + + public static function uniqueUsername(string $username): bool + { + $player = App::$db->do("SELECT id FROM players WHERE LOWER(username) = :i LIMIT 1;", ['i' => strtolower($username)]); + return $player->fetch() == false; + } + + public static function uniqueEmail(string $email): bool + { + $player = App::$db->do("SELECT id FROM players WHERE LOWER(email) = :i LIMIT 1;", ['i' => strtolower($email)]); + return $player->fetch() == false; + } } diff --git a/server/modules/GateModule.php b/server/modules/GateModule.php index 854b3c4..b768ff9 100644 --- a/server/modules/GateModule.php +++ b/server/modules/GateModule.php @@ -13,6 +13,7 @@ class GateModule if ($s == '' || $s == 'login') return self::login($m); if ($s == 'logout' && $m == 'POST') return self::logout(); + if ($s == 'register') return self::register($m); } public static function login(string $method) @@ -52,4 +53,69 @@ class GateModule App::flash('success', 'You have been logged out.'); redirect('/'); } + + private static function register(string $method) + { + // just display the register page + if ($method == 'GET') { + echo render('layout', ['title' => 'Register', 'content' => 'gate/register']); + return; + } + + // handle the register form + $un = trim($_POST['username'] ?? ''); + $em = trim($_POST['email'] ?? ''); + $pw = $_POST['password'] ?? ''; + $pw2 = $_POST['password2'] ?? ''; + $cl = $_POST['class'] ?? 1; + + $errors = []; + + // fields are required + if (empty($un)) $errors['un'] = 'Please enter a username.'; + if (empty($em)) $errors['em'] = 'Please enter an email address.'; + if (empty($pw)) $errors['pw'] = 'Please enter a password.'; + if (empty($pw2)) $errors['pw2'] = 'Please confirm your password.'; + + if (!empty($errors)) { + App::flash('errors', $errors); + redirect('/gate/register'); + } + + // password must be at least 6 characters + if (strlen($pw) < 6) $errors['pw'] = 'Password must be at least 6 characters.'; + + // passwords must match + if ($pw != $pw2) $errors['pw2'] = 'Passwords do not match.'; + + // email address must be valid format + if (!filter_var($em, FILTER_VALIDATE_EMAIL)) $errors['em'] = 'Invalid email address.'; + + // username must be alphanumeric and between 2 and 20 characters, allow single spaces + if (!Player::goodUsername($un)) $errors['un'] = 'Invalid username. Must be alphanumeric and between 2 and 20 characters, and can contain spaces.'; + + // username must be unique + if (!Player::uniqueUsername($un)) $errors['un'] = 'Username already exists.'; + + // email address must be unique + if (!Player::uniqueEmail($em)) $errors['em'] = 'Email address already exists.'; + + // flash errors and redirect back to form + if (!empty($errors)) { + App::flash('errors', $errors); + redirect('/gate/register'); + } + + // create the player + Player::create([ + 'username' => $un, + 'email' => $em, + 'password' => password_hash($pw, PASSWORD_ARGON2ID), + 'class_id' => $cl + ]); + + // redirect to login + App::flash('success', "You're now an adventurer! Go forth, $un!"); + redirect('/gate/login'); + } } diff --git a/server/templates/gate/register.php b/server/templates/gate/register.php new file mode 100644 index 0000000..9a80a3e --- /dev/null +++ b/server/templates/gate/register.php @@ -0,0 +1,18 @@ + + +
\ No newline at end of file