From 9013497a5a302c8262430a817d559071f0af26f6 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Wed, 22 May 2024 14:49:06 +0530 Subject: [PATCH] fix: authentication views updated with new workflow (#4547) * dev: update email check endpoint * fix: auth magic login check * chore: updated the error code handler and handled authentication workflow * dev: add magic link login --------- Co-authored-by: pablohashescobar Co-authored-by: guru_sainath --- apiserver/plane/authentication/urls.py | 18 +- .../plane/authentication/views/__init__.py | 4 +- .../plane/authentication/views/app/check.py | 224 ++++++++---------- .../plane/authentication/views/space/check.py | 40 +++- packages/types/src/auth.d.ts | 3 +- .../account/auth-forms/auth-header.tsx | 24 +- .../account/auth-forms/auth-root.tsx | 87 ++++--- web/components/account/auth-forms/email.tsx | 2 +- .../account/auth-forms/password.tsx | 1 - .../account/oauth/oauth-options.tsx | 9 +- web/helpers/authentication.helper.tsx | 58 +++-- web/next.config.js | 5 + web/pages/index.tsx | 14 +- web/pages/{sign-in.tsx => sign-up.tsx} | 10 +- web/services/auth.service.ts | 11 +- 15 files changed, 275 insertions(+), 235 deletions(-) rename web/pages/{sign-in.tsx => sign-up.tsx} (91%) diff --git a/apiserver/plane/authentication/urls.py b/apiserver/plane/authentication/urls.py index 4a6f8c3f4..ee860f41f 100644 --- a/apiserver/plane/authentication/urls.py +++ b/apiserver/plane/authentication/urls.py @@ -2,13 +2,12 @@ from django.urls import path from .views import ( CSRFTokenEndpoint, - EmailCheckSignInEndpoint, - EmailCheckSignUpEndpoint, ForgotPasswordEndpoint, SetUserPasswordEndpoint, ResetPasswordEndpoint, ChangePasswordEndpoint, # App + EmailCheckEndpoint, GitHubCallbackEndpoint, GitHubOauthInitiateEndpoint, GoogleCallbackEndpoint, @@ -22,7 +21,7 @@ from .views import ( ForgotPasswordSpaceEndpoint, ResetPasswordSpaceEndpoint, # Space - EmailCheckEndpoint, + EmailCheckSpaceEndpoint, GitHubCallbackSpaceEndpoint, GitHubOauthInitiateSpaceEndpoint, GoogleCallbackSpaceEndpoint, @@ -154,18 +153,13 @@ urlpatterns = [ ), # Email Check path( - "sign-up/email-check/", - EmailCheckSignUpEndpoint.as_view(), - name="email-check-sign-up", - ), - path( - "sign-in/email-check/", - EmailCheckSignInEndpoint.as_view(), - name="email-check-sign-in", + "email-check/", + EmailCheckEndpoint.as_view(), + name="email-check", ), path( "spaces/email-check/", - EmailCheckEndpoint.as_view(), + EmailCheckSpaceEndpoint.as_view(), name="email-check", ), # Password diff --git a/apiserver/plane/authentication/views/__init__.py b/apiserver/plane/authentication/views/__init__.py index a5aadf728..51ea3e60a 100644 --- a/apiserver/plane/authentication/views/__init__.py +++ b/apiserver/plane/authentication/views/__init__.py @@ -4,7 +4,7 @@ from .common import ( SetUserPasswordEndpoint, ) -from .app.check import EmailCheckSignInEndpoint, EmailCheckSignUpEndpoint +from .app.check import EmailCheckEndpoint from .app.email import ( SignInAuthEndpoint, @@ -47,7 +47,7 @@ from .space.magic import ( from .space.signout import SignOutAuthSpaceEndpoint -from .space.check import EmailCheckEndpoint +from .space.check import EmailCheckSpaceEndpoint from .space.password_management import ( ForgotPasswordSpaceEndpoint, diff --git a/apiserver/plane/authentication/views/app/check.py b/apiserver/plane/authentication/views/app/check.py index 2448aee55..5b3ac7337 100644 --- a/apiserver/plane/authentication/views/app/check.py +++ b/apiserver/plane/authentication/views/app/check.py @@ -1,3 +1,6 @@ +# Python imports +import os + # Django imports from django.core.validators import validate_email from django.core.exceptions import ValidationError @@ -16,8 +19,12 @@ from plane.authentication.adapter.error import ( AUTHENTICATION_ERROR_CODES, ) from plane.authentication.rate_limit import AuthenticationThrottle +from plane.license.utils.instance_value import ( + get_configuration_value, +) -class EmailCheckSignUpEndpoint(APIView): + +class EmailCheckEndpoint(APIView): permission_classes = [ AllowAny, @@ -28,128 +35,99 @@ class EmailCheckSignUpEndpoint(APIView): ] def post(self, request): - 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) - - 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 user already exist - raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES[ - "USER_ALREADY_EXIST" - ], - error_message="USER_ALREADY_EXIST", - ) + # 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( - {"status": True}, + exc.get_error_dict(), + status=status.HTTP_400_BAD_REQUEST, + ) + + (EMAIL_HOST, ENABLE_MAGIC_LINK_LOGIN) = get_configuration_value( + [ + { + "key": "EMAIL_HOST", + "default": os.environ.get("EMAIL_HOST", ""), + }, + { + "key": "ENABLE_MAGIC_LINK_LOGIN", + "default": os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "1"), + }, + ] + ) + + smtp_configured = bool(EMAIL_HOST) + is_magic_login_enabled = ENABLE_MAGIC_LINK_LOGIN == "1" + + 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: + validate_email(email) + except ValidationError: + exc = AuthenticationException( + error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"], + error_message="INVALID_EMAIL", + ) + return Response( + exc.get_error_dict(), + status=status.HTTP_400_BAD_REQUEST, + ) + # Check if a user already exists with the given email + existing_user = User.objects.filter(email=email).first() + + # 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( + { + "existing": True, + "status": ( + "MAGIC_CODE" + if existing_user.is_password_autoset + and smtp_configured + and is_magic_login_enabled + else "CREDENTIAL" + ), + }, status=status.HTTP_200_OK, ) - except ValidationError: - raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"], - error_message="INVALID_EMAIL", - ) - except AuthenticationException as e: - return Response( - e.get_error_dict(), status=status.HTTP_400_BAD_REQUEST - ) - - -class EmailCheckSignInEndpoint(APIView): - - permission_classes = [ - AllowAny, - ] - - throttle_classes = [ - AuthenticationThrottle, - ] - - def post(self, request): - 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) - - 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 true - return Response( - { - "status": True, - "is_password_autoset": existing_user.is_password_autoset, - }, - status=status.HTTP_200_OK, - ) - - # Raise error - raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"], - error_message="USER_DOES_NOT_EXIST", - ) - except ValidationError: - raise AuthenticationException( - error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"], - error_message="INVALID_EMAIL", - ) - except AuthenticationException as e: - return Response( - e.get_error_dict(), status=status.HTTP_400_BAD_REQUEST - ) + # Else return response + return Response( + { + "existing": False, + "status": ( + "MAGIC_CODE" + if smtp_configured and is_magic_login_enabled + else "CREDENTIAL" + ), + }, + status=status.HTTP_200_OK, + ) diff --git a/apiserver/plane/authentication/views/space/check.py b/apiserver/plane/authentication/views/space/check.py index 1b20d19a2..a86a29c09 100644 --- a/apiserver/plane/authentication/views/space/check.py +++ b/apiserver/plane/authentication/views/space/check.py @@ -1,3 +1,6 @@ +# Python imports +import os + # Django imports from django.core.validators import validate_email from django.core.exceptions import ValidationError @@ -16,8 +19,10 @@ from plane.authentication.adapter.error import ( AuthenticationException, ) from plane.authentication.rate_limit import AuthenticationThrottle +from plane.license.utils.instance_value import get_configuration_value -class EmailCheckEndpoint(APIView): + +class EmailCheckSpaceEndpoint(APIView): permission_classes = [ AllowAny, @@ -42,6 +47,22 @@ class EmailCheckEndpoint(APIView): status=status.HTTP_400_BAD_REQUEST, ) + (EMAIL_HOST, ENABLE_MAGIC_LINK_LOGIN) = get_configuration_value( + [ + { + "key": "EMAIL_HOST", + "default": os.environ.get("EMAIL_HOST", ""), + }, + { + "key": "ENABLE_MAGIC_LINK_LOGIN", + "default": os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "1"), + }, + ] + ) + + smtp_configured = bool(EMAIL_HOST) + is_magic_login_enabled = ENABLE_MAGIC_LINK_LOGIN == "1" + email = request.data.get("email", False) # Return error if email is not present @@ -86,12 +107,25 @@ class EmailCheckEndpoint(APIView): return Response( { "existing": True, - "is_password_autoset": existing_user.is_password_autoset, + "status": ( + "MAGIC_CODE" + if existing_user.is_password_autoset + and smtp_configured + and is_magic_login_enabled + else "CREDENTIAL" + ), }, status=status.HTTP_200_OK, ) # Else return response return Response( - {"existing": False, "is_password_autoset": False}, + { + "existing": False, + "status": ( + "MAGIC_CODE" + if smtp_configured and is_magic_login_enabled + else "CREDENTIAL" + ), + }, status=status.HTTP_200_OK, ) diff --git a/packages/types/src/auth.d.ts b/packages/types/src/auth.d.ts index 068062fc7..576ac45b6 100644 --- a/packages/types/src/auth.d.ts +++ b/packages/types/src/auth.d.ts @@ -5,8 +5,7 @@ export interface IEmailCheckData { } export interface IEmailCheckResponse { - is_password_autoset: boolean; - status: boolean; + status: "MAGIC_CODE" | "CREDENTIAL"; existing: boolean; } diff --git a/web/components/account/auth-forms/auth-header.tsx b/web/components/account/auth-forms/auth-header.tsx index 4051ab2ab..ef2f90242 100644 --- a/web/components/account/auth-forms/auth-header.tsx +++ b/web/components/account/auth-forms/auth-header.tsx @@ -21,30 +21,30 @@ type TAuthHeader = { const Titles = { [EAuthModes.SIGN_IN]: { [EAuthSteps.EMAIL]: { - header: "Sign in to Plane", - subHeader: "Get back to your projects and make progress", + header: "Log in or sign up", + subHeader: "", }, [EAuthSteps.PASSWORD]: { - header: "Sign in to Plane", - subHeader: "Get back to your projects and make progress", + header: "Log in or sign up", + subHeader: "Log in using your password.", }, [EAuthSteps.UNIQUE_CODE]: { - header: "Sign in to Plane", - subHeader: "Get back to your projects and make progress", + header: "Log in or sign up", + subHeader: "Log in using your unique code.", }, }, [EAuthModes.SIGN_UP]: { [EAuthSteps.EMAIL]: { - header: "Create your account", - subHeader: "Start tracking your projects with Plane", + header: "Sign up or log in", + subHeader: "", }, [EAuthSteps.PASSWORD]: { - header: "Create your account", - subHeader: "Progress, visualize, and measure work how it works best for you.", + header: "Sign up or log in", + subHeader: "Sign up using your password", }, [EAuthSteps.UNIQUE_CODE]: { - header: "Create your account", - subHeader: "Progress, visualize, and measure work how it works best for you.", + header: "Sign up or log in", + subHeader: "Sign up using your unique code", }, }, }; diff --git a/web/components/account/auth-forms/auth-root.tsx b/web/components/account/auth-forms/auth-root.tsx index 5ac9fd011..425780f66 100644 --- a/web/components/account/auth-forms/auth-root.tsx +++ b/web/components/account/auth-forms/auth-root.tsx @@ -37,67 +37,83 @@ export const AuthRoot: FC = observer((props) => { const router = useRouter(); const { email: emailParam, invitation_id, slug: workspaceSlug, error_code } = router.query; // props - const { authMode } = props; + const { authMode: currentAuthMode } = props; // states + const [authMode, setAuthMode] = useState(undefined); const [authStep, setAuthStep] = useState(EAuthSteps.EMAIL); const [email, setEmail] = useState(emailParam ? emailParam.toString() : ""); const [errorInfo, setErrorInfo] = useState(undefined); - const [isPasswordAutoset, setIsPasswordAutoset] = useState(true); // hooks const { config } = useInstance(); useEffect(() => { - if (error_code) { + if (!authMode && currentAuthMode) setAuthMode(currentAuthMode); + }, [currentAuthMode, authMode]); + + useEffect(() => { + if (error_code && authMode) { const errorhandler = authErrorHandler(error_code?.toString() as EAuthenticationErrorCodes); if (errorhandler) { - if ( - [ - EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN, - EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP, - ].includes(errorhandler.code) - ) + // password error handler + if ([EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP].includes(errorhandler.code)) { + setAuthMode(EAuthModes.SIGN_UP); setAuthStep(EAuthSteps.PASSWORD); + } + if ([EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_IN].includes(errorhandler.code)) { + setAuthMode(EAuthModes.SIGN_IN); + setAuthStep(EAuthSteps.PASSWORD); + } + // magic_code error handler if ( [ - EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN, + EAuthenticationErrorCodes.INVALID_MAGIC_CODE_SIGN_UP, EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_UP, - EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE_SIGN_IN, EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE_SIGN_UP, - EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN, EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_UP, ].includes(errorhandler.code) - ) + ) { + setAuthMode(EAuthModes.SIGN_UP); setAuthStep(EAuthSteps.UNIQUE_CODE); + } + if ( + [ + EAuthenticationErrorCodes.INVALID_MAGIC_CODE_SIGN_IN, + EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN, + EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE_SIGN_IN, + EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED_SIGN_IN, + ].includes(errorhandler.code) + ) { + setAuthMode(EAuthModes.SIGN_IN); + setAuthStep(EAuthSteps.UNIQUE_CODE); + } + setErrorInfo(errorhandler); } } }, [error_code, authMode]); const isSMTPConfigured = config?.is_smtp_configured || false; - const isMagicLoginEnabled = config?.is_magic_login_enabled || false; - const isEmailPasswordEnabled = config?.is_email_password_enabled || false; // submit handler- email verification const handleEmailVerification = async (data: IEmailCheckData) => { setEmail(data.email); - const emailCheckRequest = - authMode === EAuthModes.SIGN_IN ? authService.signInEmailCheck(data) : authService.signUpEmailCheck(data); - - await emailCheckRequest + await authService + .emailCheck(data) .then(async (response) => { - if (authMode === EAuthModes.SIGN_IN) { - if (response.is_password_autoset) { + if (response.existing) { + if (currentAuthMode === EAuthModes.SIGN_UP) setAuthMode(EAuthModes.SIGN_IN); + if (response.status === "MAGIC_CODE") { setAuthStep(EAuthSteps.UNIQUE_CODE); generateEmailUniqueCode(data.email); - } else if (isEmailPasswordEnabled) { - setIsPasswordAutoset(false); + } else if (response.status === "CREDENTIAL") { setAuthStep(EAuthSteps.PASSWORD); } } else { - if (isSMTPConfigured && isMagicLoginEnabled) { + if (currentAuthMode === EAuthModes.SIGN_IN) setAuthMode(EAuthModes.SIGN_UP); + if (response.status === "MAGIC_CODE") { setAuthStep(EAuthSteps.UNIQUE_CODE); generateEmailUniqueCode(data.email); - } else if (isEmailPasswordEnabled) { + } else if (response.status === "CREDENTIAL") { setAuthStep(EAuthSteps.PASSWORD); } } @@ -108,8 +124,17 @@ export const AuthRoot: FC = observer((props) => { }); }; + const handleEmailClear = () => { + setAuthMode(currentAuthMode); + setErrorInfo(undefined); + setEmail(""); + setAuthStep(EAuthSteps.EMAIL); + router.push(currentAuthMode === EAuthModes.SIGN_IN ? `/` : "/sign-up", undefined, { shallow: true }); + }; + // generating the unique code const generateEmailUniqueCode = async (email: string): Promise<{ code: string } | undefined> => { + if (!isSMTPConfigured) return; const payload = { email: email }; return await authService .generateUniqueCode(payload) @@ -121,6 +146,7 @@ export const AuthRoot: FC = observer((props) => { }); }; + if (!authMode) return <>; return (
= observer((props) => { { - setEmail(""); - setAuthStep(EAuthSteps.EMAIL); - }} + handleEmailClear={handleEmailClear} generateEmailUniqueCode={generateEmailUniqueCode} /> )} {authStep === EAuthSteps.PASSWORD && ( { - setEmail(""); - setAuthStep(EAuthSteps.EMAIL); - }} + handleEmailClear={handleEmailClear} handleAuthStep={(step: EAuthSteps) => { if (step === EAuthSteps.UNIQUE_CODE) generateEmailUniqueCode(email); setAuthStep(step); diff --git a/web/components/account/auth-forms/email.tsx b/web/components/account/auth-forms/email.tsx index 923bc5fcf..7bb0b48d5 100644 --- a/web/components/account/auth-forms/email.tsx +++ b/web/components/account/auth-forms/email.tsx @@ -57,7 +57,7 @@ export const AuthEmailForm: FC = observer((props) => { type="email" value={email} onChange={(e) => setEmail(e.target.value)} - placeholder="name@company.com" + placeholder="name@example.com" className={`disable-autofill-style h-[46px] w-full placeholder:text-onboarding-text-400 autofill:bg-red-500 border-0 focus:bg-none active:bg-transparent`} onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} diff --git a/web/components/account/auth-forms/password.tsx b/web/components/account/auth-forms/password.tsx index f84c2913e..11bf29ca5 100644 --- a/web/components/account/auth-forms/password.tsx +++ b/web/components/account/auth-forms/password.tsx @@ -20,7 +20,6 @@ import { AuthService } from "@/services/auth.service"; type Props = { email: string; - isPasswordAutoset: boolean; isSMTPConfigured: boolean; mode: EAuthModes; handleEmailClear: () => void; diff --git a/web/components/account/oauth/oauth-options.tsx b/web/components/account/oauth/oauth-options.tsx index 8541def90..2cdc8531b 100644 --- a/web/components/account/oauth/oauth-options.tsx +++ b/web/components/account/oauth/oauth-options.tsx @@ -8,8 +8,7 @@ type TOAuthOptionProps = { isSignUp?: boolean; }; -export const OAuthOptions: React.FC = observer((props) => { - const { isSignUp = false } = props; +export const OAuthOptions: React.FC = observer(() => { // hooks const { config } = useInstance(); @@ -17,8 +16,6 @@ export const OAuthOptions: React.FC = observer((props) => { if (!isOAuthEnabled) return null; - const oauthProviderButtonText = `Sign ${isSignUp ? "up" : "in"} with`; - return ( <>
@@ -29,10 +26,10 @@ export const OAuthOptions: React.FC = observer((props) => {
{config?.is_google_enabled && (
- +
)} - {config?.is_github_enabled && } + {config?.is_github_enabled && }
); diff --git a/web/helpers/authentication.helper.tsx b/web/helpers/authentication.helper.tsx index 0e05fb0f5..cf5907451 100644 --- a/web/helpers/authentication.helper.tsx +++ b/web/helpers/authentication.helper.tsx @@ -34,6 +34,9 @@ export enum EAuthenticationErrorCodes { INVALID_EMAIL = "5005", EMAIL_REQUIRED = "5010", SIGNUP_DISABLED = "5015", + MAGIC_LINK_LOGIN_DISABLED = "5016", + PASSWORD_LOGIN_DISABLED = "5018", + USER_ACCOUNT_DEACTIVATED = "5019", // Password strength INVALID_PASSWORD = "5020", SMTP_NOT_CONFIGURED = "5025", @@ -45,7 +48,6 @@ export enum EAuthenticationErrorCodes { INVALID_EMAIL_MAGIC_SIGN_UP = "5050", MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED = "5055", // Sign In - USER_ACCOUNT_DEACTIVATED = "5019", USER_DOES_NOT_EXIST = "5060", AUTHENTICATION_FAILED_SIGN_IN = "5065", REQUIRED_EMAIL_PASSWORD_SIGN_IN = "5070", @@ -82,6 +84,9 @@ export enum EAuthenticationErrorCodes { ADMIN_AUTHENTICATION_FAILED = "5175", ADMIN_USER_ALREADY_EXIST = "5180", ADMIN_USER_DOES_NOT_EXIST = "5185", + ADMIN_USER_DEACTIVATED = "5190", + // Rate limit + RATE_LIMIT_EXCEEDED = "5900", } export type TAuthErrorInfo = { @@ -99,10 +104,30 @@ const errorCodeMessages: { title: `Instance not configured`, message: () => `Instance not configured. Please contact your administrator.`, }, + [EAuthenticationErrorCodes.INVALID_EMAIL]: { + title: `Invalid email`, + message: () => `Invalid email. Please try again.`, + }, + [EAuthenticationErrorCodes.EMAIL_REQUIRED]: { + title: `Email required`, + message: () => `Email required. Please try again.`, + }, [EAuthenticationErrorCodes.SIGNUP_DISABLED]: { title: `Sign up disabled`, message: () => `Sign up disabled. Please contact your administrator.`, }, + [EAuthenticationErrorCodes.MAGIC_LINK_LOGIN_DISABLED]: { + title: `Magic link login disabled`, + message: () => `Magic link login disabled. Please contact your administrator.`, + }, + [EAuthenticationErrorCodes.PASSWORD_LOGIN_DISABLED]: { + title: `Password login disabled`, + message: () => `Password login disabled. Please contact your administrator.`, + }, + [EAuthenticationErrorCodes.USER_ACCOUNT_DEACTIVATED]: { + title: `User account deactivated`, + message: () => `User account deactivated. Please contact your administrator.`, + }, [EAuthenticationErrorCodes.INVALID_PASSWORD]: { title: `Invalid password`, message: () => `Invalid password. Please try again.`, @@ -112,16 +137,6 @@ const errorCodeMessages: { message: () => `SMTP not configured. Please contact your administrator.`, }, - // email check in both sign up and sign in - [EAuthenticationErrorCodes.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`, @@ -159,12 +174,6 @@ const errorCodeMessages: { message: () => `Invalid email. Please try again.`, }, - // sign in - [EAuthenticationErrorCodes.USER_ACCOUNT_DEACTIVATED]: { - title: `User account deactivated`, - message: () =>
Your account is deactivated. Contact support@plane.so.
, - }, - [EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: { title: `User does not exist`, message: (email = undefined) => ( @@ -324,6 +333,14 @@ const errorCodeMessages: {
), }, + [EAuthenticationErrorCodes.ADMIN_USER_DEACTIVATED]: { + title: `Admin user deactivated`, + message: () =>
Your account is deactivated
, + }, + [EAuthenticationErrorCodes.RATE_LIMIT_EXCEEDED]: { + title: "", + message: () => `Rate limit exceeded. Please try again later.`, + }, }; export const authErrorHandler = ( @@ -335,6 +352,9 @@ export const authErrorHandler = ( EAuthenticationErrorCodes.INVALID_EMAIL, EAuthenticationErrorCodes.EMAIL_REQUIRED, EAuthenticationErrorCodes.SIGNUP_DISABLED, + EAuthenticationErrorCodes.MAGIC_LINK_LOGIN_DISABLED, + EAuthenticationErrorCodes.PASSWORD_LOGIN_DISABLED, + EAuthenticationErrorCodes.USER_ACCOUNT_DEACTIVATED, EAuthenticationErrorCodes.INVALID_PASSWORD, EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED, EAuthenticationErrorCodes.USER_ALREADY_EXIST, @@ -362,6 +382,7 @@ export const authErrorHandler = ( EAuthenticationErrorCodes.INVALID_PASSWORD_TOKEN, EAuthenticationErrorCodes.EXPIRED_PASSWORD_TOKEN, EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD, + EAuthenticationErrorCodes.MISSING_PASSWORD, EAuthenticationErrorCodes.INVALID_NEW_PASSWORD, EAuthenticationErrorCodes.PASSWORD_ALREADY_SET, EAuthenticationErrorCodes.ADMIN_ALREADY_EXIST, @@ -372,7 +393,8 @@ export const authErrorHandler = ( EAuthenticationErrorCodes.ADMIN_AUTHENTICATION_FAILED, EAuthenticationErrorCodes.ADMIN_USER_ALREADY_EXIST, EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST, - EAuthenticationErrorCodes.USER_ACCOUNT_DEACTIVATED, + EAuthenticationErrorCodes.ADMIN_USER_DEACTIVATED, + EAuthenticationErrorCodes.RATE_LIMIT_EXCEEDED, ]; if (bannerAlertErrorCodes.includes(errorCode)) diff --git a/web/next.config.js b/web/next.config.js index 084058951..89918b1d1 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -34,6 +34,11 @@ const nextConfig = { return [ { source: "/accounts/sign-up", + destination: "/sign-up", + permanent: true + }, + { + source: "/sign-in", destination: "/", permanent: true } diff --git a/web/pages/index.tsx b/web/pages/index.tsx index 9b624b2a2..c6cd6340f 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -8,7 +8,7 @@ import { useTheme } from "next-themes"; import { AuthRoot } from "@/components/account"; import { PageHead } from "@/components/core"; // constants -import { NAVIGATE_TO_SIGNIN } from "@/constants/event-tracker"; +import { NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker"; // helpers import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper"; // hooks @@ -31,7 +31,7 @@ const HomePage: NextPageWithLayout = observer(() => { return (
- +
{ Plane
- Already have an account?{" "} + New to Plane?{" "} captureEvent(NAVIGATE_TO_SIGNIN, {})} + href="/sign-up" + onClick={() => captureEvent(NAVIGATE_TO_SIGNUP, {})} className="font-semibold text-custom-primary-100 hover:underline" > - Sign In + Create an account
- +
diff --git a/web/pages/sign-in.tsx b/web/pages/sign-up.tsx similarity index 91% rename from web/pages/sign-in.tsx rename to web/pages/sign-up.tsx index 281b504ff..dcfb2e165 100644 --- a/web/pages/sign-in.tsx +++ b/web/pages/sign-up.tsx @@ -7,7 +7,7 @@ import { useTheme } from "next-themes"; import { AuthRoot } from "@/components/account"; import { PageHead } from "@/components/core"; // constants -import { NAVIGATE_TO_SIGNUP } from "@/constants/event-tracker"; +import { NAVIGATE_TO_SIGNIN } from "@/constants/event-tracker"; // helpers import { EAuthModes, EPageTypes } from "@/helpers/authentication.helper"; // hooks @@ -48,18 +48,18 @@ const SignInPage: NextPageWithLayout = observer(() => { Plane
- New to Plane?{" "} + Already have an account?{" "} captureEvent(NAVIGATE_TO_SIGNUP, {})} + onClick={() => captureEvent(NAVIGATE_TO_SIGNIN, {})} className="font-semibold text-custom-primary-100 hover:underline" > - Create an account + Log in
- +
diff --git a/web/services/auth.service.ts b/web/services/auth.service.ts index 227aa52c1..7663afde8 100644 --- a/web/services/auth.service.ts +++ b/web/services/auth.service.ts @@ -18,15 +18,8 @@ export class AuthService extends APIService { }); } - signUpEmailCheck = async (data: IEmailCheckData): Promise => - this.post("/auth/sign-up/email-check/", data, { headers: {} }) - .then((response) => response?.data) - .catch((error) => { - throw error?.response?.data; - }); - - signInEmailCheck = async (data: IEmailCheckData): Promise => - this.post("/auth/sign-in/email-check/", data, { headers: {} }) + emailCheck = async (data: IEmailCheckData): Promise => + this.post("/auth/email-check/", data, { headers: {} }) .then((response) => response?.data) .catch((error) => { throw error?.response?.data;