forked from github/plane
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 <nikhilschacko@gmail.com> Co-authored-by: guru_sainath <gurusainath007@gmail.com>
This commit is contained in:
parent
509d5fe554
commit
9013497a5a
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
||||
|
3
packages/types/src/auth.d.ts
vendored
3
packages/types/src/auth.d.ts
vendored
@ -5,8 +5,7 @@ export interface IEmailCheckData {
|
||||
}
|
||||
|
||||
export interface IEmailCheckResponse {
|
||||
is_password_autoset: boolean;
|
||||
status: boolean;
|
||||
status: "MAGIC_CODE" | "CREDENTIAL";
|
||||
existing: boolean;
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -37,67 +37,83 @@ export const AuthRoot: FC<TAuthRoot> = 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<EAuthModes | undefined>(undefined);
|
||||
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
|
||||
const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
|
||||
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(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<TAuthRoot> = 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<TAuthRoot> = observer((props) => {
|
||||
});
|
||||
};
|
||||
|
||||
if (!authMode) return <></>;
|
||||
return (
|
||||
<div className="relative flex flex-col space-y-6">
|
||||
<AuthHeader
|
||||
@ -138,23 +164,16 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
|
||||
<AuthUniqueCodeForm
|
||||
mode={authMode}
|
||||
email={email}
|
||||
handleEmailClear={() => {
|
||||
setEmail("");
|
||||
setAuthStep(EAuthSteps.EMAIL);
|
||||
}}
|
||||
handleEmailClear={handleEmailClear}
|
||||
generateEmailUniqueCode={generateEmailUniqueCode}
|
||||
/>
|
||||
)}
|
||||
{authStep === EAuthSteps.PASSWORD && (
|
||||
<AuthPasswordForm
|
||||
mode={authMode}
|
||||
isPasswordAutoset={isPasswordAutoset}
|
||||
isSMTPConfigured={isSMTPConfigured}
|
||||
email={email}
|
||||
handleEmailClear={() => {
|
||||
setEmail("");
|
||||
setAuthStep(EAuthSteps.EMAIL);
|
||||
}}
|
||||
handleEmailClear={handleEmailClear}
|
||||
handleAuthStep={(step: EAuthSteps) => {
|
||||
if (step === EAuthSteps.UNIQUE_CODE) generateEmailUniqueCode(email);
|
||||
setAuthStep(step);
|
||||
|
@ -57,7 +57,7 @@ export const AuthEmailForm: FC<TAuthEmailForm> = 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)}
|
||||
|
@ -20,7 +20,6 @@ import { AuthService } from "@/services/auth.service";
|
||||
|
||||
type Props = {
|
||||
email: string;
|
||||
isPasswordAutoset: boolean;
|
||||
isSMTPConfigured: boolean;
|
||||
mode: EAuthModes;
|
||||
handleEmailClear: () => void;
|
||||
|
@ -8,8 +8,7 @@ type TOAuthOptionProps = {
|
||||
isSignUp?: boolean;
|
||||
};
|
||||
|
||||
export const OAuthOptions: React.FC<TOAuthOptionProps> = observer((props) => {
|
||||
const { isSignUp = false } = props;
|
||||
export const OAuthOptions: React.FC<TOAuthOptionProps> = observer(() => {
|
||||
// hooks
|
||||
const { config } = useInstance();
|
||||
|
||||
@ -17,8 +16,6 @@ export const OAuthOptions: React.FC<TOAuthOptionProps> = observer((props) => {
|
||||
|
||||
if (!isOAuthEnabled) return null;
|
||||
|
||||
const oauthProviderButtonText = `Sign ${isSignUp ? "up" : "in"} with`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-4 flex items-center">
|
||||
@ -29,10 +26,10 @@ export const OAuthOptions: React.FC<TOAuthOptionProps> = observer((props) => {
|
||||
<div className={`mt-7 grid gap-4 overflow-hidden`}>
|
||||
{config?.is_google_enabled && (
|
||||
<div className="flex h-[42px] items-center !overflow-hidden">
|
||||
<GoogleOAuthButton text={`${oauthProviderButtonText} Google`} />
|
||||
<GoogleOAuthButton text="Continue with Google" />
|
||||
</div>
|
||||
)}
|
||||
{config?.is_github_enabled && <GithubOAuthButton text={`${oauthProviderButtonText} Github`} />}
|
||||
{config?.is_github_enabled && <GithubOAuthButton text="Continue with Github" />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -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: () => <div>Your account is deactivated. Contact support@plane.so.</div>,
|
||||
},
|
||||
|
||||
[EAuthenticationErrorCodes.USER_DOES_NOT_EXIST]: {
|
||||
title: `User does not exist`,
|
||||
message: (email = undefined) => (
|
||||
@ -324,6 +333,14 @@ const errorCodeMessages: {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
[EAuthenticationErrorCodes.ADMIN_USER_DEACTIVATED]: {
|
||||
title: `Admin user deactivated`,
|
||||
message: () => <div>Your account is deactivated</div>,
|
||||
},
|
||||
[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))
|
||||
|
@ -34,6 +34,11 @@ const nextConfig = {
|
||||
return [
|
||||
{
|
||||
source: "/accounts/sign-up",
|
||||
destination: "/sign-up",
|
||||
permanent: true
|
||||
},
|
||||
{
|
||||
source: "/sign-in",
|
||||
destination: "/",
|
||||
permanent: true
|
||||
}
|
||||
|
@ -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 (
|
||||
<div className="relative w-screen h-screen overflow-hidden">
|
||||
<PageHead title="Sign Up" />
|
||||
<PageHead title="Log in to continue" />
|
||||
<div className="absolute inset-0 z-0">
|
||||
<Image
|
||||
src={resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern}
|
||||
@ -46,18 +46,18 @@ const HomePage: NextPageWithLayout = observer(() => {
|
||||
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-end sm:items-center sm:gap-2 sm:flex-row text-center text-sm font-medium text-onboarding-text-300">
|
||||
Already have an account?{" "}
|
||||
New to Plane?{" "}
|
||||
<Link
|
||||
href="/sign-in"
|
||||
onClick={() => 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
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 lg:pt-28 transition-all">
|
||||
<AuthRoot authMode={EAuthModes.SIGN_UP} />
|
||||
<AuthRoot authMode={EAuthModes.SIGN_IN} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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(() => {
|
||||
<span className="text-2xl font-semibold sm:text-3xl">Plane</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-end sm:items-center sm:gap-2 sm:flex-row text-center text-sm font-medium text-onboarding-text-300">
|
||||
New to Plane?{" "}
|
||||
Already have an account?{" "}
|
||||
<Link
|
||||
href="/"
|
||||
onClick={() => captureEvent(NAVIGATE_TO_SIGNUP, {})}
|
||||
onClick={() => captureEvent(NAVIGATE_TO_SIGNIN, {})}
|
||||
className="font-semibold text-custom-primary-100 hover:underline"
|
||||
>
|
||||
Create an account
|
||||
Log in
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-grow container mx-auto max-w-lg px-10 lg:max-w-md lg:px-5 py-10 lg:pt-28 transition-all">
|
||||
<AuthRoot authMode={EAuthModes.SIGN_IN} />
|
||||
<AuthRoot authMode={EAuthModes.SIGN_UP} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -18,15 +18,8 @@ export class AuthService extends APIService {
|
||||
});
|
||||
}
|
||||
|
||||
signUpEmailCheck = async (data: IEmailCheckData): Promise<IEmailCheckResponse> =>
|
||||
this.post("/auth/sign-up/email-check/", data, { headers: {} })
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
|
||||
signInEmailCheck = async (data: IEmailCheckData): Promise<IEmailCheckResponse> =>
|
||||
this.post("/auth/sign-in/email-check/", data, { headers: {} })
|
||||
emailCheck = async (data: IEmailCheckData): Promise<IEmailCheckResponse> =>
|
||||
this.post("/auth/email-check/", data, { headers: {} })
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
|
Loading…
Reference in New Issue
Block a user