diff --git a/assets/less/classes/profile/warning.less b/assets/less/classes/profile/warning.less index e7991fab..8bee3022 100644 --- a/assets/less/classes/profile/warning.less +++ b/assets/less/classes/profile/warning.less @@ -1,9 +1,12 @@ .profile__warning { margin: 2px; - padding: 1px; border-radius: 2px; border: 1px solid var(--accent-colour); + &__container { + margin: 2px 0; + } + &--warning { --accent-colour: #666; } @@ -16,31 +19,58 @@ --accent-colour: #c33; } - &__public { + &__background { + background-color: var(--accent-colour); + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + + &__content { + background-color: var(--background-colour-translucent); display: flex; - flex-wrap: wrap; + padding: 1px; + } + + &__type, + &__created, + &__duration { + display: inline-flex; + align-items: center; + justify-content: center; } &__type { - min-width: 60px; - text-align: center; + min-width: 80px; background-color: var(--accent-colour); border-radius: 1px; padding: 0 4px; } - &__datetime { + &__created, + &__duration { min-width: 100px; padding: 0 4px; } &__note { padding: 1px 4px; + flex: 1 1 auto; } &__private { border-top: 1px solid var(--accent-colour); margin-top: 1px; - padding: 1px 4px; + width: 100%; + opacity: .5; + transition: opacity .2s; + + &:hover, + &:active, + &:focus { + opacity: 1; + } } } diff --git a/assets/less/classes/warning.less b/assets/less/classes/warning.less index 414189ba..12fab2f5 100644 --- a/assets/less/classes/warning.less +++ b/assets/less/classes/warning.less @@ -13,4 +13,15 @@ background-color: rgba(17, 17, 17, .9); padding: 2px 5px; } + + &__link { + color: inherit; + text-decoration: underline dotted; + + &:hover, + &:active, + &:focus { + text-decoration: underline; + } + } } diff --git a/database/2018_12_24_203231_add_warnings_table.php b/database/2018_12_24_203231_add_warnings_table.php index 66fe7a66..648c745a 100644 --- a/database/2018_12_24_203231_add_warnings_table.php +++ b/database/2018_12_24_203231_add_warnings_table.php @@ -13,13 +13,14 @@ function migrate_up(PDO $conn): void `issuer_id` INT(10) UNSIGNED NULL DEFAULT NULL, `issuer_ip` VARBINARY(16) NOT NULL, `warning_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `warning_duration` TIMESTAMP NULL DEFAULT NULL, `warning_type` TINYINT(3) UNSIGNED NOT NULL, `warning_note` VARCHAR(255) NOT NULL, - `warning_note_private` TEXT NOT NULL, + `warning_note_private` TEXT NULL DEFAULT NULL, PRIMARY KEY (`warning_id`), INDEX `user_warnings_user_foreign` (`user_id`), INDEX `user_warnings_issuer_foreign` (`issuer_id`), - INDEX `user_warnings_indices` (`warning_created`, `warning_type`), + INDEX `user_warnings_indices` (`warning_created`, `warning_type`, `warning_duration`), CONSTRAINT `user_warnings_issuer_foreign` FOREIGN KEY (`issuer_id`) REFERENCES `msz_users` (`user_id`) @@ -32,6 +33,14 @@ function migrate_up(PDO $conn): void ON DELETE CASCADE ) "); + + $conn->exec(" + CREATE TABLE `msz_ip_blacklist` ( + `ip_subnet` VARBINARY(16) NOT NULL, + `ip_mask` TINYINT(3) UNSIGNED NOT NULL, + UNIQUE INDEX `ip_blacklist_unique` (`ip_subnet`, `ip_mask`) + ) + "); } function migrate_down(PDO $conn): void diff --git a/public/auth.php b/public/auth.php index be9df340..39df6d35 100644 --- a/public/auth.php +++ b/public/auth.php @@ -23,6 +23,7 @@ $authEmail = $isSubmission ? ($_POST['auth']['email'] ?? '') : ($_GET['email'] ? $authPassword = $_POST['auth']['password'] ?? ''; $authVerification = $_POST['auth']['verification'] ?? ''; $authRedirect = $_POST['auth']['redirect'] ?? $_GET['redirect'] ?? $_SERVER['HTTP_REFERER'] ?? '/'; +$authRestricted = ip_blacklist_check(ip_remote_address()); tpl_vars([ 'can_create_account' => $canCreateAccount, @@ -31,6 +32,7 @@ tpl_vars([ 'auth_username' => $authUsername, 'auth_email' => $authEmail, 'auth_redirect' => $authRedirect, + 'auth_restricted' => $authRestricted, ]); switch ($authMode) { @@ -300,7 +302,7 @@ MSG; $authRegistrationError = ''; while ($isSubmission) { - if (!$canCreateAccount) { + if (!$canCreateAccount || $authRestricted) { $authRegistrationError = 'You may not create an account right now.'; break; } diff --git a/src/Net/ip.php b/src/Net/ip.php index 02616169..f2080c04 100644 --- a/src/Net/ip.php +++ b/src/Net/ip.php @@ -58,7 +58,7 @@ function ip_get_raw_width(int $version): int return MSZ_IP_SIZES[$version] ?? 0; } -function ip_match_cidr_raw(string $address, string $subnet, int $mask = 0): bool +function ip_match_cidr_raw(string $address, string $subnet, ?int $mask = null): bool { $version = ip_get_raw_version($subnet); $bits = ip_get_raw_width($version) * 8; @@ -72,7 +72,7 @@ function ip_match_cidr_raw(string $address, string $subnet, int $mask = 0): bool } for ($i = 0; $i < ceil($mask / 8); $i++) { - $byteMask = (0xFF00 >> min(8, $mask - ($i * 8))) & 0xFF; + $byteMask = (0xFF00 >> max(0, min(8, $mask - ($i * 8)))) & 0xFF; $addressByte = ord($address[$i]) & $byteMask; $subnetByte = ord($subnet[$i]) & $byteMask; @@ -85,6 +85,15 @@ function ip_match_cidr_raw(string $address, string $subnet, int $mask = 0): bool } function ip_match_cidr(string $address, string $cidr): bool +{ + $address = inet_pton($address); + [$subnet, $mask] = ['', 0]; + extract(ip_cidr_to_raw($cidr)); + + return ip_match_cidr_raw($address, $subnet, $mask); +} + +function ip_cidr_to_raw(string $cidr): array { if (strpos($cidr, '/') !== false) { [$subnet, $mask] = explode('/', $cidr, 2); @@ -92,8 +101,56 @@ function ip_match_cidr(string $address, string $cidr): bool $subnet = $cidr; } - $address = inet_pton($address); $subnet = inet_pton($subnet); + $mask = empty($mask) ? null : $mask; - return ip_match_cidr_raw($address, $subnet, $mask ?? 0); + return compact('subnet', 'mask'); +} + +function ip_blacklist_check(string $address): bool +{ + $checkBlacklist = db_prepare(" + SELECT COUNT(*) > 0 + FROM `msz_ip_blacklist` + WHERE LENGTH(`ip_subnet`) = LENGTH(INET6_ATON(:ip1)) + AND `ip_subnet` & LPAD('', LENGTH(`ip_subnet`), X'FF') << LENGTH(`ip_subnet`) * 8 - `ip_mask` + = INET6_ATON(:ip2) & LPAD('', LENGTH(`ip_subnet`), X'FF') << LENGTH(`ip_subnet`) * 8 - `ip_mask` + "); + $checkBlacklist->bindValue('ip1', $address); + $checkBlacklist->bindValue('ip2', $address); + return (bool)($checkBlacklist->execute() ? $checkBlacklist->fetchColumn() : false); +} + +function ip_blacklist_add_raw(string $subnet, ?int $mask = null): bool +{ + $version = ip_get_raw_version($subnet); + + if ($version === 0) { + return false; + } + + $bits = ip_get_raw_width($version) * 8; + + if (empty($mask)) { + $mask = $bits; + } elseif ($mask < 1 || $mask > $bits) { + return false; + } + + $addBlacklist = db_prepare(' + INSERT INTO `msz_ip_blacklist` + (`ip_subnet`, `ip_mask`) + VALUES + (:subnet, :mask) + '); + $addBlacklist->bindValue('subnet', $subnet); + $addBlacklist->bindValue('mask', $mask); + return $addBlacklist->execute(); +} + +function ip_blacklist_add(string $cidr): bool +{ + [$subnet, $mask] = ['', 0]; + extract(ip_cidr_to_raw($cidr)); + return ip_blacklist_add_raw($subnet, $mask); } diff --git a/src/Users/warning.php b/src/Users/warning.php index 5f0268b5..ecbc9b62 100644 --- a/src/Users/warning.php +++ b/src/Users/warning.php @@ -1,10 +1,11 @@
- {{ auth_register_error }} + The IP address from which you are visiting the website appears on our blacklist, you are not allowed to register from this address but if you already have an account you can log in just fine using the form above. If you think this blacklisting is a mistake, please contact us!
+ {% else %} + {% if auth_register_error is defined %} +
+
+ {{ auth_register_error }} +
+
+ {% endif %} + +
+ {{ input_text('auth[username]', 'auth__input', auth_username|default(''), 'text', 'Username', true, null, 0, auth_mode == 'register') }} + {{ input_text('auth[password]', 'auth__input', '', 'password', 'Password', true) }} + {{ input_text('auth[email]', 'auth__input', auth_email|default(''), 'text', 'E-mail', true) }} + {{ input_text('auth[meow]', 'auth__input', '', 'text', 'What is the outcome of nine plus ten?', true) }} + + +
{% endif %} - -
- {{ input_text('auth[username]', 'auth__input', auth_username|default(''), 'text', 'Username', true, null, 0, auth_mode == 'register') }} - {{ input_text('auth[password]', 'auth__input', '', 'password', 'Password', true) }} - {{ input_text('auth[email]', 'auth__input', auth_email|default(''), 'text', 'E-mail', true) }} - {{ input_text('auth[meow]', 'auth__input', '', 'text', 'What is the outcome of nine plus ten?', true) }} - - -
{% endif %} diff --git a/templates/user/profile.twig b/templates/user/profile.twig index 01ae68f4..2b163604 100644 --- a/templates/user/profile.twig +++ b/templates/user/profile.twig @@ -156,47 +156,6 @@ {% endfor %} - - {% if warnings|length > 0 %} -
- {{ container_title('Account Standing') }} - - {% for warning in warnings %} - {% if warning.warning_type == constant('MSZ_WARN_SILENCE') %} - {% set warning_text = 'Silence' %} - {% set warning_class = 'silence' %} - {% elseif warning.warning_type == constant('MSZ_WARN_BAN') %} - {% set warning_text = 'Ban' %} - {% set warning_class = 'ban' %} - {% else %} - {% set warning_text = 'Warning' %} - {% set warning_class = 'warning' %} - {% endif %} - -
-
-
- {{ warning_text }} -
- - - -
- {{ warning.warning_note }} -
-
- - {% if can_view_private_note and warning.warning_note_private|length > 0 %} -
- {{ warning.warning_note_private|nl2br }} -
- {% endif %} -
- {% endfor %} -
- {% endif %} {% endif %} @@ -216,6 +175,81 @@ {% endif %} + {% if warnings|length > 0 %} +
+ {{ container_title('Account Standing') }} + +
+
+ +
+
+ Type +
+ +
+ Created +
+ +
+ Expires +
+ +
+ Note +
+
+
+ + {% for warning in warnings %} + {% if warning.warning_type == constant('MSZ_WARN_SILENCE') %} + {% set warning_text = 'Silence' %} + {% set warning_class = 'silence' %} + {% elseif warning.warning_type == constant('MSZ_WARN_BAN') %} + {% set warning_text = 'Ban' %} + {% set warning_class = 'ban' %} + {% elseif warning.warning_type == constant('MSZ_WARN_WARNING') %} + {% set warning_text = 'Warning' %} + {% set warning_class = 'warning' %} + {% else %} + {% set warning_text = 'Note' %} + {% set warning_class = 'note' %} + {% endif %} + +
+
+ +
+
+ {{ warning_text }} +
+ + + + {% if warning.warning_duration_secs > 0 %} + + {% else %} +
+ {% endif %} + +
+ {{ warning.warning_note }} + + {% if can_view_private_note and warning.warning_note_private|length > 0 %} +
+ {{ warning.warning_note_private|nl2br }} +
+ {% endif %} +
+
+
+ {% endfor %} +
+ {% endif %} {% if is_editing %}