diff --git a/engine/core/management/commands/initialize.py b/engine/core/management/commands/initialize.py index 909166fb..ca94dde7 100644 --- a/engine/core/management/commands/initialize.py +++ b/engine/core/management/commands/initialize.py @@ -1,11 +1,13 @@ import logging from typing import Any +from django.conf import settings from django.contrib.auth.models import Permission from django.core.management.base import BaseCommand +from django.db.models import Q from engine.core.models import Vendor -from engine.vibes_auth.models import Group +from engine.vibes_auth.models import Group, User logger = logging.getLogger(__name__) @@ -155,4 +157,7 @@ class Command(BaseCommand): perms = Permission.objects.filter(codename__in=e_commerce_admin_permissions) e_commerce_admin.permissions.add(*perms) + valid_codes = [code for code, _ in settings.LANGUAGES] + (User.objects.filter(Q(language="") | ~Q(language__in=valid_codes)).update(language=settings.LANGUAGE_CODE)) + self.stdout.write(self.style.SUCCESS("Successfully initialized must-have instances!")) diff --git a/engine/core/utils/lists.py b/engine/core/utils/lists.py index d97b638c..e21086e2 100644 --- a/engine/core/utils/lists.py +++ b/engine/core/utils/lists.py @@ -16,5 +16,7 @@ BAD_KEYS_TO_LISTEN = [ "is_staff", "is_superuser", "is_active", - "active", + "is_verified", + "groups", + "user_permissions", ] diff --git a/engine/vibes_auth/graphene/mutations.py b/engine/vibes_auth/graphene/mutations.py index 11274918..eac24e6d 100644 --- a/engine/vibes_auth/graphene/mutations.py +++ b/engine/vibes_auth/graphene/mutations.py @@ -17,6 +17,7 @@ from graphene_file_upload.scalars import Upload from engine.core.graphene import BaseMutation from engine.core.utils.messages import permission_denied_message +from engine.core.utils.security import is_safe_key from engine.vibes_auth.graphene.object_types import UserType from engine.vibes_auth.models import User from engine.vibes_auth.serializers import ( @@ -107,65 +108,62 @@ class UpdateUser(BaseMutation): try: user = User.objects.get(uuid=uuid) + if not (info.context.user.has_perm("vibes_auth.change_user") or info.context.user == user): + raise PermissionDenied(permission_denied_message) + + email = kwargs.get("email") + + if (email is not None and not is_valid_email(email)) or User.objects.filter(email=email).exclude( + uuid=uuid + ).exists(): + raise BadRequest(_("malformed email")) + + phone_number = kwargs.get("phone_number") + + if (phone_number is not None and not is_valid_phone_number(phone_number)) or ( + User.objects.filter(phone_number=phone_number).exclude(uuid=uuid).exists() and phone_number is not None + ): + raise BadRequest(_(f"malformed phone number: {phone_number}")) + + password = kwargs.get("password", "") + confirm_password = kwargs.get("confirm_password", "") + + if password: + validate_password(password=password, user=user) + + if not compare_digest(password, "") and compare_digest(password, confirm_password): + user.set_password(password) + user.save() + + attribute_pairs = kwargs.pop("attributes", "") + + if attribute_pairs: + for attribute_pair in attribute_pairs.split(";"): + if "-" in attribute_pair: + attr, value = attribute_pair.split("-", 1) + if not user.attributes: + user.attributes = {} + user.attributes.update({attr: value}) + else: + raise BadRequest(_(f"Invalid attribute format: {attribute_pair}")) + + for attr, value in kwargs.items(): + if attr == "password" or attr == "confirm_password": + continue + if is_safe_key(attr) or info.context.user.has_perm("vibes_auth.change_user"): + setattr(user, attr, value) + + user.save() + + return UpdateUser(user=user) + except User.DoesNotExist as dne: name = "User" raise Http404(_(f"{name} does not exist: {uuid}")) from dne - - if not (info.context.user.has_perm("vibes_auth.change_user") or info.context.user == user): - raise PermissionDenied(permission_denied_message) - - email = kwargs.get("email") - - if (email is not None and not is_valid_email(email)) or User.objects.filter(email=email).exclude( - uuid=uuid - ).exists(): - raise BadRequest(_("malformed email")) - - phone_number = kwargs.get("phone_number") - - if (phone_number is not None and not is_valid_phone_number(phone_number)) or ( - User.objects.filter(phone_number=phone_number).exclude(uuid=uuid).exists() and phone_number is not None - ): - raise BadRequest(_(f"malformed phone number: {phone_number}")) - - password = kwargs.get("password", "") - confirm_password = kwargs.get("confirm_password", "") - - if password: - validate_password(password=password, user=user) - - if not compare_digest(password, "") and compare_digest(password, confirm_password): - user.set_password(password) - user.save() - - attribute_pairs = kwargs.pop("attributes", "") - - if attribute_pairs: - for attribute_pair in attribute_pairs.split(";"): - if "-" in attribute_pair: - attr, value = attribute_pair.split("-", 1) - if not user.attributes: - user.attributes = {} - user.attributes.update({attr: value}) - else: - raise BadRequest(_(f"Invalid attribute format: {attribute_pair}")) - - for attr, value in kwargs.items(): - if attr == "password" or attr == "confirm_password": - continue - if attr not in [ - "groups", - "user_permissions", - "is_verified", - "is_staff", - "is_active", - "is_superuser", - ] or info.context.user.has_perm("vibes_auth.change_user"): - setattr(user, attr, value) - - user.save() - - return UpdateUser(user=user) + except Exception as e: + logger.warning("Could not update user: %s", str(e)) + logger.debug(traceback.format_exc()) + raise BadRequest(str(e)) from e class DeleteUser(BaseMutation): diff --git a/uv.lock b/uv.lock index 7590d93f..3f077c8a 100644 --- a/uv.lock +++ b/uv.lock @@ -215,11 +215,11 @@ wheels = [ [[package]] name = "asgiref" -version = "3.10.0" +version = "3.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/08/4dfec9b90758a59acc6be32ac82e98d1fbfc321cb5cfa410436dbacf821c/asgiref-3.10.0.tar.gz", hash = "sha256:d89f2d8cd8b56dada7d52fa7dc8075baa08fb836560710d38c292a7a3f78c04e", size = 37483, upload-time = "2025-10-05T09:15:06.557Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/b9/4db2509eabd14b4a8c71d1b24c8d5734c52b8560a7b1e1a8b56c8d25568b/asgiref-3.11.0.tar.gz", hash = "sha256:13acff32519542a1736223fb79a715acdebe24286d98e8b164a73085f40da2c4", size = 37969, upload-time = "2025-11-19T15:32:20.106Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/9c/fc2331f538fbf7eedba64b2052e99ccf9ba9d6888e2f41441ee28847004b/asgiref-3.10.0-py3-none-any.whl", hash = "sha256:aef8a81283a34d0ab31630c9b7dfe70c812c95eba78171367ca8745e88124734", size = 24050, upload-time = "2025-10-05T09:15:05.11Z" }, + { url = "https://files.pythonhosted.org/packages/91/be/317c2c55b8bbec407257d45f5c8d1b6867abc76d12043f2d3d58c538a4ea/asgiref-3.11.0-py3-none-any.whl", hash = "sha256:1db9021efadb0d9512ce8ffaf72fcef601c7b73a8807a1bb2ef143dc6b14846d", size = 24096, upload-time = "2025-11-19T15:32:19.004Z" }, ] [[package]] @@ -1076,14 +1076,14 @@ wheels = [ [[package]] name = "django-unfold" -version = "0.71.0" +version = "0.72.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "django" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ec/5b/406eae1a429b15ba04f4dfaaf53aa64fb03bcfdc6bdd0753a41027aa3daa/django_unfold-0.71.0.tar.gz", hash = "sha256:995a296f1c15f172b0d8458ff12beb420ea7e9a666fa865a60ec03f70aaf4066", size = 1101347, upload-time = "2025-11-11T16:24:03.289Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/e6/045b68207d8b4b395623d39d803e6566ea2c110e56665e3ff6bda07de6aa/django_unfold-0.72.0.tar.gz", hash = "sha256:43a0e8a4383037a24b73666c9f721faef12bd500c4628b4fc39d0dafd2e9c0a2", size = 1102339, upload-time = "2025-11-24T09:03:47.347Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/94/ad8ba84410655e0207ffcc7c6cba0875e9f79f914e3f2e2de883f706f7c9/django_unfold-0.71.0-py3-none-any.whl", hash = "sha256:76d4019aa9052ebe2e040d868be895d8581018fdf7debca943084aa0e79c2e31", size = 1213722, upload-time = "2025-11-11T16:24:01.985Z" }, + { url = "https://files.pythonhosted.org/packages/2d/dd/6cdb80d5d377f2bdf3b39b639025bb8655b96fac08aa3673ae1e4b3e3384/django_unfold-0.72.0-py3-none-any.whl", hash = "sha256:61448ad42ff7a33c7ad66d14071b24224bb476038a14a1bbe719a774db496e34", size = 1215396, upload-time = "2025-11-24T09:03:45.568Z" }, ] [[package]] @@ -2161,7 +2161,7 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.4.10" +version = "4.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, @@ -2178,9 +2178,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/5d/75c42a48ff5fc826a7dff3fe4004cda47c54f9d981c351efacfbc9139d3c/jupyterlab-4.4.10.tar.gz", hash = "sha256:521c017508af4e1d6d9d8a9d90f47a11c61197ad63b2178342489de42540a615", size = 22969303, upload-time = "2025-10-22T14:50:58.768Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/e5/4fa382a796a6d8e2cd867816b64f1ff27f906e43a7a83ad9eb389e448cd8/jupyterlab-4.5.0.tar.gz", hash = "sha256:aec33d6d8f1225b495ee2cf20f0514f45e6df8e360bdd7ac9bace0b7ac5177ea", size = 23989880, upload-time = "2025-11-18T13:19:00.365Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/46/1eaa5db8d54a594bdade67afbcae42e9a2da676628be3eb39f36dcff6390/jupyterlab-4.4.10-py3-none-any.whl", hash = "sha256:65939ab4c8dcd0c42185c2d0d1a9d60b254dc8c46fc4fdb286b63c51e9358e07", size = 12293385, upload-time = "2025-10-22T14:50:54.075Z" }, + { url = "https://files.pythonhosted.org/packages/6c/1e/5a4d5498eba382fee667ed797cf64ae5d1b13b04356df62f067f48bb0f61/jupyterlab-4.5.0-py3-none-any.whl", hash = "sha256:88e157c75c1afff64c7dc4b801ec471450b922a4eae4305211ddd40da8201c8a", size = 12380641, upload-time = "2025-11-18T13:18:56.252Z" }, ] [[package]] @@ -2573,7 +2573,7 @@ wheels = [ [[package]] name = "notebook" -version = "7.4.7" +version = "7.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jupyter-server" }, @@ -2582,9 +2582,9 @@ dependencies = [ { name = "notebook-shim" }, { name = "tornado" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/04/09/f6f64ba156842ef68d3ea763fa171a2f7e7224f200a15dd4af5b83c34756/notebook-7.4.7.tar.gz", hash = "sha256:3f0a04027dfcee8a876de48fba13ab77ec8c12f72f848a222ed7f5081b9e342a", size = 13937702, upload-time = "2025-09-27T08:00:22.536Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/ac/a97041621250a4fc5af379fb377942841eea2ca146aab166b8fcdfba96c2/notebook-7.5.0.tar.gz", hash = "sha256:3b27eaf9913033c28dde92d02139414c608992e1df4b969c843219acf2ff95e4", size = 14052074, upload-time = "2025-11-19T08:36:20.093Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/d7/06d13087e20388926e7423d2489e728d2e59f2453039cdb0574a7c070e76/notebook-7.4.7-py3-none-any.whl", hash = "sha256:362b7c95527f7dd3c4c84d410b782872fd9c734fb2524c11dd92758527b6eda6", size = 14342894, upload-time = "2025-09-27T08:00:18.496Z" }, + { url = "https://files.pythonhosted.org/packages/73/96/00df2a4760f10f5af0f45c4955573cae6189931f9a30265a35865f8c1031/notebook-7.5.0-py3-none-any.whl", hash = "sha256:3300262d52905ca271bd50b22617681d95f08a8360d099e097726e6d2efb5811", size = 14460968, upload-time = "2025-11-19T08:36:15.869Z" }, ] [[package]] @@ -3800,14 +3800,14 @@ wheels = [ [[package]] name = "tinycss2" -version = "1.4.0" +version = "1.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/ae/2ca4913e5c0f09781d75482874c3a95db9105462a92ddd303c7d285d3df2/tinycss2-1.5.1.tar.gz", hash = "sha256:d339d2b616ba90ccce58da8495a78f46e55d4d25f9fd71dfd526f07e7d53f957", size = 88195, upload-time = "2025-11-23T10:29:10.082Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, + { url = "https://files.pythonhosted.org/packages/60/45/c7b5c3168458db837e8ceab06dc77824e18202679d0463f0e8f002143a97/tinycss2-1.5.1-py3-none-any.whl", hash = "sha256:3415ba0f5839c062696996998176c4a3751d18b7edaaeeb658c9ce21ec150661", size = 28404, upload-time = "2025-11-23T10:29:08.676Z" }, ] [[package]]