Skip to content

Commit

Permalink
Merge pull request #3019 from NamelessMC/release/2.0.2
Browse files Browse the repository at this point in the history
Release/2.0.2
  • Loading branch information
samerton authored Aug 13, 2022
2 parents c1e179c + 8d4178b commit 26bbbae
Show file tree
Hide file tree
Showing 77 changed files with 497 additions and 222 deletions.
32 changes: 31 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,44 @@
## [Unreleased](https://github.com/NamelessMC/Nameless/compare/v2.0.0...v2)
> [Milestone](https://github.com/NamelessMC/Nameless/milestone/18)
## [2.0.2](https://github.com/NamelessMC/Nameless/compare/v2.0.1...v2.0.2) - 2022-08-13

### Added
- Add ability to rate limit via Validate class
- Add base for Croatian translation
- Allow selecting an MC server to use for group sync options
- Display each OAuth provider's redirect URL

### Changed
- Removed mentions of "pre-release" from installer
- Make update check more resilient to API being down
- Better module/template version check
- Display PHP version in die() when version is not acceptable
- Better missing/extra migration exception page
- Rework user sessions system
- Rework tab initialisation
- Rework logout
- Rework two factor authentication disabling

### Fixed
- Fix image upload error message
- Fix non-string config values not saving properly
- Fix API error name
- Fix active language file value
- Fix several profile image issues
- Fix OAuth page being stored as last page location
- Fix exception when user ID is null in email errors table

## [2.0.1](https://github.com/NamelessMC/Nameless/compare/v2.0.0...v2.0.1) - 2022-08-05

### Fixed
- Fix exception when creating admin user during installation
- Update upgrade check to use correct URL on Download button
- Fix invalid call to `getAvatar` on viewing a forum topic
- Fix not checking if config file would be writable when installing


## [2.0.0](https://github.com/NamelessMC/Nameless/compare/v2.0.0-pr13...v2) - 2022-08-05
## [2.0.0](https://github.com/NamelessMC/Nameless/compare/v2.0.0-pr13...v2.0.0) - 2022-08-05
> [Milestone](https://github.com/NamelessMC/Nameless/milestone/17)
### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The official project website, providing support and additional downloads such as

<img src="https://user-images.githubusercontent.com/26070412/137838580-168ebd24-a222-4a64-a220-d3029650f0ab.png" alt="Features" width="700">

The following list is a brief summary of the features available in v2 pre-release 13:
The following list is a brief summary of the features available in v2:
- 🙋 Forums
- 📃 Custom pages: create your own HTML pages and even restrict access to them depending on group.
- 👥 Social logins: allow your users to register/login with services such as Discord and Google.
Expand Down
4 changes: 2 additions & 2 deletions core/classes/Core/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public static function set(string $key, $value): void {
$loc = &$loc[$step];
}
// Check if it is a string here so that `null` is not converted to `''`
$loc = $value === null ? $value : addslashes($value);
$loc = !is_string($value) ? $value : addslashes($value);
}

static::write((array) $config);
Expand All @@ -140,7 +140,7 @@ public static function setMultiple(array $values): void {
foreach ($path as $step) {
$loc = &$loc[$step];
}
$loc = addslashes($value);
$loc = !is_string($value) ? $value : addslashes($value);
}
}

Expand Down
13 changes: 8 additions & 5 deletions core/classes/Core/Language.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,10 @@ class Language {
'name' => 'Vietnamese',
'htmlCode' => 'vi',
],
'hr_HR' => [
'name' => 'Croatian',
'htmlCode' =>'hr',
],
];

/**
Expand Down Expand Up @@ -184,7 +188,7 @@ public function __construct(string $module = 'core', string $active_language = n
$path = str_replace('/', DIRECTORY_SEPARATOR, $module) . DIRECTORY_SEPARATOR . '__lng__.json';
}

$this->_activeLanguageFile = $path;
$this->_activeLanguageFile = str_replace('__lng__', $this->_activeLanguage, $path);

// HTML language definition
if (!defined('HTML_LANG')) {
Expand All @@ -198,7 +202,7 @@ public function __construct(string $module = 'core', string $active_language = n

$this->_i18n = new i18next(
$this->_activeLanguage,
$this->_activeLanguageFile,
$path,
'en_UK'
);
}
Expand Down Expand Up @@ -250,13 +254,12 @@ public function getPluralForm(): ?Closure {
* @param string $value New value to set for term.
*/
public function set(string $section, string $term, string $value): void {
$editing_file = implode(DIRECTORY_SEPARATOR, [ROOT_PATH, 'custom', 'languages', $this->_activeLanguage . '.json']);
$json = json_decode(file_get_contents($editing_file), true);
$json = json_decode(file_get_contents($this->_activeLanguageFile), true);

$json[$section . '/' . $term] = $value;

ksort($json);
file_put_contents($editing_file, json_encode($json, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
file_put_contents($this->_activeLanguageFile, json_encode($json, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
}

/**
Expand Down
66 changes: 41 additions & 25 deletions core/classes/Core/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* @author Samerton
* @author Partydragen
* @author Aberdeener
* @version 2.0.0-pr13
* @version 2.0.2
* @license MIT
*/
class User {
Expand Down Expand Up @@ -73,14 +73,14 @@ public function __construct(string $user = null, string $field = 'id') {

if ($user === null) {
if (Session::exists($this->_sessionName)) {
$user = Session::get($this->_sessionName);
if ($this->find($user, $field)) {
$hash = Session::get($this->_sessionName);
if ($this->find($hash, 'hash')) {
$this->_isLoggedIn = true;
}
}
if (Session::exists($this->_admSessionName)) {
$user = Session::get($this->_admSessionName);
if ($user == $this->data()->id && $this->find($user, $field)) {
$hash = Session::get($this->_admSessionName);
if ($this->find($hash, 'hash')) {
$this->_isAdmLoggedIn = true;
}
}
Expand All @@ -107,7 +107,11 @@ public function find(string $value = null, string $field = 'id'): bool {
return true;
}

$data = $this->_db->get('users', [$field, $value]);
if ($field != 'hash') {
$data = $this->_db->get('users', [$field, $value]);
} else {
$data = $this->_db->query('SELECT nl2_users.* FROM nl2_users LEFT JOIN nl2_users_session ON nl2_users.id = user_id WHERE hash = ? AND nl2_users_session.active = 1', [$value]);
}

if ($data->count()) {
$this->_data = new UserData($data->first());
Expand Down Expand Up @@ -297,7 +301,7 @@ public function idToNickname(int $id): ?string {
* @param string|null $username Their username (or email, depending on $method).
* @param string|null $password Their password.
* @param bool $remember Whether to keep them logged in or not.
* @param string $method What column to check for their details in. Can be either `username` or `email`.
* @param string $method What column to check for their details in. Can be either `username` or `email` or `oauth`.
*
* @return bool True/false on success or failure respectfully.
*/
Expand All @@ -307,29 +311,27 @@ public function login(?string $username = null, ?string $password = null, bool $

private function _commonLogin(?string $username, ?string $password, bool $remember, string $method, bool $is_admin): bool {
$sessionName = $is_admin ? $this->_admSessionName : $this->_sessionName;
if (!$username && !$password && $this->exists()) {
Session::put($sessionName, $this->data()->id);
if (!$username && $method == 'hash' && $this->exists()) {
// Logged in using hash from cookie
Session::put($sessionName, $password);
if (!$is_admin) {
$this->_isLoggedIn = true;
}
} else if ($this->checkCredentials($username, $password, $method) === true) {
// Valid credentials
Session::put($sessionName, $this->data()->id);
$hash = SecureRandom::alphanumeric();

$this->_db->insert('users_session', [
'user_id' => $this->data()->id,
'hash' => $hash,
'remember_me' => $remember,
'active' => 1,
'login_method' => $is_admin ? 'admin' : $method
]);

if ($remember) {
$hash = SecureRandom::alphanumeric();
$table = $is_admin ? 'users_admin_session' : 'users_session';
$hashCheck = $this->_db->get($table, ['user_id', $this->data()->id]);

if (!$hashCheck->count()) {
$this->_db->insert($table, [
'user_id' => $this->data()->id,
'hash' => $hash
]);
} else {
$hash = $hashCheck->first()->hash;
}
Session::put($sessionName, $hash);

if ($remember) {
$expiry = $is_admin ? 3600 : Config::get('remember.cookie_expiry');
$cookieName = $is_admin ? ($this->_cookieName . '_adm') : $this->_cookieName;
Cookie::put($cookieName, $hash, $expiry, HttpUtils::getProtocol() === 'https', true);
Expand Down Expand Up @@ -532,12 +534,24 @@ public function hasPermission(string $permission): bool {
return false;
}

/**
* Log the user out from all other sessions.
*/
public function logoutAllOtherSessions(): void {
DB::getInstance()->query('UPDATE nl2_users_session SET `active` = 0 WHERE user_id = ? AND hash != ?', [
$this->data()->id,
Session::get(Config::get('session.session_name'))
]);
}

/**
* Log the user out.
* Deletes their cookies, sessions and database session entry.
*/
public function logout(): void {
$this->_db->delete('users_session', ['user_id', $this->data()->id]);
$this->_db->update('users_session', [['user_id', $this->data()->id], ['hash', Session::get($this->_sessionName)]], [
'active' => 0
]);

Session::delete($this->_sessionName);
Cookie::delete($this->_cookieName);
Expand All @@ -547,7 +561,9 @@ public function logout(): void {
* Process logout if user is admin
*/
public function admLogout(): void {
$this->_db->delete('users_admin_session', ['user_id', $this->data()->id]);
$this->_db->update('users_session', [['user_id', $this->data()->id], ['hash', Session::get($this->_admSessionName)]], [
'active' => 0
]);

Session::delete($this->_admSessionName);
Cookie::delete($this->_cookieName . '_adm');
Expand Down
17 changes: 16 additions & 1 deletion core/classes/Core/Util.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ public static function updateCheck() {
return $update_check_response->getError();
}

$update_check = new UpdateCheck($update_check_response->json(true));
$update_check = new UpdateCheck($update_check_response);
if ($update_check->hasError()) {
return $update_check->getErrorMessage();
}
Expand Down Expand Up @@ -524,4 +524,19 @@ public static function findBeforeAfter(array $items, string $current): array {
return [$before, $after];
}

/**
* Determine whether a module/template version is compatible with the current NamelessMC version.
* This ignores patch versions, and only checks major and minor versions.
* For example, 2.0.0 and 2.0.1 are compatible, but 2.0.0 and 2.1.0 are not.
* @param string $version Version of module/template to check
* @param string $nameless_version Current NamelessMC version
* @return bool Whether they are compatible or not
*/
public static function isCompatible(string $version, string $nameless_version): bool {
[$major, $minor, ] = explode('.', $version);
[$nameless_major, $nameless_minor, ] = explode('.', $nameless_version);

return $major == $nameless_major && $minor == $nameless_minor;
}

}
61 changes: 59 additions & 2 deletions core/classes/Core/Validate.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ class Validate {
*/
public const NOT_START_WITH = 'not_start_with';

/**
* @var string Set a rate limit
*/
public const RATE_LIMIT = 'rate_limit';

private DB $_db;

private ?string $_message = null;
Expand Down Expand Up @@ -112,6 +117,7 @@ private function __construct() {
* @param array $items subset of inputs to be validated
*
* @return Validate New instance of Validate.
* @throws Exception If provided configuration for a rule is invalid - not if a provided value is invalid!
*/
public static function check(array $source, array $items = []): Validate {
$validator = new Validate();
Expand Down Expand Up @@ -318,6 +324,51 @@ public static function check(array $source, array $items = []): Validate {
break;
}
break;

case self::RATE_LIMIT:
if (is_array($rule_value) && count($rule_value) === 2) {
// If array treat as [limit, seconds]
[$limit, $seconds] = $rule_value;
} else if (is_int($rule_value)) {
// If integer default seconds to 60
[$limit, $seconds] = [$rule_value, 60];
}

if (!isset($limit) || !isset($seconds)) {
throw new Exception('Invalid rate limit configuration');
}

$key = "rate_limit_{$item}";
$session = $_SESSION[$key];
$time = date('U');
$limit_end = $time + $seconds;

if (isset($session) && is_array($session) && count($session) === 2) {
[$count, $expires] = $session;
$diff = $expires - $time;

if (++$count >= $limit && $diff > 0) {
$validator->addError([
'field' => $item,
'rule' => self::RATE_LIMIT,
'fallback' => "$item has reached the rate limit which expires in $diff seconds.",
'meta' => ['expires' => $diff],
]);
break;
}

if ($diff <= 0) {
// Reset
$_SESSION[$key] = [1, $limit_end];
break;
}

$_SESSION[$key] = [$count, $expires];
} else {
$_SESSION[$key] = [1, $limit_end];
}

break;
}
}
}
Expand Down Expand Up @@ -379,7 +430,7 @@ public function errors(): array {
// Loop all errors to convert and get their custom messages
foreach ($this->_to_convert as $error) {

$message = $this->getMessage($error['field'], $error['rule'], $error['fallback']);
$message = $this->getMessage($error['field'], $error['rule'], $error['fallback'], $error['meta']);

// If there is no generic `message()` set or the translated message is not equal to generic message
// we can continue without worrying about duplications
Expand Down Expand Up @@ -409,10 +460,11 @@ public function errors(): array {
* @param string $field name of field to search for.
* @param string $rule rule which check failed. should be from the constants defined above.
* @param string $fallback fallback default message if custom message and generic message are not supplied.
* @param ?array $meta optional meta to provide to message.
*
* @return string Message for this field and rule.
*/
private function getMessage(string $field, string $rule, string $fallback): string {
private function getMessage(string $field, string $rule, string $fallback, ?array $meta = []): string {

// No custom messages defined for this field
if (!isset($this->_messages[$field])) {
Expand All @@ -436,6 +488,11 @@ private function getMessage(string $field, string $rule, string $fallback): stri
return $this->_message ?? $fallback;
}

// If the message is a callback function, provide it with meta
if (is_callable($this->_messages[$field][$rule])) {
return $this->_messages[$field][$rule]($meta);
}

// Rule-specific custom message was supplied
return $this->_messages[$field][$rule];
}
Expand Down
Loading

0 comments on commit 26bbbae

Please sign in to comment.