Skip to content

Commit

Permalink
Merge branch 'develop' into merge-oauth-connections
Browse files Browse the repository at this point in the history
# Conflicts:
#	modules/Core/pages/oauth.php
  • Loading branch information
partydragen committed Mar 8, 2024
2 parents 66ff15c + 86a6534 commit 2b7bd29
Show file tree
Hide file tree
Showing 74 changed files with 408 additions and 163 deletions.
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Expand Up @@ -79,7 +79,7 @@ Deprecations rule of thumb:
After adding a new module to core, you need to do the following:
1. Update the `Dockerfile.phpdoc` file to include the new module classes folder (this generates our [PHPDoc](https://phpdoc.namelessmc.com/) site)
2. Update `composer.json` to autoload the new module classes folder
3. Add a new term to the `custom/languages/en_UK.json` file for the module description to be shown during instal
3. Add a new term to the `modules/Core/language/en_UK.json` file for the module description to be shown during instal
- The term should be in the format `module_{module_name}_description`
- Don't forget to add it to the `WHITELISTED_TERMS` array in `dev/scripts/find_unused_language_terms.sh`
4. Create new database entry to install it by default
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -34,7 +34,7 @@ The following list is a brief summary of the features available in v2:
- ✨ Pretty URL option (requires mod_rewrite or special nginx config).
- 🎛 Widgets: allows modules to create widgets which can be displayed on most user-facing pages and display almost anything.
- ⏳ Queue: schedule tasks to happen at a certain point in the future
- 🚩 Translated into [over 20 languages](https://github.com/NamelessMC/Nameless/tree/v2/custom/languages)
- 🚩 Translated into [over 20 languages](https://github.com/NamelessMC/Nameless/tree/v2/modules/Core/language)


#### Customising Nameless
Expand Down
2 changes: 1 addition & 1 deletion core/classes/Core/Language.php
Expand Up @@ -199,7 +199,7 @@ public function __construct(string $module = 'core', string $active_language = n

// Require file
if ($module === 'core') {
$path = implode(DIRECTORY_SEPARATOR, [ROOT_PATH, 'custom', 'languages', '__lng__.json']);
$path = implode(DIRECTORY_SEPARATOR, [ROOT_PATH, 'modules', 'Core', 'language', '__lng__.json']);
} else {
$path = str_replace('/', DIRECTORY_SEPARATOR, $module) . DIRECTORY_SEPARATOR . '__lng__.json';
}
Expand Down
23 changes: 23 additions & 0 deletions core/classes/Core/Validate.php
Expand Up @@ -96,6 +96,11 @@ class Validate {
*/
public const NOT_START_WITH = 'not_start_with';

/**
* @var string Check that the value does not contain a pattern
*/
public const NOT_CONTAIN = 'not_contain';

/**
* @var string Set a rate limit
*/
Expand Down Expand Up @@ -365,6 +370,24 @@ public static function check(array $source, array $items = []): Validate {
}
break;

case self::NOT_CONTAIN:
if (!is_array($rule_value)) {
$rule_value = [$rule_value];
}

foreach ($rule_value as $term) {
if (strpos(strtolower($value), strtolower($term)) !== false) {
$validator->addError([
'field' => $item,
'rule' => self::NOT_CONTAIN,
'fallback' => "$item must not contain $term",
]);
break;
}
}

break;

case self::IN:
$values = is_string($rule_value) ? [$rule_value] : $rule_value;
if (!in_array($value, $values)) {
Expand Down
7 changes: 4 additions & 3 deletions core/classes/Endpoints/Endpoints.php
@@ -1,4 +1,5 @@
<?php
use Symfony\Component\HttpFoundation\Response;
/**
* Endpoint management class.
*
Expand Down Expand Up @@ -49,7 +50,7 @@ public function handle(string $route, string $method, Nameless2API $api): void {
? Nameless2API::ERROR_INVALID_API_KEY
: Nameless2API::ERROR_NOT_AUTHORIZED,
null,
403
Response::HTTP_UNAUTHORIZED
);
}

Expand All @@ -74,10 +75,10 @@ public function handle(string $route, string $method, Nameless2API $api): void {
}

if ($matched_endpoint !== null) {
$api->throwError(Nameless2API::ERROR_INVALID_API_METHOD, "The $route endpoint only accepts " . implode(', ', $available_methods) . ", $method was used.", 405);
$api->throwError(Nameless2API::ERROR_INVALID_API_METHOD, "The $route endpoint only accepts " . implode(', ', $available_methods) . ", $method was used.", Response::HTTP_METHOD_NOT_ALLOWED);
}

$api->throwError(Nameless2API::ERROR_INVALID_API_METHOD, 'If you are seeing this while in a browser, this means your API is functioning!', 404);
$api->throwError(Nameless2API::ERROR_INVALID_API_METHOD, 'If you are seeing this while in a browser, this means your API is functioning!', Response::HTTP_NOT_FOUND);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions core/classes/Endpoints/KeyAuthEndpoint.php
@@ -1,4 +1,5 @@
<?php
use Symfony\Component\HttpFoundation\Response;
/**
* Allows an endpoint to require an API key to be present (and valid) in the request.
*
Expand Down Expand Up @@ -31,23 +32,22 @@ final public function isAuthorised(Nameless2API $api): bool {
// Some hosting providers remove the Authorization header, fall back to non-standard X-API-Key heeader
$api_key_header = HttpUtils::getHeader('X-API-Key');
if ($api_key_header === null) {
$api->throwError(Nameless2API::ERROR_MISSING_API_KEY, 'Missing authorization header');
$api->throwError(Nameless2API::ERROR_MISSING_API_KEY, 'Missing authorization header', Response::HTTP_UNAUTHORIZED);
}

$api_key = $api_key_header;
}

return $this->validateKey($api, $api_key);
return $this->validateKey($api_key);
}

/**
* Validate provided API key to make sure it matches.
*
* @param Nameless2API $api Instance of API to use for database connection.
* @param string $api_key API key to check.
* @return bool Whether it matches or not.
*/
private function validateKey(Nameless2API $api, string $api_key): bool {
private function validateKey(string $api_key): bool {
$correct_key = Settings::get('mc_api_key');
if ($correct_key === null) {
die('API key is null');
Expand Down
4 changes: 2 additions & 2 deletions core/includes/image_upload.php
Expand Up @@ -119,7 +119,7 @@
Redirect::to(URL::build('/profile/' . urlencode($user->data()->username)));
}

http_response_code(500);
http_response_code(\Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST);
$error = $image->getError() ?: 'Unknown error, check logs for more details';
ErrorHandler::logWarning('Image upload error: ' . $error);
die($error);
Expand Down Expand Up @@ -155,7 +155,7 @@

die('OK');
} catch (Exception $e) {
http_response_code(500);
http_response_code(\Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST);
$error = $e->getMessage() ?: 'Unknown error, check logs for more details';
ErrorHandler::logWarning('Image upload exception: ' . $error);
die($error);
Expand Down
2 changes: 1 addition & 1 deletion core/installation/includes/header.php
Expand Up @@ -2,7 +2,7 @@
$readme = file(ROOT_PATH . '/README.md');
$subheader = str_replace('#', '', $readme[0]);

if (isset($_SESSION['installer_language']) && is_file('custom/languages/' . $_SESSION['installer_language'] . '.json')) {
if (isset($_SESSION['installer_language']) && is_file('modules/Core/language/' . $_SESSION['installer_language'] . '.json')) {
$installer_language = $_SESSION['installer_language'];
} else {
$installer_language = 'en_UK';
Expand Down
6 changes: 6 additions & 0 deletions custom/panel_templates/Default/auth.tpl
Expand Up @@ -21,6 +21,12 @@
<input type="password" name="password" id="password"
class="form-control form-control-user" placeholder="{$PASSWORD}">
</div>
{if isset($TWO_FACTOR_AUTH)}
<div class="form-group has-feedback">
<input type="text" name="tfa_code" id="tfa"
class="form-control form-control-user" placeholder="{$TFA_ENTER_CODE}">
</div>
{/if}
<div class="row">
<div class="col-6">
<input type="hidden" name="token" value="{$TOKEN}">
Expand Down
11 changes: 11 additions & 0 deletions custom/panel_templates/Default/core/general_settings.tpl
Expand Up @@ -217,6 +217,17 @@
</option>
</select>
</div>
<div class="col-md-6">
<label for="inputRequirePanelTFA">{$REQUIRE_STAFFCP_TFA}</label>
<select name="require_staffcp_tfa" class="form-control" id="inputRequirePanelTFA">
<option value="true" {if $REQUIRE_STAFFCP_TFA_VALUE} selected{/if}>
{$ENABLED}
</option>
<option value="false" {if !$REQUIRE_STAFFCP_TFA_VALUE} selected{/if}>
{$DISABLED}
</option>
</select>
</div>
</div>
</div>
<div class="form-group">
Expand Down
12 changes: 12 additions & 0 deletions custom/panel_templates/Default/forum/forums_settings.tpl
Expand Up @@ -67,6 +67,18 @@
</label>
</div>

<div class="form-group">
<label for="InputBannedTerms">
{$BANNED_TERMS}
</label>
<span class="badge badge-info">
<i class="fas fa-question-circle" data-container="body" data-toggle="popover"
data-placement="top" title="{$INFO}"
data-content="{$BANNED_TERMS_INFO}"></i>
</span>
<textarea id="InputBannedTerms" class="form-control" name="banned_terms">{$BANNED_TERMS_VALUE}</textarea>
</div>

<div class="form-group">
<input type="hidden" name="token" value="{$TOKEN}">
<input type="submit" class="btn btn-primary" value="{$SUBMIT}" />
Expand Down
2 changes: 1 addition & 1 deletion dev/scripts/delete_empty_language_strings.php
Expand Up @@ -5,7 +5,7 @@
}

$language_files = glob('modules/*/language/*.json');
$language_files = array_merge($language_files, glob('custom/languages/*.json'));
$language_files = array_merge($language_files, glob('modules/Core/language/*.json'));

foreach ($language_files as $language_file) {
$modified = false;
Expand Down
2 changes: 1 addition & 1 deletion dev/scripts/find_unused_language_terms.sh
Expand Up @@ -7,7 +7,7 @@ fi

UNUSED_TERMS_FOUND=false
FILES=(
"custom/languages/en_UK.json"
"modules/Core/language/en_UK.json"
"modules/Forum/language/en_UK.json"
"modules/Cookie Consent/language/en_UK.json"
"modules/Discord Integration/language/en_UK.json"
Expand Down
7 changes: 5 additions & 2 deletions install.php
Expand Up @@ -37,7 +37,7 @@
// Select language
if (
isset($_SESSION['installer_language'])
&& is_file('custom/languages/' . $_SESSION['installer_language'] . '.json')
&& is_file('modules/Core/language/' . $_SESSION['installer_language'] . '.json')
) {
$language_short_code = $_SESSION['installer_language'];
} else {
Expand All @@ -56,8 +56,11 @@
}

if (isset($_GET['language'])) {
if (str_contains($_GET['language'], '/')) {
die('Language code must not contain slash');
}
// Set language
if (is_file('custom/languages/' . $_GET['language'] . '.json')) {
if (is_file('modules/Core/language/' . $_GET['language'] . '.json')) {
$_SESSION['installer_language'] = $_GET['language'];
die('OK');
}
Expand Down
2 changes: 1 addition & 1 deletion modules/Cookie Consent/language/cs_CZ.json
Expand Up @@ -7,7 +7,7 @@
"cookie/cookie_popup_disallow": "Zakázat cookies",
"cookie/cookie_popup_more_info": "Více informací",
"cookie/cookies": "Cookies",
"cookie/update_settings": "Zobrazit cookies okno",
"cookie/update_settings": "Zobrazit nastavení cookies",
"cookie/cookie_notice_error": "Zadejte prosím oznámení o cookies s maximálně 10 000 znaky.",
"cookie/cookie_popup": "Tento web používá cookies pro vylepšení vašeho zážitku."
}
8 changes: 4 additions & 4 deletions modules/Core/classes/Misc/Nameless2API.php
@@ -1,4 +1,5 @@
<?php
use Symfony\Component\HttpFoundation\Response;
/**
* NamelessMC API v2 class
*
Expand Down Expand Up @@ -53,9 +54,8 @@ public function __construct(string $route, Language $api_language, Endpoints $en
$_SERVER['REQUEST_METHOD'],
$this
);

} catch (Exception $e) {
$this->throwError(self::ERROR_UNKNOWN_ERROR, $e->getMessage());
$this->throwError(self::ERROR_UNKNOWN_ERROR, $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}

Expand All @@ -67,7 +67,7 @@ public function __construct(string $route, Language $api_language, Endpoints $en
* @param int $status HTTP status code
* @return never
*/
public function throwError(string $error, $meta = null, int $status = 400): void {
public function throwError(string $error, $meta = null, int $status = Response::HTTP_BAD_REQUEST): void {
$this->returnArray(
array_merge(['error' => $error], $meta ? ['meta' => $meta] : []),
$status
Expand Down Expand Up @@ -112,7 +112,7 @@ public function getLanguage(): Language {
* @param int $status HTTP status code
* @return never
*/
public function returnArray(array $array, int $status = 200): void {
public function returnArray(array $array, int $status = Response::HTTP_OK): void {
http_response_code($status);

die(self::encodeJson($array));
Expand Down
5 changes: 3 additions & 2 deletions modules/Core/includes/endpoints/RegisterEndpoint.php
@@ -1,4 +1,5 @@
<?php
use Symfony\Component\HttpFoundation\Response;

/**
* @param string $username The username of the new user to create
Expand Down Expand Up @@ -171,7 +172,7 @@ private function createUser(Nameless2API $api, string $username, string $email,
return ['user_id' => $user_id];

} catch (Exception $e) {
$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_CREATE_ACCOUNT, $e->getMessage());
$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_CREATE_ACCOUNT, $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}

Expand Down Expand Up @@ -210,7 +211,7 @@ private function sendRegistrationEmail(Nameless2API $api, string $username, stri
'user_id' => $user_id
]);

$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_SEND_REGISTRATION_EMAIL);
$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_SEND_REGISTRATION_EMAIL, null, Response::HTTP_INTERNAL_SERVER_ERROR);
}

$api->returnArray(['message' => $api->getLanguage()->get('api', 'finish_registration_email')]);
Expand Down
7 changes: 4 additions & 3 deletions modules/Core/includes/endpoints/ServerInfoEndpoint.php
@@ -1,4 +1,5 @@
<?php
use Symfony\Component\HttpFoundation\Response;

class ServerInfoEndpoint extends KeyAuthEndpoint {

Expand Down Expand Up @@ -61,7 +62,7 @@ public function execute(Nameless2API $api): void {
file_put_contents(ROOT_PATH . DIRECTORY_SEPARATOR . 'cache' . DIRECTORY_SEPARATOR . sha1('server_query_cache') . '.cache', json_encode($to_cache));
}
} catch (Exception $e) {
$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_UPDATE_SERVER_INFO, $e->getMessage(), 500);
$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_UPDATE_SERVER_INFO, $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}

$cache = new Cache(['name' => 'nameless', 'extension' => '.cache', 'path' => ROOT_PATH . '/cache/']);
Expand All @@ -83,7 +84,7 @@ public function execute(Nameless2API $api): void {
}
}
} catch (Exception $e) {
$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_UPDATE_SERVER_INFO, $e->getMessage(), 500);
$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_UPDATE_SERVER_INFO, $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}
}

Expand All @@ -105,7 +106,7 @@ public function execute(Nameless2API $api): void {
], intval($_POST['interval_seconds'] ?? 10) * 2);
}
} catch (Exception $e) {
$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_UPDATE_SERVER_INFO, $e->getMessage(), 500);
$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_UPDATE_SERVER_INFO, $e->getMessage(), Response::HTTP_INTERNAL_SERVER_ERROR);
}

$api->returnArray(array_merge(['message' => $api->getLanguage()->get('api', 'server_info_updated')]));
Expand Down
3 changes: 2 additions & 1 deletion modules/Core/includes/endpoints/UpdateUsernameEndpoint.php
@@ -1,4 +1,5 @@
<?php
use Symfony\Component\HttpFoundation\Response;

/**
* @param int $id The NamelessMC user to update
Expand Down Expand Up @@ -27,7 +28,7 @@ public function execute(Nameless2API $api, User $user): void {
try {
$api->getDb()->update('users', $user->data()->id, $fields);
} catch (Exception $e) {
$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_UPDATE_USERNAME, null, 500);
$api->throwError(CoreApiErrors::ERROR_UNABLE_TO_UPDATE_USERNAME, null, Response::HTTP_INTERNAL_SERVER_ERROR);
}

$api->returnArray(['message' => $api->getLanguage()->get('api', 'username_updated')]);
Expand Down

0 comments on commit 2b7bd29

Please sign in to comment.