forked from github/plane
[WEB-1309] fix: auth fixes (#4456)
* dev: magic link login and email password disable * dev: user account deactivation * dev: change nginx conf routes * feat: changemod space * fix: space app dir fixes * dev: invalidate cache for instances when creating workspace * dev: update email templates for test email * dev: fix build errors * fix: auth fixes and improvement (#4452) * chore: change password api updated and missing password error code added * chore: auth helper updated * chore: disable send code input suggestion * chore: change password function updated * fix: application error on sign in page * chore: change password validation added and enhancement * dev: space base path in web * dev: admin user deactivated * dev: user and instance admin session endpoint * fix: last_workspace_id endpoint updated * fix: magic sign in and email password check added --------- Co-authored-by: pablohashescobar <nikhilschacko@gmail.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: guru_sainath <gurusainath007@gmail.com>
This commit is contained in:
parent
ab6f1ef780
commit
9b7b23f5a2
@ -14,6 +14,7 @@
|
|||||||
"@headlessui/react": "^1.7.19",
|
"@headlessui/react": "^1.7.19",
|
||||||
"@plane/types": "*",
|
"@plane/types": "*",
|
||||||
"@plane/ui": "*",
|
"@plane/ui": "*",
|
||||||
|
"@plane/constants": "*",
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
"@types/lodash": "^4.17.0",
|
"@types/lodash": "^4.17.0",
|
||||||
"autoprefixer": "10.4.14",
|
"autoprefixer": "10.4.14",
|
||||||
|
@ -11,6 +11,7 @@ from plane.app.views import (
|
|||||||
UserEndpoint,
|
UserEndpoint,
|
||||||
UserIssueCompletedGraphEndpoint,
|
UserIssueCompletedGraphEndpoint,
|
||||||
UserWorkspaceDashboardEndpoint,
|
UserWorkspaceDashboardEndpoint,
|
||||||
|
UserSessionEndpoint,
|
||||||
## End User
|
## End User
|
||||||
## Workspaces
|
## Workspaces
|
||||||
UserWorkSpacesEndpoint,
|
UserWorkSpacesEndpoint,
|
||||||
@ -29,6 +30,11 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
name="users",
|
name="users",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"users/session/",
|
||||||
|
UserSessionEndpoint.as_view(),
|
||||||
|
name="user-session",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"users/me/settings/",
|
"users/me/settings/",
|
||||||
UserEndpoint.as_view(
|
UserEndpoint.as_view(
|
||||||
|
@ -222,4 +222,4 @@ from .error_404 import custom_404_view
|
|||||||
|
|
||||||
from .exporter.base import ExportIssuesEndpoint
|
from .exporter.base import ExportIssuesEndpoint
|
||||||
from .notification.base import MarkAllReadNotificationViewSet
|
from .notification.base import MarkAllReadNotificationViewSet
|
||||||
from .user.base import AccountEndpoint, ProfileEndpoint
|
from .user.base import AccountEndpoint, ProfileEndpoint, UserSessionEndpoint
|
||||||
|
@ -6,6 +6,7 @@ from django.utils import timezone
|
|||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.app.serializers import (
|
from plane.app.serializers import (
|
||||||
@ -180,6 +181,25 @@ class UserEndpoint(BaseViewSet):
|
|||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
class UserSessionEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
AllowAny,
|
||||||
|
]
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
user = User.objects.get(pk=request.user.id)
|
||||||
|
serializer = UserMeSerializer(user)
|
||||||
|
data = {"is_authenticated": True}
|
||||||
|
data["user"] = serializer.data
|
||||||
|
return Response(data, status=status.HTTP_200_OK)
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
{"is_authenticated": False}, status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
class UpdateUserOnBoardedEndpoint(BaseAPIView):
|
||||||
|
|
||||||
@invalidate_cache(path="/api/users/me/")
|
@invalidate_cache(path="/api/users/me/")
|
||||||
|
@ -96,6 +96,7 @@ class WorkSpaceViewSet(BaseViewSet):
|
|||||||
|
|
||||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||||
@invalidate_cache(path="/api/users/me/workspaces/")
|
@invalidate_cache(path="/api/users/me/workspaces/")
|
||||||
|
@invalidate_cache(path="/api/instances/", user=False)
|
||||||
def create(self, request):
|
def create(self, request):
|
||||||
try:
|
try:
|
||||||
serializer = WorkSpaceSerializer(data=request.data)
|
serializer = WorkSpaceSerializer(data=request.data)
|
||||||
@ -151,8 +152,12 @@ class WorkSpaceViewSet(BaseViewSet):
|
|||||||
return super().partial_update(request, *args, **kwargs)
|
return super().partial_update(request, *args, **kwargs)
|
||||||
|
|
||||||
@invalidate_cache(path="/api/workspaces/", user=False)
|
@invalidate_cache(path="/api/workspaces/", user=False)
|
||||||
@invalidate_cache(path="/api/users/me/workspaces/", multiple=True, user=False)
|
@invalidate_cache(
|
||||||
@invalidate_cache(path="/api/users/me/settings/", multiple=True, user=False)
|
path="/api/users/me/workspaces/", multiple=True, user=False
|
||||||
|
)
|
||||||
|
@invalidate_cache(
|
||||||
|
path="/api/users/me/settings/", multiple=True, user=False
|
||||||
|
)
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
return super().destroy(request, *args, **kwargs)
|
return super().destroy(request, *args, **kwargs)
|
||||||
|
|
||||||
|
@ -100,6 +100,12 @@ class Adapter:
|
|||||||
user.save()
|
user.save()
|
||||||
Profile.objects.create(user=user)
|
Profile.objects.create(user=user)
|
||||||
|
|
||||||
|
if not user.is_active:
|
||||||
|
raise AuthenticationException(
|
||||||
|
AUTHENTICATION_ERROR_CODES["USER_ACCOUNT_DEACTIVATED"],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
|
||||||
# Update user details
|
# Update user details
|
||||||
user.last_login_medium = self.provider
|
user.last_login_medium = self.provider
|
||||||
user.last_active = timezone.now()
|
user.last_active = timezone.now()
|
||||||
|
@ -4,6 +4,9 @@ AUTHENTICATION_ERROR_CODES = {
|
|||||||
"INVALID_EMAIL": 5005,
|
"INVALID_EMAIL": 5005,
|
||||||
"EMAIL_REQUIRED": 5010,
|
"EMAIL_REQUIRED": 5010,
|
||||||
"SIGNUP_DISABLED": 5015,
|
"SIGNUP_DISABLED": 5015,
|
||||||
|
"MAGIC_LINK_LOGIN_DISABLED": 5016,
|
||||||
|
"PASSWORD_LOGIN_DISABLED": 5018,
|
||||||
|
"USER_ACCOUNT_DEACTIVATED": 5019,
|
||||||
# Password strength
|
# Password strength
|
||||||
"INVALID_PASSWORD": 5020,
|
"INVALID_PASSWORD": 5020,
|
||||||
"SMTP_NOT_CONFIGURED": 5025,
|
"SMTP_NOT_CONFIGURED": 5025,
|
||||||
@ -35,6 +38,7 @@ AUTHENTICATION_ERROR_CODES = {
|
|||||||
"EXPIRED_PASSWORD_TOKEN": 5130,
|
"EXPIRED_PASSWORD_TOKEN": 5130,
|
||||||
# Change password
|
# Change password
|
||||||
"INCORRECT_OLD_PASSWORD": 5135,
|
"INCORRECT_OLD_PASSWORD": 5135,
|
||||||
|
"MISSING_PASSWORD": 5138,
|
||||||
"INVALID_NEW_PASSWORD": 5140,
|
"INVALID_NEW_PASSWORD": 5140,
|
||||||
# set passowrd
|
# set passowrd
|
||||||
"PASSWORD_ALREADY_SET": 5145,
|
"PASSWORD_ALREADY_SET": 5145,
|
||||||
@ -47,6 +51,7 @@ AUTHENTICATION_ERROR_CODES = {
|
|||||||
"ADMIN_AUTHENTICATION_FAILED": 5175,
|
"ADMIN_AUTHENTICATION_FAILED": 5175,
|
||||||
"ADMIN_USER_ALREADY_EXIST": 5180,
|
"ADMIN_USER_ALREADY_EXIST": 5180,
|
||||||
"ADMIN_USER_DOES_NOT_EXIST": 5185,
|
"ADMIN_USER_DOES_NOT_EXIST": 5185,
|
||||||
|
"ADMIN_USER_DEACTIVATED": 5190,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
# Python imports
|
||||||
|
import os
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from plane.authentication.adapter.credential import CredentialAdapter
|
from plane.authentication.adapter.credential import CredentialAdapter
|
||||||
from plane.db.models import User
|
from plane.db.models import User
|
||||||
@ -5,6 +8,7 @@ from plane.authentication.adapter.error import (
|
|||||||
AUTHENTICATION_ERROR_CODES,
|
AUTHENTICATION_ERROR_CODES,
|
||||||
AuthenticationException,
|
AuthenticationException,
|
||||||
)
|
)
|
||||||
|
from plane.license.utils.instance_value import get_configuration_value
|
||||||
|
|
||||||
|
|
||||||
class EmailProvider(CredentialAdapter):
|
class EmailProvider(CredentialAdapter):
|
||||||
@ -23,6 +27,21 @@ class EmailProvider(CredentialAdapter):
|
|||||||
self.code = code
|
self.code = code
|
||||||
self.is_signup = is_signup
|
self.is_signup = is_signup
|
||||||
|
|
||||||
|
(ENABLE_EMAIL_PASSWORD,) = get_configuration_value(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "ENABLE_EMAIL_PASSWORD",
|
||||||
|
"default": os.environ.get("ENABLE_EMAIL_PASSWORD"),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if ENABLE_EMAIL_PASSWORD == "0":
|
||||||
|
raise AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES["ENABLE_EMAIL_PASSWORD"],
|
||||||
|
error_message="ENABLE_EMAIL_PASSWORD",
|
||||||
|
)
|
||||||
|
|
||||||
def set_user_data(self):
|
def set_user_data(self):
|
||||||
if self.is_signup:
|
if self.is_signup:
|
||||||
# Check if the user already exists
|
# Check if the user already exists
|
||||||
|
@ -26,23 +26,20 @@ class MagicCodeProvider(CredentialAdapter):
|
|||||||
code=None,
|
code=None,
|
||||||
):
|
):
|
||||||
|
|
||||||
(EMAIL_HOST, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD) = (
|
(
|
||||||
get_configuration_value(
|
EMAIL_HOST,
|
||||||
[
|
ENABLE_MAGIC_LINK_LOGIN,
|
||||||
{
|
) = get_configuration_value(
|
||||||
"key": "EMAIL_HOST",
|
[
|
||||||
"default": os.environ.get("EMAIL_HOST"),
|
{
|
||||||
},
|
"key": "EMAIL_HOST",
|
||||||
{
|
"default": os.environ.get("EMAIL_HOST"),
|
||||||
"key": "EMAIL_HOST_USER",
|
},
|
||||||
"default": os.environ.get("EMAIL_HOST_USER"),
|
{
|
||||||
},
|
"key": "ENABLE_MAGIC_LINK_LOGIN",
|
||||||
{
|
"default": os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "1"),
|
||||||
"key": "EMAIL_HOST_PASSWORD",
|
},
|
||||||
"default": os.environ.get("EMAIL_HOST_PASSWORD"),
|
]
|
||||||
},
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not (EMAIL_HOST):
|
if not (EMAIL_HOST):
|
||||||
@ -52,6 +49,15 @@ class MagicCodeProvider(CredentialAdapter):
|
|||||||
payload={"email": str(self.key)},
|
payload={"email": str(self.key)},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if ENABLE_MAGIC_LINK_LOGIN == "0":
|
||||||
|
raise AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"MAGIC_LINK_LOGIN_DISABLED"
|
||||||
|
],
|
||||||
|
error_message="MAGIC_LINK_LOGIN_DISABLED",
|
||||||
|
payload={"email": str(self.key)},
|
||||||
|
)
|
||||||
|
|
||||||
super().__init__(request, self.provider)
|
super().__init__(request, self.provider)
|
||||||
self.key = key
|
self.key = key
|
||||||
self.code = code
|
self.code = code
|
||||||
|
@ -24,62 +24,59 @@ class EmailCheckSignUpEndpoint(APIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
# Check instance configuration
|
|
||||||
instance = Instance.objects.first()
|
|
||||||
if instance is None or not instance.is_setup_done:
|
|
||||||
exc = AuthenticationException(
|
|
||||||
error_code=AUTHENTICATION_ERROR_CODES[
|
|
||||||
"INSTANCE_NOT_CONFIGURED"
|
|
||||||
],
|
|
||||||
error_message="INSTANCE_NOT_CONFIGURED",
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
exc.get_error_dict(),
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
email = request.data.get("email", False)
|
|
||||||
|
|
||||||
# Return error if email is not present
|
|
||||||
if not email:
|
|
||||||
exc = AuthenticationException(
|
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["EMAIL_REQUIRED"],
|
|
||||||
error_message="EMAIL_REQUIRED",
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
exc.get_error_dict(),
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate email
|
|
||||||
try:
|
try:
|
||||||
|
# Check instance configuration
|
||||||
|
instance = Instance.objects.first()
|
||||||
|
if instance is None or not instance.is_setup_done:
|
||||||
|
raise AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"INSTANCE_NOT_CONFIGURED"
|
||||||
|
],
|
||||||
|
error_message="INSTANCE_NOT_CONFIGURED",
|
||||||
|
)
|
||||||
|
email = request.data.get("email", False)
|
||||||
|
|
||||||
|
# Return error if email is not present
|
||||||
|
if not email:
|
||||||
|
raise AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES["EMAIL_REQUIRED"],
|
||||||
|
error_message="EMAIL_REQUIRED",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate email
|
||||||
validate_email(email)
|
validate_email(email)
|
||||||
|
|
||||||
|
existing_user = User.objects.filter(email=email).first()
|
||||||
|
|
||||||
|
if existing_user:
|
||||||
|
# check if the account is the deactivated
|
||||||
|
if not existing_user.is_active:
|
||||||
|
raise AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ACCOUNT_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
raise AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ALREADY_EXIST"
|
||||||
|
],
|
||||||
|
error_message="USER_ALREADY_EXIST",
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
{"status": True},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
exc = AuthenticationException(
|
raise AuthenticationException(
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"],
|
error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"],
|
||||||
error_message="INVALID_EMAIL",
|
error_message="INVALID_EMAIL",
|
||||||
)
|
)
|
||||||
|
except AuthenticationException as e:
|
||||||
return Response(
|
return Response(
|
||||||
exc.get_error_dict(),
|
e.get_error_dict(), status=status.HTTP_400_BAD_REQUEST
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
existing_user = User.objects.filter(email=email).first()
|
|
||||||
|
|
||||||
if existing_user:
|
|
||||||
exc = AuthenticationException(
|
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
|
||||||
error_message="USER_ALREADY_EXIST",
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
exc.get_error_dict(),
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
{"status": True},
|
|
||||||
status=status.HTTP_200_OK,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class EmailCheckSignInEndpoint(APIView):
|
class EmailCheckSignInEndpoint(APIView):
|
||||||
|
|
||||||
@ -88,61 +85,59 @@ class EmailCheckSignInEndpoint(APIView):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
# Check instance configuration
|
|
||||||
instance = Instance.objects.first()
|
|
||||||
if instance is None or not instance.is_setup_done:
|
|
||||||
exc = AuthenticationException(
|
|
||||||
error_code=AUTHENTICATION_ERROR_CODES[
|
|
||||||
"INSTANCE_NOT_CONFIGURED"
|
|
||||||
],
|
|
||||||
error_message="INSTANCE_NOT_CONFIGURED",
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
exc.get_error_dict(),
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
email = request.data.get("email", False)
|
|
||||||
|
|
||||||
# Return error if email is not present
|
|
||||||
if not email:
|
|
||||||
exc = AuthenticationException(
|
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["EMAIL_REQUIRED"],
|
|
||||||
error_message="EMAIL_REQUIRED",
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
exc.get_error_dict(),
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate email
|
|
||||||
try:
|
try:
|
||||||
|
# Check instance configuration
|
||||||
|
instance = Instance.objects.first()
|
||||||
|
if instance is None or not instance.is_setup_done:
|
||||||
|
raise AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"INSTANCE_NOT_CONFIGURED"
|
||||||
|
],
|
||||||
|
error_message="INSTANCE_NOT_CONFIGURED",
|
||||||
|
)
|
||||||
|
|
||||||
|
email = request.data.get("email", False)
|
||||||
|
|
||||||
|
# Return error if email is not present
|
||||||
|
if not email:
|
||||||
|
raise AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES["EMAIL_REQUIRED"],
|
||||||
|
error_message="EMAIL_REQUIRED",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Validate email
|
||||||
validate_email(email)
|
validate_email(email)
|
||||||
|
|
||||||
|
existing_user = User.objects.filter(email=email).first()
|
||||||
|
|
||||||
|
# If existing user
|
||||||
|
if existing_user:
|
||||||
|
# Raise different exception when user is not active
|
||||||
|
if not existing_user.is_active:
|
||||||
|
raise AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ACCOUNT_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
{
|
||||||
|
"status": True,
|
||||||
|
"is_password_autoset": existing_user.is_password_autoset,
|
||||||
|
},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
raise AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
||||||
|
error_message="USER_DOES_NOT_EXIST",
|
||||||
|
)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
exc = AuthenticationException(
|
raise AuthenticationException(
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"],
|
error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"],
|
||||||
error_message="INVALID_EMAIL",
|
error_message="INVALID_EMAIL",
|
||||||
)
|
)
|
||||||
|
except AuthenticationException as e:
|
||||||
return Response(
|
return Response(
|
||||||
exc.get_error_dict(),
|
e.get_error_dict(), status=status.HTTP_400_BAD_REQUEST
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
existing_user = User.objects.filter(email=email).first()
|
|
||||||
|
|
||||||
if existing_user:
|
|
||||||
return Response(
|
|
||||||
{
|
|
||||||
"status": True,
|
|
||||||
"is_password_autoset": existing_user.is_password_autoset,
|
|
||||||
},
|
|
||||||
status=status.HTTP_200_OK,
|
|
||||||
)
|
|
||||||
exc = AuthenticationException(
|
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
|
||||||
error_message="USER_DOES_NOT_EXIST",
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
exc.get_error_dict(),
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
|
@ -90,7 +90,9 @@ class SignInAuthEndpoint(View):
|
|||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
if not User.objects.filter(email=email).exists():
|
existing_user = User.objects.filter(email=email).first()
|
||||||
|
|
||||||
|
if not existing_user:
|
||||||
exc = AuthenticationException(
|
exc = AuthenticationException(
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
||||||
error_message="USER_DOES_NOT_EXIST",
|
error_message="USER_DOES_NOT_EXIST",
|
||||||
@ -105,6 +107,22 @@ class SignInAuthEndpoint(View):
|
|||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
if not existing_user.is_active:
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ACCOUNT_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
params = exc.get_error_dict()
|
||||||
|
if next_path:
|
||||||
|
params["next_path"] = str(next_path)
|
||||||
|
url = urljoin(
|
||||||
|
base_host(request=request, is_app=True),
|
||||||
|
"sign-in?" + urlencode(params),
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
provider = EmailProvider(
|
provider = EmailProvider(
|
||||||
request=request, key=email, code=password, is_signup=False
|
request=request, key=email, code=password, is_signup=False
|
||||||
@ -197,7 +215,26 @@ class SignUpAuthEndpoint(View):
|
|||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
if User.objects.filter(email=email).exists():
|
existing_user = User.objects.filter(email=email).first()
|
||||||
|
|
||||||
|
if existing_user:
|
||||||
|
# Existing User
|
||||||
|
if not existing_user.is_active:
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ACCOUNT_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
params = exc.get_error_dict()
|
||||||
|
if next_path:
|
||||||
|
params["next_path"] = str(next_path)
|
||||||
|
url = urljoin(
|
||||||
|
base_host(request=request, is_app=True),
|
||||||
|
"?" + urlencode(params),
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
exc = AuthenticationException(
|
exc = AuthenticationException(
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
||||||
error_message="USER_ALREADY_EXIST",
|
error_message="USER_ALREADY_EXIST",
|
||||||
|
@ -95,7 +95,26 @@ class MagicSignInEndpoint(View):
|
|||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
if not User.objects.filter(email=email).exists():
|
# Existing User
|
||||||
|
existing_user = User.objects.filter(email=email).first()
|
||||||
|
|
||||||
|
if not existing_user:
|
||||||
|
if not existing_user.is_active:
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ACCOUNT_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
params = exc.get_error_dict()
|
||||||
|
if next_path:
|
||||||
|
params["next_path"] = str(next_path)
|
||||||
|
url = urljoin(
|
||||||
|
base_host(request=request, is_app=True),
|
||||||
|
"sign-in?" + urlencode(params),
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
exc = AuthenticationException(
|
exc = AuthenticationException(
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
||||||
error_message="USER_DOES_NOT_EXIST",
|
error_message="USER_DOES_NOT_EXIST",
|
||||||
@ -167,8 +186,25 @@ class MagicSignUpEndpoint(View):
|
|||||||
"?" + urlencode(params),
|
"?" + urlencode(params),
|
||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
# Existing user
|
||||||
|
existing_user = User.objects.filter(email=email).first()
|
||||||
|
if not existing_user:
|
||||||
|
if not existing_user.is_active:
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ACCOUNT_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
params = exc.get_error_dict()
|
||||||
|
if next_path:
|
||||||
|
params["next_path"] = str(next_path)
|
||||||
|
url = urljoin(
|
||||||
|
base_host(request=request, is_app=True),
|
||||||
|
"?" + urlencode(params),
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
if User.objects.filter(email=email).exists():
|
|
||||||
exc = AuthenticationException(
|
exc = AuthenticationException(
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
||||||
error_message="USER_ALREADY_EXIST",
|
error_message="USER_ALREADY_EXIST",
|
||||||
|
@ -63,23 +63,13 @@ class ForgotPasswordEndpoint(APIView):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
(EMAIL_HOST, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD) = (
|
(EMAIL_HOST,) = get_configuration_value(
|
||||||
get_configuration_value(
|
[
|
||||||
[
|
{
|
||||||
{
|
"key": "EMAIL_HOST",
|
||||||
"key": "EMAIL_HOST",
|
"default": os.environ.get("EMAIL_HOST"),
|
||||||
"default": os.environ.get("EMAIL_HOST"),
|
},
|
||||||
},
|
]
|
||||||
{
|
|
||||||
"key": "EMAIL_HOST_USER",
|
|
||||||
"default": os.environ.get("EMAIL_HOST_USER"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"key": "EMAIL_HOST_PASSWORD",
|
|
||||||
"default": os.environ.get("EMAIL_HOST_PASSWORD"),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if not (EMAIL_HOST):
|
if not (EMAIL_HOST):
|
||||||
|
@ -36,55 +36,60 @@ class CSRFTokenEndpoint(APIView):
|
|||||||
|
|
||||||
class ChangePasswordEndpoint(APIView):
|
class ChangePasswordEndpoint(APIView):
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = ChangePasswordSerializer(data=request.data)
|
|
||||||
user = User.objects.get(pk=request.user.id)
|
user = User.objects.get(pk=request.user.id)
|
||||||
if serializer.is_valid():
|
|
||||||
if not user.check_password(serializer.data.get("old_password")):
|
|
||||||
exc = AuthenticationException(
|
|
||||||
error_code=AUTHENTICATION_ERROR_CODES[
|
|
||||||
"INCORRECT_OLD_PASSWORD"
|
|
||||||
],
|
|
||||||
error_message="INCORRECT_OLD_PASSWORD",
|
|
||||||
payload={"error": "Old password is not correct"},
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
exc.get_error_dict(),
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
# check the password score
|
old_password = request.data.get("old_password", False)
|
||||||
results = zxcvbn(serializer.data.get("new_password"))
|
new_password = request.data.get("new_password", False)
|
||||||
if results["score"] < 3:
|
|
||||||
exc = AuthenticationException(
|
|
||||||
error_code=AUTHENTICATION_ERROR_CODES[
|
|
||||||
"INVALID_NEW_PASSWORD"
|
|
||||||
],
|
|
||||||
error_message="INVALID_NEW_PASSWORD",
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
exc.get_error_dict(),
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
# set_password also hashes the password that the user will get
|
if not old_password or not new_password:
|
||||||
user.set_password(serializer.data.get("new_password"))
|
exc = AuthenticationException(
|
||||||
user.is_password_autoset = False
|
error_code=AUTHENTICATION_ERROR_CODES["MISSING_PASSWORD"],
|
||||||
user.save()
|
error_message="MISSING_PASSWORD",
|
||||||
user_login(user=user, request=request, is_app=True)
|
payload={"error": "Old or new password is missing"},
|
||||||
return Response(
|
)
|
||||||
{"message": "Password updated successfully"},
|
return Response(
|
||||||
status=status.HTTP_200_OK,
|
exc.get_error_dict(),
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
exc = AuthenticationException(
|
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"],
|
|
||||||
error_message="INVALID_PASSWORD",
|
|
||||||
)
|
|
||||||
return Response(
|
|
||||||
exc.get_error_dict(),
|
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
|
if not user.check_password(old_password):
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"INCORRECT_OLD_PASSWORD"
|
||||||
|
],
|
||||||
|
error_message="INCORRECT_OLD_PASSWORD",
|
||||||
|
payload={"error": "Old password is not correct"},
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
exc.get_error_dict(),
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
# check the password score
|
||||||
|
results = zxcvbn(new_password)
|
||||||
|
if results["score"] < 3:
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"INVALID_NEW_PASSWORD"
|
||||||
|
],
|
||||||
|
error_message="INVALID_NEW_PASSWORD",
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
exc.get_error_dict(),
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
# set_password also hashes the password that the user will get
|
||||||
|
user.set_password(new_password)
|
||||||
|
user.is_password_autoset = False
|
||||||
|
user.save()
|
||||||
|
user_login(user=user, request=request, is_app=True)
|
||||||
|
return Response(
|
||||||
|
{"message": "Password updated successfully"},
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
|
||||||
class SetUserPasswordEndpoint(APIView):
|
class SetUserPasswordEndpoint(APIView):
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
user = User.objects.get(pk=request.user.id)
|
user = User.objects.get(pk=request.user.id)
|
||||||
|
@ -68,6 +68,17 @@ class EmailCheckEndpoint(APIView):
|
|||||||
|
|
||||||
# If existing user
|
# If existing user
|
||||||
if existing_user:
|
if existing_user:
|
||||||
|
if not existing_user.is_active:
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ACCOUNT_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
return Response(
|
||||||
|
exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"existing": True,
|
"existing": True,
|
||||||
|
@ -83,7 +83,10 @@ class SignInAuthSpaceEndpoint(View):
|
|||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
if not User.objects.filter(email=email).exists():
|
# Existing User
|
||||||
|
existing_user = User.objects.filter(email=email).first()
|
||||||
|
|
||||||
|
if not existing_user:
|
||||||
exc = AuthenticationException(
|
exc = AuthenticationException(
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
||||||
error_message="USER_DOES_NOT_EXIST",
|
error_message="USER_DOES_NOT_EXIST",
|
||||||
@ -98,6 +101,22 @@ class SignInAuthSpaceEndpoint(View):
|
|||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
if not existing_user.is_active:
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ACCOUNT_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
params = exc.get_error_dict()
|
||||||
|
if next_path:
|
||||||
|
params["next_path"] = str(next_path)
|
||||||
|
url = urljoin(
|
||||||
|
base_host(request=request, is_space=True),
|
||||||
|
"?" + urlencode(params),
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
provider = EmailProvider(
|
provider = EmailProvider(
|
||||||
request=request, key=email, code=password, is_signup=False
|
request=request, key=email, code=password, is_signup=False
|
||||||
@ -185,7 +204,26 @@ class SignUpAuthSpaceEndpoint(View):
|
|||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
if User.objects.filter(email=email).exists():
|
# Existing User
|
||||||
|
existing_user = User.objects.filter(email=email).first()
|
||||||
|
|
||||||
|
if existing_user:
|
||||||
|
if not existing_user.is_active:
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ACCOUNT_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
params = exc.get_error_dict()
|
||||||
|
if next_path:
|
||||||
|
params["next_path"] = str(next_path)
|
||||||
|
url = urljoin(
|
||||||
|
base_host(request=request, is_space=True),
|
||||||
|
"?" + urlencode(params),
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
exc = AuthenticationException(
|
exc = AuthenticationException(
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
||||||
error_message="USER_ALREADY_EXIST",
|
error_message="USER_ALREADY_EXIST",
|
||||||
|
@ -90,11 +90,14 @@ class MagicSignInSpaceEndpoint(View):
|
|||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
if not User.objects.filter(email=email).exists():
|
existing_user = User.objects.filter(email=email).first()
|
||||||
params = {
|
|
||||||
"error_code": "USER_DOES_NOT_EXIST",
|
if not existing_user:
|
||||||
"error_message": "User could not be found with the given email.",
|
exc = AuthenticationException(
|
||||||
}
|
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
|
||||||
|
error_message="USER_DOES_NOT_EXIST",
|
||||||
|
)
|
||||||
|
params = exc.get_error_dict()
|
||||||
if next_path:
|
if next_path:
|
||||||
params["next_path"] = str(next_path)
|
params["next_path"] = str(next_path)
|
||||||
url = urljoin(
|
url = urljoin(
|
||||||
@ -103,6 +106,22 @@ class MagicSignInSpaceEndpoint(View):
|
|||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
|
# Active User
|
||||||
|
if not existing_user.is_active:
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ACCOUNT_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
params = exc.get_error_dict()
|
||||||
|
if next_path:
|
||||||
|
params["next_path"] = str(next_path)
|
||||||
|
url = urljoin(
|
||||||
|
base_host(request=request, is_space=True),
|
||||||
|
"?" + urlencode(params),
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
try:
|
try:
|
||||||
provider = MagicCodeProvider(
|
provider = MagicCodeProvider(
|
||||||
request=request, key=f"magic_{email}", code=code
|
request=request, key=f"magic_{email}", code=code
|
||||||
@ -155,8 +174,25 @@ class MagicSignUpSpaceEndpoint(View):
|
|||||||
"?" + urlencode(params),
|
"?" + urlencode(params),
|
||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
|
# Existing User
|
||||||
|
existing_user = User.objects.filter(email=email).first()
|
||||||
|
if existing_user:
|
||||||
|
if not existing_user.is_active:
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"USER_ACCOUNT_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="USER_ACCOUNT_DEACTIVATED",
|
||||||
|
)
|
||||||
|
params = exc.get_error_dict()
|
||||||
|
if next_path:
|
||||||
|
params["next_path"] = str(next_path)
|
||||||
|
url = urljoin(
|
||||||
|
base_host(request=request, is_space=True),
|
||||||
|
"?" + urlencode(params),
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
if User.objects.filter(email=email).exists():
|
|
||||||
exc = AuthenticationException(
|
exc = AuthenticationException(
|
||||||
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
|
||||||
error_message="USER_ALREADY_EXIST",
|
error_message="USER_ALREADY_EXIST",
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
# Python imports
|
|
||||||
from urllib.parse import urlencode, urljoin
|
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.contrib.auth import logout
|
from django.contrib.auth import logout
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
from django.core.mail import EmailMultiAlternatives, get_connection
|
from django.core.mail import EmailMultiAlternatives, get_connection
|
||||||
from django.core.management import BaseCommand, CommandError
|
from django.core.management import BaseCommand, CommandError
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.html import strip_tags
|
||||||
|
|
||||||
|
# Module imports
|
||||||
from plane.license.utils.instance_value import get_email_configuration
|
from plane.license.utils.instance_value import get_email_configuration
|
||||||
|
|
||||||
|
|
||||||
@ -37,10 +40,10 @@ class Command(BaseCommand):
|
|||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
# Prepare email details
|
# Prepare email details
|
||||||
subject = "Email Notification from Plane"
|
subject = "Test email from Plane"
|
||||||
message = (
|
|
||||||
"This is a sample email notification sent from Plane application."
|
html_content = render_to_string("emails/test_email.html")
|
||||||
)
|
text_content = strip_tags(html_content)
|
||||||
|
|
||||||
self.stdout.write(self.style.SUCCESS("Trying to send test email..."))
|
self.stdout.write(self.style.SUCCESS("Trying to send test email..."))
|
||||||
|
|
||||||
@ -48,11 +51,14 @@ class Command(BaseCommand):
|
|||||||
try:
|
try:
|
||||||
msg = EmailMultiAlternatives(
|
msg = EmailMultiAlternatives(
|
||||||
subject=subject,
|
subject=subject,
|
||||||
body=message,
|
body=text_content,
|
||||||
from_email=EMAIL_FROM,
|
from_email=EMAIL_FROM,
|
||||||
to=[receiver_email],
|
to=[
|
||||||
|
receiver_email,
|
||||||
|
],
|
||||||
connection=connection,
|
connection=connection,
|
||||||
)
|
)
|
||||||
|
msg.attach_alternative(html_content, "text/html")
|
||||||
msg.send()
|
msg.send()
|
||||||
self.stdout.write(self.style.SUCCESS("Email successfully sent"))
|
self.stdout.write(self.style.SUCCESS("Email successfully sent"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -16,4 +16,5 @@ from .admin import (
|
|||||||
InstanceAdminSignUpEndpoint,
|
InstanceAdminSignUpEndpoint,
|
||||||
InstanceAdminUserMeEndpoint,
|
InstanceAdminUserMeEndpoint,
|
||||||
InstanceAdminSignOutEndpoint,
|
InstanceAdminSignOutEndpoint,
|
||||||
|
InstanceAdminUserSessionEndpoint,
|
||||||
)
|
)
|
||||||
|
@ -316,6 +316,20 @@ class InstanceAdminSignInEndpoint(View):
|
|||||||
# Fetch the user
|
# Fetch the user
|
||||||
user = User.objects.filter(email=email).first()
|
user = User.objects.filter(email=email).first()
|
||||||
|
|
||||||
|
# is_active
|
||||||
|
if not user.is_active:
|
||||||
|
exc = AuthenticationException(
|
||||||
|
error_code=AUTHENTICATION_ERROR_CODES[
|
||||||
|
"ADMIN_USER_DEACTIVATED"
|
||||||
|
],
|
||||||
|
error_message="ADMIN_USER_DEACTIVATED",
|
||||||
|
)
|
||||||
|
url = urljoin(
|
||||||
|
base_host(request=request, is_admin=True),
|
||||||
|
"?" + urlencode(exc.get_error_dict()),
|
||||||
|
)
|
||||||
|
return HttpResponseRedirect(url)
|
||||||
|
|
||||||
# Error out if the user is not present
|
# Error out if the user is not present
|
||||||
if not user:
|
if not user:
|
||||||
exc = AuthenticationException(
|
exc = AuthenticationException(
|
||||||
@ -395,6 +409,30 @@ class InstanceAdminUserMeEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceAdminUserSessionEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
AllowAny,
|
||||||
|
]
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
if (
|
||||||
|
request.user.is_authenticated
|
||||||
|
and InstanceAdmin.objects.filter(user=request.user).exists()
|
||||||
|
):
|
||||||
|
serializer = InstanceAdminMeSerializer(request.user)
|
||||||
|
data = {"is_authenticated": True}
|
||||||
|
data["user"] = serializer.data
|
||||||
|
return Response(
|
||||||
|
data,
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
{"is_authenticated": False}, status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InstanceAdminSignOutEndpoint(View):
|
class InstanceAdminSignOutEndpoint(View):
|
||||||
|
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
|
@ -10,6 +10,7 @@ from plane.license.api.views import (
|
|||||||
SignUpScreenVisitedEndpoint,
|
SignUpScreenVisitedEndpoint,
|
||||||
InstanceAdminUserMeEndpoint,
|
InstanceAdminUserMeEndpoint,
|
||||||
InstanceAdminSignOutEndpoint,
|
InstanceAdminSignOutEndpoint,
|
||||||
|
InstanceAdminUserSessionEndpoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -28,6 +29,11 @@ urlpatterns = [
|
|||||||
InstanceAdminUserMeEndpoint.as_view(),
|
InstanceAdminUserMeEndpoint.as_view(),
|
||||||
name="instance-admins",
|
name="instance-admins",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"admins/session/",
|
||||||
|
InstanceAdminUserSessionEndpoint.as_view(),
|
||||||
|
name="instance-admin-session",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"admins/sign-out/",
|
"admins/sign-out/",
|
||||||
InstanceAdminSignOutEndpoint.as_view(),
|
InstanceAdminSignOutEndpoint.as_view(),
|
||||||
|
6
apiserver/templates/emails/test_email.html
Normal file
6
apiserver/templates/emails/test_email.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html>
|
||||||
|
<p>This is a test email sent to verify if email configuration is working as expected in your Plane instance.</p>
|
||||||
|
|
||||||
|
<p>Regards,</br> Team Plane </p>
|
||||||
|
</html>
|
@ -28,7 +28,7 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location /god-mode/ {
|
location /god-mode/ {
|
||||||
proxy_pass http://admin:3000/god-mode/;
|
proxy_pass http://admin:3001/god-mode/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
@ -45,7 +45,7 @@ http {
|
|||||||
|
|
||||||
location /spaces/ {
|
location /spaces/ {
|
||||||
rewrite ^/spaces/?$ /spaces/login break;
|
rewrite ^/spaces/?$ /spaces/login break;
|
||||||
proxy_pass http://space:4000/spaces/;
|
proxy_pass http://space:3002/spaces/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /${BUCKET_NAME}/ {
|
location /${BUCKET_NAME}/ {
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
"packages/tailwind-config-custom",
|
"packages/tailwind-config-custom",
|
||||||
"packages/tsconfig",
|
"packages/tsconfig",
|
||||||
"packages/ui",
|
"packages/ui",
|
||||||
"packages/types"
|
"packages/types",
|
||||||
|
"packages/constants"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
@ -25,7 +26,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^10.4.15",
|
"autoprefixer": "^10.4.15",
|
||||||
"eslint-config-custom": "*",
|
"eslint-config-custom": "*",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.29",
|
||||||
"prettier": "latest",
|
"prettier": "latest",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.4",
|
"prettier-plugin-tailwindcss": "^0.5.4",
|
||||||
"tailwindcss": "^3.3.3",
|
"tailwindcss": "^3.3.3",
|
||||||
|
10
packages/constants/package.json
Normal file
10
packages/constants/package.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "@plane/constants",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"main": "./src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
".": "./src/index.ts",
|
||||||
|
"./*": "./src/*"
|
||||||
|
}
|
||||||
|
}
|
371
packages/constants/src/auth.ts
Normal file
371
packages/constants/src/auth.ts
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
export enum EPageTypes {
|
||||||
|
PUBLIC = "PUBLIC",
|
||||||
|
NON_AUTHENTICATED = "NON_AUTHENTICATED",
|
||||||
|
SET_PASSWORD = "SET_PASSWORD",
|
||||||
|
ONBOARDING = "ONBOARDING",
|
||||||
|
AUTHENTICATED = "AUTHENTICATED",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EAuthModes {
|
||||||
|
SIGN_IN = "SIGN_IN",
|
||||||
|
SIGN_UP = "SIGN_UP",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EAuthSteps {
|
||||||
|
EMAIL = "EMAIL",
|
||||||
|
PASSWORD = "PASSWORD",
|
||||||
|
UNIQUE_CODE = "UNIQUE_CODE",
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: remove this
|
||||||
|
export enum EErrorAlertType {
|
||||||
|
BANNER_ALERT = "BANNER_ALERT",
|
||||||
|
INLINE_FIRST_NAME = "INLINE_FIRST_NAME",
|
||||||
|
INLINE_EMAIL = "INLINE_EMAIL",
|
||||||
|
INLINE_PASSWORD = "INLINE_PASSWORD",
|
||||||
|
INLINE_EMAIL_CODE = "INLINE_EMAIL_CODE",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EAuthErrorCodes {
|
||||||
|
// Global
|
||||||
|
INSTANCE_NOT_CONFIGURED = "5000",
|
||||||
|
INVALID_EMAIL = "5005",
|
||||||
|
EMAIL_REQUIRED = "5010",
|
||||||
|
SIGNUP_DISABLED = "5015",
|
||||||
|
MAGIC_LINK_LOGIN_DISABLED = "5017",
|
||||||
|
PASSWORD_LOGIN_DISABLED = "5019",
|
||||||
|
SMTP_NOT_CONFIGURED = "5025",
|
||||||
|
// Password strength
|
||||||
|
INVALID_PASSWORD = "5020",
|
||||||
|
// Sign Up
|
||||||
|
USER_ACCOUNT_DEACTIVATED = "5019",
|
||||||
|
USER_ALREADY_EXIST = "5030",
|
||||||
|
AUTHENTICATION_FAILED_SIGN_UP = "5035",
|
||||||
|
REQUIRED_EMAIL_PASSWORD_SIGN_UP = "5040",
|
||||||
|
INVALID_EMAIL_SIGN_UP = "5045",
|
||||||
|
INVALID_EMAIL_MAGIC_SIGN_UP = "5050",
|
||||||
|
MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED = "5055",
|
||||||
|
// Sign In
|
||||||
|
USER_DOES_NOT_EXIST = "5060",
|
||||||
|
AUTHENTICATION_FAILED_SIGN_IN = "5065",
|
||||||
|
REQUIRED_EMAIL_PASSWORD_SIGN_IN = "5070",
|
||||||
|
INVALID_EMAIL_SIGN_IN = "5075",
|
||||||
|
INVALID_EMAIL_MAGIC_SIGN_IN = "5080",
|
||||||
|
MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED = "5085",
|
||||||
|
// Both Sign in and Sign up for magic
|
||||||
|
INVALID_MAGIC_CODE = "5090",
|
||||||
|
EXPIRED_MAGIC_CODE = "5095",
|
||||||
|
EMAIL_CODE_ATTEMPT_EXHAUSTED = "5100",
|
||||||
|
// Oauth
|
||||||
|
GOOGLE_NOT_CONFIGURED = "5105",
|
||||||
|
GITHUB_NOT_CONFIGURED = "5110",
|
||||||
|
GOOGLE_OAUTH_PROVIDER_ERROR = "5115",
|
||||||
|
GITHUB_OAUTH_PROVIDER_ERROR = "5120",
|
||||||
|
// Reset Password
|
||||||
|
INVALID_PASSWORD_TOKEN = "5125",
|
||||||
|
EXPIRED_PASSWORD_TOKEN = "5130",
|
||||||
|
// Change password
|
||||||
|
INCORRECT_OLD_PASSWORD = "5135",
|
||||||
|
MISSING_PASSWORD= "5138",
|
||||||
|
INVALID_NEW_PASSWORD = "5140",
|
||||||
|
// set passowrd
|
||||||
|
PASSWORD_ALREADY_SET = "5145",
|
||||||
|
// Admin
|
||||||
|
ADMIN_ALREADY_EXIST = "5150",
|
||||||
|
REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME = "5155",
|
||||||
|
INVALID_ADMIN_EMAIL = "5160",
|
||||||
|
INVALID_ADMIN_PASSWORD = "5165",
|
||||||
|
REQUIRED_ADMIN_EMAIL_PASSWORD = "5170",
|
||||||
|
ADMIN_AUTHENTICATION_FAILED = "5175",
|
||||||
|
ADMIN_USER_ALREADY_EXIST = "5180",
|
||||||
|
ADMIN_USER_DOES_NOT_EXIST = "5185",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TAuthErrorInfo = {
|
||||||
|
type: EErrorAlertType;
|
||||||
|
code: EAuthErrorCodes;
|
||||||
|
title: string;
|
||||||
|
message: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const errorCodeMessages: {
|
||||||
|
[key in EAuthErrorCodes]: { title: string; message: (email?: string | undefined) => ReactNode };
|
||||||
|
} = {
|
||||||
|
// global
|
||||||
|
[EAuthErrorCodes.INSTANCE_NOT_CONFIGURED]: {
|
||||||
|
title: `Instance not configured`,
|
||||||
|
message: () => `Instance not configured. Please contact your administrator.`,
|
||||||
|
},
|
||||||
|
[EAuthErrorCodes.SIGNUP_DISABLED]: {
|
||||||
|
title: `Sign up disabled`,
|
||||||
|
message: () => `Sign up disabled. Please contact your administrator.`,
|
||||||
|
},
|
||||||
|
[EAuthErrorCodes.INVALID_PASSWORD]: {
|
||||||
|
title: `Invalid password`,
|
||||||
|
message: () => `Invalid password. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthErrorCodes.SMTP_NOT_CONFIGURED]: {
|
||||||
|
title: `SMTP not configured`,
|
||||||
|
message: () => `SMTP not configured. Please contact your administrator.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// email check in both sign up and sign in
|
||||||
|
[EAuthErrorCodes.INVALID_EMAIL]: {
|
||||||
|
title: `Invalid email`,
|
||||||
|
message: () => `Invalid email. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.EMAIL_REQUIRED]: {
|
||||||
|
title: `Email required`,
|
||||||
|
message: () => `Email required. Please try again.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// sign up
|
||||||
|
[EAuthenticationErrorCodes.USER_ALREADY_EXIST]: {
|
||||||
|
title: `User already exists`,
|
||||||
|
message: (email = undefined) => (
|
||||||
|
<div>
|
||||||
|
Your account is already registered.
|
||||||
|
<Link
|
||||||
|
className="underline underline-offset-4 font-medium hover:font-bold transition-all"
|
||||||
|
href={`/sign-in${email ? `?email=${email}` : ``}`}
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
now.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP]: {
|
||||||
|
title: `Email and password required`,
|
||||||
|
message: () => `Email and password required. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP]: {
|
||||||
|
title: `Authentication failed`,
|
||||||
|
message: () => `Authentication failed. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP]: {
|
||||||
|
title: `Invalid email`,
|
||||||
|
message: () => `Invalid email. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED]: {
|
||||||
|
title: `Email and code required`,
|
||||||
|
message: () => `Email and code required. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP]: {
|
||||||
|
title: `Invalid email`,
|
||||||
|
message: () => `Invalid email. Please try again.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// sign in
|
||||||
|
[EAuthenticationErrorCodes.USER_ACCOUNT_DEACTIVATED]: {
|
||||||
|
title: `User account deactivated`,
|
||||||
|
message: () => <div>Your account is deactivated. Contact support@plane.so.</div>,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
|
||||||
|
title: `User does not exist`,
|
||||||
|
message: (email = undefined) => (
|
||||||
|
<div>
|
||||||
|
No account found.
|
||||||
|
<Link
|
||||||
|
className="underline underline-offset-4 font-medium hover:font-bold transition-all"
|
||||||
|
href={`/${email ? `?email=${email}` : ``}`}
|
||||||
|
>
|
||||||
|
Create one
|
||||||
|
</Link>
|
||||||
|
to get started.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN]: {
|
||||||
|
title: `Email and password required`,
|
||||||
|
message: () => `Email and password required. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN]: {
|
||||||
|
title: `Authentication failed`,
|
||||||
|
message: () => `Authentication failed. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN]: {
|
||||||
|
title: `Invalid email`,
|
||||||
|
message: () => `Invalid email. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED]: {
|
||||||
|
title: `Email and code required`,
|
||||||
|
message: () => `Email and code required. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN]: {
|
||||||
|
title: `Invalid email`,
|
||||||
|
message: () => `Invalid email. Please try again.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Both Sign in and Sign up
|
||||||
|
[EAuthenticationErrorCodes.INVALID_MAGIC_CODE]: {
|
||||||
|
title: `Authentication failed`,
|
||||||
|
message: () => `Invalid magic code. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE]: {
|
||||||
|
title: `Expired magic code`,
|
||||||
|
message: () => `Expired magic code. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED]: {
|
||||||
|
title: `Expired magic code`,
|
||||||
|
message: () => `Expired magic code. Please try again.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Oauth
|
||||||
|
[EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED]: {
|
||||||
|
title: `Google not configured`,
|
||||||
|
message: () => `Google not configured. Please contact your administrator.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED]: {
|
||||||
|
title: `GitHub not configured`,
|
||||||
|
message: () => `GitHub not configured. Please contact your administrator.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR]: {
|
||||||
|
title: `Google OAuth provider error`,
|
||||||
|
message: () => `Google OAuth provider error. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR]: {
|
||||||
|
title: `GitHub OAuth provider error`,
|
||||||
|
message: () => `GitHub OAuth provider error. Please try again.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Reset Password
|
||||||
|
[EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN]: {
|
||||||
|
title: `Invalid password token`,
|
||||||
|
message: () => `Invalid password token. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN]: {
|
||||||
|
title: `Expired password token`,
|
||||||
|
message: () => `Expired password token. Please try again.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Change password
|
||||||
|
|
||||||
|
[EAuthenticationErrorCodes.MISSING_PASSWORD]: {
|
||||||
|
title: `Password required`,
|
||||||
|
message: () => `Password required. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD]: {
|
||||||
|
title: `Incorrect old password`,
|
||||||
|
message: () => `Incorrect old password. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.INVALID_NEW_PASSWORD]: {
|
||||||
|
title: `Invalid new password`,
|
||||||
|
message: () => `Invalid new password. Please try again.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// set password
|
||||||
|
[EAuthenticationErrorCodes.PASSWORD_ALREADY_SET]: {
|
||||||
|
title: `Password already set`,
|
||||||
|
message: () => `Password already set. Please try again.`,
|
||||||
|
},
|
||||||
|
|
||||||
|
// admin
|
||||||
|
[EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST]: {
|
||||||
|
title: `Admin already exists`,
|
||||||
|
message: () => `Admin already exists. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME]: {
|
||||||
|
title: `Email, password and first name required`,
|
||||||
|
message: () => `Email, password and first name required. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL]: {
|
||||||
|
title: `Invalid admin email`,
|
||||||
|
message: () => `Invalid admin email. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD]: {
|
||||||
|
title: `Invalid admin password`,
|
||||||
|
message: () => `Invalid admin password. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD]: {
|
||||||
|
title: `Email and password required`,
|
||||||
|
message: () => `Email and password required. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED]: {
|
||||||
|
title: `Authentication failed`,
|
||||||
|
message: () => `Authentication failed. Please try again.`,
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST]: {
|
||||||
|
title: `Admin user already exists`,
|
||||||
|
message: () => (
|
||||||
|
<div>
|
||||||
|
Admin user already exists.
|
||||||
|
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
now.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
[EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: {
|
||||||
|
title: `Admin user does not exist`,
|
||||||
|
message: () => (
|
||||||
|
<div>
|
||||||
|
Admin user does not exist.
|
||||||
|
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
|
||||||
|
Sign In
|
||||||
|
</Link>
|
||||||
|
now.
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const authErrorHandler = (
|
||||||
|
errorCode: EAuthenticationErrorCodes,
|
||||||
|
email?: string | undefined
|
||||||
|
): TAuthErrorInfo | undefined => {
|
||||||
|
const bannerAlertErrorCodes = [
|
||||||
|
EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED,
|
||||||
|
EAuthenticationErrorCodes.INVALID_EMAIL,
|
||||||
|
EAuthenticationErrorCodes.EMAIL_REQUIRED,
|
||||||
|
EAuthenticationErrorCodes.SIGNUP_DISABLED,
|
||||||
|
EAuthenticationErrorCodes.INVALID_PASSWORD,
|
||||||
|
EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED,
|
||||||
|
EAuthenticationErrorCodes.USER_ALREADY_EXIST,
|
||||||
|
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP,
|
||||||
|
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP,
|
||||||
|
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP,
|
||||||
|
EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP,
|
||||||
|
EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED,
|
||||||
|
EAuthenticationErrorCodes.USER_DOES_NOT_EXIST,
|
||||||
|
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN,
|
||||||
|
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN,
|
||||||
|
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN,
|
||||||
|
EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN,
|
||||||
|
EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED,
|
||||||
|
EAuthenticationErrorCodes.INVALID_MAGIC_CODE,
|
||||||
|
EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE,
|
||||||
|
EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED,
|
||||||
|
EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED,
|
||||||
|
EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED,
|
||||||
|
EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR,
|
||||||
|
EAuthenticationErrorCodes.GITHUB_OAUTH_PROVIDER_ERROR,
|
||||||
|
EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN,
|
||||||
|
EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN,
|
||||||
|
EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD,
|
||||||
|
EAuthenticationErrorCodes.INVALID_NEW_PASSWORD,
|
||||||
|
EAuthenticationErrorCodes.PASSWORD_ALREADY_SET,
|
||||||
|
EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST,
|
||||||
|
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME,
|
||||||
|
EAuthenticationErrorCodes.INVALID_ADMIN_EMAIL,
|
||||||
|
EAuthenticationErrorCodes.INVALID_ADMIN_PASSWORD,
|
||||||
|
EAuthenticationErrorCodes.REQUIRED_ADMIN_EMAIL_PASSWORD,
|
||||||
|
EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED,
|
||||||
|
EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST,
|
||||||
|
EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (bannerAlertErrorCodes.includes(errorCode))
|
||||||
|
return {
|
||||||
|
type: EErrorAlertType.BANNER_ALERT,
|
||||||
|
code: errorCode,
|
||||||
|
title: errorCodeMessages[errorCode]?.title || "Error",
|
||||||
|
message: errorCodeMessages[errorCode]?.message(email) || "Something went wrong. Please try again.",
|
||||||
|
};
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
1
packages/constants/src/index.ts
Normal file
1
packages/constants/src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./auth";
|
@ -65,6 +65,8 @@ export const AuthRoot = observer(() => {
|
|||||||
const { config: instanceConfig } = useInstance();
|
const { config: instanceConfig } = useInstance();
|
||||||
// derived values
|
// derived values
|
||||||
const isSmtpConfigured = instanceConfig?.is_smtp_configured;
|
const isSmtpConfigured = instanceConfig?.is_smtp_configured;
|
||||||
|
const isMagicLoginEnabled = instanceConfig?.is_magic_login_enabled;
|
||||||
|
const isEmailPasswordEnabled = instanceConfig?.is_email_password_enabled;
|
||||||
|
|
||||||
const { header, subHeader } = getHeaderSubHeader(authMode);
|
const { header, subHeader } = getHeaderSubHeader(authMode);
|
||||||
|
|
||||||
@ -87,9 +89,9 @@ export const AuthRoot = observer(() => {
|
|||||||
setAuthStep(EAuthSteps.PASSWORD);
|
setAuthStep(EAuthSteps.PASSWORD);
|
||||||
} else {
|
} else {
|
||||||
// Else if SMTP is configured, move to unique code sign-in/ sign-up.
|
// Else if SMTP is configured, move to unique code sign-in/ sign-up.
|
||||||
if (isSmtpConfigured) {
|
if (isSmtpConfigured && isMagicLoginEnabled) {
|
||||||
setAuthStep(EAuthSteps.UNIQUE_CODE);
|
setAuthStep(EAuthSteps.UNIQUE_CODE);
|
||||||
} else {
|
} else if (isEmailPasswordEnabled) {
|
||||||
// Else show error message if SMTP is not configured and password is not set.
|
// Else show error message if SMTP is not configured and password is not set.
|
||||||
if (res.existing) {
|
if (res.existing) {
|
||||||
setAuthMode(null);
|
setAuthMode(null);
|
||||||
|
@ -151,6 +151,7 @@ export const UniqueCodeForm: React.FC<Props> = (props) => {
|
|||||||
placeholder="gets-sets-flys"
|
placeholder="gets-sets-flys"
|
||||||
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||||
autoFocus
|
autoFocus
|
||||||
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
<div className="flex w-full items-center justify-between px-1 text-xs">
|
<div className="flex w-full items-center justify-between px-1 text-xs">
|
||||||
<p className="flex items-center gap-1 font-medium text-green-700">
|
<p className="flex items-center gap-1 font-medium text-green-700">
|
||||||
|
@ -40,7 +40,7 @@ const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspaceSlug && projectId && settings) {
|
if (workspaceSlug && projectId && settings) {
|
||||||
const viewsAcceptable: string[] = [];
|
const viewsAcceptable: string[] = [];
|
||||||
let currentBoard: TIssueBoardKeys | null = null;
|
const currentBoard: TIssueBoardKeys | null = null;
|
||||||
|
|
||||||
if (settings?.views?.list) viewsAcceptable.push("list");
|
if (settings?.views?.list) viewsAcceptable.push("list");
|
||||||
if (settings?.views?.kanban) viewsAcceptable.push("kanban");
|
if (settings?.views?.kanban) viewsAcceptable.push("kanban");
|
||||||
@ -48,19 +48,19 @@ const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
|
|||||||
if (settings?.views?.gantt) viewsAcceptable.push("gantt");
|
if (settings?.views?.gantt) viewsAcceptable.push("gantt");
|
||||||
if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet");
|
if (settings?.views?.spreadsheet) viewsAcceptable.push("spreadsheet");
|
||||||
|
|
||||||
if (board) {
|
// if (board) {
|
||||||
if (viewsAcceptable.includes(board.toString())) {
|
// if (viewsAcceptable.includes(board.toString())) {
|
||||||
currentBoard = board.toString() as TIssueBoardKeys;
|
// currentBoard = board.toString() as TIssueBoardKeys;
|
||||||
} else {
|
// } else {
|
||||||
if (viewsAcceptable && viewsAcceptable.length > 0) {
|
// if (viewsAcceptable && viewsAcceptable.length > 0) {
|
||||||
currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
if (viewsAcceptable && viewsAcceptable.length > 0) {
|
// if (viewsAcceptable && viewsAcceptable.length > 0) {
|
||||||
currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
// currentBoard = viewsAcceptable[0] as TIssueBoardKeys;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (currentBoard) {
|
if (currentBoard) {
|
||||||
if (activeLayout === null || activeLayout !== currentBoard) {
|
if (activeLayout === null || activeLayout !== currentBoard) {
|
||||||
|
@ -39,12 +39,12 @@ export enum EAuthenticationErrorCodes {
|
|||||||
INVALID_EMAIL = "5012",
|
INVALID_EMAIL = "5012",
|
||||||
EMAIL_REQUIRED = "5013",
|
EMAIL_REQUIRED = "5013",
|
||||||
// Sign Up
|
// Sign Up
|
||||||
|
USER_ACCOUNT_DEACTIVATED = "5019",
|
||||||
USER_ALREADY_EXIST = "5003",
|
USER_ALREADY_EXIST = "5003",
|
||||||
REQUIRED_EMAIL_PASSWORD_SIGN_UP = "5015",
|
REQUIRED_EMAIL_PASSWORD_SIGN_UP = "5015",
|
||||||
AUTHENTICATION_FAILED_SIGN_UP = "5006",
|
AUTHENTICATION_FAILED_SIGN_UP = "5006",
|
||||||
INVALID_EMAIL_SIGN_UP = "5017",
|
INVALID_EMAIL_SIGN_UP = "5017",
|
||||||
MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED = "5023",
|
MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED = "5023",
|
||||||
INVALID_EMAIL_MAGIC_SIGN_UP = "5019",
|
|
||||||
// Sign In
|
// Sign In
|
||||||
USER_DOES_NOT_EXIST = "5004",
|
USER_DOES_NOT_EXIST = "5004",
|
||||||
REQUIRED_EMAIL_PASSWORD_SIGN_IN = "5014",
|
REQUIRED_EMAIL_PASSWORD_SIGN_IN = "5014",
|
||||||
@ -140,12 +140,14 @@ const errorCodeMessages: {
|
|||||||
title: `Email and code required`,
|
title: `Email and code required`,
|
||||||
message: () => `Email and code required. Please try again.`,
|
message: () => `Email and code required. Please try again.`,
|
||||||
},
|
},
|
||||||
[EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP]: {
|
|
||||||
title: `Invalid email`,
|
|
||||||
message: () => `Invalid email. Please try again.`,
|
|
||||||
},
|
|
||||||
|
|
||||||
// sign in
|
// sign in
|
||||||
|
|
||||||
|
[EAuthenticationErrorCodes.USER_ACCOUNT_DEACTIVATED]: {
|
||||||
|
title: `User account deactivated`,
|
||||||
|
message: () => <div>Your account is deactivated. Please reach out to support@plane.so</div>,
|
||||||
|
},
|
||||||
|
|
||||||
[EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
|
[EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
|
||||||
title: `User does not exist`,
|
title: `User does not exist`,
|
||||||
message: (email = undefined) => (
|
message: (email = undefined) => (
|
||||||
@ -250,7 +252,6 @@ export const authErrorHandler = (
|
|||||||
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP,
|
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP,
|
||||||
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP,
|
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP,
|
||||||
EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED,
|
EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED,
|
||||||
EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP,
|
|
||||||
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN,
|
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN,
|
||||||
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN,
|
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN,
|
||||||
EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN,
|
EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN,
|
||||||
@ -273,6 +274,7 @@ export const authErrorHandler = (
|
|||||||
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP,
|
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP,
|
||||||
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN,
|
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN,
|
||||||
EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED,
|
EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED,
|
||||||
|
EAuthenticationErrorCodes.USER_ACCOUNT_DEACTIVATED,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (toastAlertErrorCodes.includes(errorCode))
|
if (toastAlertErrorCodes.includes(errorCode))
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"@plane/rich-text-editor": "*",
|
"@plane/rich-text-editor": "*",
|
||||||
"@plane/types": "*",
|
"@plane/types": "*",
|
||||||
"@plane/ui": "*",
|
"@plane/ui": "*",
|
||||||
|
"@plane/constants": "*",
|
||||||
"@sentry/nextjs": "^7.108.0",
|
"@sentry/nextjs": "^7.108.0",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
@ -68,6 +68,10 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
|
|||||||
}
|
}
|
||||||
}, [error_code, authMode]);
|
}, [error_code, authMode]);
|
||||||
|
|
||||||
|
const isSMTPConfigured = instance?.config?.is_smtp_configured || false;
|
||||||
|
const isMagicLoginEnabled = instance?.config?.is_magic_login_enabled || false;
|
||||||
|
const isEmailPasswordEnabled = instance?.config?.is_email_password_enabled || false;
|
||||||
|
|
||||||
// submit handler- email verification
|
// submit handler- email verification
|
||||||
const handleEmailVerification = async (data: IEmailCheckData) => {
|
const handleEmailVerification = async (data: IEmailCheckData) => {
|
||||||
setEmail(data.email);
|
setEmail(data.email);
|
||||||
@ -80,19 +84,21 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
|
|||||||
if (response.is_password_autoset) {
|
if (response.is_password_autoset) {
|
||||||
setAuthStep(EAuthSteps.UNIQUE_CODE);
|
setAuthStep(EAuthSteps.UNIQUE_CODE);
|
||||||
generateEmailUniqueCode(data.email);
|
generateEmailUniqueCode(data.email);
|
||||||
} else {
|
} else if (isEmailPasswordEnabled) {
|
||||||
setIsPasswordAutoset(false);
|
setIsPasswordAutoset(false);
|
||||||
setAuthStep(EAuthSteps.PASSWORD);
|
setAuthStep(EAuthSteps.PASSWORD);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (instance && instance?.config?.is_smtp_configured) {
|
if (isSMTPConfigured && isMagicLoginEnabled) {
|
||||||
setAuthStep(EAuthSteps.UNIQUE_CODE);
|
setAuthStep(EAuthSteps.UNIQUE_CODE);
|
||||||
generateEmailUniqueCode(data.email);
|
generateEmailUniqueCode(data.email);
|
||||||
} else setAuthStep(EAuthSteps.PASSWORD);
|
} else if (isEmailPasswordEnabled) {
|
||||||
|
setAuthStep(EAuthSteps.PASSWORD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const errorhandler = authErrorHandler(error?.error_code.toString(), data?.email || undefined);
|
const errorhandler = authErrorHandler(error?.error_code?.toString(), data?.email || undefined);
|
||||||
if (errorhandler?.type) setErrorInfo(errorhandler);
|
if (errorhandler?.type) setErrorInfo(errorhandler);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -113,8 +119,6 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
|
|||||||
const isOAuthEnabled =
|
const isOAuthEnabled =
|
||||||
(instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled)) || false;
|
(instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled)) || false;
|
||||||
|
|
||||||
const isSMTPConfigured = (instance?.config && instance?.config?.is_smtp_configured) || false;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col space-y-6">
|
<div className="relative flex flex-col space-y-6">
|
||||||
<AuthHeader
|
<AuthHeader
|
||||||
|
@ -8,7 +8,7 @@ import { Button, CustomSelect, Input, Spinner, TOAST_TYPE, setToast } from "@pla
|
|||||||
import { WORKSPACE_CREATED } from "@/constants/event-tracker";
|
import { WORKSPACE_CREATED } from "@/constants/event-tracker";
|
||||||
import { ORGANIZATION_SIZE, RESTRICTED_URLS } from "@/constants/workspace";
|
import { ORGANIZATION_SIZE, RESTRICTED_URLS } from "@/constants/workspace";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useUser, useWorkspace } from "@/hooks/store";
|
import { useEventTracker, useUserProfile, useWorkspace } from "@/hooks/store";
|
||||||
// services
|
// services
|
||||||
import { WorkspaceService } from "@/services/workspace.service";
|
import { WorkspaceService } from "@/services/workspace.service";
|
||||||
|
|
||||||
@ -28,7 +28,8 @@ export const CreateWorkspace: React.FC<Props> = (props) => {
|
|||||||
const [slugError, setSlugError] = useState(false);
|
const [slugError, setSlugError] = useState(false);
|
||||||
const [invalidSlug, setInvalidSlug] = useState(false);
|
const [invalidSlug, setInvalidSlug] = useState(false);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { updateCurrentUser } = useUser();
|
const { updateUserProfile } = useUserProfile();
|
||||||
|
|
||||||
const { createWorkspace, fetchWorkspaces, workspaces } = useWorkspace();
|
const { createWorkspace, fetchWorkspaces, workspaces } = useWorkspace();
|
||||||
const { captureWorkspaceEvent } = useEventTracker();
|
const { captureWorkspaceEvent } = useEventTracker();
|
||||||
// form info
|
// form info
|
||||||
@ -111,7 +112,7 @@ export const CreateWorkspace: React.FC<Props> = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await stepChange(payload);
|
await stepChange(payload);
|
||||||
await updateCurrentUser({
|
await updateUserProfile({
|
||||||
last_workspace_id: firstWorkspace?.id,
|
last_workspace_id: firstWorkspace?.id,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -62,7 +62,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
|||||||
const [isUpdateRequired, setIsUpdateRequired] = useState(false);
|
const [isUpdateRequired, setIsUpdateRequired] = useState(false);
|
||||||
|
|
||||||
// const plane_deploy_url = instance?.config?.space_base_url || "";
|
// const plane_deploy_url = instance?.config?.space_base_url || "";
|
||||||
const SPACE_URL = SPACE_BASE_URL + SPACE_BASE_PATH;
|
const SPACE_URL = (SPACE_BASE_URL === "" ? window.location.origin : SPACE_BASE_URL) + SPACE_BASE_PATH;
|
||||||
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -12,7 +12,7 @@ import { IWorkspace } from "@plane/types";
|
|||||||
// plane ui
|
// plane ui
|
||||||
import { Avatar, Loader, TOAST_TYPE, setToast } from "@plane/ui";
|
import { Avatar, Loader, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useAppTheme, useUser, useWorkspace } from "@/hooks/store";
|
import { useAppTheme, useUser, useUserProfile, useWorkspace } from "@/hooks/store";
|
||||||
import { WorkspaceLogo } from "./logo";
|
import { WorkspaceLogo } from "./logo";
|
||||||
// types
|
// types
|
||||||
// Static Data
|
// Static Data
|
||||||
@ -56,10 +56,12 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
const { sidebarCollapsed, toggleSidebar } = useAppTheme();
|
const { sidebarCollapsed, toggleSidebar } = useAppTheme();
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
const {
|
const {
|
||||||
updateCurrentUser,
|
// updateCurrentUser,
|
||||||
// isUserInstanceAdmin,
|
// isUserInstanceAdmin,
|
||||||
signOut,
|
signOut,
|
||||||
} = useUser();
|
} = useUser();
|
||||||
|
const { updateUserProfile } = useUserProfile();
|
||||||
|
|
||||||
const isUserInstanceAdmin = false;
|
const isUserInstanceAdmin = false;
|
||||||
const { currentWorkspace: activeWorkspace, workspaces } = useWorkspace();
|
const { currentWorkspace: activeWorkspace, workspaces } = useWorkspace();
|
||||||
// popper-js refs
|
// popper-js refs
|
||||||
@ -78,7 +80,7 @@ export const WorkspaceSidebarDropdown = observer(() => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
const handleWorkspaceNavigation = (workspace: IWorkspace) =>
|
const handleWorkspaceNavigation = (workspace: IWorkspace) =>
|
||||||
updateCurrentUser({
|
updateUserProfile({
|
||||||
last_workspace_id: workspace?.id,
|
last_workspace_id: workspace?.id,
|
||||||
});
|
});
|
||||||
const handleSignOut = async () => {
|
const handleSignOut = async () => {
|
||||||
|
@ -45,6 +45,7 @@ export enum EAuthenticationErrorCodes {
|
|||||||
INVALID_EMAIL_MAGIC_SIGN_UP = "5050",
|
INVALID_EMAIL_MAGIC_SIGN_UP = "5050",
|
||||||
MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED = "5055",
|
MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED = "5055",
|
||||||
// Sign In
|
// Sign In
|
||||||
|
USER_ACCOUNT_DEACTIVATED = "5019",
|
||||||
USER_DOES_NOT_EXIST = "5060",
|
USER_DOES_NOT_EXIST = "5060",
|
||||||
AUTHENTICATION_FAILED_SIGN_IN = "5065",
|
AUTHENTICATION_FAILED_SIGN_IN = "5065",
|
||||||
REQUIRED_EMAIL_PASSWORD_SIGN_IN = "5070",
|
REQUIRED_EMAIL_PASSWORD_SIGN_IN = "5070",
|
||||||
@ -65,6 +66,7 @@ export enum EAuthenticationErrorCodes {
|
|||||||
EXPIRED_PASSWORD_TOKEN = "5130",
|
EXPIRED_PASSWORD_TOKEN = "5130",
|
||||||
// Change password
|
// Change password
|
||||||
INCORRECT_OLD_PASSWORD = "5135",
|
INCORRECT_OLD_PASSWORD = "5135",
|
||||||
|
MISSING_PASSWORD = "5138",
|
||||||
INVALID_NEW_PASSWORD = "5140",
|
INVALID_NEW_PASSWORD = "5140",
|
||||||
// set passowrd
|
// set passowrd
|
||||||
PASSWORD_ALREADY_SET = "5145",
|
PASSWORD_ALREADY_SET = "5145",
|
||||||
@ -155,6 +157,11 @@ const errorCodeMessages: {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// sign in
|
// sign in
|
||||||
|
[EAuthenticationErrorCodes.USER_ACCOUNT_DEACTIVATED]: {
|
||||||
|
title: `User account deactivated`,
|
||||||
|
message: () => <div>Your account is deactivated. Contact support@plane.so.</div>,
|
||||||
|
},
|
||||||
|
|
||||||
[EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
|
[EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
|
||||||
title: `User does not exist`,
|
title: `User does not exist`,
|
||||||
message: (email = undefined) => (
|
message: (email = undefined) => (
|
||||||
@ -234,6 +241,10 @@ const errorCodeMessages: {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Change password
|
// Change password
|
||||||
|
[EAuthenticationErrorCodes.MISSING_PASSWORD]: {
|
||||||
|
title: `Password required`,
|
||||||
|
message: () => `Password required. Please try again.`,
|
||||||
|
},
|
||||||
[EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD]: {
|
[EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD]: {
|
||||||
title: `Incorrect old password`,
|
title: `Incorrect old password`,
|
||||||
message: () => `Incorrect old password. Please try again.`,
|
message: () => `Incorrect old password. Please try again.`,
|
||||||
@ -343,6 +354,7 @@ export const authErrorHandler = (
|
|||||||
EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED,
|
EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED,
|
||||||
EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST,
|
EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST,
|
||||||
EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST,
|
EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST,
|
||||||
|
EAuthenticationErrorCodes.USER_ACCOUNT_DEACTIVATED,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (bannerAlertErrorCodes.includes(errorCode))
|
if (bannerAlertErrorCodes.includes(errorCode))
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
"@plane/rich-text-editor": "*",
|
"@plane/rich-text-editor": "*",
|
||||||
"@plane/types": "*",
|
"@plane/types": "*",
|
||||||
"@plane/ui": "*",
|
"@plane/ui": "*",
|
||||||
|
"@plane/constants": "*",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
"@sentry/nextjs": "^7.108.0",
|
"@sentry/nextjs": "^7.108.0",
|
||||||
"axios": "^1.1.3",
|
"axios": "^1.1.3",
|
||||||
|
@ -8,7 +8,7 @@ import { IWorkspace } from "@plane/types";
|
|||||||
// hooks
|
// hooks
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { CreateWorkspaceForm } from "@/components/workspace";
|
import { CreateWorkspaceForm } from "@/components/workspace";
|
||||||
import { useUser } from "@/hooks/store";
|
import { useUser, useUserProfile } from "@/hooks/store";
|
||||||
// layouts
|
// layouts
|
||||||
import DefaultLayout from "@/layouts/default-layout";
|
import DefaultLayout from "@/layouts/default-layout";
|
||||||
// components
|
// components
|
||||||
@ -25,7 +25,7 @@ const CreateWorkspacePage: NextPageWithLayout = observer(() => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// store hooks
|
// store hooks
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
const { updateCurrentUser } = useUser();
|
const { updateUserProfile } = useUserProfile();
|
||||||
// states
|
// states
|
||||||
const [defaultValues, setDefaultValues] = useState({
|
const [defaultValues, setDefaultValues] = useState({
|
||||||
name: "",
|
name: "",
|
||||||
@ -36,7 +36,7 @@ const CreateWorkspacePage: NextPageWithLayout = observer(() => {
|
|||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const onSubmit = async (workspace: IWorkspace) => {
|
const onSubmit = async (workspace: IWorkspace) => {
|
||||||
await updateCurrentUser({ last_workspace_id: workspace.id }).then(() => router.push(`/${workspace.slug}`));
|
await updateUserProfile({ last_workspace_id: workspace.id }).then(() => router.push(`/${workspace.slug}`));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -21,14 +21,13 @@ import { ROLE } from "@/constants/workspace";
|
|||||||
// helpers
|
// helpers
|
||||||
import { truncateText } from "@/helpers/string.helper";
|
import { truncateText } from "@/helpers/string.helper";
|
||||||
import { getUserRole } from "@/helpers/user.helper";
|
import { getUserRole } from "@/helpers/user.helper";
|
||||||
import { useEventTracker, useUser, useWorkspace } from "@/hooks/store";
|
import { useEventTracker, useUser, useUserProfile, useWorkspace } from "@/hooks/store";
|
||||||
import DefaultLayout from "@/layouts/default-layout";
|
import DefaultLayout from "@/layouts/default-layout";
|
||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "@/lib/types";
|
import { NextPageWithLayout } from "@/lib/types";
|
||||||
// wrappers
|
// wrappers
|
||||||
import { AuthenticationWrapper } from "@/lib/wrappers";
|
import { AuthenticationWrapper } from "@/lib/wrappers";
|
||||||
// services
|
// services
|
||||||
import { UserService } from "@/services/user.service";
|
|
||||||
import { WorkspaceService } from "@/services/workspace.service";
|
import { WorkspaceService } from "@/services/workspace.service";
|
||||||
// images
|
// images
|
||||||
import emptyInvitation from "public/empty-state/invitation.svg";
|
import emptyInvitation from "public/empty-state/invitation.svg";
|
||||||
@ -36,7 +35,6 @@ import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-l
|
|||||||
import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg";
|
import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg";
|
||||||
|
|
||||||
const workspaceService = new WorkspaceService();
|
const workspaceService = new WorkspaceService();
|
||||||
const userService = new UserService();
|
|
||||||
|
|
||||||
const UserInvitationsPage: NextPageWithLayout = observer(() => {
|
const UserInvitationsPage: NextPageWithLayout = observer(() => {
|
||||||
// states
|
// states
|
||||||
@ -47,6 +45,8 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => {
|
|||||||
// store hooks
|
// store hooks
|
||||||
const { captureEvent, joinWorkspaceMetricGroup } = useEventTracker();
|
const { captureEvent, joinWorkspaceMetricGroup } = useEventTracker();
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
|
const { updateUserProfile } = useUserProfile();
|
||||||
|
|
||||||
const { fetchWorkspaces } = useWorkspace();
|
const { fetchWorkspaces } = useWorkspace();
|
||||||
// next-themes
|
// next-themes
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
@ -95,8 +95,7 @@ const UserInvitationsPage: NextPageWithLayout = observer(() => {
|
|||||||
state: "SUCCESS",
|
state: "SUCCESS",
|
||||||
element: "Workspace invitations page",
|
element: "Workspace invitations page",
|
||||||
});
|
});
|
||||||
userService
|
updateUserProfile({ last_workspace_id: redirectWorkspace?.id })
|
||||||
.updateUser({ last_workspace_id: redirectWorkspace?.id })
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setIsJoiningWorkspaces(false);
|
setIsJoiningWorkspaces(false);
|
||||||
fetchWorkspaces().then(() => {
|
fetchWorkspaces().then(() => {
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import { ReactElement, useEffect, useState } from "react";
|
import { ReactElement, useEffect, useMemo, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input, Spinner, TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
|
import { Button, Input, Spinner, TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
|
import { PasswordStrengthMeter } from "@/components/account";
|
||||||
import { PageHead } from "@/components/core";
|
import { PageHead } from "@/components/core";
|
||||||
import { SidebarHamburgerToggle } from "@/components/core/sidebar";
|
import { SidebarHamburgerToggle } from "@/components/core/sidebar";
|
||||||
|
import { getPasswordStrength } from "@/helpers/password.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useAppTheme, useUser } from "@/hooks/store";
|
import { useAppTheme, useUser } from "@/hooks/store";
|
||||||
// layout
|
// layout
|
||||||
@ -14,6 +17,7 @@ import { ProfileSettingsLayout } from "@/layouts/settings-layout";
|
|||||||
// types
|
// types
|
||||||
import { NextPageWithLayout } from "@/lib/types";
|
import { NextPageWithLayout } from "@/lib/types";
|
||||||
// services
|
// services
|
||||||
|
import { AuthService } from "@/services/auth.service";
|
||||||
import { UserService } from "@/services/user.service";
|
import { UserService } from "@/services/user.service";
|
||||||
|
|
||||||
export interface FormValues {
|
export interface FormValues {
|
||||||
@ -29,9 +33,18 @@ const defaultValues: FormValues = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const userService = new UserService();
|
export const userService = new UserService();
|
||||||
|
export const authService = new AuthService();
|
||||||
|
|
||||||
const ChangePasswordPage: NextPageWithLayout = observer(() => {
|
const ChangePasswordPage: NextPageWithLayout = observer(() => {
|
||||||
|
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
|
||||||
const [isPageLoading, setIsPageLoading] = useState(true);
|
const [isPageLoading, setIsPageLoading] = useState(true);
|
||||||
|
const [showPassword, setShowPassword] = useState({
|
||||||
|
oldPassword: false,
|
||||||
|
password: false,
|
||||||
|
retypePassword: false,
|
||||||
|
});
|
||||||
|
const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
|
||||||
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// store hooks
|
// store hooks
|
||||||
@ -42,32 +55,54 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => {
|
|||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
watch,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
|
reset,
|
||||||
} = useForm<FormValues>({ defaultValues });
|
} = useForm<FormValues>({ defaultValues });
|
||||||
|
|
||||||
|
const oldPassword = watch("old_password");
|
||||||
|
const password = watch("new_password");
|
||||||
|
const retypePassword = watch("confirm_password");
|
||||||
|
|
||||||
|
const handleShowPassword = (key: keyof typeof showPassword) =>
|
||||||
|
setShowPassword((prev) => ({ ...prev, [key]: !prev[key] }));
|
||||||
|
|
||||||
const handleChangePassword = async (formData: FormValues) => {
|
const handleChangePassword = async (formData: FormValues) => {
|
||||||
if (formData.new_password !== formData.confirm_password) {
|
try {
|
||||||
|
if (!csrfToken) throw new Error("csrf token not found");
|
||||||
|
const changePasswordPromise = userService
|
||||||
|
.changePassword(csrfToken, {
|
||||||
|
old_password: formData.old_password,
|
||||||
|
new_password: formData.new_password,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
reset(defaultValues);
|
||||||
|
});
|
||||||
|
setPromiseToast(changePasswordPromise, {
|
||||||
|
loading: "Changing password...",
|
||||||
|
success: {
|
||||||
|
title: "Success!",
|
||||||
|
message: () => "Password changed successfully.",
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
title: "Error!",
|
||||||
|
message: () => "Something went wrong. Please try again 1.",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "The new password and the confirm password don't match.",
|
message: err?.error ?? "Something went wrong. Please try again 2.",
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const changePasswordPromise = userService.changePassword(formData);
|
|
||||||
setPromiseToast(changePasswordPromise, {
|
|
||||||
loading: "Changing password...",
|
|
||||||
success: {
|
|
||||||
title: "Success!",
|
|
||||||
message: () => "Password changed successfully.",
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
title: "Error!",
|
|
||||||
message: () => "Something went wrong. Please try again.",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (csrfToken === undefined)
|
||||||
|
authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token));
|
||||||
|
}, [csrfToken]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentUser) return;
|
if (!currentUser) return;
|
||||||
|
|
||||||
@ -75,6 +110,25 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => {
|
|||||||
else setIsPageLoading(false);
|
else setIsPageLoading(false);
|
||||||
}, [currentUser, router]);
|
}, [currentUser, router]);
|
||||||
|
|
||||||
|
const isButtonDisabled = useMemo(
|
||||||
|
() =>
|
||||||
|
!isSubmitting &&
|
||||||
|
!!oldPassword &&
|
||||||
|
!!password &&
|
||||||
|
!!retypePassword &&
|
||||||
|
getPasswordStrength(password) >= 3 &&
|
||||||
|
password === retypePassword &&
|
||||||
|
password !== oldPassword
|
||||||
|
? false
|
||||||
|
: true,
|
||||||
|
|
||||||
|
[isSubmitting, oldPassword, password, retypePassword]
|
||||||
|
);
|
||||||
|
|
||||||
|
const passwordSupport = password.length > 0 && (getPasswordStrength(password) < 3 || isPasswordInputFocused) && (
|
||||||
|
<PasswordStrengthMeter password={password} />
|
||||||
|
);
|
||||||
|
|
||||||
if (isPageLoading)
|
if (isPageLoading)
|
||||||
return (
|
return (
|
||||||
<div className="grid h-screen w-full place-items-center">
|
<div className="grid h-screen w-full place-items-center">
|
||||||
@ -93,82 +147,126 @@ const ChangePasswordPage: NextPageWithLayout = observer(() => {
|
|||||||
onSubmit={handleSubmit(handleChangePassword)}
|
onSubmit={handleSubmit(handleChangePassword)}
|
||||||
className="mx-auto md:mt-16 mt-10 flex h-full w-full flex-col gap-8 px-4 md:px-8 pb-8 lg:w-3/5"
|
className="mx-auto md:mt-16 mt-10 flex h-full w-full flex-col gap-8 px-4 md:px-8 pb-8 lg:w-3/5"
|
||||||
>
|
>
|
||||||
|
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
|
||||||
|
|
||||||
<h3 className="text-xl font-medium">Change password</h3>
|
<h3 className="text-xl font-medium">Change password</h3>
|
||||||
<div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-10 xl:grid-cols-2 2xl:grid-cols-3">
|
<div className="grid-col grid w-full grid-cols-1 items-center justify-between gap-10 xl:grid-cols-2 2xl:grid-cols-3">
|
||||||
<div className="flex flex-col gap-1 ">
|
<div className="flex flex-col gap-1 ">
|
||||||
<h4 className="text-sm">Current password</h4>
|
<h4 className="text-sm">Current password</h4>
|
||||||
<Controller
|
<div className="relative flex items-center rounded-md">
|
||||||
control={control}
|
<Controller
|
||||||
name="old_password"
|
control={control}
|
||||||
rules={{
|
name="old_password"
|
||||||
required: "This field is required",
|
rules={{
|
||||||
}}
|
required: "This field is required",
|
||||||
render={({ field: { value, onChange } }) => (
|
}}
|
||||||
<Input
|
render={({ field: { value, onChange } }) => (
|
||||||
id="old_password"
|
<Input
|
||||||
type="password"
|
id="old_password"
|
||||||
value={value}
|
type={showPassword?.oldPassword ? "text" : "password"}
|
||||||
onChange={onChange}
|
value={value}
|
||||||
placeholder="Old password"
|
onChange={onChange}
|
||||||
className="w-full rounded-md font-medium"
|
placeholder="Old password"
|
||||||
hasError={Boolean(errors.old_password)}
|
className="w-full rounded-md font-medium"
|
||||||
|
hasError={Boolean(errors.old_password)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{showPassword?.oldPassword ? (
|
||||||
|
<EyeOff
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => handleShowPassword("oldPassword")}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Eye
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => handleShowPassword("oldPassword")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
</div>
|
||||||
|
|
||||||
{errors.old_password && <span className="text-xs text-red-500">{errors.old_password.message}</span>}
|
{errors.old_password && <span className="text-xs text-red-500">{errors.old_password.message}</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1 ">
|
<div className="flex flex-col gap-1 ">
|
||||||
<h4 className="text-sm">New password</h4>
|
<h4 className="text-sm">New password</h4>
|
||||||
<Controller
|
<div className="relative flex items-center rounded-md">
|
||||||
control={control}
|
<Controller
|
||||||
name="new_password"
|
control={control}
|
||||||
rules={{
|
name="new_password"
|
||||||
required: "This field is required",
|
rules={{
|
||||||
}}
|
required: "This field is required",
|
||||||
render={({ field: { value, onChange } }) => (
|
}}
|
||||||
<Input
|
render={({ field: { value, onChange } }) => (
|
||||||
id="new_password"
|
<Input
|
||||||
type="password"
|
id="new_password"
|
||||||
value={value}
|
type={showPassword?.password ? "text" : "password"}
|
||||||
placeholder="New password"
|
value={value}
|
||||||
onChange={onChange}
|
placeholder="New password"
|
||||||
className="w-full"
|
onChange={onChange}
|
||||||
hasError={Boolean(errors.new_password)}
|
className="w-full"
|
||||||
|
hasError={Boolean(errors.new_password)}
|
||||||
|
onFocus={() => setIsPasswordInputFocused(true)}
|
||||||
|
onBlur={() => setIsPasswordInputFocused(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{showPassword?.password ? (
|
||||||
|
<EyeOff
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => handleShowPassword("password")}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Eye
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => handleShowPassword("password")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
</div>
|
||||||
{errors.new_password && <span className="text-xs text-red-500">{errors.new_password.message}</span>}
|
{passwordSupport}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-1 ">
|
<div className="flex flex-col gap-1 ">
|
||||||
<h4 className="text-sm">Confirm password</h4>
|
<h4 className="text-sm">Confirm password</h4>
|
||||||
<Controller
|
<div className="relative flex items-center rounded-md">
|
||||||
control={control}
|
<Controller
|
||||||
name="confirm_password"
|
control={control}
|
||||||
rules={{
|
name="confirm_password"
|
||||||
required: "This field is required",
|
rules={{
|
||||||
}}
|
required: "This field is required",
|
||||||
render={({ field: { value, onChange } }) => (
|
}}
|
||||||
<Input
|
render={({ field: { value, onChange } }) => (
|
||||||
id="confirm_password"
|
<Input
|
||||||
type="password"
|
id="confirm_password"
|
||||||
placeholder="Confirm password"
|
type={showPassword?.retypePassword ? "text" : "password"}
|
||||||
value={value}
|
placeholder="Confirm password"
|
||||||
onChange={onChange}
|
value={value}
|
||||||
className="w-full"
|
onChange={onChange}
|
||||||
hasError={Boolean(errors.confirm_password)}
|
className="w-full"
|
||||||
|
hasError={Boolean(errors.confirm_password)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{showPassword?.retypePassword ? (
|
||||||
|
<EyeOff
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => handleShowPassword("retypePassword")}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Eye
|
||||||
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
|
onClick={() => handleShowPassword("retypePassword")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
</div>
|
||||||
{errors.confirm_password && (
|
{!!retypePassword && password !== retypePassword && (
|
||||||
<span className="text-xs text-red-500">{errors.confirm_password.message}</span>
|
<span className="text-sm text-red-500">Passwords don{"'"}t match</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between py-2">
|
<div className="flex items-center justify-between py-2">
|
||||||
<Button variant="primary" type="submit" loading={isSubmitting}>
|
<Button variant="primary" type="submit" loading={isSubmitting} disabled={isButtonDisabled}>
|
||||||
{isSubmitting ? "Changing password..." : "Change password"}
|
{isSubmitting ? "Changing password..." : "Change password"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -143,8 +143,12 @@ export class UserService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async changePassword(data: { old_password: string; new_password: string; confirm_password: string }): Promise<any> {
|
async changePassword(token: string, data: { old_password: string; new_password: string }): Promise<any> {
|
||||||
return this.post(`/api/users/me/change-password/`, data)
|
return this.post(`/auth/change-password/`, data, {
|
||||||
|
headers: {
|
||||||
|
"X-CSRFTOKEN": token,
|
||||||
|
},
|
||||||
|
})
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
|
23
yarn.lock
23
yarn.lock
@ -6917,7 +6917,7 @@ postcss@8.4.31:
|
|||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
postcss@^8.4.23, postcss@^8.4.38:
|
postcss@^8.4.23, postcss@^8.4.29, postcss@^8.4.38:
|
||||||
version "8.4.38"
|
version "8.4.38"
|
||||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e"
|
||||||
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
|
integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==
|
||||||
@ -7945,8 +7945,16 @@ streamx@^2.15.0, streamx@^2.16.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
bare-events "^2.2.0"
|
bare-events "^2.2.0"
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
name string-width-cjs
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
string-width@^4.1.0:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@ -8026,7 +8034,14 @@ stringify-object@^3.3.0:
|
|||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
is-regexp "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
Loading…
Reference in New Issue
Block a user