fix(demo): block external account actions
Some checks failed
Backend CI/CD / test (push) Has been cancelled
Backend CI/CD / deploy (push) Has been cancelled

This commit is contained in:
2026-06-07 00:50:42 +03:30
parent 30a324c6f4
commit 170ec90ec1
4 changed files with 47 additions and 3 deletions

View File

@@ -212,10 +212,12 @@ class UserProfileSerializer(BaseModelSerializer):
"profile_picture", "profile_picture",
"birth_date", "birth_date",
"is_verified", "is_verified",
"is_demo",
"demo_expires_at",
"full_name", "full_name",
"age", "age",
) )
read_only_fields = BaseModelSerializer.Meta.fields + ("mobile", "is_verified") read_only_fields = BaseModelSerializer.Meta.fields + ("mobile", "is_verified", "is_demo", "demo_expires_at")
class UserSearchSerializer(serializers.ModelSerializer): class UserSearchSerializer(serializers.ModelSerializer):

View File

@@ -293,6 +293,11 @@ class ChangePasswordView(APIView):
@extend_schema(request=ChangePasswordSerializer) @extend_schema(request=ChangePasswordSerializer)
def patch(self, request, *args, **kwargs): def patch(self, request, *args, **kwargs):
if getattr(request.user, "is_demo", False):
return Response(
{"detail": "Demo accounts cannot change passwords."},
status=status.HTTP_403_FORBIDDEN,
)
serializer = ChangePasswordSerializer(data=request.data, context={"request": request}) serializer = ChangePasswordSerializer(data=request.data, context={"request": request})
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
@@ -327,6 +332,11 @@ class SetPasswordView(UpdateAPIView):
@extend_schema(request=ChangePasswordSerializer, responses=None) @extend_schema(request=ChangePasswordSerializer, responses=None)
def patch(self, request, *args, **kwargs): def patch(self, request, *args, **kwargs):
if getattr(request.user, "is_demo", False):
return Response(
{"detail": "Demo accounts cannot change passwords."},
status=status.HTTP_403_FORBIDDEN,
)
return super().patch(request, *args, **kwargs) return super().patch(request, *args, **kwargs)
def get_object(self): def get_object(self):
@@ -347,6 +357,11 @@ class ProfilePictureView(APIView):
operation_id="users_profile_picture_self_create", operation_id="users_profile_picture_self_create",
) )
def post(self, request): def post(self, request):
if getattr(request.user, "is_demo", False):
return Response(
{"detail": "Demo accounts cannot upload profile pictures."},
status=status.HTTP_403_FORBIDDEN,
)
serializer = UserProfilePictureSerializer( serializer = UserProfilePictureSerializer(
instance=request.user, instance=request.user,
data=request.data, data=request.data,
@@ -362,6 +377,11 @@ class ProfilePictureView(APIView):
operation_id="users_profile_picture_self_delete", operation_id="users_profile_picture_self_delete",
) )
def delete(self, request): def delete(self, request):
if getattr(request.user, "is_demo", False):
return Response(
{"detail": "Demo accounts cannot remove profile pictures."},
status=status.HTTP_403_FORBIDDEN,
)
request.user.profile_picture.delete(save=False) request.user.profile_picture.delete(save=False)
request.user.profile_picture = None request.user.profile_picture = None
request.user.save(update_fields=["profile_picture", "updated_at"]) request.user.save(update_fields=["profile_picture", "updated_at"])
@@ -401,6 +421,11 @@ class UserSearchAPIView(APIView):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def get(self, request): def get(self, request):
if getattr(request.user, "is_demo", False):
return Response(
{"detail": "Demo accounts cannot search external users."},
status=status.HTTP_403_FORBIDDEN,
)
mobile = request.query_params.get('mobile') mobile = request.query_params.get('mobile')
if not mobile: if not mobile:
return Response( return Response(

View File

@@ -81,7 +81,11 @@ def register_user_with_otp(mobile, code, password, first_name="", last_name=""):
def generate_and_send_otp(mobile, mode): def generate_and_send_otp(mobile, mode):
"""Business logic for generating OTP, checking existence rules, and sending SMS.""" """Business logic for generating OTP, checking existence rules, and sending SMS."""
user_exists = User.objects.filter(mobile=mobile).exists() user = User.objects.filter(mobile=mobile).only("is_demo").first()
user_exists = user is not None
if user and user.is_demo:
raise ValidationError({"mobile": "Demo accounts cannot use SMS verification."})
if mode == "register" and user_exists: if mode == "register" and user_exists:
raise ValidationError({"mobile": "این شماره قبلاً ثبت‌نام شده است."}) raise ValidationError({"mobile": "این شماره قبلاً ثبت‌نام شده است."})

View File

@@ -94,6 +94,14 @@ class WorkspaceViewSet(ModelViewSet):
def perform_create(self, serializer): def perform_create(self, serializer):
serializer.save(owner=self.request.user) serializer.save(owner=self.request.user)
def create(self, request, *args, **kwargs):
if getattr(request.user, "is_demo", False):
return Response(
{"detail": "Demo accounts cannot create additional workspaces."},
status=status.HTTP_403_FORBIDDEN,
)
return super().create(request, *args, **kwargs)
@action(detail=True, methods=["get"], url_path="my-rates") @action(detail=True, methods=["get"], url_path="my-rates")
def my_rates(self, request, pk=None): def my_rates(self, request, pk=None):
workspace = self.get_object() workspace = self.get_object()
@@ -247,6 +255,11 @@ class WorkspaceMembershipViewSet(ModelViewSet):
) )
workspace = get_object_or_404(Workspace, id=workspace_id) workspace = get_object_or_404(Workspace, id=workspace_id)
if getattr(request.user, "is_demo", False):
return Response(
{"detail": "Demo accounts cannot add workspace members."},
status=status.HTTP_403_FORBIDDEN,
)
permission = IsWorkspaceAdmin() permission = IsWorkspaceAdmin()
if not permission.has_object_permission(request, self, workspace): if not permission.has_object_permission(request, self, workspace):