diff --git a/CHANGELOG.md b/CHANGELOG.md
index 97c4cf70..d110e9d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,10 +10,11 @@ TBD
- Do not allow users at server limit to clone servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/718))
- Fix bug where you cannot get to config with unloaded server ([Commit](https://gitlab.com/crafty-controller/crafty-4/-/commit/9de08973b6bb2ddf91283c5c6b0e189ff34f7e24))
- Fix forge install v1.20, 1.20.1 and 1.20.2 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/710))
+- Fix Sanitisation on Passwords ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/715))
### Tweaks
- Bump pyOpenSSL & cryptography for CVE-2024-0727, CVE-2023-50782 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/716))
### Lang
-TBD
+- Update `de_DE, en_EN, es_ES, fr_FR, he_IL, lol_EN, lv_LV, nl_BE pl_PL, th_TH, tr_TR, uk_UA, zh_CN` translations for `4.3.0` ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/715))
## --- [4.2.3] - 2023/02/02
diff --git a/app/classes/controllers/users_controller.py b/app/classes/controllers/users_controller.py
index 5c6dd3d2..5425fbf8 100644
--- a/app/classes/controllers/users_controller.py
+++ b/app/classes/controllers/users_controller.py
@@ -52,7 +52,7 @@ class UsersController:
},
"password": {
"type": "string",
- "minLength": 8,
+ "minLength": self.helper.minimum_password_length,
"examples": ["crafty"],
"title": "Password",
},
diff --git a/app/classes/shared/command.py b/app/classes/shared/command.py
index 155fe083..95a83047 100644
--- a/app/classes/shared/command.py
+++ b/app/classes/shared/command.py
@@ -77,11 +77,11 @@ class MainPrompt(cmd.Cmd):
# get new password from user
new_pass = getpass.getpass(prompt=f"NEW password for: {username} > ")
# check to make sure it fits our requirements.
- if len(new_pass) > 512:
- Console.warning("Passwords must be greater than 6char long and under 512")
- return False
- if len(new_pass) < 6:
- Console.warning("Passwords must be greater than 6char long and under 512")
+ if len(new_pass) < self.helper.minimum_password_length:
+ Console.warning(
+ "Passwords must be greater than"
+ f" {self.helper.minimum_password_length} char long"
+ )
return False
# grab repeated password input
new_pass_conf = getpass.getpass(prompt="Re-enter your password: > ")
diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py
index 1ed3d71f..63538487 100644
--- a/app/classes/shared/helpers.py
+++ b/app/classes/shared/helpers.py
@@ -81,6 +81,7 @@ class Helpers:
self.update_available = False
self.ignored_names = ["crafty_managed.txt", "db_stats"]
self.crafty_starting = False
+ self.minimum_password_length = 8
@staticmethod
def auto_installer_fix(ex):
diff --git a/app/classes/shared/main_models.py b/app/classes/shared/main_models.py
index c166b7fb..0cced56f 100644
--- a/app/classes/shared/main_models.py
+++ b/app/classes/shared/main_models.py
@@ -18,13 +18,20 @@ class DatabaseBuilder:
logger.info("Fresh Install Detected - Creating Default Settings")
Console.info("Fresh Install Detected - Creating Default Settings")
default_data = self.helper.find_default_password()
- if password not in default_data:
+ if "password" not in default_data:
Console.help(
"No default password found. Using password created "
"by Crafty. Find it in app/config/default-creds.txt"
)
username = default_data.get("username", "admin")
- password = default_data.get("password", password)
+ if self.helper.minimum_password_length > default_data.get("password", password):
+ Console.critical(
+ "Default password too short"
+ " using Crafty's created default."
+ " Find it in app/config/default-creds.txt"
+ )
+ else:
+ password = default_data.get("password", password)
self.users_helper.add_user(
username=username,
diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py
index 762d3fb1..467765ea 100644
--- a/app/classes/web/public_handler.py
+++ b/app/classes/web/public_handler.py
@@ -1,5 +1,8 @@
import logging
+import json
import nh3
+from jsonschema import validate
+from jsonschema.exceptions import ValidationError
from app.classes.shared.helpers import Helpers
from app.classes.models.users import HelperUsers
@@ -45,7 +48,7 @@ class PublicHandler(BaseHandler):
}
if self.request.query:
- page_data["query"] = self.request.query
+ page_data["query"] = self.request.query_arguments.get("next")[0].decode()
# sensible defaults
template = "public/404.html"
@@ -75,11 +78,7 @@ class PublicHandler(BaseHandler):
# if we have no page, let's go to login
else:
- if self.request.query:
- self.redirect("/login?" + self.request.query)
- else:
- self.redirect("/login")
- return
+ return self.redirect("/login")
self.render(
template,
@@ -89,33 +88,61 @@ class PublicHandler(BaseHandler):
)
def post(self, page=None):
- # pylint: disable=no-member
- error = nh3.clean(self.get_argument("error", "Invalid Login!"))
- error_msg = nh3.clean(self.get_argument("error_msg", ""))
- # pylint: enable=no-member
+ login_schema = {
+ "type": "object",
+ "properties": {
+ "username": {
+ "type": "string",
+ },
+ "password": {"type": "string"},
+ },
+ "required": ["username", "password"],
+ "additionalProperties": False,
+ }
+ try:
+ data = json.loads(self.request.body)
+ except json.decoder.JSONDecodeError as e:
+ logger.error(
+ "Invalid JSON schema for API"
+ f" login attempt from {self.get_remote_ip()}"
+ )
+ return self.finish_json(
+ 400, {"status": "error", "error": "INVALID_JSON", "error_data": str(e)}
+ )
+
+ try:
+ validate(data, login_schema)
+ except ValidationError as e:
+ logger.error(
+ "Invalid JSON schema for API"
+ f" login attempt from {self.get_remote_ip()}"
+ )
+ return self.finish_json(
+ 400,
+ {
+ "status": "error",
+ "error": "VWggb2ghIFN0aW5reS 🪠",
+ "error_data": str(e),
+ },
+ )
page_data = {
"version": self.helper.get_version_string(),
- "error": error,
"lang": self.helper.get_setting("language"),
"lang_page": self.helper.get_lang_page(self.helper.get_setting("language")),
"query": "",
}
if self.request.query:
- page_data["query"] = self.request.query
+ page_data["query"] = self.request.query_arguments.get("next")[0].decode()
if page == "login":
+ data = json.loads(self.request.body)
+
auth_log.info(
f"User attempting to authenticate from {self.get_remote_ip()}"
)
- next_page = "/login"
- if self.request.query:
- next_page = "/login?" + self.request.query
-
- # pylint: disable=no-member
- entered_username = nh3.clean(self.get_argument("username"))
- entered_password = self.get_argument("password")
- # pylint: enable=no-member
+ entered_username = nh3.clean(data["username"]) # pylint: disable=no-member
+ entered_password = data["password"]
try:
user_id = HelperUsers.get_user_id_by_name(entered_username.lower())
@@ -127,16 +154,18 @@ class PublicHandler(BaseHandler):
f" Authentication failed from remote IP {self.get_remote_ip()}"
" Users does not exist."
)
- error_msg = "Incorrect username or password. Please try again."
+ self.finish_json(
+ 403,
+ {
+ "status": "error",
+ "error": self.helper.translation.translate(
+ "login", "incorrect", self.helper.get_setting("language")
+ ),
+ },
+ )
# self.clear_cookie("user")
# self.clear_cookie("user_data")
- self.clear_cookie("token")
- if self.request.query:
- self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
- else:
- self.redirect(f"/login?error_msg={error_msg}")
- return
-
+ return self.clear_cookie("token")
# if we don't have a user
if not user_data:
auth_log.error(
@@ -145,15 +174,18 @@ class PublicHandler(BaseHandler):
" User does not exist."
)
self.controller.log_attempt(self.get_remote_ip(), entered_username)
- error_msg = "Incorrect username or password. Please try again."
+ self.finish_json(
+ 403,
+ {
+ "status": "error",
+ "error": self.helper.translation.translate(
+ "login", "incorrect", self.helper.get_setting("language")
+ ),
+ },
+ )
# self.clear_cookie("user")
# self.clear_cookie("user_data")
- self.clear_cookie("token")
- if self.request.query:
- self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
- else:
- self.redirect(f"/login?error_msg={error_msg}")
- return
+ return self.clear_cookie("token")
# if they are disabled
if not user_data.enabled:
@@ -163,19 +195,18 @@ class PublicHandler(BaseHandler):
" User account disabled"
)
self.controller.log_attempt(self.get_remote_ip(), entered_username)
- error_msg = (
- "User account disabled. Please contact "
- "your system administrator for more info."
+ self.finish_json(
+ 403,
+ {
+ "status": "error",
+ "error": self.helper.translation.translate(
+ "login", "disabled", self.helper.get_setting("language")
+ ),
+ },
)
# self.clear_cookie("user")
# self.clear_cookie("user_data")
- self.clear_cookie("token")
- if self.request.query:
- self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
- else:
- self.redirect(f"/login?error_msg={error_msg}")
- return
-
+ return self.clear_cookie("token")
login_result = self.helper.verify_pass(entered_password, user_data.password)
# Valid Login
@@ -200,32 +231,34 @@ class PublicHandler(BaseHandler):
user_data.user_id, "Logged in", 0, self.get_remote_ip()
)
- if self.request.query_arguments.get("next"):
- next_page = self.request.query_arguments.get("next")[0].decode()
- else:
- next_page = "/panel/dashboard"
+ return self.finish_json(
+ 200, {"status": "ok", "data": {"message": "login successful!"}}
+ )
- self.redirect(next_page)
- else:
- auth_log.error(
- f"User attempted to log into {entered_username}."
- f" Authentication failed from remote IP {self.get_remote_ip()}"
+ # We'll continue on and handle unsuccessful logins
+ auth_log.error(
+ f"User attempted to log into {entered_username}."
+ f" Authentication failed from remote IP {self.get_remote_ip()}"
+ )
+ self.controller.log_attempt(self.get_remote_ip(), entered_username)
+ # self.clear_cookie("user")
+ # self.clear_cookie("user_data")
+ self.clear_cookie("token")
+ error_msg = self.helper.translation.translate(
+ "login", "incorrect", self.helper.get_setting("language")
+ )
+ if entered_password == "app/config/default-creds.txt":
+ error_msg += ". "
+ error_msg += self.helper.translation.translate(
+ "login", "defaultPath", self.helper.get_setting("language")
)
- self.controller.log_attempt(self.get_remote_ip(), entered_username)
- # self.clear_cookie("user")
- # self.clear_cookie("user_data")
- self.clear_cookie("token")
- error_msg = "Incorrect username or password. Please try again."
- # log this failed login attempt
- self.controller.management.add_to_audit_log(
- user_data.user_id, "Tried to log in", 0, self.get_remote_ip()
- )
- if self.request.query:
- self.redirect(f"/login?error_msg={error_msg}&{self.request.query}")
- else:
- self.redirect(f"/login?error_msg={error_msg}")
+ # log this failed login attempt
+ self.controller.management.add_to_audit_log(
+ user_data.user_id, "Tried to log in", 0, self.get_remote_ip()
+ )
+ return self.finish_json(
+ 403,
+ {"status": "error", "error": error_msg},
+ )
else:
- if self.request.query:
- self.redirect("/login?" + self.request.query)
- else:
- self.redirect("/login")
+ self.redirect("/login?")
diff --git a/app/classes/web/routes/api/auth/login.py b/app/classes/web/routes/api/auth/login.py
index b91b295d..7a27c6f8 100644
--- a/app/classes/web/routes/api/auth/login.py
+++ b/app/classes/web/routes/api/auth/login.py
@@ -17,7 +17,7 @@ login_schema = {
"minLength": 4,
"pattern": "^[a-z0-9_]+$",
},
- "password": {"type": "string", "maxLength": 20, "minLength": 4},
+ "password": {"type": "string", "minLength": 4},
},
"required": ["username", "password"],
"additionalProperties": False,
diff --git a/app/frontend/templates/public/login.html b/app/frontend/templates/public/login.html
index 1b39d8c4..5a54ecca 100644
--- a/app/frontend/templates/public/login.html
+++ b/app/frontend/templates/public/login.html
@@ -77,55 +77,49 @@
box-shadow: 0 12px 16px 0 hsla(0, 0%, 0%, 0.4);
}
- {% if data['query'] %}
-