diff --git a/apps/admin/package.json b/apps/admin/package.json index 07f0b51d39..d2fa5a4c26 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -1,7 +1,7 @@ { "name": "admin", "description": "Admin UI for Plane", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "private": true, "type": "module", diff --git a/apps/api/package.json b/apps/api/package.json index 4767d0cb46..46fb0dd47e 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,6 +1,6 @@ { "name": "plane-api", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "private": true, "description": "API server powering Plane's backend" diff --git a/apps/api/plane/app/serializers/api.py b/apps/api/plane/app/serializers/api.py index 009f7a611f..d14dcacffc 100644 --- a/apps/api/plane/app/serializers/api.py +++ b/apps/api/plane/app/serializers/api.py @@ -15,6 +15,9 @@ class APITokenSerializer(BaseSerializer): "updated_at", "workspace", "user", + "is_active", + "last_used", + "user_type", ] diff --git a/apps/api/plane/app/views/asset/v2.py b/apps/api/plane/app/views/asset/v2.py index c0580c1149..3677537bb1 100644 --- a/apps/api/plane/app/views/asset/v2.py +++ b/apps/api/plane/app/views/asset/v2.py @@ -603,7 +603,7 @@ class ProjectAssetEndpoint(BaseAPIView): @allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST]) def patch(self, request, slug, project_id, pk): # get the asset id - asset = FileAsset.objects.get(id=pk) + asset = FileAsset.objects.get(id=pk, workspace__slug=slug, project_id=project_id) # get the storage metadata asset.is_uploaded = True # get the storage metadata diff --git a/apps/api/plane/app/views/issue/attachment.py b/apps/api/plane/app/views/issue/attachment.py index 7b7ecf378b..2207d24192 100644 --- a/apps/api/plane/app/views/issue/attachment.py +++ b/apps/api/plane/app/views/issue/attachment.py @@ -56,7 +56,14 @@ class IssueAttachmentEndpoint(BaseAPIView): @allow_permission([ROLE.ADMIN], creator=True, model=FileAsset) def delete(self, request, slug, project_id, issue_id, pk): - issue_attachment = FileAsset.objects.get(pk=pk) + issue_attachment = FileAsset.objects.filter( + pk=pk, workspace__slug=slug, project_id=project_id, issue_id=issue_id + ).first() + if not issue_attachment: + return Response( + {"error": "Issue attachment not found."}, + status=status.HTTP_404_NOT_FOUND, + ) issue_attachment.asset.delete(save=False) issue_attachment.delete() issue_activity.delay( diff --git a/apps/api/plane/bgtasks/work_item_link_task.py b/apps/api/plane/bgtasks/work_item_link_task.py index 7ceaacaf5a..442396c7f0 100644 --- a/apps/api/plane/bgtasks/work_item_link_task.py +++ b/apps/api/plane/bgtasks/work_item_link_task.py @@ -1,6 +1,10 @@ +# Copyright (c) 2023-present Plane Software, Inc. and contributors +# SPDX-License-Identifier: AGPL-3.0-only +# See the LICENSE file for details. + # Python imports import logging - +import socket # Third party imports from celery import shared_task @@ -20,6 +24,48 @@ logger = logging.getLogger("plane.worker") DEFAULT_FAVICON = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLWxpbmstaWNvbiBsdWNpZGUtbGluayI+PHBhdGggZD0iTTEwIDEzYTUgNSAwIDAgMCA3LjU0LjU0bDMtM2E1IDUgMCAwIDAtNy4wNy03LjA3bC0xLjcyIDEuNzEiLz48cGF0aCBkPSJNMTQgMTFhNSA1IDAgMCAwLTcuNTQtLjU0bC0zIDNhNSA1IDAgMCAwIDcuMDcgNy4wN2wxLjcxLTEuNzEiLz48L3N2Zz4=" # noqa: E501 +def validate_url_ip(url: str) -> None: + """ + Validate that a URL doesn't point to a private/internal IP address. + Resolves hostnames to IPs before checking. + + Args: + url: The URL to validate + + Raises: + ValueError: If the URL points to a private/internal IP + """ + parsed = urlparse(url) + hostname = parsed.hostname + + if not hostname: + raise ValueError("Invalid URL: No hostname found") + + # Only allow HTTP and HTTPS to prevent file://, gopher://, etc. + if parsed.scheme not in ("http", "https"): + raise ValueError("Invalid URL scheme. Only HTTP and HTTPS are allowed") + + # Resolve hostname to IP addresses — this catches domain names that + # point to internal IPs (e.g. attacker.com -> 169.254.169.254) + + try: + addr_info = socket.getaddrinfo(hostname, None) + except socket.gaierror: + raise ValueError("Hostname could not be resolved") + + if not addr_info: + raise ValueError("No IP addresses found for the hostname") + + # Check every resolved IP against blocked ranges to prevent SSRF + for addr in addr_info: + ip = ipaddress.ip_address(addr[4][0]) + if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local: + raise ValueError("Access to private/internal networks is not allowed") + + +MAX_REDIRECTS = 5 + + def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]: """ Crawls a URL to extract the title and favicon. @@ -31,17 +77,6 @@ def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]: str: JSON string containing title and base64-encoded favicon """ try: - # Prevent access to private IP ranges - parsed = urlparse(url) - - try: - ip = ipaddress.ip_address(parsed.hostname) - if ip.is_private or ip.is_loopback or ip.is_reserved: - raise ValueError("Access to private/internal networks is not allowed") - except ValueError: - # Not an IP address, continue with domain validation - pass - # Set up headers to mimic a real browser headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" # noqa: E501 @@ -49,9 +84,28 @@ def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]: soup = None title = None + final_url = url + + validate_url_ip(final_url) try: - response = requests.get(url, headers=headers, timeout=1) + # Manually follow redirects to validate each URL before requesting + redirect_count = 0 + response = requests.get(final_url, headers=headers, timeout=1, allow_redirects=False) + + while response.is_redirect and redirect_count < MAX_REDIRECTS: + redirect_url = response.headers.get("Location") + if not redirect_url: + break + # Resolve relative redirects against current URL + final_url = urljoin(final_url, redirect_url) + # Validate the redirect target BEFORE making the request + validate_url_ip(final_url) + redirect_count += 1 + response = requests.get(final_url, headers=headers, timeout=1, allow_redirects=False) + + if redirect_count >= MAX_REDIRECTS: + logger.warning(f"Too many redirects for URL: {url}") soup = BeautifulSoup(response.content, "html.parser") title_tag = soup.find("title") @@ -60,8 +114,8 @@ def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]: except requests.RequestException as e: logger.warning(f"Failed to fetch HTML for title: {str(e)}") - # Fetch and encode favicon - favicon_base64 = fetch_and_encode_favicon(headers, soup, url) + # Fetch and encode favicon using final URL (after redirects) + favicon_base64 = fetch_and_encode_favicon(headers, soup, final_url) # Prepare result result = { @@ -107,7 +161,9 @@ def find_favicon_url(soup: Optional[BeautifulSoup], base_url: str) -> Optional[s for selector in favicon_selectors: favicon_tag = soup.select_one(selector) if favicon_tag and favicon_tag.get("href"): - return urljoin(base_url, favicon_tag["href"]) + favicon_href = urljoin(base_url, favicon_tag["href"]) + validate_url_ip(favicon_href) + return favicon_href # Fallback to /favicon.ico parsed_url = urlparse(base_url) @@ -115,7 +171,9 @@ def find_favicon_url(soup: Optional[BeautifulSoup], base_url: str) -> Optional[s # Check if fallback exists try: - response = requests.head(fallback_url, timeout=2) + validate_url_ip(fallback_url) + response = requests.head(fallback_url, timeout=2, allow_redirects=False) + if response.status_code == 200: return fallback_url except requests.RequestException as e: @@ -146,6 +204,8 @@ def fetch_and_encode_favicon( "favicon_base64": f"data:image/svg+xml;base64,{DEFAULT_FAVICON}", } + validate_url_ip(favicon_url) + response = requests.get(favicon_url, headers=headers, timeout=1) # Get content type diff --git a/apps/api/plane/space/views/project.py b/apps/api/plane/space/views/project.py index 6f332781ff..0e19085a03 100644 --- a/apps/api/plane/space/views/project.py +++ b/apps/api/plane/space/views/project.py @@ -63,6 +63,11 @@ class ProjectMembersEndpoint(BaseAPIView): def get(self, request, anchor): deploy_board = DeployBoard.objects.filter(anchor=anchor).first() + if not deploy_board: + return Response( + {"error": "Invalid anchor"}, + status=status.HTTP_404_NOT_FOUND, + ) members = ProjectMember.objects.filter( project=deploy_board.project, @@ -71,10 +76,7 @@ class ProjectMembersEndpoint(BaseAPIView): ).values( "id", "member", - "member__first_name", - "member__last_name", "member__display_name", - "project", - "workspace", + "member__avatar", ) return Response(members, status=status.HTTP_200_OK) diff --git a/apps/api/plane/tests/contract/app/test_api_token.py b/apps/api/plane/tests/contract/app/test_api_token.py index 35d92b11e1..24fac7bb42 100644 --- a/apps/api/plane/tests/contract/app/test_api_token.py +++ b/apps/api/plane/tests/contract/app/test_api_token.py @@ -138,7 +138,7 @@ class TestApiTokenEndpoint: """Test retrieving a specific API token""" # Arrange session_client.force_authenticate(user=create_user) - url = reverse("api-tokens", kwargs={"pk": create_api_token_for_user.pk}) + url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk}) # Act response = session_client.get(url) @@ -155,7 +155,7 @@ class TestApiTokenEndpoint: # Arrange session_client.force_authenticate(user=create_user) fake_pk = uuid4() - url = reverse("api-tokens", kwargs={"pk": fake_pk}) + url = reverse("api-tokens-details", kwargs={"pk": fake_pk}) # Act response = session_client.get(url) @@ -174,7 +174,7 @@ class TestApiTokenEndpoint: other_user = User.objects.create(email=unique_email, username=unique_username) other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0) session_client.force_authenticate(user=create_user) - url = reverse("api-tokens", kwargs={"pk": other_token.pk}) + url = reverse("api-tokens-details", kwargs={"pk": other_token.pk}) # Act response = session_client.get(url) @@ -188,7 +188,7 @@ class TestApiTokenEndpoint: """Test successful API token deletion""" # Arrange session_client.force_authenticate(user=create_user) - url = reverse("api-tokens", kwargs={"pk": create_api_token_for_user.pk}) + url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk}) # Act response = session_client.delete(url) @@ -203,7 +203,7 @@ class TestApiTokenEndpoint: # Arrange session_client.force_authenticate(user=create_user) fake_pk = uuid4() - url = reverse("api-tokens", kwargs={"pk": fake_pk}) + url = reverse("api-tokens-details", kwargs={"pk": fake_pk}) # Act response = session_client.delete(url) @@ -222,7 +222,7 @@ class TestApiTokenEndpoint: other_user = User.objects.create(email=unique_email, username=unique_username) other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0) session_client.force_authenticate(user=create_user) - url = reverse("api-tokens", kwargs={"pk": other_token.pk}) + url = reverse("api-tokens-details", kwargs={"pk": other_token.pk}) # Act response = session_client.delete(url) @@ -238,7 +238,7 @@ class TestApiTokenEndpoint: # Arrange service_token = APIToken.objects.create(label="Service Token", user=create_user, user_type=0, is_service=True) session_client.force_authenticate(user=create_user) - url = reverse("api-tokens", kwargs={"pk": service_token.pk}) + url = reverse("api-tokens-details", kwargs={"pk": service_token.pk}) # Act response = session_client.delete(url) @@ -254,7 +254,7 @@ class TestApiTokenEndpoint: """Test successful API token update""" # Arrange session_client.force_authenticate(user=create_user) - url = reverse("api-tokens", kwargs={"pk": create_api_token_for_user.pk}) + url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk}) update_data = { "label": "Updated Token Label", "description": "Updated description", @@ -278,7 +278,7 @@ class TestApiTokenEndpoint: """Test partial API token update""" # Arrange session_client.force_authenticate(user=create_user) - url = reverse("api-tokens", kwargs={"pk": create_api_token_for_user.pk}) + url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk}) original_description = create_api_token_for_user.description update_data = {"label": "Only Label Updated"} @@ -296,7 +296,7 @@ class TestApiTokenEndpoint: # Arrange session_client.force_authenticate(user=create_user) fake_pk = uuid4() - url = reverse("api-tokens", kwargs={"pk": fake_pk}) + url = reverse("api-tokens-details", kwargs={"pk": fake_pk}) update_data = {"label": "New Label"} # Act @@ -316,7 +316,7 @@ class TestApiTokenEndpoint: other_user = User.objects.create(email=unique_email, username=unique_username) other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0) session_client.force_authenticate(user=create_user) - url = reverse("api-tokens", kwargs={"pk": other_token.pk}) + url = reverse("api-tokens-details", kwargs={"pk": other_token.pk}) update_data = {"label": "Hacked Label"} # Act @@ -329,6 +329,56 @@ class TestApiTokenEndpoint: other_token.refresh_from_db() assert other_token.label == "Other Token" + @pytest.mark.django_db + def test_patch_cannot_modify_token(self, session_client, create_user, create_api_token_for_user): + """Test that token value cannot be modified via PATCH""" + # Arrange + session_client.force_authenticate(user=create_user) + url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk}) + original_token = create_api_token_for_user.token + update_data = {"token": "plane_api_malicious_token_value"} + + # Act + response = session_client.patch(url, update_data, format="json") + + # Assert + assert response.status_code == status.HTTP_200_OK + create_api_token_for_user.refresh_from_db() + assert create_api_token_for_user.token == original_token + + @pytest.mark.django_db + def test_patch_cannot_modify_user_type(self, session_client, create_user, create_api_token_for_user): + """Test that user_type cannot be modified via PATCH""" + # Arrange + session_client.force_authenticate(user=create_user) + url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk}) + update_data = {"user_type": 1} + + # Act + response = session_client.patch(url, update_data, format="json") + + # Assert + assert response.status_code == status.HTTP_200_OK + create_api_token_for_user.refresh_from_db() + assert create_api_token_for_user.user_type == 0 + + @pytest.mark.django_db + def test_patch_cannot_modify_service_token(self, session_client, create_user): + """Test that service tokens cannot be modified through user token endpoint""" + # Arrange + service_token = APIToken.objects.create(label="Service Token", user=create_user, user_type=0, is_service=True) + session_client.force_authenticate(user=create_user) + url = reverse("api-tokens-details", kwargs={"pk": service_token.pk}) + update_data = {"label": "Hacked Service Token"} + + # Act + response = session_client.patch(url, update_data, format="json") + + # Assert + assert response.status_code == status.HTTP_404_NOT_FOUND + service_token.refresh_from_db() + assert service_token.label == "Service Token" + # Authentication tests @pytest.mark.django_db def test_all_endpoints_require_authentication(self, api_client): @@ -337,9 +387,9 @@ class TestApiTokenEndpoint: endpoints = [ (reverse("api-tokens"), "get"), (reverse("api-tokens"), "post"), - (reverse("api-tokens", kwargs={"pk": uuid4()}), "get"), - (reverse("api-tokens", kwargs={"pk": uuid4()}), "patch"), - (reverse("api-tokens", kwargs={"pk": uuid4()}), "delete"), + (reverse("api-tokens-details", kwargs={"pk": uuid4()}), "get"), + (reverse("api-tokens-details", kwargs={"pk": uuid4()}), "patch"), + (reverse("api-tokens-details", kwargs={"pk": uuid4()}), "delete"), ] # Act & Assert diff --git a/apps/api/requirements/base.txt b/apps/api/requirements/base.txt index 7f2c64be47..b0ffb54e83 100644 --- a/apps/api/requirements/base.txt +++ b/apps/api/requirements/base.txt @@ -1,7 +1,7 @@ # base requirements # django -Django==4.2.27 +Django==4.2.28 # rest framework djangorestframework==3.15.2 # postgres @@ -51,7 +51,7 @@ beautifulsoup4==4.12.3 # analytics posthog==3.5.0 # crypto -cryptography==44.0.1 +cryptography==46.0.5 # html validator lxml==6.0.0 # s3 diff --git a/apps/live/package.json b/apps/live/package.json index 7115af53a4..bd09ce1420 100644 --- a/apps/live/package.json +++ b/apps/live/package.json @@ -1,6 +1,6 @@ { "name": "live", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "description": "A realtime collaborative server powers Plane's rich text editor", "main": "./dist/start.mjs", diff --git a/apps/space/core/types/member.d.ts b/apps/space/core/types/member.d.ts index 721ccd98fc..34c95daf68 100644 --- a/apps/space/core/types/member.d.ts +++ b/apps/space/core/types/member.d.ts @@ -1,10 +1,6 @@ export type TPublicMember = { id: string; member: string; - member__avatar: string; - member__first_name: string; - member__last_name: string; member__display_name: string; - project: string; - workspace: string; + member__avatar: string; }; diff --git a/apps/space/package.json b/apps/space/package.json index c3e6f3dbf4..e40f609277 100644 --- a/apps/space/package.json +++ b/apps/space/package.json @@ -1,6 +1,6 @@ { "name": "space", - "version": "1.2.1", + "version": "1.2.2", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/apps/web/package.json b/apps/web/package.json index fbf921ff76..3a116f6e07 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "web", - "version": "1.2.1", + "version": "1.2.2", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/package.json b/package.json index 1229aa3c9c..eef3f0ca61 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "plane", "description": "Open-source project management that unlocks customer value", "repository": "https://github.com/makeplane/plane.git", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "private": true, "scripts": { diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 8b7104d818..218bc986b2 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,6 +1,6 @@ { "name": "@plane/codemods", - "version": "1.2.1", + "version": "1.2.2", "private": true, "scripts": { "test": "vitest run", diff --git a/packages/constants/package.json b/packages/constants/package.json index 05b5e8e85a..ecb8d1c5b5 100644 --- a/packages/constants/package.json +++ b/packages/constants/package.json @@ -1,6 +1,6 @@ { "name": "@plane/constants", - "version": "1.2.1", + "version": "1.2.2", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/packages/editor/package.json b/packages/editor/package.json index 930e9f4b61..ac4f67e11a 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -1,6 +1,6 @@ { "name": "@plane/editor", - "version": "1.2.1", + "version": "1.2.2", "description": "Core Editor that powers Plane", "license": "AGPL-3.0", "private": true, diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 5fa0cd6377..e63a99d72a 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -1,6 +1,6 @@ { "name": "@plane/hooks", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "description": "React hooks that are shared across multiple apps internally", "private": true, diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 2cf34926d1..046c75cbb3 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,6 +1,6 @@ { "name": "@plane/i18n", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "description": "I18n shared across multiple apps internally", "private": true, diff --git a/packages/logger/package.json b/packages/logger/package.json index a4a57328b1..5f9de0fc3f 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { "name": "@plane/logger", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "description": "Logger shared across multiple apps internally", "private": true, diff --git a/packages/propel/package.json b/packages/propel/package.json index 230c7b0764..97d67c8db5 100644 --- a/packages/propel/package.json +++ b/packages/propel/package.json @@ -1,6 +1,6 @@ { "name": "@plane/propel", - "version": "1.2.1", + "version": "1.2.2", "private": true, "license": "AGPL-3.0", "type": "module", diff --git a/packages/services/package.json b/packages/services/package.json index 981b86df31..cf0bec6eed 100644 --- a/packages/services/package.json +++ b/packages/services/package.json @@ -1,6 +1,6 @@ { "name": "@plane/services", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "private": true, "type": "module", diff --git a/packages/shared-state/package.json b/packages/shared-state/package.json index effd12ef64..af792136c7 100644 --- a/packages/shared-state/package.json +++ b/packages/shared-state/package.json @@ -1,6 +1,6 @@ { "name": "@plane/shared-state", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "description": "Shared state shared across multiple apps internally", "private": true, diff --git a/packages/tailwind-config/package.json b/packages/tailwind-config/package.json index ece3b586ae..61b73daecf 100644 --- a/packages/tailwind-config/package.json +++ b/packages/tailwind-config/package.json @@ -1,6 +1,6 @@ { "name": "@plane/tailwind-config", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "description": "common tailwind configuration across monorepo", "main": "tailwind.config.js", diff --git a/packages/types/package.json b/packages/types/package.json index 47be226847..e37e9b877d 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@plane/types", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "private": true, "type": "module", diff --git a/packages/types/src/users.ts b/packages/types/src/users.ts index 9278996a7a..7760a0a8c0 100644 --- a/packages/types/src/users.ts +++ b/packages/types/src/users.ts @@ -196,12 +196,8 @@ export type TProfileViews = "assigned" | "created" | "subscribed"; export type TPublicMember = { id: string; member: string; - member__avatar: string; - member__first_name: string; - member__last_name: string; member__display_name: string; - project: string; - workspace: string; + member__avatar: string; }; // export interface ICurrentUser { diff --git a/packages/typescript-config/package.json b/packages/typescript-config/package.json index b8190c40db..f7f44b2039 100644 --- a/packages/typescript-config/package.json +++ b/packages/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@plane/typescript-config", - "version": "1.2.1", + "version": "1.2.2", "license": "AGPL-3.0", "private": true, "files": [ diff --git a/packages/ui/package.json b/packages/ui/package.json index ffed74cdeb..f84e6bddd7 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -2,7 +2,7 @@ "name": "@plane/ui", "description": "UI components shared across multiple apps internally", "private": true, - "version": "1.2.1", + "version": "1.2.2", "sideEffects": false, "license": "AGPL-3.0", "type": "module", diff --git a/packages/utils/package.json b/packages/utils/package.json index 4e60cac8cb..71a9b44297 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@plane/utils", - "version": "1.2.1", + "version": "1.2.2", "description": "Helper functions shared across multiple apps internally", "license": "AGPL-3.0", "private": true,