A bunch of housekeeping.
This commit is contained in:
parent
489a347c22
commit
fca5b423a4
19 changed files with 555 additions and 343 deletions
11
assets/less/manage/classes/avatar.less
Normal file
11
assets/less/manage/classes/avatar.less
Normal file
|
@ -0,0 +1,11 @@
|
|||
.avatar {
|
||||
flex-shrink: 0;
|
||||
background-color: #333;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
display: block;
|
||||
border: 1px solid #444;
|
||||
max-height: 200px;
|
||||
max-width: 200px;
|
||||
}
|
|
@ -9,7 +9,19 @@
|
|||
|
||||
&__content {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ body {
|
|||
color: #fff;
|
||||
}
|
||||
|
||||
@import "classes/avatar";
|
||||
@import "classes/button";
|
||||
@import "classes/container";
|
||||
@import "classes/footer";
|
||||
|
|
|
@ -9,6 +9,10 @@ require_once __DIR__ . '/src/Forum/post.php';
|
|||
require_once __DIR__ . '/src/Forum/topic.php';
|
||||
require_once __DIR__ . '/src/Forum/validate.php';
|
||||
require_once __DIR__ . '/src/Users/login_attempt.php';
|
||||
require_once __DIR__ . '/src/Users/profile.php';
|
||||
require_once __DIR__ . '/src/Users/role.php';
|
||||
require_once __DIR__ . '/src/Users/session.php';
|
||||
require_once __DIR__ . '/src/Users/user.php';
|
||||
require_once __DIR__ . '/src/Users/validation.php';
|
||||
|
||||
$app = new Application(
|
||||
|
|
185
public/auth.php
185
public/auth.php
|
@ -10,31 +10,28 @@ $db = Database::connection();
|
|||
$config = $app->getConfig();
|
||||
$templating = $app->getTemplating();
|
||||
|
||||
$username_validation_errors = [
|
||||
$usernameValidationErrors = [
|
||||
'trim' => 'Your username may not start or end with spaces!',
|
||||
'short' => "Your username is too short, it has to be at least " . MSZ_USERNAME_MIN_LENGTH . " characters!",
|
||||
'long' => "Your username is too long, it can't be longer than " . MSZ_USERNAME_MAX_LENGTH . " characters!",
|
||||
'short' => sprintf('Your username is too short, it has to be at least %d characters!', MSZ_USERNAME_MIN_LENGTH),
|
||||
'long' => sprintf("Your username is too long, it can't be longer than %d characters!", MSZ_USERNAME_MAX_LENGTH),
|
||||
'double-spaces' => "Your username can't contain double spaces.",
|
||||
'invalid' => 'Your username contains invalid characters.',
|
||||
'spacing' => 'Please use either underscores or spaces, not both!',
|
||||
'in-use' => 'This username is already taken!',
|
||||
];
|
||||
|
||||
$mode = $_GET['m'] ?? 'login';
|
||||
$prevent_registration = $config->get('Auth', 'prevent_registration', 'bool', false);
|
||||
$templating->var('auth_mode', $mode);
|
||||
$authMode = $_GET['m'] ?? 'login';
|
||||
$preventRegistration = $config->get('Auth', 'prevent_registration', 'bool', false);
|
||||
$templating->addPath('auth', __DIR__ . '/../views/auth');
|
||||
$templating->var('prevent_registration', $prevent_registration);
|
||||
|
||||
if (!empty($_REQUEST['username'])) {
|
||||
$templating->var('auth_username', $_REQUEST['username']);
|
||||
}
|
||||
$templating->vars([
|
||||
'prevent_registration' => $preventRegistration,
|
||||
'auth_mode' => $authMode,
|
||||
'auth_username' => $_REQUEST['username'] ?? '',
|
||||
'auth_email' => $_REQUEST['email'] ?? '',
|
||||
]);
|
||||
|
||||
if (!empty($_REQUEST['email'])) {
|
||||
$templating->var('auth_email', $_REQUEST['email']);
|
||||
}
|
||||
|
||||
switch ($mode) {
|
||||
switch ($authMode) {
|
||||
case 'logout':
|
||||
if (!$app->hasActiveSession()) {
|
||||
header('Location: /');
|
||||
|
@ -44,12 +41,7 @@ switch ($mode) {
|
|||
if (isset($_GET['s']) && tmp_csrf_verify($_GET['s'])) {
|
||||
set_cookie_m('uid', '', -3600);
|
||||
set_cookie_m('sid', '', -3600);
|
||||
$deleteSession = $db->prepare('
|
||||
DELETE FROM `msz_sessions`
|
||||
WHERE `session_id` = :session_id
|
||||
');
|
||||
$deleteSession->bindValue('session_id', $app->getSessionId());
|
||||
$deleteSession->execute();
|
||||
user_session_delete($app->getSessionId());
|
||||
header('Location: /');
|
||||
return;
|
||||
}
|
||||
|
@ -63,35 +55,24 @@ switch ($mode) {
|
|||
break;
|
||||
}
|
||||
|
||||
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
$auth_login_error = '';
|
||||
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
$authLoginError = '';
|
||||
|
||||
while ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$ipAddress = IPAddress::remote()->getString();
|
||||
|
||||
if (!isset($_POST['username'], $_POST['password'])) {
|
||||
$auth_login_error = "You didn't fill all the forms!";
|
||||
$authLoginError = "You didn't fill all the forms!";
|
||||
break;
|
||||
}
|
||||
|
||||
$fetchRemainingAttempts = $db->prepare('
|
||||
SELECT 5 - COUNT(`attempt_id`)
|
||||
FROM `msz_login_attempts`
|
||||
WHERE `was_successful` = false
|
||||
AND `created_at` > NOW() - INTERVAL 1 HOUR
|
||||
AND `attempt_ip` = INET6_ATON(:remote_ip)
|
||||
');
|
||||
$fetchRemainingAttempts->bindValue('remote_ip', $ipAddress);
|
||||
$remainingAttempts = $fetchRemainingAttempts->execute()
|
||||
? (int)$fetchRemainingAttempts->fetchColumn()
|
||||
: 0;
|
||||
$remainingAttempts = user_login_attempts_remaining($ipAddress);
|
||||
|
||||
if ($remainingAttempts < 1) {
|
||||
$auth_login_error = 'Too many failed login attempts, try again later.';
|
||||
$authLoginError = 'Too many failed login attempts, try again later.';
|
||||
break;
|
||||
}
|
||||
|
||||
$remainingAttempts -= 1;
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
|
@ -106,44 +87,29 @@ switch ($mode) {
|
|||
$userData = $getUser->execute() ? $getUser->fetch() : [];
|
||||
$userId = (int)($userData['user_id'] ?? 0);
|
||||
|
||||
$auth_error_str = "Invalid username or password, {$remainingAttempts} attempt(s) remaining.";
|
||||
$loginFailedError = sprintf(
|
||||
"Invalid username or password, %d attempt%s remaining.",
|
||||
$remainingAttempts - 1,
|
||||
$remainingAttempts === 2 ? '' : 's'
|
||||
);
|
||||
|
||||
if ($userId < 1) {
|
||||
user_login_attempt_record(false, null, $ipAddress, $user_agent);
|
||||
$auth_login_error = $auth_error_str;
|
||||
user_login_attempt_record(false, null, $ipAddress, $userAgent);
|
||||
$authLoginError = $loginFailedError;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!password_verify($password, $userData['password'])) {
|
||||
user_login_attempt_record(false, $userId, $ipAddress, $user_agent);
|
||||
$auth_login_error = $auth_error_str;
|
||||
user_login_attempt_record(false, $userId, $ipAddress, $userAgent);
|
||||
$authLoginError = $loginFailedError;
|
||||
break;
|
||||
}
|
||||
|
||||
user_login_attempt_record(true, $userId, $ipAddress, $user_agent);
|
||||
user_login_attempt_record(true, $userId, $ipAddress, $userAgent);
|
||||
$sessionKey = user_session_create($userId, $ipAddress, $userAgent);
|
||||
|
||||
$sessionKey = bin2hex(random_bytes(32));
|
||||
|
||||
$createSession = $db->prepare('
|
||||
INSERT INTO `msz_sessions`
|
||||
(
|
||||
`user_id`, `session_ip`, `session_country`,
|
||||
`user_agent`, `session_key`, `created_at`, `expires_on`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
:user_id, INET6_ATON(:session_ip), :session_country,
|
||||
:user_agent, :session_key, NOW(), NOW() + INTERVAL 1 MONTH
|
||||
)
|
||||
');
|
||||
$createSession->bindValue('user_id', $userId);
|
||||
$createSession->bindValue('session_ip', $ipAddress);
|
||||
$createSession->bindValue('session_country', get_country_code($ipAddress));
|
||||
$createSession->bindValue('user_agent', $user_agent);
|
||||
$createSession->bindValue('session_key', $sessionKey);
|
||||
|
||||
if (!$createSession->execute()) {
|
||||
$auth_login_error = 'Unable to create new session, contact an administrator.';
|
||||
if ($sessionKey === '') {
|
||||
$authLoginError = 'Unable to create new session, contact an administrator ASAP.';
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -152,30 +118,21 @@ switch ($mode) {
|
|||
set_cookie_m('uid', $userId, $cookieLife);
|
||||
set_cookie_m('sid', $sessionKey, $cookieLife);
|
||||
|
||||
// Temporary key generation for chat login.
|
||||
// Should eventually be replaced with a callback login system.
|
||||
// Also uses different cookies since $httponly is required to be false for these.
|
||||
$chatKey = bin2hex(random_bytes(16));
|
||||
if (strpos($_SERVER['HTTP_HOST'], 'flashii.net') !== false) {
|
||||
$chatKey = user_generate_chat_key($userId);
|
||||
|
||||
$setChatKey = $db->prepare('
|
||||
UPDATE `msz_users`
|
||||
SET `user_chat_key` = :user_chat_key
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$setChatKey->bindValue('user_chat_key', $chatKey);
|
||||
$setChatKey->bindValue('user_id', $userId);
|
||||
|
||||
if ($setChatKey->execute()) {
|
||||
setcookie('msz_tmp_id', $userId, $cookieLife, '/', '.flashii.net');
|
||||
setcookie('msz_tmp_key', $chatKey, $cookieLife, '/', '.flashii.net');
|
||||
if ($chatKey !== '') {
|
||||
setcookie('msz_tmp_id', $userId, $cookieLife, '/', '.flashii.net');
|
||||
setcookie('msz_tmp_key', $chatKey, $cookieLife, '/', '.flashii.net');
|
||||
}
|
||||
}
|
||||
|
||||
header('Location: /');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($auth_login_error)) {
|
||||
$templating->var('auth_login_error', $auth_login_error);
|
||||
if (!empty($authLoginError)) {
|
||||
$templating->var('auth_login_error', $authLoginError);
|
||||
}
|
||||
|
||||
echo $templating->render('auth');
|
||||
|
@ -186,16 +143,16 @@ switch ($mode) {
|
|||
header('Location: /');
|
||||
}
|
||||
|
||||
$auth_register_error = '';
|
||||
$authRegistrationError = '';
|
||||
|
||||
while ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if ($prevent_registration) {
|
||||
$auth_register_error = 'Registration is not allowed on this instance.';
|
||||
if ($preventRegistration) {
|
||||
$authRegistrationError = 'Registration is not allowed on this instance.';
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isset($_POST['username'], $_POST['password'], $_POST['email'])) {
|
||||
$auth_register_error = "You didn't fill all the forms!";
|
||||
$authRegistrationError = "You didn't fill all the forms!";
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -203,65 +160,45 @@ switch ($mode) {
|
|||
$password = $_POST['password'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
|
||||
$username_validate = user_validate_username($username, true);
|
||||
if ($username_validate !== '') {
|
||||
$auth_register_error = $username_validation_errors[$username_validate];
|
||||
$usernameValidation = user_validate_username($username, true);
|
||||
if ($usernameValidation !== '') {
|
||||
$authRegistrationError = $usernameValidationErrors[$usernameValidation];
|
||||
break;
|
||||
}
|
||||
|
||||
$email_validate = user_validate_email($email, true);
|
||||
if ($email_validate !== '') {
|
||||
$auth_register_error = $email_validate === 'in-use'
|
||||
$emailValidation = user_validate_email($email, true);
|
||||
if ($emailValidation !== '') {
|
||||
$authRegistrationError = $emailValidation === 'in-use'
|
||||
? 'This e-mail address has already been used!'
|
||||
: 'The e-mail address you entered is invalid!';
|
||||
break;
|
||||
}
|
||||
|
||||
if (user_validate_password($password) !== '') {
|
||||
$auth_register_error = 'Your password is too weak!';
|
||||
$authRegistrationError = 'Your password is too weak!';
|
||||
break;
|
||||
}
|
||||
|
||||
$ipAddress = IPAddress::remote()->getString();
|
||||
$createUser = $db->prepare('
|
||||
INSERT INTO `msz_users`
|
||||
(
|
||||
`username`, `password`, `email`, `register_ip`,
|
||||
`last_ip`, `user_country`, `created_at`, `display_role`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
:username, :password, :email, INET6_ATON(:register_ip),
|
||||
INET6_ATON(:last_ip), :user_country, NOW(), 1
|
||||
)
|
||||
');
|
||||
$createUser->bindValue('username', $username);
|
||||
$createUser->bindValue('password', password_hash($password, PASSWORD_ARGON2I));
|
||||
$createUser->bindValue('email', $email);
|
||||
$createUser->bindValue('register_ip', $ipAddress);
|
||||
$createUser->bindValue('last_ip', $ipAddress);
|
||||
$createUser->bindValue('user_country', get_country_code($ipAddress));
|
||||
$createUser = user_create(
|
||||
$username,
|
||||
$password,
|
||||
$email,
|
||||
IPAddress::remote()->getString()
|
||||
);
|
||||
|
||||
if (!$createUser->execute()) {
|
||||
$auth_register_error = 'Something happened?';
|
||||
if ($createUser < 1) {
|
||||
$authRegistrationError = 'Something happened?';
|
||||
break;
|
||||
}
|
||||
|
||||
$addRole = $db->prepare('
|
||||
INSERT INTO `msz_user_roles`
|
||||
(`user_id`, `role_id`)
|
||||
VALUES
|
||||
(:user_id, 1)
|
||||
');
|
||||
$addRole->bindValue('user_id', $db->lastInsertId());
|
||||
$addRole->execute();
|
||||
user_role_add($createUser, MSZ_ROLE_MAIN);
|
||||
|
||||
$templating->var('auth_register_message', 'Welcome to Flashii! You may now log in.');
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($auth_register_error)) {
|
||||
$templating->var('auth_register_error', $auth_register_error);
|
||||
if (!empty($authRegistrationError)) {
|
||||
$templating->var('auth_register_error', $authRegistrationError);
|
||||
}
|
||||
|
||||
echo $templating->render('@auth.auth');
|
||||
|
|
|
@ -21,4 +21,6 @@ $featuredNews = Database::connection()
|
|||
|
||||
//var_dump(Database::connection()->query('SHOW SESSION STATUS LIKE "Questions"')->fetch()['Value']);
|
||||
|
||||
echo $app->getTemplating()->render('home.landing', compact('featuredNews'));
|
||||
echo $app->getTemplating()->render('home.landing', [
|
||||
'featured_news' => $featuredNews,
|
||||
]);
|
||||
|
|
|
@ -6,9 +6,8 @@ require_once __DIR__ . '/../../misuzu.php';
|
|||
$db = Database::connection();
|
||||
$templating = $app->getTemplating();
|
||||
|
||||
$is_post_request = $_SERVER['REQUEST_METHOD'] === 'POST';
|
||||
$isPostRequest = $_SERVER['REQUEST_METHOD'] === 'POST';
|
||||
$queryQffset = (int)($_GET['o'] ?? 0);
|
||||
$page_id = (int)($_GET['p'] ?? 1);
|
||||
|
||||
switch ($_GET['v'] ?? null) {
|
||||
case 'listing':
|
||||
|
@ -42,9 +41,9 @@ switch ($_GET['v'] ?? null) {
|
|||
break;
|
||||
|
||||
case 'view':
|
||||
$user_id = $_GET['u'] ?? null;
|
||||
$userId = $_GET['u'] ?? null;
|
||||
|
||||
if ($user_id === null || ($user_id = (int)$user_id) < 1) {
|
||||
if ($userId === null || ($userId = (int)$userId) < 1) {
|
||||
echo 'no';
|
||||
break;
|
||||
}
|
||||
|
@ -60,7 +59,7 @@ switch ($_GET['v'] ?? null) {
|
|||
ON u.`display_role` = r.`role_id`
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$getUser->bindValue('user_id', $user_id);
|
||||
$getUser->bindValue('user_id', $userId);
|
||||
$getUser->execute();
|
||||
$manageUser = $getUser->execute() ? $getUser->fetch() : [];
|
||||
|
||||
|
@ -106,9 +105,9 @@ switch ($_GET['v'] ?? null) {
|
|||
break;
|
||||
|
||||
case 'role':
|
||||
$role_id = $_GET['r'] ?? null;
|
||||
$roleId = $_GET['r'] ?? null;
|
||||
|
||||
if ($is_post_request) {
|
||||
if ($isPostRequest) {
|
||||
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
|
||||
echo 'csrf err';
|
||||
break;
|
||||
|
@ -119,27 +118,27 @@ switch ($_GET['v'] ?? null) {
|
|||
break;
|
||||
}
|
||||
|
||||
$role_name = $_POST['role']['name'] ?? '';
|
||||
$role_name_length = strlen($role_name);
|
||||
$roleName = $_POST['role']['name'] ?? '';
|
||||
$roleNameLength = strlen($roleName);
|
||||
|
||||
if ($role_name_length < 1 || $role_name_length > 255) {
|
||||
if ($roleNameLength < 1 || $roleNameLength > 255) {
|
||||
echo 'invalid name length';
|
||||
break;
|
||||
}
|
||||
|
||||
$role_secret = !empty($_POST['role']['secret']);
|
||||
$roleSecret = !empty($_POST['role']['secret']);
|
||||
|
||||
$role_hierarchy = (int)($_POST['role']['hierarchy'] ?? -1);
|
||||
$roleHierarchy = (int)($_POST['role']['hierarchy'] ?? -1);
|
||||
|
||||
if ($role_hierarchy < 1 || $role_hierarchy > 100) {
|
||||
if ($roleHierarchy < 1 || $roleHierarchy > 100) {
|
||||
echo 'Invalid hierarchy value.';
|
||||
break;
|
||||
}
|
||||
|
||||
$role_colour = colour_create();
|
||||
$roleColour = colour_create();
|
||||
|
||||
if (!empty($_POST['role']['colour']['inherit'])) {
|
||||
colour_set_inherit($role_colour);
|
||||
colour_set_inherit($roleColour);
|
||||
} else {
|
||||
foreach (['red', 'green', 'blue'] as $key) {
|
||||
$value = (int)($_POST['role']['colour'][$key] ?? -1);
|
||||
|
@ -150,18 +149,18 @@ switch ($_GET['v'] ?? null) {
|
|||
break 2;
|
||||
}
|
||||
|
||||
$func($role_colour, $value);
|
||||
$func($roleColour, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$role_description = $_POST['role']['description'] ?? '';
|
||||
$roleDescription = $_POST['role']['description'] ?? '';
|
||||
|
||||
if (strlen($role_description) > 1000) {
|
||||
if (strlen($roleDescription) > 1000) {
|
||||
echo 'description is too long';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($role_id < 1) {
|
||||
if ($roleId < 1) {
|
||||
$updateRole = $db->prepare('
|
||||
INSERT INTO `msz_roles`
|
||||
(`role_name`, `role_hierarchy`, `role_secret`, `role_colour`, `role_description`, `created_at`)
|
||||
|
@ -178,26 +177,26 @@ switch ($_GET['v'] ?? null) {
|
|||
`role_description` = :role_description
|
||||
WHERE `role_id` = :role_id
|
||||
');
|
||||
$updateRole->bindValue('role_id', $role_id);
|
||||
$updateRole->bindValue('role_id', $roleId);
|
||||
}
|
||||
|
||||
$updateRole->bindValue('role_name', $role_name);
|
||||
$updateRole->bindValue('role_hierarchy', $role_hierarchy);
|
||||
$updateRole->bindValue('role_secret', $role_secret ? 1 : 0);
|
||||
$updateRole->bindValue('role_colour', $role_colour);
|
||||
$updateRole->bindValue('role_description', $role_description);
|
||||
$updateRole->bindValue('role_name', $roleName);
|
||||
$updateRole->bindValue('role_hierarchy', $roleHierarchy);
|
||||
$updateRole->bindValue('role_secret', $roleSecret ? 1 : 0);
|
||||
$updateRole->bindValue('role_colour', $roleColour);
|
||||
$updateRole->bindValue('role_description', $roleDescription);
|
||||
$updateRole->execute();
|
||||
|
||||
if ($role_id < 1) {
|
||||
$role_id = (int)$db->lastInsertId();
|
||||
if ($roleId < 1) {
|
||||
$roleId = (int)$db->lastInsertId();
|
||||
}
|
||||
|
||||
header("Location: ?v=role&r={$role_id}");
|
||||
header("Location: ?v=role&r={$roleId}");
|
||||
break;
|
||||
}
|
||||
|
||||
if ($role_id !== null) {
|
||||
if ($role_id < 1) {
|
||||
if ($roleId !== null) {
|
||||
if ($roleId < 1) {
|
||||
echo 'no';
|
||||
break;
|
||||
}
|
||||
|
@ -207,15 +206,15 @@ switch ($_GET['v'] ?? null) {
|
|||
FROM `msz_roles`
|
||||
WHERE `role_id` = :role_id
|
||||
');
|
||||
$getEditRole->bindValue('role_id', $role_id);
|
||||
$edit_role = $getEditRole->execute() ? $getEditRole->fetch() : [];
|
||||
$getEditRole->bindValue('role_id', $roleId);
|
||||
$editRole = $getEditRole->execute() ? $getEditRole->fetch() : [];
|
||||
|
||||
if (!$edit_role) {
|
||||
if (!$editRole) {
|
||||
echo 'invalid role';
|
||||
break;
|
||||
}
|
||||
|
||||
$templating->vars(compact('edit_role'));
|
||||
$templating->vars(['edit_role' => $editRole]);
|
||||
}
|
||||
|
||||
echo $templating->render('@manage.users.roles_create');
|
||||
|
|
|
@ -6,14 +6,17 @@ require_once __DIR__ . '/../misuzu.php';
|
|||
$db = Database::connection();
|
||||
$templating = $app->getTemplating();
|
||||
|
||||
$category_id = isset($_GET['c']) ? (int)$_GET['c'] : null;
|
||||
$post_id = isset($_GET['p']) ? (int)$_GET['p'] : (isset($_GET['n']) ? (int)$_GET['n'] : null);
|
||||
$posts_offset = (int)($_GET['o'] ?? 0);
|
||||
$posts_take = 5;
|
||||
$categoryId = isset($_GET['c']) ? (int)$_GET['c'] : null;
|
||||
$postId = isset($_GET['p']) ? (int)$_GET['p'] : (isset($_GET['n']) ? (int)$_GET['n'] : null);
|
||||
$postsOffset = (int)($_GET['o'] ?? 0);
|
||||
$postsTake = 5;
|
||||
|
||||
$templating->vars(compact('posts_offset', 'posts_take'));
|
||||
$templating->vars([
|
||||
'posts_offset' => $postsOffset,
|
||||
'posts_take' => $postsTake,
|
||||
]);
|
||||
|
||||
if ($post_id !== null) {
|
||||
if ($postId !== null) {
|
||||
$getPost = $db->prepare('
|
||||
SELECT
|
||||
p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`,
|
||||
|
@ -29,7 +32,7 @@ if ($post_id !== null) {
|
|||
ON u.`display_role` = r.`role_id`
|
||||
WHERE `post_id` = :post_id
|
||||
');
|
||||
$getPost->bindValue(':post_id', $post_id, PDO::PARAM_INT);
|
||||
$getPost->bindValue(':post_id', $postId, PDO::PARAM_INT);
|
||||
$post = $getPost->execute() ? $getPost->fetch() : false;
|
||||
|
||||
if ($post === false) {
|
||||
|
@ -41,7 +44,7 @@ if ($post_id !== null) {
|
|||
return;
|
||||
}
|
||||
|
||||
if ($category_id !== null) {
|
||||
if ($categoryId !== null) {
|
||||
$getCategory = $db->prepare('
|
||||
SELECT
|
||||
c.`category_id`, c.`category_name`, c.`category_description`,
|
||||
|
@ -52,10 +55,10 @@ if ($category_id !== null) {
|
|||
WHERE c.`category_id` = :category_id
|
||||
GROUP BY c.`category_id`
|
||||
');
|
||||
$getCategory->bindValue(':category_id', $category_id, PDO::PARAM_INT);
|
||||
$getCategory->bindValue(':category_id', $categoryId, PDO::PARAM_INT);
|
||||
$category = $getCategory->execute() ? $getCategory->fetch() : false;
|
||||
|
||||
if ($category === false || $posts_offset < 0 || $posts_offset >= $category['posts_count']) {
|
||||
if ($category === false || $postsOffset < 0 || $postsOffset >= $category['posts_count']) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
@ -77,8 +80,8 @@ if ($category_id !== null) {
|
|||
ORDER BY `created_at` DESC
|
||||
LIMIT :offset, :take
|
||||
');
|
||||
$getPosts->bindValue('offset', $posts_offset);
|
||||
$getPosts->bindValue('take', $posts_take);
|
||||
$getPosts->bindValue('offset', $postsOffset);
|
||||
$getPosts->bindValue('take', $postsTake);
|
||||
$getPosts->bindValue('category_id', $category['category_id'], PDO::PARAM_INT);
|
||||
$posts = $getPosts->execute() ? $getPosts->fetchAll() : false;
|
||||
|
||||
|
@ -121,7 +124,7 @@ $postsCount = (int)$db->query('
|
|||
|
||||
$templating->var('posts_count', $postsCount);
|
||||
|
||||
if ($posts_offset < 0 || $posts_offset >= $postsCount) {
|
||||
if ($postsOffset < 0 || $postsOffset >= $postsCount) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
@ -144,8 +147,8 @@ $getPosts = $db->prepare('
|
|||
ORDER BY p.`created_at` DESC
|
||||
LIMIT :offset, :take
|
||||
');
|
||||
$getPosts->bindValue('offset', $posts_offset);
|
||||
$getPosts->bindValue('take', $posts_take);
|
||||
$getPosts->bindValue('offset', $postsOffset);
|
||||
$getPosts->bindValue('take', $postsTake);
|
||||
$posts = $getPosts->execute() ? $getPosts->fetchAll() : [];
|
||||
|
||||
if (!$posts) {
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
User-agent: *
|
||||
Disallow:
|
||||
Disallow: /manage/
|
||||
|
|
|
@ -7,138 +7,90 @@ require_once __DIR__ . '/../misuzu.php';
|
|||
$db = Database::connection();
|
||||
$templating = $app->getTemplating();
|
||||
|
||||
$query_offset = (int)($_GET['o'] ?? 0);
|
||||
$query_take = 15;
|
||||
$queryOffset = (int)($_GET['o'] ?? 0);
|
||||
$queryTake = 15;
|
||||
|
||||
if (!$app->hasActiveSession()) {
|
||||
echo render_error(403);
|
||||
return;
|
||||
}
|
||||
|
||||
$csrf_error_str = "Couldn't verify you, please refresh the page and retry.";
|
||||
$csrfErrorString = "Couldn't verify you, please refresh the page and retry.";
|
||||
|
||||
$settings_profile_fields = [
|
||||
'twitter' => [
|
||||
'name' => 'Twitter',
|
||||
'regex' => '#^(?:https?://(?:www\.)?twitter.com/(?:\#!\/)?)?@?([A-Za-z0-9_]{1,20})/?$#u',
|
||||
'no-match' => 'Twitter field was invalid.',
|
||||
],
|
||||
'osu' => [
|
||||
'name' => 'osu!',
|
||||
'regex' => '#^(?:https?://osu.ppy.sh/u(?:sers)?/)?([a-zA-Z0-9-\[\]_ ]{1,20})/?$#u',
|
||||
'no-match' => 'osu! field was invalid.',
|
||||
],
|
||||
'website' => [
|
||||
'name' => 'Website',
|
||||
'type' => 'url',
|
||||
'regex' => '#^((?:https?)://.{1,240})$#u',
|
||||
'no-match' => 'Website field was invalid.',
|
||||
],
|
||||
'youtube' => [
|
||||
'name' => 'Youtube',
|
||||
'regex' => '#^(?:https?://(?:www.)?youtube.com/(?:(?:user|c|channel)/)?)?(UC[a-zA-Z0-9-_]{1,22}|[a-zA-Z0-9-_%]{1,100})/?$#u',
|
||||
'no-match' => 'Youtube field was invalid.',
|
||||
],
|
||||
'steam' => [
|
||||
'name' => 'Steam',
|
||||
'regex' => '#^(?:https?://(?:www.)?steamcommunity.com/(?:id|profiles)/)?([a-zA-Z0-9_-]{2,100})/?$#u',
|
||||
'no-match' => 'Steam field was invalid.',
|
||||
],
|
||||
'twitchtv' => [
|
||||
'name' => 'Twitch.tv',
|
||||
'regex' => '#^(?:https?://(?:www.)?twitch.tv/)?([0-9A-Za-z_]{3,25})/?$#u',
|
||||
'no-match' => 'Twitch.tv field was invalid.',
|
||||
],
|
||||
'lastfm' => [
|
||||
'name' => 'Last.fm',
|
||||
'regex' => '#^(?:https?://(?:www.)?last.fm/user/)?([a-zA-Z]{1}[a-zA-Z0-9_-]{1,14})/?$#u',
|
||||
'no-match' => 'Last.fm field was invalid.',
|
||||
],
|
||||
'github' => [
|
||||
'name' => 'Github',
|
||||
'regex' => '#^(?:https?://(?:www.)?github.com/?)?([a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38})/?$#u',
|
||||
'no-match' => 'Github field was invalid.',
|
||||
],
|
||||
'skype' => [
|
||||
'name' => 'Skype',
|
||||
'regex' => '#^((?:live:)?[a-zA-Z][\w\.,\-_@]{1,100})$#u',
|
||||
'no-match' => 'Skype field was invalid.',
|
||||
],
|
||||
'discord' => [
|
||||
'name' => 'Discord',
|
||||
'regex' => '#^(.{1,32}\#[0-9]{4})$#u',
|
||||
'no-match' => 'Discord field was invalid.',
|
||||
],
|
||||
];
|
||||
|
||||
$settings_modes = [
|
||||
$settingsModes = [
|
||||
'account' => 'Account',
|
||||
'avatar' => 'Avatar',
|
||||
'sessions' => 'Sessions',
|
||||
'login-history' => 'Login History',
|
||||
];
|
||||
$settings_mode = $_GET['m'] ?? key($settings_modes);
|
||||
$settingsMode = $_GET['m'] ?? key($settingsModes);
|
||||
|
||||
$templating->vars(compact('settings_mode', 'settings_modes'));
|
||||
$templating->vars([
|
||||
'settings_mode' => $settingsMode,
|
||||
'settings_modes' => $settingsModes,
|
||||
]);
|
||||
|
||||
if (!array_key_exists($settings_mode, $settings_modes)) {
|
||||
if (!array_key_exists($settingsMode, $settingsModes)) {
|
||||
http_response_code(404);
|
||||
$templating->var('settings_title', 'Not Found');
|
||||
echo $templating->render('settings.notfound');
|
||||
return;
|
||||
}
|
||||
|
||||
$settings_errors = [];
|
||||
$settingsErrors = [];
|
||||
|
||||
$prevent_registration = $app->getConfig()->get('Auth', 'prevent_registration', 'bool', false);
|
||||
$avatar_filename = "{$app->getUserId()}.msz";
|
||||
$avatar_max_width = $app->getConfig()->get('Avatar', 'max_width', 'int', 4000);
|
||||
$avatar_max_height = $app->getConfig()->get('Avatar', 'max_height', 'int', 4000);
|
||||
$avatar_max_filesize = $app->getConfig()->get('Avatar', 'max_filesize', 'int', 1000000);
|
||||
$avatar_max_filesize_human = byte_symbol($avatar_max_filesize, true);
|
||||
$disableAccountOptions = $app->getConfig()->get('Auth', 'prevent_registration', 'bool', false);
|
||||
$avatarFileName = "{$app->getUserId()}.msz";
|
||||
$avatarWidthMax = $app->getConfig()->get('Avatar', 'max_width', 'int', 4000);
|
||||
$avatarHeightMax = $app->getConfig()->get('Avatar', 'max_height', 'int', 4000);
|
||||
$avatarFileSizeMax = $app->getConfig()->get('Avatar', 'max_filesize', 'int', 1000000);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
switch ($settings_mode) {
|
||||
switch ($settingsMode) {
|
||||
case 'account':
|
||||
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
|
||||
$settings_errors[] = $csrf_error_str;
|
||||
$settingsErrors[] = $csrfErrorString;
|
||||
break;
|
||||
}
|
||||
|
||||
$updatedUserFields = [];
|
||||
|
||||
if (isset($_POST['profile']) && is_array($_POST['profile'])) {
|
||||
foreach ($settings_profile_fields as $name => $props) {
|
||||
if (isset($_POST['profile'][$name])) {
|
||||
$field_value = '';
|
||||
$setUserFieldErrors = user_profile_fields_set($app->getUserId(), $_POST['profile']);
|
||||
|
||||
if (!empty($_POST['profile'][$name])) {
|
||||
$field_regex = preg_match(
|
||||
$props['regex'],
|
||||
$_POST['profile'][$name],
|
||||
$field_matches
|
||||
);
|
||||
|
||||
if ($field_regex !== 1) {
|
||||
$settings_errors[] = $props['no-match'];
|
||||
if (count($setUserFieldErrors) > 0) {
|
||||
foreach ($setUserFieldErrors as $name => $error) {
|
||||
switch ($error) {
|
||||
case MSZ_USER_PROFILE_INVALID_FIELD:
|
||||
$settingsErrors[] = sprintf("Field '%s' does not exist!", $name);
|
||||
break;
|
||||
}
|
||||
|
||||
$field_value = $field_matches[1];
|
||||
case MSZ_USER_PROFILE_FILTER_FAILED:
|
||||
$settingsErrors[] = sprintf(
|
||||
'%s field was invalid!',
|
||||
user_profile_field_get_display_name($name)
|
||||
);
|
||||
break;
|
||||
|
||||
case MSZ_USER_PROFILE_UPDATE_FAILED:
|
||||
$settingsErrors[] = 'Failed to update values, contact an administator.';
|
||||
break;
|
||||
|
||||
default:
|
||||
$settingsErrors[] = 'An unexpected error occurred, contact an administator.';
|
||||
break;
|
||||
}
|
||||
|
||||
$updatedUserFields["user_{$name}"] = $field_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$prevent_registration) {
|
||||
if (!$disableAccountOptions) {
|
||||
if (!empty($_POST['current_password'])
|
||||
|| (
|
||||
(isset($_POST['password']) || isset($_OST['email']))
|
||||
(isset($_POST['password']) || isset($_POST['email']))
|
||||
&& (!empty($_POST['password']['new']) || !empty($_POST['email']['new']))
|
||||
)
|
||||
) {
|
||||
$updateAccountFields = [];
|
||||
|
||||
$fetchPassword = $db->prepare('
|
||||
SELECT `password`
|
||||
FROM `msz_users`
|
||||
|
@ -148,19 +100,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
$currentPassword = $fetchPassword->execute() ? $fetchPassword->fetchColumn() : null;
|
||||
|
||||
if (empty($currentPassword)) {
|
||||
$settings_errors[] = 'Something went horribly wrong.';
|
||||
$settingsErrors[] = 'Something went horribly wrong.';
|
||||
break;
|
||||
}
|
||||
|
||||
if (!password_verify($_POST['current_password'], $currentPassword)) {
|
||||
$settings_errors[] = 'Your current password was incorrect.';
|
||||
$settingsErrors[] = 'Your current password was incorrect.';
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($_POST['email']['new'])) {
|
||||
if (empty($_POST['email']['confirm'])
|
||||
|| $_POST['email']['new'] !== $_POST['email']['confirm']) {
|
||||
$settings_errors[] = 'The given e-mail addresses did not match.';
|
||||
$settingsErrors[] = 'The given e-mail addresses did not match.';
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -175,7 +127,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
: false;
|
||||
|
||||
if ($isAlreadySet) {
|
||||
$settings_errors[] = 'This is your e-mail address already!';
|
||||
$settingsErrors[] = 'This is your e-mail address already!';
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -184,66 +136,66 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
if ($email_validate !== '') {
|
||||
switch ($email_validate) {
|
||||
case 'dns':
|
||||
$settings_errors[] = 'No valid MX record exists for this domain.';
|
||||
$settingsErrors[] = 'No valid MX record exists for this domain.';
|
||||
break;
|
||||
|
||||
case 'format':
|
||||
$settings_errors[] = 'The given e-mail address was incorrectly formatted.';
|
||||
$settingsErrors[] = 'The given e-mail address was incorrectly formatted.';
|
||||
break;
|
||||
|
||||
case 'in-use':
|
||||
$settings_errors[] = 'This e-mail address has already been used by another user.';
|
||||
$settingsErrors[] = 'This e-mail address has already been used by another user.';
|
||||
break;
|
||||
|
||||
default:
|
||||
$settings_errors[] = 'Unknown e-mail validation error.';
|
||||
$settingsErrors[] = 'Unknown e-mail validation error.';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$updatedUserFields['email'] = strtolower($_POST['email']['new']);
|
||||
$updateAccountFields['email'] = strtolower($_POST['email']['new']);
|
||||
}
|
||||
|
||||
if (!empty($_POST['password']['new'])) {
|
||||
if (empty($_POST['password']['confirm'])
|
||||
|| $_POST['password']['new'] !== $_POST['password']['confirm']) {
|
||||
$settings_errors[] = "The given passwords did not match.";
|
||||
$settingsErrors[] = "The given passwords did not match.";
|
||||
break;
|
||||
}
|
||||
|
||||
$password_validate = user_validate_password($_POST['password']['new']);
|
||||
|
||||
if ($password_validate !== '') {
|
||||
$settings_errors[] = "The given passwords was too weak.";
|
||||
$settingsErrors[] = "The given passwords was too weak.";
|
||||
break;
|
||||
}
|
||||
|
||||
$updatedUserFields['password'] = password_hash($_POST['password']['new'], PASSWORD_ARGON2I);
|
||||
$updateAccountFields['password'] = user_password_hash($_POST['password']['new']);
|
||||
}
|
||||
|
||||
if (count($updateAccountFields) > 0) {
|
||||
$updateUser = $db->prepare('
|
||||
UPDATE `msz_users`
|
||||
SET ' . pdo_prepare_array_update($updateAccountFields, true) . '
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$updateAccountFields['user_id'] = $app->getUserId();
|
||||
$updateUser->execute($updateAccountFields);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($settings_errors) < 1 && count($updatedUserFields) > 0) {
|
||||
$updateUser = $db->prepare('
|
||||
UPDATE `msz_users`
|
||||
SET ' . pdo_prepare_array_update($updatedUserFields, true) . '
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$updatedUserFields['user_id'] = $app->getUserId();
|
||||
$updateUser->execute($updatedUserFields);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'avatar':
|
||||
if (isset($_POST['delete'])) {
|
||||
if (!tmp_csrf_verify($_POST['delete'])) {
|
||||
$settings_errors[] = $csrf_error_str;
|
||||
$settingsErrors[] = $csrfErrorString;
|
||||
break;
|
||||
}
|
||||
|
||||
$delete_this = [
|
||||
$app->getStore('avatars/original')->filename($avatar_filename),
|
||||
$app->getStore('avatars/200x200')->filename($avatar_filename),
|
||||
$app->getStore('avatars/original')->filename($avatarFileName),
|
||||
$app->getStore('avatars/200x200')->filename($avatarFileName),
|
||||
];
|
||||
|
||||
foreach ($delete_this as $delete_avatar) {
|
||||
|
@ -256,7 +208,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
|
||||
if (isset($_POST['upload'])) {
|
||||
if (!tmp_csrf_verify($_POST['upload'])) {
|
||||
$settings_errors[] = $csrf_error_str;
|
||||
$settingsErrors[] = $csrfErrorString;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -265,30 +217,33 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
break;
|
||||
|
||||
case UPLOAD_ERR_NO_FILE:
|
||||
$settings_errors[] = 'Select a file before hitting upload!';
|
||||
$settingsErrors[] = 'Select a file before hitting upload!';
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_PARTIAL:
|
||||
$settings_errors[] = 'The upload was interrupted, please try again!';
|
||||
$settingsErrors[] = 'The upload was interrupted, please try again!';
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_INI_SIZE:
|
||||
case UPLOAD_ERR_FORM_SIZE:
|
||||
$settings_errors[] = "Your avatar is not allowed to be larger in filesize than {$avatar_max_filesize_human}!";
|
||||
$settingsErrors[] = sprintf(
|
||||
'Your avatar is not allowed to be larger in filesize than %s',
|
||||
byte_symbol($avatarFileSizeMax, true)
|
||||
);
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_NO_TMP_DIR:
|
||||
case UPLOAD_ERR_CANT_WRITE:
|
||||
$settings_errors[] = 'Unable to save your avatar, contact an administator!';
|
||||
$settingsErrors[] = 'Unable to save your avatar, contact an administator!';
|
||||
break;
|
||||
|
||||
case UPLOAD_ERR_EXTENSION:
|
||||
default:
|
||||
$settings_errors[] = 'Something happened?';
|
||||
$settingsErrors[] = 'Something happened?';
|
||||
break;
|
||||
}
|
||||
|
||||
if (count($settings_errors) > 0) {
|
||||
if (count($settingsErrors) > 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -299,24 +254,33 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
|| !in_array($upload_meta[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG], true)
|
||||
|| $upload_meta[0] < 1
|
||||
|| $upload_meta[1] < 1) {
|
||||
$settings_errors[] = 'Please provide a valid image.';
|
||||
$settingsErrors[] = 'Please provide a valid image.';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($upload_meta[0] > $avatar_max_width || $upload_meta[1] > $avatar_max_height) {
|
||||
$settings_errors[] = "Your avatar can't be larger than {$avatar_max_width}x{$avatar_max_height}, yours was {$upload_meta[0]}x{$upload_meta[1]}";
|
||||
if ($upload_meta[0] > $avatarWidthMax || $upload_meta[1] > $avatarHeightMax) {
|
||||
$settingsErrors[] = sprintf(
|
||||
"Your avatar can't be larger than %dx%d, yours was %dx%d",
|
||||
$avatarWidthMax,
|
||||
$avatarHeightMax,
|
||||
$upload_meta[0],
|
||||
$upload_meta[1]
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (filesize($upload_path) > $avatar_max_filesize) {
|
||||
$settings_errors[] = "Your avatar is not allowed to be larger in filesize than {$avatar_max_filesize_human}!";
|
||||
if (filesize($upload_path) > $avatarFileSizeMax) {
|
||||
$settingsErrors[] = sprintf(
|
||||
'Your avatar is not allowed to be larger in filesize than %s!',
|
||||
byte_symbol($avatarFileSizeMax, true)
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
$avatar_path = $app->getStore('avatars/original')->filename($avatar_filename);
|
||||
$avatar_path = $app->getStore('avatars/original')->filename($avatarFileName);
|
||||
move_uploaded_file($upload_path, $avatar_path);
|
||||
|
||||
$crop_path = $app->getStore('avatars/200x200')->filename($avatar_filename);
|
||||
$crop_path = $app->getStore('avatars/200x200')->filename($avatarFileName);
|
||||
|
||||
if (File::exists($crop_path)) {
|
||||
File::delete($crop_path);
|
||||
|
@ -324,19 +288,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
break;
|
||||
}
|
||||
|
||||
$settings_errors[] = "You shouldn't have done that.";
|
||||
$settingsErrors[] = "You shouldn't have done that.";
|
||||
break;
|
||||
|
||||
case 'sessions':
|
||||
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
|
||||
$settings_errors[] = $csrf_error_str;
|
||||
$settingsErrors[] = $csrfErrorString;
|
||||
break;
|
||||
}
|
||||
|
||||
$session_id = (int)($_POST['session'] ?? 0);
|
||||
|
||||
if ($session_id < 1) {
|
||||
$settings_errors[] = 'Invalid session.';
|
||||
$settingsErrors[] = 'Invalid session.';
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -349,7 +313,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
$session = $findSession->execute() ? $findSession->fetch() : null;
|
||||
|
||||
if (!$session || (int)$session['user_id'] !== $app->getUserId()) {
|
||||
$settings_errors[] = 'You may only end your own sessions.';
|
||||
$settingsErrors[] = 'You may only end your own sessions.';
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -358,42 +322,41 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
return;
|
||||
}
|
||||
|
||||
$deleteSession = $db->prepare('
|
||||
DELETE FROM `msz_sessions`
|
||||
WHERE `session_id` = :session_id
|
||||
');
|
||||
$deleteSession->bindValue('session_id', $session['session_id']);
|
||||
$deleteSession->execute();
|
||||
user_session_delete($session['session_id']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$templating->vars(compact('settings_errors'));
|
||||
$templating->var('settings_title', $settings_modes[$settings_mode]);
|
||||
$templating->var('settings_title', $settingsModes[$settingsMode]);
|
||||
$templating->var('settings_errors', $settingsErrors);
|
||||
|
||||
switch ($settings_mode) {
|
||||
switch ($settingsMode) {
|
||||
case 'account':
|
||||
$profileFields = user_profile_fields_get();
|
||||
$getUserFields = $db->prepare('
|
||||
SELECT ' . pdo_prepare_array($settings_profile_fields, true, '`user_%s`') . '
|
||||
SELECT ' . pdo_prepare_array($profileFields, true, '`user_%s`') . '
|
||||
FROM `msz_users`
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$getUserFields->bindValue('user_id', $app->getUserId());
|
||||
$userFields = $getUserFields->execute() ? $getUserFields->fetch() : [];
|
||||
|
||||
$templating->var('settings_profile_values', $userFields);
|
||||
$templating->vars(compact('settings_profile_fields', 'prevent_registration'));
|
||||
$templating->vars([
|
||||
'settings_profile_fields' => $profileFields,
|
||||
'settings_profile_values' => $userFields,
|
||||
'settings_disable_account_options' => $disableAccountOptions,
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'avatar':
|
||||
$user_has_avatar = File::exists($app->getStore('avatars/original')->filename($avatar_filename));
|
||||
$templating->var('avatar_user_id', $app->getUserId());
|
||||
$templating->vars(compact(
|
||||
'avatar_max_width',
|
||||
'avatar_max_height',
|
||||
'avatar_max_filesize',
|
||||
'user_has_avatar'
|
||||
));
|
||||
$userHasAvatar = File::exists($app->getStore('avatars/original')->filename($avatarFileName));
|
||||
$templating->vars([
|
||||
'avatar_user_id' => $app->getUserId(),
|
||||
'avatar_max_width' => $avatarWidthMax,
|
||||
'avatar_max_height' => $avatarHeightMax,
|
||||
'avatar_max_filesize' => $avatarFileSizeMax,
|
||||
'user_has_avatar' => $userHasAvatar,
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'sessions':
|
||||
|
@ -414,16 +377,16 @@ switch ($settings_mode) {
|
|||
ORDER BY `session_id` DESC
|
||||
LIMIT :offset, :take
|
||||
');
|
||||
$getSessions->bindValue('offset', $query_offset);
|
||||
$getSessions->bindValue('take', $query_take);
|
||||
$getSessions->bindValue('offset', $queryOffset);
|
||||
$getSessions->bindValue('take', $queryTake);
|
||||
$getSessions->bindValue('user_id', $app->getUserId());
|
||||
$sessions = $getSessions->execute() ? $getSessions->fetchAll() : [];
|
||||
|
||||
$templating->vars([
|
||||
'active_session_id' => $app->getSessionId(),
|
||||
'user_sessions' => $sessions,
|
||||
'sessions_offset' => $query_offset,
|
||||
'sessions_take' => $query_take,
|
||||
'sessions_offset' => $queryOffset,
|
||||
'sessions_take' => $queryTake,
|
||||
'sessions_count' => $sessionCount,
|
||||
]);
|
||||
break;
|
||||
|
@ -446,18 +409,18 @@ switch ($settings_mode) {
|
|||
ORDER BY `attempt_id` DESC
|
||||
LIMIT :offset, :take
|
||||
');
|
||||
$getLoginAttempts->bindValue('offset', $query_offset);
|
||||
$getLoginAttempts->bindValue('take', $query_take);
|
||||
$getLoginAttempts->bindValue('offset', $queryOffset);
|
||||
$getLoginAttempts->bindValue('take', $queryTake);
|
||||
$getLoginAttempts->bindValue('user_id', $app->getUserId());
|
||||
$loginAttempts = $getLoginAttempts->execute() ? $getLoginAttempts->fetchAll() : [];
|
||||
|
||||
$templating->vars([
|
||||
'user_login_attempts' => $loginAttempts,
|
||||
'login_attempts_offset' => $query_offset,
|
||||
'login_attempts_take' => $query_take,
|
||||
'login_attempts_offset' => $queryOffset,
|
||||
'login_attempts_take' => $queryTake,
|
||||
'login_attempts_count' => $loginAttemptsCount,
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
echo $templating->render("settings.{$settings_mode}");
|
||||
echo $templating->render("settings.{$settingsMode}");
|
||||
|
|
|
@ -17,3 +17,19 @@ function user_login_attempt_record(bool $success, ?int $userId, string $ipAddres
|
|||
$storeAttempt->bindValue('user_id', $userId, $userId === null ? PDO::PARAM_NULL : PDO::PARAM_INT);
|
||||
$storeAttempt->execute();
|
||||
}
|
||||
|
||||
function user_login_attempts_remaining(string $ipAddress): int
|
||||
{
|
||||
$getRemaining = Database::connection()->prepare('
|
||||
SELECT 5 - COUNT(`attempt_id`)
|
||||
FROM `msz_login_attempts`
|
||||
WHERE `was_successful` = false
|
||||
AND `created_at` > NOW() - INTERVAL 1 HOUR
|
||||
AND `attempt_ip` = INET6_ATON(:remote_ip)
|
||||
');
|
||||
$getRemaining->bindValue('remote_ip', $ipAddress);
|
||||
|
||||
return $getRemaining->execute()
|
||||
? (int)$getRemaining->fetchColumn()
|
||||
: 0;
|
||||
}
|
||||
|
|
141
src/Users/profile.php
Normal file
141
src/Users/profile.php
Normal file
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
use Misuzu\Database;
|
||||
|
||||
define('MSZ_USER_PROFILE_SET_ERROR', '_err');
|
||||
define('MSZ_USER_PROFILE_INVALID_FIELD', 1);
|
||||
define('MSZ_USER_PROFILE_FILTER_FAILED', 2);
|
||||
define('MSZ_USER_PROFILE_UPDATE_FAILED', 3);
|
||||
define('MSZ_USER_PROFILE_FIELD_SET_ERRORS', [
|
||||
MSZ_USER_PROFILE_INVALID_FIELD,
|
||||
MSZ_USER_PROFILE_FILTER_FAILED,
|
||||
MSZ_USER_PROFILE_UPDATE_FAILED,
|
||||
]);
|
||||
|
||||
define('MSZ_USER_PROFILE_FIELD_FORMAT', 'user_%s');
|
||||
define('MSZ_USER_PROFILE_FIELDS', [
|
||||
'twitter' => [
|
||||
'name' => 'Twitter',
|
||||
'regex' => '#^(?:https?://(?:www\.)?twitter.com/(?:\#!\/)?)?@?([A-Za-z0-9_]{1,20})/?$#u',
|
||||
],
|
||||
'osu' => [
|
||||
'name' => 'osu!',
|
||||
'regex' => '#^(?:https?://osu.ppy.sh/u(?:sers)?/)?([a-zA-Z0-9-\[\]_ ]{1,20})/?$#u',
|
||||
],
|
||||
'website' => [
|
||||
'name' => 'Website',
|
||||
'type' => 'url',
|
||||
'regex' => '#^((?:https?)://.{1,240})$#u',
|
||||
],
|
||||
'youtube' => [
|
||||
'name' => 'Youtube',
|
||||
'regex' => '#^(?:https?://(?:www.)?youtube.com/(?:(?:user|c|channel)/)?)?(UC[a-zA-Z0-9-_]{1,22}|[a-zA-Z0-9-_%]{1,100})/?$#u',
|
||||
],
|
||||
'steam' => [
|
||||
'name' => 'Steam',
|
||||
'regex' => '#^(?:https?://(?:www.)?steamcommunity.com/(?:id|profiles)/)?([a-zA-Z0-9_-]{2,100})/?$#u',
|
||||
],
|
||||
'twitchtv' => [
|
||||
'name' => 'Twitch.tv',
|
||||
'regex' => '#^(?:https?://(?:www.)?twitch.tv/)?([0-9A-Za-z_]{3,25})/?$#u',
|
||||
],
|
||||
'lastfm' => [
|
||||
'name' => 'Last.fm',
|
||||
'regex' => '#^(?:https?://(?:www.)?last.fm/user/)?([a-zA-Z]{1}[a-zA-Z0-9_-]{1,14})/?$#u',
|
||||
],
|
||||
'github' => [
|
||||
'name' => 'Github',
|
||||
'regex' => '#^(?:https?://(?:www.)?github.com/?)?([a-zA-Z0-9](?:[a-zA-Z0-9]|-(?=[a-zA-Z0-9])){0,38})/?$#u',
|
||||
],
|
||||
'skype' => [
|
||||
'name' => 'Skype',
|
||||
'regex' => '#^((?:live:)?[a-zA-Z][\w\.,\-_@]{1,100})$#u',
|
||||
],
|
||||
'discord' => [
|
||||
'name' => 'Discord',
|
||||
'regex' => '#^(.{1,32}\#[0-9]{4})$#u',
|
||||
],
|
||||
]);
|
||||
|
||||
function user_profile_field_is_valid(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, MSZ_USER_PROFILE_FIELDS);
|
||||
}
|
||||
|
||||
function user_profile_field_get_display_name(string $name): string
|
||||
{
|
||||
return MSZ_USER_PROFILE_FIELDS[$name]['name'] ?? '';
|
||||
}
|
||||
|
||||
function user_profile_fields_get(): array
|
||||
{
|
||||
return MSZ_USER_PROFILE_FIELDS;
|
||||
}
|
||||
|
||||
function user_profile_field_get_regex(string $name): string
|
||||
{
|
||||
return MSZ_USER_PROFILE_FIELDS[$name]['regex'] ?? '';
|
||||
}
|
||||
|
||||
// === NULL if field is invalid
|
||||
function user_profile_field_filter(string $name, string $value): ?string
|
||||
{
|
||||
if (!user_profile_field_is_valid($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$regex = user_profile_field_get_regex($name);
|
||||
|
||||
if (empty($regex) || empty($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$checkRegex = preg_match($regex, $value, $matches);
|
||||
|
||||
if (!$checkRegex || empty($matches[1])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
function user_profile_fields_set(int $userId, array $fields): array
|
||||
{
|
||||
if (count($fields) < 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$errors = [];
|
||||
$values = [];
|
||||
|
||||
foreach ($fields as $name => $value) {
|
||||
// should these just be ignored?
|
||||
if (!user_profile_field_is_valid($name)) {
|
||||
$errors[$name] = MSZ_USER_PROFILE_INVALID_FIELD;
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = user_profile_field_filter($name, $value);
|
||||
|
||||
if ($value === null) {
|
||||
$errors[$name] = MSZ_USER_PROFILE_FILTER_FAILED;
|
||||
continue;
|
||||
}
|
||||
|
||||
$values[sprintf(MSZ_USER_PROFILE_FIELD_FORMAT, $name)] = $value;
|
||||
}
|
||||
|
||||
if (count($values) > 0) {
|
||||
$updateFields = Database::connection()->prepare('
|
||||
UPDATE `msz_users`
|
||||
SET ' . pdo_prepare_array_update($values, true) . '
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$values['user_id'] = $userId;
|
||||
|
||||
if (!$updateFields->execute($values)) {
|
||||
$errors[MSZ_USER_PROFILE_SET_ERROR] = MSZ_USER_PROFILE_UPDATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
17
src/Users/role.php
Normal file
17
src/Users/role.php
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
use Misuzu\Database;
|
||||
|
||||
define('MSZ_ROLE_MAIN', 1);
|
||||
|
||||
function user_role_add(int $userId, int $roleId): bool
|
||||
{
|
||||
$addRole = Database::connection()->prepare('
|
||||
INSERT INTO `msz_user_roles`
|
||||
(`user_id`, `role_id`)
|
||||
VALUES
|
||||
(:user_id, :role_id)
|
||||
');
|
||||
$addRole->bindValue('user_id', $userId);
|
||||
$addRole->bindValue('role_id', $roleId);
|
||||
return $addRole->execute();
|
||||
}
|
47
src/Users/session.php
Normal file
47
src/Users/session.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
use Misuzu\Database;
|
||||
|
||||
define('MSZ_SESSION_KEY_SIZE', 64);
|
||||
|
||||
function user_session_create(
|
||||
int $userId,
|
||||
string $ipAddress,
|
||||
string $userAgent
|
||||
): string {
|
||||
$sessionKey = user_session_generate_key();
|
||||
|
||||
$createSession = Database::connection()->prepare('
|
||||
INSERT INTO `msz_sessions`
|
||||
(
|
||||
`user_id`, `session_ip`, `session_country`,
|
||||
`user_agent`, `session_key`, `created_at`, `expires_on`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
:user_id, INET6_ATON(:session_ip), :session_country,
|
||||
:user_agent, :session_key, NOW(), NOW() + INTERVAL 1 MONTH
|
||||
)
|
||||
');
|
||||
$createSession->bindValue('user_id', $userId);
|
||||
$createSession->bindValue('session_ip', $ipAddress);
|
||||
$createSession->bindValue('session_country', get_country_code($ipAddress));
|
||||
$createSession->bindValue('user_agent', $userAgent);
|
||||
$createSession->bindValue('session_key', $sessionKey);
|
||||
|
||||
return $createSession->execute() ? $sessionKey : '';
|
||||
}
|
||||
|
||||
function user_session_delete(int $sessionId): bool
|
||||
{
|
||||
$deleteSession = Database::connection()->prepare('
|
||||
DELETE FROM `msz_sessions`
|
||||
WHERE `session_id` = :session_id
|
||||
');
|
||||
$deleteSession->bindValue('session_id', $sessionId);
|
||||
return $deleteSession->execute();
|
||||
}
|
||||
|
||||
function user_session_generate_key(): string
|
||||
{
|
||||
return bin2hex(random_bytes(MSZ_SESSION_KEY_SIZE / 2));
|
||||
}
|
55
src/Users/user.php
Normal file
55
src/Users/user.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
use Misuzu\Database;
|
||||
|
||||
define('MSZ_USERS_PASSWORD_HASH_ALGO', PASSWORD_ARGON2I);
|
||||
|
||||
function user_create(
|
||||
string $username,
|
||||
string $password,
|
||||
string $email,
|
||||
string $ipAddress
|
||||
): int {
|
||||
$dbc = Database::connection();
|
||||
$createUser = $dbc->prepare('
|
||||
INSERT INTO `msz_users`
|
||||
(
|
||||
`username`, `password`, `email`, `register_ip`,
|
||||
`last_ip`, `user_country`, `created_at`, `display_role`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
:username, :password, :email, INET6_ATON(:register_ip),
|
||||
INET6_ATON(:last_ip), :user_country, NOW(), 1
|
||||
)
|
||||
');
|
||||
$createUser->bindValue('username', $username);
|
||||
$createUser->bindValue('password', user_password_hash($password));
|
||||
$createUser->bindValue('email', $email);
|
||||
$createUser->bindValue('register_ip', $ipAddress);
|
||||
$createUser->bindValue('last_ip', $ipAddress);
|
||||
$createUser->bindValue('user_country', get_country_code($ipAddress));
|
||||
|
||||
return $createUser->execute() ? (int)$dbc->lastInsertId() : 0;
|
||||
}
|
||||
|
||||
function user_password_hash(string $password): string
|
||||
{
|
||||
return password_hash($password, MSZ_USERS_PASSWORD_HASH_ALGO);
|
||||
}
|
||||
|
||||
// Temporary key generation for chat login.
|
||||
// Should eventually be replaced with a callback login system.
|
||||
function user_generate_chat_key(int $userId): string
|
||||
{
|
||||
$chatKey = bin2hex(random_bytes(16));
|
||||
|
||||
$setChatKey = Database::connection()->prepare('
|
||||
UPDATE `msz_users`
|
||||
SET `user_chat_key` = :user_chat_key
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$setChatKey->bindValue('user_chat_key', $chatKey);
|
||||
$setChatKey->bindValue('user_id', $userId);
|
||||
|
||||
return $setChatKey->execute() ? $chatKey : '';
|
||||
}
|
|
@ -105,6 +105,7 @@
|
|||
<title>Flashii Broom Closet</title>
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
|
||||
<link href="/css/manage.css" rel="stylesheet">
|
||||
<link href="/css/libraries.css" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body class="manage">
|
||||
|
@ -164,5 +165,6 @@
|
|||
{{ link('https://github.com/flashwave/misuzu/tree/' ~ git_branch(), git_branch(), 'footer__link') }} # {{ link('https://github.com/flashwave/misuzu/commit/' ~ git_hash(true), git_hash(), 'footer__link') }}
|
||||
</footer>
|
||||
</div>
|
||||
<script src="/js/libraries.js" charset="utf-8"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -6,10 +6,12 @@
|
|||
{% for user in manage_users %}
|
||||
<a href="?v=view&u={{ user.user_id }}" class="listing__entry user-listing__entry"{% if not user.colour|colour_get_inherit %} style="border-color: {{ user.colour|colour_get_css }}"{% endif %}>
|
||||
<div class="listing__entry__content user-listing__entry__content">
|
||||
<div class="listing__entry__column user-listing__entry__column user-listing__entry__column--username">
|
||||
{{ user.username }}
|
||||
<div class="user-listing__info">
|
||||
<div class="user-listing__username">
|
||||
{{ user.username }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="user-listing__avatar" style="background-image:url('/profile.php?u={{ user.user_id }}&m=avatar');"></div>
|
||||
<div class="avatar user-listing__avatar" style="background-image:url('/profile.php?u={{ user.user_id }}&m=avatar');"></div>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<a class="container__title container__title--link" href="/news.php">Featured News</a>
|
||||
<div class="container__content news__container__content">
|
||||
<div class="news__preview__listing">
|
||||
{% for post in featuredNews %}
|
||||
{% for post in featured_news %}
|
||||
{{ news_preview(post) }}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if prevent_registration %}
|
||||
{% if settings_disable_account_options %}
|
||||
<div class="settings__account__column settings__account__column--no-margin settings__account__column--disabled">
|
||||
<div class="settings__account__row">
|
||||
<div class="settings__account__column">
|
||||
|
|
Loading…
Add table
Reference in a new issue