diff --git a/plane-app/docker-compose.yaml b/plane-app/docker-compose.yaml index 60517af..6395033 100644 --- a/plane-app/docker-compose.yaml +++ b/plane-app/docker-compose.yaml @@ -48,9 +48,9 @@ x-live-env: &live-env LIVE_SERVER_SECRET_KEY: ${LIVE_SERVER_SECRET_KEY:-2FiJk1U2aiVPEQtzLehYGlTSnTnrs7LW} x-app-env: &app-env - WEB_URL: ${WEB_URL:-http://localhost} + WEB_URL: ${WEB_URL:-http://localhost:8090} DEBUG: ${DEBUG:-0} - CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS} + CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:8090} GUNICORN_WORKERS: 1 POSTHOG_API_KEY: ${POSTHOG_API_KEY:-} POSTHOG_HOST: ${POSTHOG_HOST:-} diff --git a/plane-src/apps/api/plane/app/urls/user.py b/plane-src/apps/api/plane/app/urls/user.py index bc110a2..ebc2ea5 100644 --- a/plane-src/apps/api/plane/app/urls/user.py +++ b/plane-src/apps/api/plane/app/urls/user.py @@ -44,6 +44,11 @@ urlpatterns = [ UserEndpoint.as_view({"patch": "update_email"}), name="user-email-update", ), + path( + "users/me/email/direct/", + UserEndpoint.as_view({"patch": "update_email_without_verification"}), + name="user-email-direct-update", + ), # Profile path("users/me/profile/", ProfileEndpoint.as_view(), name="accounts"), # End profile diff --git a/plane-src/apps/api/plane/app/views/user/base.py b/plane-src/apps/api/plane/app/views/user/base.py index 914dffb..cd58d74 100644 --- a/plane-src/apps/api/plane/app/views/user/base.py +++ b/plane-src/apps/api/plane/app/views/user/base.py @@ -3,6 +3,7 @@ # See the LICENSE file for details. # Python imports +import os import uuid import json import logging @@ -50,6 +51,7 @@ from plane.bgtasks.user_deactivation_email_task import user_deactivation_email from plane.utils.host import base_host from plane.bgtasks.user_email_update_task import send_email_update_magic_code, send_email_update_confirmation from plane.authentication.rate_limit import EmailVerificationThrottle +from plane.license.utils.instance_value import get_configuration_value logger = logging.getLogger("plane") @@ -133,6 +135,10 @@ class UserEndpoint(BaseViewSet): return None + def _is_smtp_configured(self): + (email_host,) = get_configuration_value([{"key": "EMAIL_HOST", "default": os.environ.get("EMAIL_HOST", "")}]) + return bool(email_host) + def generate_email_verification_code(self, request): """ Generate and send a magic code to the new email address for verification. @@ -248,6 +254,33 @@ class UserEndpoint(BaseViewSet): serialized_data = UserMeSerializer(user).data return Response(serialized_data, status=status.HTTP_200_OK) + def update_email_without_verification(self, request): + """ + Update the current user's email when the instance has no SMTP configured. + Verified SMTP-backed installations must continue using the magic-code flow. + """ + if self._is_smtp_configured(): + return Response( + {"error": "Email verification is required when SMTP is configured"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + user = self.get_object() + new_email = request.data.get("email", "").strip().lower() + + validation_error = self._validate_new_email(user, new_email) + if validation_error: + return validation_error + + user.email = new_email + user.is_email_verified = False + user.save() + + logout(request) + + serialized_data = UserMeSerializer(user).data + return Response(serialized_data, status=status.HTTP_200_OK) + def deactivate(self, request): # Check all workspace user is active user = self.get_object() diff --git a/plane-src/apps/web/ce/components/projects/external-contours/board-item.tsx b/plane-src/apps/web/ce/components/projects/external-contours/board-item.tsx index 84ba33b..4be5db9 100644 --- a/plane-src/apps/web/ce/components/projects/external-contours/board-item.tsx +++ b/plane-src/apps/web/ce/components/projects/external-contours/board-item.tsx @@ -219,6 +219,7 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
{changeEmailT("description")}
++ {isSMTPConfigured ? changeEmailT("description") : changeEmailT("direct_description")} +