diff --git a/.gitlab/scripts/lang_sort.sh b/.gitlab/scripts/lang_sort.sh
index 5710ce1b..9a1e1cf0 100644
--- a/.gitlab/scripts/lang_sort.sh
+++ b/.gitlab/scripts/lang_sort.sh
@@ -56,8 +56,8 @@ get_keys "${DIR}/en_EN.json" | sort > "${ref_keys}"
# Iterate over each .json file in the directory
for file in "${DIR}"/*.json; do
- # Check if file is a regular file and not en_EN.json, and does not contain "_incomplete" in its name
- if [[ -f "${file}" && "${file}" != "${DIR}/en_EN.json" && ! "${file}" =~ _incomplete ]]; then
+ # Check if file is a regular file and not en_EN.json, humanized index and does not contain "_incomplete" in its name
+ if [[ -f "${file}" && "${file}" != "${DIR}/en_EN.json" && "${file}" != "${DIR}/humanized_index.json" && ! "${file}" =~ _incomplete ]]; then
# Get keys and subkeys from the current file
current_keys=$(mktemp)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee1e8ef5..8d2e1b61 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,15 +5,24 @@ TBD
### Bug fixes
- Fix zip imports so the root dir selection is functional ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/764))
- Fix bug where full access gives minimal access ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/768))
+- Bump tornado & requests for sec advisories ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/774))
- Ensure audit.log exists or create it on Crafty startup ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/771))
+- Fix typing issue on ID comparison causing general users to not be able to delete their own API keys ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/775))
+- Fix user creation bug where it would fail when a role was selected ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763))
+- Security improvements for general user creations on roles page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763))
+- Security improvements for general user creations on user page ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763))
+- Use UTC for tokens_valid_from in user config, to resolve token invalidation on instance TZ change ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/765))
### Tweaks
- Add info note to default creds file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/760))
- Remove navigation label from sidebar ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/766))
- Do not allow slashes in server names ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/767))
- Add a thread dump to support logs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/769))
- Remove text from status page and use symbols ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/770))
+- Add better feedback on when errors appear on user creation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763))
### Lang
+- Show natural language name instead of country code in User Config Lang select list ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/773))
- Add remaining `he_IL`, `th_TH` translations for 4.4.0 Release ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/761))
+- Mark `he_IL` incomplete ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/763))
## --- [4.4.0] - 2024/05/11
diff --git a/app/classes/controllers/users_controller.py b/app/classes/controllers/users_controller.py
index 5425fbf8..d45797bd 100644
--- a/app/classes/controllers/users_controller.py
+++ b/app/classes/controllers/users_controller.py
@@ -55,6 +55,7 @@ class UsersController:
"minLength": self.helper.minimum_password_length,
"examples": ["crafty"],
"title": "Password",
+ "error": "passLength",
},
"email": {
"type": "string",
diff --git a/app/classes/models/users.py b/app/classes/models/users.py
index 3f96e651..6f6a6bde 100644
--- a/app/classes/models/users.py
+++ b/app/classes/models/users.py
@@ -38,7 +38,7 @@ class Users(BaseModel):
superuser = BooleanField(default=False)
lang = CharField(default="en_EN")
support_logs = CharField(default="")
- valid_tokens_from = DateTimeField(default=datetime.datetime.now)
+ valid_tokens_from = DateTimeField(default=Helpers.get_utc_now)
server_order = CharField(default="")
preparing = BooleanField(default=False)
hints = BooleanField(default=True)
@@ -119,7 +119,6 @@ class HelperUsers:
@staticmethod
def get_user_total():
count = Users.select().where(Users.username != "system").count()
- print(count)
return count
@staticmethod
diff --git a/app/classes/shared/authentication.py b/app/classes/shared/authentication.py
index fad8b730..94db5532 100644
--- a/app/classes/shared/authentication.py
+++ b/app/classes/shared/authentication.py
@@ -1,5 +1,6 @@
import logging
import time
+from datetime import datetime
from typing import Optional, Dict, Any, Tuple
import jwt
from jwt import PyJWTError
@@ -62,7 +63,17 @@ class Authentication:
user = HelperUsers.get_user(user_id)
# TODO: Have a cache or something so we don't constantly
# have to query the database
- if int(user.get("valid_tokens_from").timestamp()) < iat:
+ valid_tokens_from_str = user.get("valid_tokens_from")
+ # It's possible this will be a string or a dt coming from the DB
+ # We need to account for that
+ try:
+ valid_tokens_from_dt = datetime.strptime(
+ valid_tokens_from_str, "%Y-%m-%d %H:%M:%S.%f%z"
+ )
+ except TypeError:
+ valid_tokens_from_dt = valid_tokens_from_str
+ # Convert the string to a datetime object
+ if int(valid_tokens_from_dt.timestamp()) < iat:
# Success!
return key, data, user
return None
diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py
index 55a588fc..64d4e1d1 100644
--- a/app/classes/shared/helpers.py
+++ b/app/classes/shared/helpers.py
@@ -19,7 +19,7 @@ import shutil
import shlex
import subprocess
import itertools
-from datetime import datetime
+from datetime import datetime, timezone
from socket import gethostname
from contextlib import redirect_stderr, suppress
import libgravatar
@@ -640,6 +640,10 @@ class Helpers:
version = f"{major}.{minor}.{sub}"
return str(version)
+ @staticmethod
+ def get_utc_now() -> datetime:
+ return datetime.fromtimestamp(time.time(), tz=timezone.utc)
+
def encode_pass(self, password):
return self.passhasher.hash(password)
diff --git a/app/classes/shared/translation.py b/app/classes/shared/translation.py
index 0e441808..538856a8 100644
--- a/app/classes/shared/translation.py
+++ b/app/classes/shared/translation.py
@@ -20,7 +20,7 @@ class Translation:
def get_language_file(self, language: str):
return os.path.join(self.translations_path, str(language) + ".json")
- def translate(self, page, word, language):
+ def translate(self, page, word, language, error=True):
fallback_language = "en_EN"
translated_word = self.translate_inner(page, word, language)
@@ -37,7 +37,9 @@ class Translation:
if hasattr(translated_word, "__iter__"):
# Multiline strings
return "\n".join(translated_word)
- return "Error while getting translation"
+ if error:
+ return "Error while getting translation"
+ return word
def translate_inner(self, page, word, language) -> t.Union[t.Any, None]:
language_file = self.get_language_file(language)
diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py
index bbbc9d9e..6e122b2d 100644
--- a/app/classes/web/panel_handler.py
+++ b/app/classes/web/panel_handler.py
@@ -892,6 +892,8 @@ class PanelHandler(BaseHandler):
os.path.join(self.helper.root_dir, "app", "translations")
)
):
+ if file == "humanized_index.json":
+ continue
if file.endswith(".json"):
if file.split(".")[0] not in self.helper.get_setting(
"disabled_language_files"
@@ -1307,6 +1309,8 @@ class PanelHandler(BaseHandler):
for file in sorted(
os.listdir(os.path.join(self.helper.root_dir, "app", "translations"))
):
+ if file == "humanized_index.json":
+ continue
if file.endswith(".json"):
if file.split(".")[0] not in self.helper.get_setting(
"disabled_language_files"
diff --git a/app/classes/web/routes/api/auth/invalidate_tokens.py b/app/classes/web/routes/api/auth/invalidate_tokens.py
index f15bf60d..9e38670a 100644
--- a/app/classes/web/routes/api/auth/invalidate_tokens.py
+++ b/app/classes/web/routes/api/auth/invalidate_tokens.py
@@ -1,6 +1,6 @@
-import datetime
import logging
from app.classes.web.base_api_handler import BaseApiHandler
+from app.classes.shared.helpers import Helpers
logger = logging.getLogger(__name__)
@@ -13,7 +13,7 @@ class ApiAuthInvalidateTokensHandler(BaseApiHandler):
logger.debug(f"Invalidate tokens for user {auth_data[4]['user_id']}")
self.controller.users.raw_update_user(
- auth_data[4]["user_id"], {"valid_tokens_from": datetime.datetime.now()}
+ auth_data[4]["user_id"], {"valid_tokens_from": Helpers.get_utc_now()}
)
self.finish_json(200, {"status": "ok"})
diff --git a/app/classes/web/routes/api/roles/index.py b/app/classes/web/routes/api/roles/index.py
index a8612c75..17d38123 100644
--- a/app/classes/web/routes/api/roles/index.py
+++ b/app/classes/web/routes/api/roles/index.py
@@ -2,6 +2,7 @@ import typing as t
from jsonschema import ValidationError, validate
import orjson
from playhouse.shortcuts import model_to_dict
+from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.web.base_api_handler import BaseApiHandler
create_role_schema = {
@@ -71,7 +72,7 @@ class ApiRolesIndexHandler(BaseApiHandler):
return
(
_,
- _,
+ exec_user_permissions_crafty,
_,
superuser,
_,
@@ -81,7 +82,10 @@ class ApiRolesIndexHandler(BaseApiHandler):
# GET /api/v2/roles?ids=true
get_only_ids = self.get_query_argument("ids", None) == "true"
- if not superuser:
+ if (
+ not superuser
+ and EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_permissions_crafty
+ ):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.finish_json(
@@ -104,14 +108,17 @@ class ApiRolesIndexHandler(BaseApiHandler):
return
(
_,
- _,
+ exec_user_permissions_crafty,
_,
superuser,
user,
_,
) = auth_data
- if not superuser:
+ if (
+ not superuser
+ and EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_permissions_crafty
+ ):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
@@ -138,6 +145,8 @@ class ApiRolesIndexHandler(BaseApiHandler):
role_name = data["name"]
manager = data.get("manager", None)
+ if not superuser and not manager:
+ manager = auth_data[4]["user_id"]
if manager == self.controller.users.get_id_by_name("SYSTEM") or manager == 0:
manager = None
diff --git a/app/classes/web/routes/api/roles/role/index.py b/app/classes/web/routes/api/roles/role/index.py
index 73fd9ff3..5cd46918 100644
--- a/app/classes/web/routes/api/roles/role/index.py
+++ b/app/classes/web/routes/api/roles/role/index.py
@@ -1,6 +1,7 @@
from jsonschema import ValidationError, validate
import orjson
-from peewee import DoesNotExist
+from peewee import DoesNotExist, IntegrityError
+from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.web.base_api_handler import BaseApiHandler
modify_role_schema = {
@@ -70,14 +71,17 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
return
(
_,
- _,
+ exec_user_permissions_crafty,
_,
superuser,
_,
_,
) = auth_data
- if not superuser:
+ if (
+ not superuser
+ and EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_permissions_crafty
+ ):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
try:
@@ -100,8 +104,11 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
user,
_,
) = auth_data
-
- if not superuser:
+ role = self.controller.roles.get_role(role_id)
+ if (
+ str(role.get("manager", "no manager found")) != str(auth_data[4]["user_id"])
+ and not superuser
+ ):
return self.finish_json(400, {"status": "error", "error": "NOT_AUTHORIZED"})
self.controller.roles.remove_role(role_id)
@@ -124,7 +131,7 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
return
(
_,
- _,
+ exec_user_permissions_crafty,
_,
superuser,
user,
@@ -132,7 +139,10 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
) = auth_data
role = self.controller.roles.get_role(role_id)
- if not superuser and user["user_id"] != role["manager"]:
+ if not superuser and (
+ user["user_id"] != role["manager"]
+ or EnumPermissionsCrafty.ROLES_CONFIG not in exec_user_permissions_crafty
+ ):
return self.finish_json(
400,
{
@@ -179,7 +189,10 @@ class ApiRolesRoleIndexHandler(BaseApiHandler):
)
except DoesNotExist:
return self.finish_json(404, {"status": "error", "error": "ROLE_NOT_FOUND"})
-
+ except IntegrityError:
+ return self.finish_json(
+ 404, {"status": "error", "error": "ROLE_NAME_EXISTS"}
+ )
self.controller.management.add_to_audit_log(
user["user_id"],
f"modified role with ID {role_id}",
diff --git a/app/classes/web/routes/api/users/index.py b/app/classes/web/routes/api/users/index.py
index dbdb1ac0..32ebd283 100644
--- a/app/classes/web/routes/api/users/index.py
+++ b/app/classes/web/routes/api/users/index.py
@@ -2,6 +2,7 @@ import logging
import json
from jsonschema import validate
from jsonschema.exceptions import ValidationError
+from app.classes.shared.translation import Translation
from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.models.roles import Roles, HelperRoles
from app.classes.models.users import PUBLIC_USER_ATTRS
@@ -54,6 +55,7 @@ class ApiUsersIndexHandler(BaseApiHandler):
)
def post(self):
+ self.translator = Translation(self.helper)
new_user_schema = {
"type": "object",
"properties": {
@@ -87,12 +89,17 @@ class ApiUsersIndexHandler(BaseApiHandler):
try:
validate(data, new_user_schema)
except ValidationError as e:
+ err = self.translator.translate(
+ "validators",
+ e.schema["error"],
+ self.controller.users.get_user_lang_by_id(auth_data[4]["user_id"]),
+ )
return self.finish_json(
400,
{
"status": "error",
"error": "INVALID_JSON_SCHEMA",
- "error_data": str(e),
+ "error_data": f"{str(err)}",
},
)
username = data["username"]
@@ -153,7 +160,11 @@ class ApiUsersIndexHandler(BaseApiHandler):
for role in roles:
role = self.controller.roles.get_role(role)
- if int(role["manager"]) != int(auth_data[4]["user_id"]) and not superuser:
+ if (
+ str(role.get("manager", "no manager found"))
+ != str(auth_data[4]["user_id"])
+ and not superuser
+ ):
return self.finish_json(
400, {"status": "error", "error": "INVALID_ROLES_CREATE"}
)
diff --git a/app/classes/web/routes/api/users/user/api.py b/app/classes/web/routes/api/users/user/api.py
index 3891ef83..4baac898 100644
--- a/app/classes/web/routes/api/users/user/api.py
+++ b/app/classes/web/routes/api/users/user/api.py
@@ -217,7 +217,7 @@ class ApiUsersUserKeyHandler(BaseApiHandler):
)
if (
- target_key.user_id != auth_data[4]["user_id"]
+ str(target_key.user_id) != str(auth_data[4]["user_id"])
and not auth_data[4]["superuser"]
):
return self.finish_json(
diff --git a/app/classes/web/routes/api/users/user/index.py b/app/classes/web/routes/api/users/user/index.py
index 9fa46200..b05e4ac3 100644
--- a/app/classes/web/routes/api/users/user/index.py
+++ b/app/classes/web/routes/api/users/user/index.py
@@ -132,7 +132,6 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
return self.finish_json(
400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
)
-
try:
validate(data, user_patch_schema)
except ValidationError as e:
@@ -144,10 +143,8 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
"error_data": str(e),
},
)
-
if user_id == "@me":
user_id = user["user_id"]
-
if (
EnumPermissionsCrafty.USER_CONFIG not in exec_user_crafty_permissions
and str(user["user_id"]) != str(user_id)
@@ -215,6 +212,25 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
return self.finish_json(
400, {"status": "error", "error": "INVALID_ROLES_MODIFY"}
)
+ user_modify = self.controller.users.get_user_roles_id(user_id)
+
+ for role in data["roles"]:
+ # Check if user is not a super user and that the exec user is the role
+ # manager or that the role already exists in the user's list
+ if not superuser and (
+ str(
+ self.controller.roles.get_role(role).get(
+ "manager", "no manager found"
+ )
+ )
+ != str(auth_data[4]["user_id"])
+ and role not in user_modify
+ ):
+ for item in user_modify:
+ print(type(role), type(item))
+ return self.finish_json(
+ 400, {"status": "error", "error": "INVALID_ROLES_MODIFY"}
+ )
user_obj = HelperUsers.get_user_model(user_id)
if "password" in data and str(user["user_id"]) != str(user_id):
diff --git a/app/frontend/templates/panel/panel_edit_role.html b/app/frontend/templates/panel/panel_edit_role.html
index df065bf9..b72d3a2a 100644
--- a/app/frontend/templates/panel/panel_edit_role.html
+++ b/app/frontend/templates/panel/panel_edit_role.html
@@ -428,10 +428,13 @@
if (responseData.status === "ok") {
window.location.href = "/panel/panel_config";
} else {
-
+ let errordata = responseData.error;
+ if (responseData.error_data){
+ errordata = responseData.error
+ }
bootbox.alert({
title: responseData.error,
- message: responseData.error_data
+ message: errordata
});
}
});
diff --git a/app/frontend/templates/panel/panel_edit_user.html b/app/frontend/templates/panel/panel_edit_user.html
index 87631219..fdb5afd8 100644
--- a/app/frontend/templates/panel/panel_edit_user.html
+++ b/app/frontend/templates/panel/panel_edit_user.html
@@ -122,7 +122,7 @@ data['lang']) }}{% end %}
name="lang" form="user_form">
{% for lang in data['languages'] %}
{% if not 'incomplete' in lang %}
-
+
{% else %}
{% end %}
@@ -393,6 +393,7 @@ data['lang']) }}{% end %}
}
function replacer(key, value) {
if (typeof value == "boolean" || key === "email" || key === "permissions" || key === "roles") {
+ console.log(key)
return value
} else {
console.log(key, value)
@@ -433,6 +434,7 @@ data['lang']) }}{% end %}
let disabled_flag = false;
let roles = null;
if (superuser || userId != edit_id){
+ console.log("ROLES")
roles = $('.role_check').map(function() {
if ($(this).attr("disabled")){
disabled_flag = true;
@@ -457,9 +459,7 @@ data['lang']) }}{% end %}
delete formDataObject.username
}
if (superuser || userId != edit_id){
- if (!disabled_flag){
formDataObject.roles = roles;
- }
if ($("#permissions").length){
formDataObject.permissions = permissions;
}
diff --git a/app/migrations/20211120221511_api_keys.py b/app/migrations/20211120221511_api_keys.py
index bede2c92..f5dd1e46 100644
--- a/app/migrations/20211120221511_api_keys.py
+++ b/app/migrations/20211120221511_api_keys.py
@@ -1,10 +1,11 @@
import peewee
import datetime
+from app.classes.shared.helpers import Helpers
def migrate(migrator, database, **kwargs):
migrator.add_columns(
- "users", valid_tokens_from=peewee.DateTimeField(default=datetime.datetime.now)
+ "users", valid_tokens_from=peewee.DateTimeField(default=Helpers.get_utc_now)
)
migrator.drop_columns("users", ["api_token"])
diff --git a/app/translations/cs_CS.json b/app/translations/cs_CS.json
index 839eabb9..142593df 100644
--- a/app/translations/cs_CS.json
+++ b/app/translations/cs_CS.json
@@ -672,6 +672,9 @@
"userTheme": "Motiv UI",
"uses": "Počet povolených použití (-1==bez omezení)"
},
+ "validators": {
+ "passLength": "Heslo je příliš krátké. Minimální délka je 8 znaků"
+ },
"webhooks": {
"areYouSureDel": "Seš si jistý že chceš smazat tento webhook?",
"areYouSureRun": "Seš si jistý že chceš otestovat tento webhook?",
diff --git a/app/translations/de_DE.json b/app/translations/de_DE.json
index 937db853..41242532 100644
--- a/app/translations/de_DE.json
+++ b/app/translations/de_DE.json
@@ -653,6 +653,9 @@
"userTheme": "Design für die Benutzeroberfläche",
"uses": "Anzahl der erlaubten Verwendungen (-1==Keine Begrenzung)"
},
+ "validators": {
+ "passLength": "Passwort zu kurz. Mindestlänge: 8"
+ },
"webhooks": {
"areYouSureDel": "Sind Sie sicher, dass Sie diesen Webhook löschen möchten?",
"areYouSureRun": "Sind Sie sicher, dass Sie diesen Webhook testen möchten?",
diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json
index af6b1d36..5a82408a 100644
--- a/app/translations/en_EN.json
+++ b/app/translations/en_EN.json
@@ -649,6 +649,9 @@
"userTheme": "UI Theme",
"uses": "Number of uses allowed (-1==No Limit)"
},
+ "validators": {
+ "passLength": "Password Too Short. Minimum Length: 8"
+ },
"webhooks": {
"areYouSureDel": "Are you sure you want to delete this webhook?",
"areYouSureRun": "Are you sure you want to test this webhook?",
diff --git a/app/translations/es_ES.json b/app/translations/es_ES.json
index a0a079c5..c098dff1 100644
--- a/app/translations/es_ES.json
+++ b/app/translations/es_ES.json
@@ -653,6 +653,9 @@
"userTheme": "Tema de Interfaz",
"uses": "Número de usos permitidos. (Sin límite: -1)"
},
+ "validators": {
+ "passLength": "Contraseña demasiado corta. Longitud mínima: 8"
+ },
"webhooks": {
"areYouSureDel": "¿Estás seguro de que quieres eliminar este webhook?",
"areYouSureRun": "¿Estás seguro de que quieres probar este webhook?",
diff --git a/app/translations/fr_FR.json b/app/translations/fr_FR.json
index 092536ee..07074ea3 100644
--- a/app/translations/fr_FR.json
+++ b/app/translations/fr_FR.json
@@ -653,6 +653,9 @@
"userTheme": "Theme d'Interface Utilisateur",
"uses": "Nombre d'utilisation Authorisé (-1 == Illimité)"
},
+ "validators": {
+ "passLength": "Mot de passe trop court. Longueur minimum : 8"
+ },
"webhooks": {
"areYouSureDel": "Es-tu sûr de vouloir supprimer ce webhook ?",
"areYouSureRun": "Es-tu sûr de vouloir tester ce webhook ?",
diff --git a/app/translations/he_IL.json b/app/translations/he_IL_incomplete.json
similarity index 100%
rename from app/translations/he_IL.json
rename to app/translations/he_IL_incomplete.json
diff --git a/app/translations/humanized_index.json b/app/translations/humanized_index.json
new file mode 100644
index 00000000..6f2ea8c7
--- /dev/null
+++ b/app/translations/humanized_index.json
@@ -0,0 +1,19 @@
+{
+ "language": {
+ "cs_CS": "Čeština",
+ "de_DE": "Deutsch",
+ "en_EN": "English (US)",
+ "es_ES": "Español",
+ "fr_FR": "Français (France)",
+ "he_IL": "he_IL",
+ "it_IT": "Italiano",
+ "lol_EN": "Lolcatz",
+ "lv_LV": "Latviešu",
+ "nl_BE": "nl_BE",
+ "pl_PL": "Polski",
+ "th_TH": "ไทย",
+ "tr_TR": "Türkçe",
+ "uk_UA": "Українська",
+ "zh_CN": "中文(中国)"
+ }
+}
diff --git a/app/translations/it_IT.json b/app/translations/it_IT.json
index 7aec26b3..b59971b5 100644
--- a/app/translations/it_IT.json
+++ b/app/translations/it_IT.json
@@ -653,6 +653,9 @@
"userTheme": "Tema IU",
"uses": "Numero di usi permessi (-1==Nessun limite)"
},
+ "validators": {
+ "passLength": "La password è troppo corta. Lunghezza minima: 8"
+ },
"webhooks": {
"areYouSureDel": "Sei sicuro di voler eliminare questo webhook?",
"areYouSureRun": "Sei sicuro di voler testare questo webhook?",
diff --git a/app/translations/lol_EN.json b/app/translations/lol_EN.json
index 282ffb36..d6075623 100644
--- a/app/translations/lol_EN.json
+++ b/app/translations/lol_EN.json
@@ -653,6 +653,9 @@
"userTheme": "THEMEZ",
"uses": "NUMBER OV USES ALLOWED (-1==NO LIMIT)"
},
+ "validators": {
+ "passLength": "PASSWRD TOO SMOL. NEEDZ 8 CATZ PLZ"
+ },
"webhooks": {
"areYouSureDel": "U SURE U WANTZ TO EATZ DIS WEBHOOK?",
"areYouSureRun": "U SURE U WANTZ TO TESTZ DIS WEBHOOK?",
diff --git a/app/translations/lv_LV.json b/app/translations/lv_LV.json
index 6e1f1397..0ac11432 100644
--- a/app/translations/lv_LV.json
+++ b/app/translations/lv_LV.json
@@ -654,6 +654,9 @@
"userTheme": "UI Tēma",
"uses": "Dauzums, cik reizes lietot (-1==Bez Limita)"
},
+ "validators": {
+ "passLength": "Parole pārāk īsa. Minimālais Garums: 8"
+ },
"webhooks": {
"areYouSureDel": "Vai tiešām vēlies noņemt šo webhook?",
"areYouSureRun": "Vai tiešām vēlies testēt šo webhook?",
diff --git a/app/translations/nl_BE.json b/app/translations/nl_BE.json
index 0abef0b4..4d3644b8 100644
--- a/app/translations/nl_BE.json
+++ b/app/translations/nl_BE.json
@@ -653,6 +653,9 @@
"userTheme": "UI-thema",
"uses": "Aantal toegestane gebruiken (-1==Geen Limiet)"
},
+ "validators": {
+ "passLength": "Wachtwoord te kort. Minimumlengte: 8 tekens"
+ },
"webhooks": {
"areYouSureDel": "Weet u zeker dat u deze webhook wilt verwijderen?",
"areYouSureRun": "Weet u zeker dat u deze webhook wilt testen?",
diff --git a/app/translations/pl_PL.json b/app/translations/pl_PL.json
index 7385db3f..7115b869 100644
--- a/app/translations/pl_PL.json
+++ b/app/translations/pl_PL.json
@@ -652,6 +652,9 @@
"userTheme": "Wygląd interfejsu",
"uses": "Ilość użyć (-1==Bez limitu)"
},
+ "validators": {
+ "passLength": "Hasło jest zbyt krótkie. Hasło musi posiadać minimum 8 znaków."
+ },
"webhooks": {
"areYouSureDel": "Usunąć ten webhook?",
"areYouSureRun": "Przetestować ten webhook?",
diff --git a/app/translations/th_TH.json b/app/translations/th_TH.json
index 12c24fa7..88b97435 100644
--- a/app/translations/th_TH.json
+++ b/app/translations/th_TH.json
@@ -652,6 +652,9 @@
"userTheme": "ธีม UI",
"uses": "จำนวนการใช้งานที่อนุญาต (-1==ไม่มีขีดจำกัด)"
},
+ "validators": {
+ "passLength": "รหัสผ่านสั้นเกินไป จำนวนตัวอักขระขั้นต่ำ: 8"
+ },
"webhooks": {
"areYouSureDel": "คุณแน่ใจหรือไม่ว่าต้องการลบ Webhook นี้?",
"areYouSureRun": "คุณแน่ใจหรือไม่ว่าต้องการทดสอบ Webhook นี้?",
diff --git a/app/translations/tr_TR.json b/app/translations/tr_TR.json
index 709f9e43..ccdf7414 100644
--- a/app/translations/tr_TR.json
+++ b/app/translations/tr_TR.json
@@ -652,6 +652,9 @@
"userTheme": "UI Teması",
"uses": "İzin verilen kullanım sayısı (-1==Sınır Yok)"
},
+ "validators": {
+ "passLength": "Şifre çok kısa. Şifre en az 8 karakter olmalı."
+ },
"webhooks": {
"areYouSureDel": "Bu webhooku silmek istediğinizden emin misiniz?",
"areYouSureRun": "Bu webhooku test etmek istediğinizden emin misiniz?",
diff --git a/app/translations/uk_UA.json b/app/translations/uk_UA.json
index 792b6f56..a12e2e40 100644
--- a/app/translations/uk_UA.json
+++ b/app/translations/uk_UA.json
@@ -652,6 +652,9 @@
"userTheme": "Тема інтерфейсу",
"uses": "Дозволена кількість використань(-1==Без ліміту)"
},
+ "validators": {
+ "passLength": "Пароль, надто короткий. Мінімальна довжина: 8 символів"
+ },
"webhooks": {
"areYouSureDel": "Ви впевнені, що хочете видалити цей Вебхук?",
"areYouSureRun": "Ви впевнені, що хочете перевірити цей Вебхук?",
diff --git a/app/translations/zh_CN.json b/app/translations/zh_CN.json
index 95e71d70..e30ecef6 100644
--- a/app/translations/zh_CN.json
+++ b/app/translations/zh_CN.json
@@ -653,6 +653,9 @@
"userTheme": "UI 主题",
"uses": "使用次数限制(-1==无限制)"
},
+ "validators": {
+ "passLength": "密码过短。最短长度:8"
+ },
"webhooks": {
"areYouSureDel": "您确定要删除此 webhook 吗?",
"areYouSureRun": "您确定要测试此 webhook 吗?",
diff --git a/requirements.txt b/requirements.txt
index ed0f7698..2ca0ff8b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -13,9 +13,9 @@ psutil==5.9.5
pyOpenSSL==24.0.0
pyjwt==2.8.0
PyYAML==6.0.1
-requests==2.31.0
+requests==2.32.0
termcolor==1.1
-tornado==6.3.3
+tornado==6.4.1
tzlocal==5.1
jsonschema==4.19.1
orjson==3.9.15