fix: authentication redirection and UI (#4432)

* dev: update python version

* dev: handle magic code attempt exhausted

* dev: update app, space and god mode redirection paths

* fix: handled signup and signin workflow

* chore: auth input error indication and autofill styling improvement

* dev: add app redirection urls

* dev: update redirections

* chore: onboarding improvement

* chore: onboarding improvement

* chore: redirection issue in space resolved

* chore: instance empty state added

* dev: fix app, space, admin redirection in docker setitngs

---------

Co-authored-by: guru_sainath <gurusainath007@gmail.com>
Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
This commit is contained in:
Nikhil 2024-05-10 17:30:38 +05:30 committed by GitHub
parent 2d1201cc92
commit 88ebda42ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 1336 additions and 541 deletions

View File

@ -32,7 +32,7 @@ ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_WEB_BASE_URL="" ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_URL="" ARG NEXT_PUBLIC_SPACE_BASE_URL="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode" ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
@ -62,7 +62,7 @@ ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_WEB_BASE_URL="" ARG NEXT_PUBLIC_WEB_BASE_URL=""
ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL ENV NEXT_PUBLIC_WEB_BASE_URL=$NEXT_PUBLIC_WEB_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_URL="" ARG NEXT_PUBLIC_SPACE_BASE_URL="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode" ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"

View File

@ -0,0 +1,46 @@
import React from "react";
import Image from "next/image";
import { Button } from "@plane/ui";
type Props = {
title: string;
description?: React.ReactNode;
image?: any;
primaryButton?: {
icon?: any;
text: string;
onClick: () => void;
};
secondaryButton?: React.ReactNode;
disabled?: boolean;
};
export const EmptyState: React.FC<Props> = ({
title,
description,
image,
primaryButton,
secondaryButton,
disabled = false,
}) => (
<div className={`flex h-full w-full items-center justify-center`}>
<div className="flex w-full flex-col items-center text-center">
{image && <Image src={image} className="w-52 sm:w-60" alt={primaryButton?.text || "button image"} />}
<h6 className="mb-3 mt-6 text-xl font-semibold sm:mt-8">{title}</h6>
{description && <p className="mb-7 px-5 text-custom-text-300 sm:mb-8">{description}</p>}
<div className="flex items-center gap-4">
{primaryButton && (
<Button
variant="primary"
prependIcon={primaryButton.icon}
onClick={primaryButton.onClick}
disabled={disabled}
>
{primaryButton.text}
</Button>
)}
{secondaryButton}
</div>
</div>
</div>
);

View File

@ -4,3 +4,4 @@ export * from "./controller-input";
export * from "./copy-field"; export * from "./copy-field";
export * from "./password-strength-meter"; export * from "./password-strength-meter";
export * from "./banner"; export * from "./banner";
export * from "./empty-state";

View File

@ -13,6 +13,7 @@ import { InstanceNotReady } from "@/components/instance";
import { useInstance } from "@/hooks/store"; import { useInstance } from "@/hooks/store";
// helpers // helpers
import { EInstancePageType } from "@/helpers"; import { EInstancePageType } from "@/helpers";
import { EmptyState } from "@/components/common";
type TInstanceWrapper = { type TInstanceWrapper = {
children: ReactNode; children: ReactNode;
@ -41,7 +42,12 @@ export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
); );
if (!instance) { if (!instance) {
return <>Something went wrong</>; return (
<EmptyState
title="Your instance wasn't configured successfully."
description="Please try re-installing Plane to fix the problem. If the issue still persists please reach out to support@plane.so."
/>
);
} }
if (instance?.instance?.is_setup_done === false && authEnabled === "1") if (instance?.instance?.is_setup_done === false && authEnabled === "1")

View File

@ -1,51 +1,52 @@
AUTHENTICATION_ERROR_CODES = { AUTHENTICATION_ERROR_CODES = {
# Global # Global
"INSTANCE_NOT_CONFIGURED": 5000, "INSTANCE_NOT_CONFIGURED": 5000,
"INVALID_EMAIL": 5012, "INVALID_EMAIL": 5005,
"EMAIL_REQUIRED": 5013, "EMAIL_REQUIRED": 5010,
"SIGNUP_DISABLED": 5001, "SIGNUP_DISABLED": 5015,
# Password strength # Password strength
"INVALID_PASSWORD": 5002, "INVALID_PASSWORD": 5020,
"SMTP_NOT_CONFIGURED": 5007, "SMTP_NOT_CONFIGURED": 5025,
# Sign Up # Sign Up
"USER_ALREADY_EXIST": 5003, "USER_ALREADY_EXIST": 5030,
"AUTHENTICATION_FAILED_SIGN_UP": 5006, "AUTHENTICATION_FAILED_SIGN_UP": 5035,
"REQUIRED_EMAIL_PASSWORD_SIGN_UP": 5015, "REQUIRED_EMAIL_PASSWORD_SIGN_UP": 5040,
"INVALID_EMAIL_SIGN_UP": 5017, "INVALID_EMAIL_SIGN_UP": 5045,
"INVALID_EMAIL_MAGIC_SIGN_UP": 5019, "INVALID_EMAIL_MAGIC_SIGN_UP": 5050,
"MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED": 5023, "MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED": 5055,
# Sign In # Sign In
"USER_DOES_NOT_EXIST": 5004, "USER_DOES_NOT_EXIST": 5060,
"AUTHENTICATION_FAILED_SIGN_IN": 5005, "AUTHENTICATION_FAILED_SIGN_IN": 5065,
"REQUIRED_EMAIL_PASSWORD_SIGN_IN": 5014, "REQUIRED_EMAIL_PASSWORD_SIGN_IN": 5070,
"INVALID_EMAIL_SIGN_IN": 5016, "INVALID_EMAIL_SIGN_IN": 5075,
"INVALID_EMAIL_MAGIC_SIGN_IN": 5018, "INVALID_EMAIL_MAGIC_SIGN_IN": 5080,
"MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED": 5022, "MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED": 5085,
# Both Sign in and Sign up # Both Sign in and Sign up for magic
"INVALID_MAGIC_CODE": 5008, "INVALID_MAGIC_CODE": 5090,
"EXPIRED_MAGIC_CODE": 5009, "EXPIRED_MAGIC_CODE": 5095,
"EMAIL_CODE_ATTEMPT_EXHAUSTED": 5100,
# Oauth # Oauth
"GOOGLE_NOT_CONFIGURED": 5010, "GOOGLE_NOT_CONFIGURED": 5105,
"GITHUB_NOT_CONFIGURED": 5011, "GITHUB_NOT_CONFIGURED": 5110,
"GOOGLE_OAUTH_PROVIDER_ERROR": 5021, "GOOGLE_OAUTH_PROVIDER_ERROR": 5115,
"GITHUB_OAUTH_PROVIDER_ERROR": 5020, "GITHUB_OAUTH_PROVIDER_ERROR": 5120,
# Reset Password # Reset Password
"INVALID_PASSWORD_TOKEN": 5024, "INVALID_PASSWORD_TOKEN": 5125,
"EXPIRED_PASSWORD_TOKEN": 5025, "EXPIRED_PASSWORD_TOKEN": 5130,
# Change password # Change password
"INCORRECT_OLD_PASSWORD": 5026, "INCORRECT_OLD_PASSWORD": 5135,
"INVALID_NEW_PASSWORD": 5027, "INVALID_NEW_PASSWORD": 5140,
# set passowrd # set passowrd
"PASSWORD_ALREADY_SET": 5028, "PASSWORD_ALREADY_SET": 5145,
# Admin # Admin
"ADMIN_ALREADY_EXIST": 5029, "ADMIN_ALREADY_EXIST": 5150,
"REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME": 5030, "REQUIRED_ADMIN_EMAIL_PASSWORD_FIRST_NAME": 5155,
"INVALID_ADMIN_EMAIL": 5031, "INVALID_ADMIN_EMAIL": 5160,
"INVALID_ADMIN_PASSWORD": 5032, "INVALID_ADMIN_PASSWORD": 5165,
"REQUIRED_ADMIN_EMAIL_PASSWORD": 5033, "REQUIRED_ADMIN_EMAIL_PASSWORD": 5170,
"ADMIN_AUTHENTICATION_FAILED": 5034, "ADMIN_AUTHENTICATION_FAILED": 5175,
"ADMIN_USER_ALREADY_EXIST": 5035, "ADMIN_USER_ALREADY_EXIST": 5180,
"ADMIN_USER_DOES_NOT_EXIST": 5036, "ADMIN_USER_DOES_NOT_EXIST": 5185,
} }

View File

@ -77,7 +77,13 @@ class MagicCodeProvider(CredentialAdapter):
current_attempt = data["current_attempt"] + 1 current_attempt = data["current_attempt"] + 1
if data["current_attempt"] > 2: if data["current_attempt"] > 2:
return key, "" raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[
"EMAIL_CODE_ATTEMPT_EXHAUSTED"
],
error_message="EMAIL_CODE_ATTEMPT_EXHAUSTED",
payload={"email": self.key},
)
value = { value = {
"current_attempt": current_attempt, "current_attempt": current_attempt,

View File

@ -5,21 +5,38 @@ from urllib.parse import urlsplit
from django.conf import settings from django.conf import settings
def base_host(request, is_admin=False, is_space=False): def base_host(request, is_admin=False, is_space=False, is_app=False):
"""Utility function to return host / origin from the request""" """Utility function to return host / origin from the request"""
# Calculate the base origin from request
if is_admin and settings.ADMIN_BASE_URL: base_origin = str(
return settings.ADMIN_BASE_URL
if is_space and settings.SPACE_BASE_URL:
return settings.SPACE_BASE_URL
return (
request.META.get("HTTP_ORIGIN") request.META.get("HTTP_ORIGIN")
or f"{urlsplit(request.META.get('HTTP_REFERER')).scheme}://{urlsplit(request.META.get('HTTP_REFERER')).netloc}" or f"{urlsplit(request.META.get('HTTP_REFERER')).scheme}://{urlsplit(request.META.get('HTTP_REFERER')).netloc}"
or f"""{"https" if request.is_secure() else "http"}://{request.get_host()}""" or f"""{"https" if request.is_secure() else "http"}://{request.get_host()}"""
) )
# Admin redirections
if is_admin:
if settings.ADMIN_BASE_URL:
return settings.ADMIN_BASE_URL
else:
return base_origin + "/god-mode/"
# Space redirections
if is_space:
if settings.SPACE_BASE_URL:
return settings.SPACE_BASE_URL
else:
return base_origin + "/spaces/"
# App Redirection
if is_app:
if settings.APP_BASE_URL:
return settings.APP_BASE_URL
else:
return base_origin
return base_origin
def user_ip(request): def user_ip(request):
return str(request.META.get("REMOTE_ADDR")) return str(request.META.get("REMOTE_ADDR"))

View File

@ -5,12 +5,17 @@ from django.contrib.auth import login
from plane.authentication.utils.host import base_host from plane.authentication.utils.host import base_host
def user_login(request, user): def user_login(request, user, is_app=False, is_admin=False, is_space=False):
login(request=request, user=user) login(request=request, user=user)
device_info = { device_info = {
"user_agent": request.META.get("HTTP_USER_AGENT", ""), "user_agent": request.META.get("HTTP_USER_AGENT", ""),
"ip_address": request.META.get("REMOTE_ADDR", ""), "ip_address": request.META.get("REMOTE_ADDR", ""),
"domain": base_host(request=request), "domain": base_host(
request=request,
is_app=is_app,
is_admin=is_admin,
is_space=is_space,
),
} }
request.session["device_info"] = device_info request.session["device_info"] = device_info
request.session.save() request.session.save()

View File

@ -42,8 +42,8 @@ class SignInAuthEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
# Base URL join # Base URL join
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/sign-in?" + urlencode(params), "sign-in?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -66,8 +66,8 @@ class SignInAuthEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/sign-in?" + urlencode(params), "sign-in?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -85,8 +85,8 @@ class SignInAuthEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/sign-in?" + urlencode(params), "sign-in?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -100,8 +100,8 @@ class SignInAuthEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/sign-in?" + urlencode(params), "sign-in?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -111,7 +111,7 @@ class SignInAuthEndpoint(View):
) )
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_app=True)
# Process workspace and project invitations # Process workspace and project invitations
process_workspace_project_invitations(user=user) process_workspace_project_invitations(user=user)
# Get the redirection path # Get the redirection path
@ -121,15 +121,15 @@ class SignInAuthEndpoint(View):
path = get_redirection_path(user=user) path = get_redirection_path(user=user)
# redirect to referer path # redirect to referer path
url = urljoin(base_host(request=request), path) url = urljoin(base_host(request=request, is_app=True), path)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except AuthenticationException as e: except AuthenticationException as e:
params = e.get_error_dict() params = e.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(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/sign-in?" + urlencode(params), "sign-in?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -152,7 +152,7 @@ class SignUpAuthEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -173,7 +173,7 @@ class SignUpAuthEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -192,7 +192,7 @@ class SignUpAuthEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -207,7 +207,7 @@ class SignUpAuthEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -218,7 +218,7 @@ class SignUpAuthEndpoint(View):
) )
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_app=True)
# Process workspace and project invitations # Process workspace and project invitations
process_workspace_project_invitations(user=user) process_workspace_project_invitations(user=user)
# Get the redirection path # Get the redirection path
@ -227,14 +227,14 @@ class SignUpAuthEndpoint(View):
else: else:
path = get_redirection_path(user=user) path = get_redirection_path(user=user)
# redirect to referer path # redirect to referer path
url = urljoin(base_host(request=request), path) url = urljoin(base_host(request=request, is_app=True), path)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except AuthenticationException as e: except AuthenticationException as e:
params = e.get_error_dict() params = e.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(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)

View File

@ -24,7 +24,7 @@ class GitHubOauthInitiateEndpoint(View):
def get(self, request): def get(self, request):
# Get host and next path # Get host and next path
request.session["host"] = base_host(request=request) request.session["host"] = base_host(request=request, is_app=True)
next_path = request.GET.get("next_path") next_path = request.GET.get("next_path")
if next_path: if next_path:
request.session["next_path"] = str(next_path) request.session["next_path"] = str(next_path)
@ -42,7 +42,7 @@ class GitHubOauthInitiateEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -57,7 +57,7 @@ class GitHubOauthInitiateEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -110,7 +110,7 @@ class GitHubCallbackEndpoint(View):
) )
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_app=True)
# Process workspace and project invitations # Process workspace and project invitations
process_workspace_project_invitations(user=user) process_workspace_project_invitations(user=user)
# Get the redirection path # Get the redirection path

View File

@ -24,7 +24,7 @@ from plane.authentication.adapter.error import (
class GoogleOauthInitiateEndpoint(View): class GoogleOauthInitiateEndpoint(View):
def get(self, request): def get(self, request):
request.session["host"] = base_host(request=request) request.session["host"] = base_host(request=request, is_app=True)
next_path = request.GET.get("next_path") next_path = request.GET.get("next_path")
if next_path: if next_path:
request.session["next_path"] = str(next_path) request.session["next_path"] = str(next_path)
@ -42,7 +42,7 @@ class GoogleOauthInitiateEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -58,7 +58,7 @@ class GoogleOauthInitiateEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -108,7 +108,7 @@ class GoogleCallbackEndpoint(View):
) )
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_app=True)
# Process workspace and project invitations # Process workspace and project invitations
process_workspace_project_invitations(user=user) process_workspace_project_invitations(user=user)
# Get the redirection path # Get the redirection path

View File

@ -90,8 +90,8 @@ class MagicSignInEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/sign-in?" + urlencode(params), "sign-in?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -104,8 +104,8 @@ class MagicSignInEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/sign-in?" + urlencode(params), "sign-in?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -116,7 +116,7 @@ class MagicSignInEndpoint(View):
user = provider.authenticate() user = provider.authenticate()
profile = Profile.objects.get(user=user) profile = Profile.objects.get(user=user)
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_app=True)
# Process workspace and project invitations # Process workspace and project invitations
process_workspace_project_invitations(user=user) process_workspace_project_invitations(user=user)
if user.is_password_autoset and profile.is_onboarded: if user.is_password_autoset and profile.is_onboarded:
@ -129,7 +129,7 @@ class MagicSignInEndpoint(View):
else str(process_workspace_project_invitations(user=user)) else str(process_workspace_project_invitations(user=user))
) )
# redirect to referer path # redirect to referer path
url = urljoin(base_host(request=request), path) url = urljoin(base_host(request=request, is_app=True), path)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except AuthenticationException as e: except AuthenticationException as e:
@ -137,8 +137,8 @@ class MagicSignInEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/sign-in?" + urlencode(params), "sign-in?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -163,7 +163,7 @@ class MagicSignUpEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -177,7 +177,7 @@ class MagicSignUpEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -188,7 +188,7 @@ class MagicSignUpEndpoint(View):
) )
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_app=True)
# Process workspace and project invitations # Process workspace and project invitations
process_workspace_project_invitations(user=user) process_workspace_project_invitations(user=user)
# Get the redirection path # Get the redirection path
@ -197,7 +197,7 @@ class MagicSignUpEndpoint(View):
else: else:
path = get_redirection_path(user=user) path = get_redirection_path(user=user)
# redirect to referer path # redirect to referer path
url = urljoin(base_host(request=request), path) url = urljoin(base_host(request=request, is_app=True), path)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except AuthenticationException as e: except AuthenticationException as e:
@ -205,7 +205,7 @@ class MagicSignUpEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)

View File

@ -146,7 +146,7 @@ class ResetPasswordEndpoint(View):
) )
params = exc.get_error_dict() params = exc.get_error_dict()
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/reset-password?" + urlencode(params), "accounts/reset-password?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -159,8 +159,9 @@ class ResetPasswordEndpoint(View):
error_message="INVALID_PASSWORD", error_message="INVALID_PASSWORD",
) )
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"?" + urlencode(exc.get_error_dict()), "accounts/reset-password?"
+ urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -172,7 +173,7 @@ class ResetPasswordEndpoint(View):
error_message="INVALID_PASSWORD", error_message="INVALID_PASSWORD",
) )
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/reset-password?" "accounts/reset-password?"
+ urlencode(exc.get_error_dict()), + urlencode(exc.get_error_dict()),
) )
@ -184,8 +185,8 @@ class ResetPasswordEndpoint(View):
user.save() user.save()
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/sign-in?" + urlencode({"success": True}), "sign-in?" + urlencode({"success": True}),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except DjangoUnicodeDecodeError: except DjangoUnicodeDecodeError:
@ -196,7 +197,7 @@ class ResetPasswordEndpoint(View):
error_message="EXPIRED_PASSWORD_TOKEN", error_message="EXPIRED_PASSWORD_TOKEN",
) )
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_app=True),
"accounts/reset-password?" + urlencode(exc.get_error_dict()), "accounts/reset-password?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)

View File

@ -1,5 +1,5 @@
# Python imports # Python imports
from urllib.parse import urlencode, urljoin from urllib.parse import urljoin
# Django imports # Django imports
from django.views import View from django.views import View
@ -23,12 +23,9 @@ class SignOutAuthEndpoint(View):
user.save() user.save()
# Log the user out # Log the user out
logout(request) logout(request)
url = urljoin( url = urljoin(base_host(request=request, is_app=True), "sign-in")
base_host(request=request),
"accounts/sign-in?" + urlencode({"success": "true"}),
)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except Exception: except Exception:
return HttpResponseRedirect( return HttpResponseRedirect(
base_host(request=request), "accounts/sign-in" base_host(request=request, is_app=True), "sign-in"
) )

View File

@ -70,7 +70,7 @@ class ChangePasswordEndpoint(APIView):
user.set_password(serializer.data.get("new_password")) user.set_password(serializer.data.get("new_password"))
user.is_password_autoset = False user.is_password_autoset = False
user.save() user.save()
user_login(user=user, request=request) user_login(user=user, request=request, is_app=True)
return Response( return Response(
{"message": "Password updated successfully"}, {"message": "Password updated successfully"},
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
@ -131,7 +131,7 @@ class SetUserPasswordEndpoint(APIView):
user.is_password_autoset = False user.is_password_autoset = False
user.save() user.save()
# Login the user as the session is invalidated # Login the user as the session is invalidated
user_login(user=user, request=request) user_login(user=user, request=request, is_app=True)
# Return the user # Return the user
serializer = UserSerializer(user) serializer = UserSerializer(user)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)

View File

@ -38,7 +38,7 @@ class SignInAuthSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"accounts/sign-in?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -60,7 +60,7 @@ class SignInAuthSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces/accounts/sign-in?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -79,7 +79,7 @@ class SignInAuthSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces/accounts/sign-in?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -94,7 +94,7 @@ class SignInAuthSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces/accounts/sign-in?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -104,11 +104,11 @@ class SignInAuthSpaceEndpoint(View):
) )
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_space=True)
# redirect to next path # redirect to next path
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
str(next_path) if next_path else "/", str(next_path) if next_path else "",
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except AuthenticationException as e: except AuthenticationException as e:
@ -117,7 +117,7 @@ class SignInAuthSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces/accounts/sign-in?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -141,7 +141,7 @@ class SignUpAuthSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -162,7 +162,7 @@ class SignUpAuthSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
# Validate the email # Validate the email
@ -181,7 +181,7 @@ class SignUpAuthSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -196,7 +196,7 @@ class SignUpAuthSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -206,11 +206,11 @@ class SignUpAuthSpaceEndpoint(View):
) )
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_space=True)
# redirect to referer path # redirect to referer path
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
str(next_path) if next_path else "spaces", str(next_path) if next_path else "",
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except AuthenticationException as e: except AuthenticationException as e:
@ -219,6 +219,6 @@ class SignUpAuthSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)

View File

@ -55,7 +55,7 @@ class GitHubOauthInitiateSpaceEndpoint(View):
if next_path: if next_path:
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request), base_host(request=request, is_space=True),
"?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -108,10 +108,10 @@ class GitHubCallbackSpaceEndpoint(View):
) )
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_space=True)
# Process workspace and project invitations # Process workspace and project invitations
# redirect to referer path # redirect to referer path
url = urljoin(base_host, str(next_path) if next_path else "/") url = urljoin(base_host, str(next_path) if next_path else "")
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except AuthenticationException as e: except AuthenticationException as e:
params = e.get_error_dict() params = e.get_error_dict()

View File

@ -103,7 +103,7 @@ class GoogleCallbackSpaceEndpoint(View):
) )
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_space=True)
# redirect to referer path # redirect to referer path
url = urljoin( url = urljoin(
base_host, str(next_path) if next_path else "/spaces" base_host, str(next_path) if next_path else "/spaces"

View File

@ -86,7 +86,7 @@ class MagicSignInSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces/accounts/sign-in?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -99,7 +99,7 @@ class MagicSignInSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"accounts/sign-in?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -109,14 +109,14 @@ class MagicSignInSpaceEndpoint(View):
) )
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_space=True)
# redirect to referer path # redirect to referer path
profile = Profile.objects.get(user=user) profile = Profile.objects.get(user=user)
if user.is_password_autoset and profile.is_onboarded: if user.is_password_autoset and profile.is_onboarded:
path = "spaces/accounts/set-password" path = "accounts/set-password"
else: else:
# Get the redirection path # Get the redirection path
path = str(next_path) if next_path else "spaces" path = str(next_path) if next_path else ""
url = urljoin(base_host(request=request, is_space=True), path) url = urljoin(base_host(request=request, is_space=True), path)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -126,7 +126,7 @@ class MagicSignInSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces/accounts/sign-in?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -152,7 +152,7 @@ class MagicSignUpSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces/accounts/sign-in?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -176,7 +176,7 @@ class MagicSignUpSpaceEndpoint(View):
) )
user = provider.authenticate() user = provider.authenticate()
# Login the user and record his device info # Login the user and record his device info
user_login(request=request, user=user) user_login(request=request, user=user, is_space=True)
# redirect to referer path # redirect to referer path
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
@ -190,6 +190,6 @@ class MagicSignUpSpaceEndpoint(View):
params["next_path"] = str(next_path) params["next_path"] = str(next_path)
url = urljoin( url = urljoin(
base_host(request=request, is_space=True), base_host(request=request, is_space=True),
"spaces/accounts/sign-in?" + urlencode(params), "?" + urlencode(params),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)

View File

@ -183,11 +183,9 @@ class ResetPasswordSpaceEndpoint(View):
user.is_password_autoset = False user.is_password_autoset = False
user.save() user.save()
url = urljoin( return HttpResponseRedirect(
base_host(request=request, is_space=True), base_host(request=request, is_space=True)
"accounts/sign-in?" + urlencode({"success": True}),
) )
return HttpResponseRedirect(url)
except DjangoUnicodeDecodeError: except DjangoUnicodeDecodeError:
exc = AuthenticationException( exc = AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES[ error_code=AUTHENTICATION_ERROR_CODES[

View File

@ -23,12 +23,10 @@ class SignOutAuthSpaceEndpoint(View):
user.save() user.save()
# Log the user out # Log the user out
logout(request) logout(request)
url = urljoin( return HttpResponseRedirect(
base_host(request=request, is_space=True), base_host(request=request, is_space=True)
"accounts/sign-in?" + urlencode({"success": "true"}),
) )
return HttpResponseRedirect(url)
except Exception: except Exception:
return HttpResponseRedirect( return HttpResponseRedirect(
base_host(request=request, is_space=True), "accounts/sign-in" base_host(request=request, is_space=True)
) )

View File

@ -107,7 +107,7 @@ class InstanceAdminSignUpEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/setup?" + urlencode(exc.get_error_dict()), "setup?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -119,7 +119,7 @@ class InstanceAdminSignUpEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/setup?" + urlencode(exc.get_error_dict()), "setup?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -148,7 +148,7 @@ class InstanceAdminSignUpEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/setup?" + urlencode(exc.get_error_dict()), "setup?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -170,7 +170,7 @@ class InstanceAdminSignUpEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/setup?" + urlencode(exc.get_error_dict()), "setup?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -192,7 +192,7 @@ class InstanceAdminSignUpEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/setup?" + urlencode(exc.get_error_dict()), "setup?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
else: else:
@ -214,7 +214,7 @@ class InstanceAdminSignUpEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/setup?" + urlencode(exc.get_error_dict()), "setup?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -247,10 +247,8 @@ class InstanceAdminSignUpEndpoint(View):
instance.save() instance.save()
# get tokens for user # get tokens for user
user_login(request=request, user=user) user_login(request=request, user=user, is_admin=True)
url = urljoin( url = urljoin(base_host(request=request, is_admin=True), "general")
base_host(request=request, is_admin=True), "god-mode/general"
)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -272,7 +270,7 @@ class InstanceAdminSignInEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/login?" + urlencode(exc.get_error_dict()), "?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -293,7 +291,7 @@ class InstanceAdminSignInEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/login?" + urlencode(exc.get_error_dict()), "?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -311,7 +309,7 @@ class InstanceAdminSignInEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/login?" + urlencode(exc.get_error_dict()), "?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -331,7 +329,7 @@ class InstanceAdminSignInEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/login?" + urlencode(exc.get_error_dict()), "?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -348,7 +346,7 @@ class InstanceAdminSignInEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/login?" + urlencode(exc.get_error_dict()), "?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -365,7 +363,7 @@ class InstanceAdminSignInEndpoint(View):
) )
url = urljoin( url = urljoin(
base_host(request=request, is_admin=True), base_host(request=request, is_admin=True),
"god-mode/login?" + urlencode(exc.get_error_dict()), "?" + urlencode(exc.get_error_dict()),
) )
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
# settings last active for the user # settings last active for the user
@ -378,10 +376,8 @@ class InstanceAdminSignInEndpoint(View):
user.save() user.save()
# get tokens for user # get tokens for user
user_login(request=request, user=user) user_login(request=request, user=user, is_admin=True)
url = urljoin( url = urljoin(base_host(request=request, is_admin=True), "general")
base_host(request=request, is_admin=True), "god-mode/general"
)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
@ -414,12 +410,9 @@ class InstanceAdminSignOutEndpoint(View):
user.save() user.save()
# Log the user out # Log the user out
logout(request) logout(request)
url = urljoin( url = urljoin(base_host(request=request, is_admin=True))
base_host(request=request, is_admin=True),
"accounts/sign-in?" + urlencode({"success": "true"}),
)
return HttpResponseRedirect(url) return HttpResponseRedirect(url)
except Exception: except Exception:
return HttpResponseRedirect( return HttpResponseRedirect(
base_host(request=request, is_admin=True), "accounts/sign-in" base_host(request=request, is_admin=True)
) )

View File

@ -346,4 +346,4 @@ CSRF_COOKIE_DOMAIN = os.environ.get("COOKIE_DOMAIN", None)
# Base URLs # Base URLs
ADMIN_BASE_URL = os.environ.get("ADMIN_BASE_URL", None) ADMIN_BASE_URL = os.environ.get("ADMIN_BASE_URL", None)
SPACE_BASE_URL = os.environ.get("SPACE_BASE_URL", None) SPACE_BASE_URL = os.environ.get("SPACE_BASE_URL", None)
APP_BASE_URL = os.environ.get("ADMIN_BASE_URL", None) APP_BASE_URL = os.environ.get("APP_BASE_URL") or os.environ.get("WEB_URL")

View File

@ -35,10 +35,10 @@ CORS_ALLOWED_ORIGINS = [
"http://127.0.0.1", "http://127.0.0.1",
"http://localhost:3000", "http://localhost:3000",
"http://127.0.0.1:3000", "http://127.0.0.1:3000",
"http://localhost:4000", "http://localhost:3001",
"http://127.0.0.1:4000", "http://127.0.0.1:3001",
"http://localhost:3333", "http://localhost:3002",
"http://127.0.0.1:3333", "http://127.0.0.1:3002",
] ]
CSRF_TRUSTED_ORIGINS = CORS_ALLOWED_ORIGINS CSRF_TRUSTED_ORIGINS = CORS_ALLOWED_ORIGINS
CORS_ALLOW_ALL_ORIGINS = True CORS_ALLOW_ALL_ORIGINS = True

View File

@ -12,8 +12,6 @@ SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
INSTALLED_APPS += ("scout_apm.django",) # noqa INSTALLED_APPS += ("scout_apm.django",) # noqa
# Honor the 'X-Forwarded-Proto' header for request.is_secure()
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Scout Settings # Scout Settings
SCOUT_MONITOR = os.environ.get("SCOUT_MONITOR", False) SCOUT_MONITOR = os.environ.get("SCOUT_MONITOR", False)

View File

@ -1 +1 @@
python-3.11.9 python-3.12.3

View File

@ -140,8 +140,11 @@ export const OnBoardingForm: React.FC<Props> = observer((props) => {
</button> </button>
</div> </div>
<div className="flex gap-4"> <div className="flex gap-4">
<div className="space-y-1"> <div className="space-y-1 w-full">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="first_name"> <label
className="text-sm text-onboarding-text-300 font-medium after:content-['*'] after:ml-0.5 after:text-red-500"
htmlFor="first_name"
>
First name First name
</label> </label>
<Controller <Controller
@ -171,8 +174,11 @@ export const OnBoardingForm: React.FC<Props> = observer((props) => {
/> />
{errors.first_name && <span className="text-sm text-red-500">{errors.first_name.message}</span>} {errors.first_name && <span className="text-sm text-red-500">{errors.first_name.message}</span>}
</div> </div>
<div className="space-y-1"> <div className="space-y-1 w-full">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="last_name"> <label
className="text-sm text-onboarding-text-300 font-medium after:content-['*'] after:ml-0.5 after:text-red-500"
htmlFor="last_name"
>
Last name Last name
</label> </label>
<Controller <Controller

View File

@ -6,5 +6,5 @@ import { IProfileStore } from "@/store/user/profile.store";
export const useUserProfile = (): IProfileStore => { export const useUserProfile = (): IProfileStore => {
const context = useContext(StoreContext); const context = useContext(StoreContext);
if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider"); if (context === undefined) throw new Error("useUserProfile must be used within StoreProvider");
return context.profile; return context.user.userProfile;
}; };

View File

@ -32,13 +32,17 @@ export const AuthWrapper: FC<TAuthWrapper> = observer((props) => {
<Spinner /> <Spinner />
</div> </div>
); );
if (pageType === EPageTypes.PUBLIC) return <>{children}</>; if (pageType === EPageTypes.PUBLIC) return <>{children}</>;
if (pageType === EPageTypes.INIT) { if (pageType === EPageTypes.INIT) {
if (!currentUser?.id) return <>{children}</>; if (!currentUser?.id) return <>{children}</>;
else { else {
if (currentUserProfile?.id && currentUserProfile?.onboarding_step?.profile_complete) return <>{children}</>; if (
currentUserProfile &&
currentUserProfile?.id &&
Boolean(currentUserProfile?.onboarding_step?.profile_complete)
)
return <>{children}</>;
else { else {
router.push(`/onboarding`); router.push(`/onboarding`);
return <></>; return <></>;

View File

@ -16,7 +16,7 @@ import { useUser, useUserProfile } from "@/hooks/store";
import { AuthWrapper } from "@/lib/wrappers"; import { AuthWrapper } from "@/lib/wrappers";
// assets // assets
import ProfileSetupDark from "public/onboarding/profile-setup-dark.svg"; import ProfileSetupDark from "public/onboarding/profile-setup-dark.svg";
import ProfileSetup from "public/onboarding/profile-setup.svg"; import ProfileSetup from "public/onboarding/profile-setup-light.svg";
const OnBoardingPage = observer(() => { const OnBoardingPage = observer(() => {
// router // router
@ -47,8 +47,8 @@ const OnBoardingPage = observer(() => {
console.log("Failed to update onboarding status"); console.log("Failed to update onboarding status");
}); });
if (next_path) router.replace(next_path.toString()); if (next_path) router.push(next_path.toString());
router.replace("/"); router.push("/");
}; };
return ( return (

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 729 KiB

After

Width:  |  Height:  |  Size: 994 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 993 KiB

View File

@ -4,7 +4,6 @@ import { enableStaticRendering } from "mobx-react-lite";
import { IInstanceStore, InstanceStore } from "@/store/instance.store"; import { IInstanceStore, InstanceStore } from "@/store/instance.store";
import { IProjectStore, ProjectStore } from "@/store/project"; import { IProjectStore, ProjectStore } from "@/store/project";
import { IUserStore, UserStore } from "@/store/user"; import { IUserStore, UserStore } from "@/store/user";
import { IProfileStore, ProfileStore } from "@/store/user/profile.store";
import IssueStore, { IIssueStore } from "./issue"; import IssueStore, { IIssueStore } from "./issue";
import IssueDetailStore, { IIssueDetailStore } from "./issue_details"; import IssueDetailStore, { IIssueDetailStore } from "./issue_details";
@ -16,7 +15,6 @@ enableStaticRendering(typeof window === "undefined");
export class RootStore { export class RootStore {
instance: IInstanceStore; instance: IInstanceStore;
user: IUserStore; user: IUserStore;
profile: IProfileStore;
project: IProjectStore; project: IProjectStore;
issue: IIssueStore; issue: IIssueStore;
@ -27,9 +25,8 @@ export class RootStore {
constructor() { constructor() {
this.instance = new InstanceStore(this); this.instance = new InstanceStore(this);
this.user = new UserStore(this); this.user = new UserStore(this);
this.profile = new ProfileStore(this);
this.project = new ProjectStore(this);
this.project = new ProjectStore(this);
this.issue = new IssueStore(this); this.issue = new IssueStore(this);
this.issueDetails = new IssueDetailStore(this); this.issueDetails = new IssueDetailStore(this);
this.mentionsStore = new MentionsStore(this); this.mentionsStore = new MentionsStore(this);
@ -41,7 +38,6 @@ export class RootStore {
this.instance = new InstanceStore(this); this.instance = new InstanceStore(this);
this.user = new UserStore(this); this.user = new UserStore(this);
this.profile = new ProfileStore(this);
this.project = new ProjectStore(this); this.project = new ProjectStore(this);
this.issue = new IssueStore(this); this.issue = new IssueStore(this);

View File

@ -74,7 +74,7 @@ ENV NEXT_PUBLIC_ADMIN_BASE_URL=$NEXT_PUBLIC_ADMIN_BASE_URL
ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode" ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH ENV NEXT_PUBLIC_ADMIN_BASE_PATH=$NEXT_PUBLIC_ADMIN_BASE_PATH
ARG NEXT_PUBLIC_SPACE_BASE_URL="" ARG NEXT_PUBLIC_SPACE_BASE_URL="/spaces"
ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL ENV NEXT_PUBLIC_SPACE_BASE_URL=$NEXT_PUBLIC_SPACE_BASE_URL
ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces" ARG NEXT_PUBLIC_SPACE_BASE_PATH="/spaces"

View File

@ -2,7 +2,6 @@ import React, { FC, useEffect, useState } from "react";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { IEmailCheckData } from "@plane/types"; import { IEmailCheckData } from "@plane/types";
import { TOAST_TYPE, setToast } from "@plane/ui";
// components // components
import { import {
AuthHeader, AuthHeader,
@ -43,6 +42,7 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL); const [authStep, setAuthStep] = useState<EAuthSteps>(EAuthSteps.EMAIL);
const [email, setEmail] = useState(emailParam ? emailParam.toString() : ""); const [email, setEmail] = useState(emailParam ? emailParam.toString() : "");
const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined); const [errorInfo, setErrorInfo] = useState<TAuthErrorInfo | undefined>(undefined);
const [isPasswordAutoset, setIsPasswordAutoset] = useState(true);
// hooks // hooks
const { instance } = useInstance(); const { instance } = useInstance();
@ -63,52 +63,57 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
) )
) )
setAuthStep(EAuthSteps.UNIQUE_CODE); setAuthStep(EAuthSteps.UNIQUE_CODE);
setErrorInfo(errorhandler);
// validating weather to show alert to banner
if (errorhandler?.type === EErrorAlertType.TOAST_ALERT) {
setToast({
type: TOAST_TYPE.ERROR,
title: errorhandler?.title,
message: errorhandler?.message as string,
});
} else setErrorInfo(errorhandler);
} }
} }
}, [error_code, authMode]); }, [error_code, authMode]);
// step 1 submit handler- email verification // submit handler- email verification
const handleEmailVerification = async (data: IEmailCheckData) => { const handleEmailVerification = async (data: IEmailCheckData) => {
setEmail(data.email); setEmail(data.email);
const emailCheckRequest = const emailCheckRequest =
authMode === EAuthModes.SIGN_IN ? authService.signInEmailCheck(data) : authService.signUpEmailCheck(data); authMode === EAuthModes.SIGN_IN ? authService.signInEmailCheck(data) : authService.signUpEmailCheck(data);
await emailCheckRequest await emailCheckRequest
.then((response) => { .then(async (response) => {
if (authMode === EAuthModes.SIGN_IN) { if (authMode === EAuthModes.SIGN_IN) {
if (response.is_password_autoset) setAuthStep(EAuthSteps.UNIQUE_CODE); if (response.is_password_autoset) {
else setAuthStep(EAuthSteps.PASSWORD); setAuthStep(EAuthSteps.UNIQUE_CODE);
generateEmailUniqueCode(data.email);
} else { } else {
if (instance && instance?.config?.is_smtp_configured) setAuthStep(EAuthSteps.UNIQUE_CODE); setIsPasswordAutoset(false);
else setAuthStep(EAuthSteps.PASSWORD); setAuthStep(EAuthSteps.PASSWORD);
}
} else {
if (instance && instance?.config?.is_smtp_configured) {
setAuthStep(EAuthSteps.UNIQUE_CODE);
generateEmailUniqueCode(data.email);
} else 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 === EErrorAlertType.BANNER_ALERT) { if (errorhandler?.type) setErrorInfo(errorhandler);
setErrorInfo(errorhandler);
return;
} else if (errorhandler?.type === EErrorAlertType.TOAST_ALERT)
setToast({
type: TOAST_TYPE.ERROR,
title: errorhandler?.title,
message: (errorhandler?.message as string) || "Something went wrong. Please try again.",
}); });
};
// generating the unique code
const generateEmailUniqueCode = async (email: string): Promise<{ code: string } | undefined> => {
const payload = { email: email };
return await authService
.generateUniqueCode(payload)
.then(() => ({ code: "" }))
.catch((error) => {
const errorhandler = authErrorHandler(error?.error_code.toString());
if (errorhandler?.type) setErrorInfo(errorhandler);
throw error;
}); });
}; };
const isOAuthEnabled = const isOAuthEnabled =
instance?.config && (instance?.config?.is_google_enabled || instance?.config?.is_github_enabled); (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">
@ -125,24 +130,29 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
{authStep === EAuthSteps.EMAIL && <AuthEmailForm defaultEmail={email} onSubmit={handleEmailVerification} />} {authStep === EAuthSteps.EMAIL && <AuthEmailForm defaultEmail={email} onSubmit={handleEmailVerification} />}
{authStep === EAuthSteps.UNIQUE_CODE && ( {authStep === EAuthSteps.UNIQUE_CODE && (
<AuthUniqueCodeForm <AuthUniqueCodeForm
mode={authMode}
email={email} email={email}
handleEmailClear={() => { handleEmailClear={() => {
setEmail(""); setEmail("");
setAuthStep(EAuthSteps.EMAIL); setAuthStep(EAuthSteps.EMAIL);
}} }}
submitButtonText="Continue" generateEmailUniqueCode={generateEmailUniqueCode}
mode={authMode}
/> />
)} )}
{authStep === EAuthSteps.PASSWORD && ( {authStep === EAuthSteps.PASSWORD && (
<AuthPasswordForm <AuthPasswordForm
mode={authMode}
isPasswordAutoset={isPasswordAutoset}
isSMTPConfigured={isSMTPConfigured}
email={email} email={email}
handleEmailClear={() => { handleEmailClear={() => {
setEmail(""); setEmail("");
setAuthStep(EAuthSteps.EMAIL); setAuthStep(EAuthSteps.EMAIL);
}} }}
handleStepChange={(step) => setAuthStep(step)} handleAuthStep={(step: EAuthSteps) => {
mode={authMode} if (step === EAuthSteps.UNIQUE_CODE) generateEmailUniqueCode(email);
setAuthStep(step);
}}
/> />
)} )}
{isOAuthEnabled && <OAuthOptions />} {isOAuthEnabled && <OAuthOptions />}

View File

@ -7,6 +7,7 @@ import { IEmailCheckData } from "@plane/types";
// ui // ui
import { Button, Input, Spinner } from "@plane/ui"; import { Button, Input, Spinner } from "@plane/ui";
// helpers // helpers
import { cn } from "@/helpers/common.helper";
import { checkEmailValidity } from "@/helpers/string.helper"; import { checkEmailValidity } from "@/helpers/string.helper";
type TAuthEmailForm = { type TAuthEmailForm = {
@ -19,6 +20,7 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
// states // states
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [email, setEmail] = useState(defaultEmail); const [email, setEmail] = useState(defaultEmail);
const [isFocused, setFocused] = useState(false);
const emailError = useMemo( const emailError = useMemo(
() => (email && !checkEmailValidity(email) ? { email: "Email is invalid" } : undefined), () => (email && !checkEmailValidity(email) ? { email: "Email is invalid" } : undefined),
@ -38,31 +40,36 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
const isButtonDisabled = email.length === 0 || Boolean(emailError?.email) || isSubmitting; const isButtonDisabled = email.length === 0 || Boolean(emailError?.email) || isSubmitting;
return ( return (
<form onSubmit={handleFormSubmit} className="mt-8 space-y-4"> <form onSubmit={handleFormSubmit} className="mt-5 space-y-4">
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email"> <label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
Email Email
</label> </label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200"> <div
className={cn(
`relative flex items-center rounded-md bg-onboarding-background-200 border`,
!isFocused && Boolean(emailError?.email) ? `border-red-500` : `border-onboarding-border-100`
)}
>
<Input <Input
id="email" id="email"
name="email" name="email"
type="email" type="email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
hasError={Boolean(emailError?.email)}
placeholder="name@company.com" placeholder="name@company.com"
className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400" 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)}
autoFocus autoFocus
/> />
{email.length > 0 && ( {email.length > 0 && (
<XCircle <div className="flex-shrink-0 h-5 w-5 mr-2 bg-onboarding-background-200 hover:cursor-pointer">
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer" <XCircle className="h-5 w-5 stroke-custom-text-400" onClick={() => setEmail("")} />
onClick={() => setEmail("")} </div>
/>
)} )}
</div> </div>
{emailError?.email && ( {emailError?.email && !isFocused && (
<p className="flex items-center gap-1 text-xs text-red-600 px-0.5"> <p className="flex items-center gap-1 text-xs text-red-600 px-0.5">
<CircleAlert height={12} width={12} /> <CircleAlert height={12} width={12} />
{emailError.email} {emailError.email}

View File

@ -14,15 +14,17 @@ import { EAuthModes, EAuthSteps } from "@/helpers/authentication.helper";
import { API_BASE_URL } from "@/helpers/common.helper"; import { API_BASE_URL } from "@/helpers/common.helper";
import { getPasswordStrength } from "@/helpers/password.helper"; import { getPasswordStrength } from "@/helpers/password.helper";
// hooks // hooks
import { useEventTracker, useInstance } from "@/hooks/store"; import { useEventTracker } from "@/hooks/store";
// services // services
import { AuthService } from "@/services/auth.service"; import { AuthService } from "@/services/auth.service";
type Props = { type Props = {
email: string; email: string;
isPasswordAutoset: boolean;
isSMTPConfigured: boolean;
mode: EAuthModes; mode: EAuthModes;
handleStepChange: (step: EAuthSteps) => void;
handleEmailClear: () => void; handleEmailClear: () => void;
handleAuthStep: (step: EAuthSteps) => void;
}; };
type TPasswordFormValues = { type TPasswordFormValues = {
@ -39,9 +41,8 @@ const defaultValues: TPasswordFormValues = {
const authService = new AuthService(); const authService = new AuthService();
export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => { export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
const { email, handleStepChange, handleEmailClear, mode } = props; const { email, isSMTPConfigured, handleAuthStep, handleEmailClear, mode } = props;
// hooks // hooks
const { instance } = useInstance();
const { captureEvent } = useEventTracker(); const { captureEvent } = useEventTracker();
// states // states
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined); const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
@ -56,9 +57,6 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
const handleShowPassword = (key: keyof typeof showPassword) => const handleShowPassword = (key: keyof typeof showPassword) =>
setShowPassword((prev) => ({ ...prev, [key]: !prev[key] })); setShowPassword((prev) => ({ ...prev, [key]: !prev[key] }));
// derived values
const isSmtpConfigured = instance?.config?.is_smtp_configured;
const handleFormChange = (key: keyof TPasswordFormValues, value: string) => const handleFormChange = (key: keyof TPasswordFormValues, value: string) =>
setPasswordFormData((prev) => ({ ...prev, [key]: value })); setPasswordFormData((prev) => ({ ...prev, [key]: value }));
@ -68,13 +66,13 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
}, [csrfToken]); }, [csrfToken]);
const redirectToUniqueCodeSignIn = async () => { const redirectToUniqueCodeSignIn = async () => {
handleStepChange(EAuthSteps.UNIQUE_CODE); handleAuthStep(EAuthSteps.UNIQUE_CODE);
}; };
const passwordSupport = const passwordSupport =
mode === EAuthModes.SIGN_IN ? ( mode === EAuthModes.SIGN_IN ? (
<div className="mt-2 w-full pb-3"> <div className="w-full">
{isSmtpConfigured ? ( {isSMTPConfigured ? (
<Link <Link
onClick={() => captureEvent(FORGOT_PASSWORD)} onClick={() => captureEvent(FORGOT_PASSWORD)}
href={`/accounts/forgot-password?email=${email}`} href={`/accounts/forgot-password?email=${email}`}
@ -87,7 +85,10 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
)} )}
</div> </div>
) : ( ) : (
isPasswordInputFocused && <PasswordStrengthMeter password={passwordFormData.password} /> passwordFormData.password.length > 0 &&
(getPasswordStrength(passwordFormData.password) < 3 || isPasswordInputFocused) && (
<PasswordStrengthMeter password={passwordFormData.password} />
)
); );
const isButtonDisabled = useMemo( const isButtonDisabled = useMemo(
@ -112,11 +113,14 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
onError={() => setIsSubmitting(false)} onError={() => setIsSubmitting(false)}
> >
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} /> <input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
<input type="hidden" value={passwordFormData.email} name="email" />
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email"> <label className="text-sm font-medium text-onboarding-text-300" htmlFor="email">
Email Email
</label> </label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200"> <div
className={`relative flex items-center rounded-md bg-onboarding-background-200 border border-onboarding-border-100`}
>
<Input <Input
id="email" id="email"
name="email" name="email"
@ -124,18 +128,17 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
value={passwordFormData.email} value={passwordFormData.email}
onChange={(e) => handleFormChange("email", e.target.value)} onChange={(e) => handleFormChange("email", e.target.value)}
placeholder="name@company.com" placeholder="name@company.com"
className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400" className={`disable-autofill-style h-[46px] w-full placeholder:text-onboarding-text-400 border-0`}
disabled disabled
/> />
{passwordFormData.email.length > 0 && ( {passwordFormData.email.length > 0 && (
<XCircle <div className="flex-shrink-0 h-5 w-5 mr-2 bg-onboarding-background-200 hover:cursor-pointer">
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer" <XCircle className="h-5 w-5 stroke-custom-text-400" onClick={handleEmailClear} />
onClick={handleEmailClear} </div>
/>
)} )}
</div> </div>
<input type="hidden" value={passwordFormData.email} name="email" />
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password"> <label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
{mode === EAuthModes.SIGN_IN ? "Password" : "Set a password"} {mode === EAuthModes.SIGN_IN ? "Password" : "Set a password"}
@ -147,7 +150,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
value={passwordFormData.password} value={passwordFormData.password}
onChange={(e) => handleFormChange("password", e.target.value)} onChange={(e) => handleFormChange("password", e.target.value)}
placeholder="Enter password" placeholder="Enter password"
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" className="disable-autofill-style h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
onFocus={() => setIsPasswordInputFocused(true)} onFocus={() => setIsPasswordInputFocused(true)}
onBlur={() => setIsPasswordInputFocused(false)} onBlur={() => setIsPasswordInputFocused(false)}
autoFocus autoFocus
@ -166,6 +169,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
</div> </div>
{passwordSupport} {passwordSupport}
</div> </div>
{mode === EAuthModes.SIGN_UP && ( {mode === EAuthModes.SIGN_UP && (
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password"> <label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
@ -178,7 +182,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
value={passwordFormData.confirm_password} value={passwordFormData.confirm_password}
onChange={(e) => handleFormChange("confirm_password", e.target.value)} onChange={(e) => handleFormChange("confirm_password", e.target.value)}
placeholder="Confirm password" placeholder="Confirm password"
className="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400" className="disable-autofill-style h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
/> />
{showPassword?.retypePassword ? ( {showPassword?.retypePassword ? (
<EyeOff <EyeOff
@ -197,19 +201,20 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
)} )}
</div> </div>
)} )}
<div className="space-y-2.5"> <div className="space-y-2.5">
{mode === EAuthModes.SIGN_IN ? ( {mode === EAuthModes.SIGN_IN ? (
<> <>
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}> <Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
{isSubmitting ? ( {isSubmitting ? (
<Spinner height="20px" width="20px" /> <Spinner height="20px" width="20px" />
) : isSmtpConfigured ? ( ) : isSMTPConfigured ? (
"Continue" "Continue"
) : ( ) : (
"Go to workspace" "Go to workspace"
)} )}
</Button> </Button>
{instance && isSmtpConfigured && ( {isSMTPConfigured && (
<Button <Button
type="button" type="button"
onClick={redirectToUniqueCodeSignIn} onClick={redirectToUniqueCodeSignIn}

View File

@ -1,7 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { CircleCheck, XCircle } from "lucide-react"; import { CircleCheck, XCircle } from "lucide-react";
import { IEmailCheckData } from "@plane/types"; import { Button, Input, Spinner } from "@plane/ui";
import { Button, Input, Spinner, TOAST_TYPE, setToast } from "@plane/ui";
// helpers // helpers
import { EAuthModes } from "@/helpers/authentication.helper"; import { EAuthModes } from "@/helpers/authentication.helper";
import { API_BASE_URL } from "@/helpers/common.helper"; import { API_BASE_URL } from "@/helpers/common.helper";
@ -10,11 +9,14 @@ import useTimer from "@/hooks/use-timer";
// services // services
import { AuthService } from "@/services/auth.service"; import { AuthService } from "@/services/auth.service";
type Props = { // services
const authService = new AuthService();
type TAuthUniqueCodeForm = {
mode: EAuthModes;
email: string; email: string;
handleEmailClear: () => void; handleEmailClear: () => void;
submitButtonText: string; generateEmailUniqueCode: (email: string) => Promise<{ code: string } | undefined>;
mode: EAuthModes;
}; };
type TUniqueCodeFormValues = { type TUniqueCodeFormValues = {
@ -27,55 +29,35 @@ const defaultValues: TUniqueCodeFormValues = {
code: "", code: "",
}; };
// services export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
const authService = new AuthService(); const { mode, email, handleEmailClear, generateEmailUniqueCode } = props;
// hooks
export const AuthUniqueCodeForm: React.FC<Props> = (props) => { // const { captureEvent } = useEventTracker();
const { email, handleEmailClear, submitButtonText, mode } = props; // derived values
const defaultResetTimerValue = 5;
// states // states
const [uniqueCodeFormData, setUniqueCodeFormData] = useState<TUniqueCodeFormValues>({ ...defaultValues, email }); const [uniqueCodeFormData, setUniqueCodeFormData] = useState<TUniqueCodeFormValues>({ ...defaultValues, email });
const [isRequestingNewCode, setIsRequestingNewCode] = useState(false); const [isRequestingNewCode, setIsRequestingNewCode] = useState(false);
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined); const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
// store hooks
// const { captureEvent } = useEventTracker();
// timer // timer
const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(30); const { timer: resendTimerCode, setTimer: setResendCodeTimer } = useTimer(0);
const handleFormChange = (key: keyof TUniqueCodeFormValues, value: string) => const handleFormChange = (key: keyof TUniqueCodeFormValues, value: string) =>
setUniqueCodeFormData((prev) => ({ ...prev, [key]: value })); setUniqueCodeFormData((prev) => ({ ...prev, [key]: value }));
const handleSendNewCode = async (email: string) => { const generateNewCode = async (email: string) => {
const payload: IEmailCheckData = { try {
email,
};
await authService
.generateUniqueCode(payload)
.then(() => {
setResendCodeTimer(30);
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "A new unique code has been sent to your email.",
});
handleFormChange("code", "");
})
.catch((err) =>
setToast({
type: TOAST_TYPE.ERROR,
title: "Error!",
message: err?.error ?? "Something went wrong while generating unique code. Please try again.",
})
);
};
const handleRequestNewCode = async (email: string) => {
setIsRequestingNewCode(true); setIsRequestingNewCode(true);
const uniqueCode = await generateEmailUniqueCode(email);
await handleSendNewCode(email) setResendCodeTimer(defaultResetTimerValue);
.then(() => setResendCodeTimer(30)) handleFormChange("code", uniqueCode?.code || "");
.finally(() => setIsRequestingNewCode(false)); setIsRequestingNewCode(false);
} catch {
setResendCodeTimer(0);
console.error("Error while requesting new code");
setIsRequestingNewCode(false);
}
}; };
useEffect(() => { useEffect(() => {
@ -83,11 +65,6 @@ export const AuthUniqueCodeForm: React.FC<Props> = (props) => {
authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token)); authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token));
}, [csrfToken]); }, [csrfToken]);
useEffect(() => {
handleRequestNewCode(email);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const isRequestNewCodeDisabled = isRequestingNewCode || resendTimerCode > 0; const isRequestNewCodeDisabled = isRequestingNewCode || resendTimerCode > 0;
const isButtonDisabled = isRequestingNewCode || !uniqueCodeFormData.code || isSubmitting; const isButtonDisabled = isRequestingNewCode || !uniqueCodeFormData.code || isSubmitting;
@ -100,11 +77,14 @@ export const AuthUniqueCodeForm: React.FC<Props> = (props) => {
onError={() => setIsSubmitting(false)} onError={() => setIsSubmitting(false)}
> >
<input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} /> <input type="hidden" name="csrfmiddlewaretoken" value={csrfToken} />
<input type="hidden" value={uniqueCodeFormData.email} name="email" />
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm font-medium text-onboarding-text-300" htmlFor="email"> <label className="text-sm font-medium text-onboarding-text-300" htmlFor="email">
Email Email
</label> </label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200"> <div
className={`relative flex items-center rounded-md bg-onboarding-background-200 border border-onboarding-border-100`}
>
<Input <Input
id="email" id="email"
name="email" name="email"
@ -112,18 +92,17 @@ export const AuthUniqueCodeForm: React.FC<Props> = (props) => {
value={uniqueCodeFormData.email} value={uniqueCodeFormData.email}
onChange={(e) => handleFormChange("email", e.target.value)} onChange={(e) => handleFormChange("email", e.target.value)}
placeholder="name@company.com" placeholder="name@company.com"
className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400" className={`disable-autofill-style h-[46px] w-full placeholder:text-onboarding-text-400 border-0`}
disabled disabled
/> />
{uniqueCodeFormData.email.length > 0 && ( {uniqueCodeFormData.email.length > 0 && (
<XCircle <div className="flex-shrink-0 h-5 w-5 mr-2 bg-onboarding-background-200 hover:cursor-pointer">
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer" <XCircle className="h-5 w-5 stroke-custom-text-400" onClick={handleEmailClear} />
onClick={handleEmailClear} </div>
/>
)} )}
<input type="hidden" value={uniqueCodeFormData.email} name="email" />
</div> </div>
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm font-medium text-onboarding-text-300" htmlFor="code"> <label className="text-sm font-medium text-onboarding-text-300" htmlFor="code">
Unique code Unique code
@ -132,22 +111,18 @@ export const AuthUniqueCodeForm: React.FC<Props> = (props) => {
name="code" name="code"
value={uniqueCodeFormData.code} value={uniqueCodeFormData.code}
onChange={(e) => handleFormChange("code", e.target.value)} onChange={(e) => handleFormChange("code", e.target.value)}
// FIXME:
// hasError={Boolean(errors.code)}
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="disable-autofill-style h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
autoFocus autoFocus
/> />
{/* )} <div className="flex w-full items-center justify-between px-1 text-xs pt-1">
/> */}
<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">
<CircleCheck height={12} width={12} /> <CircleCheck height={12} width={12} />
Paste the code sent to your email Paste the code sent to your email
</p> </p>
<button <button
type="button" type="button"
onClick={() => handleRequestNewCode(uniqueCodeFormData.email)} onClick={() => generateNewCode(uniqueCodeFormData.email)}
className={`${ className={`${
isRequestNewCodeDisabled isRequestNewCodeDisabled
? "text-onboarding-text-400" ? "text-onboarding-text-400"
@ -163,15 +138,12 @@ export const AuthUniqueCodeForm: React.FC<Props> = (props) => {
</button> </button>
</div> </div>
</div> </div>
<div className="space-y-2.5">
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}> <Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
{isRequestingNewCode ? ( {isRequestingNewCode ? "Sending code" : isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"}
"Sending code"
) : isSubmitting ? (
<Spinner height="20px" width="20px" />
) : (
submitButtonText
)}
</Button> </Button>
</div>
</form> </form>
); );
}; };

View File

@ -24,9 +24,9 @@ export const PasswordStrengthMeter: React.FC<Props> = (props: Props) => {
text = "Password is too short"; text = "Password is too short";
textColor = `text-[#DC3E42]`; textColor = `text-[#DC3E42]`;
} else if (strength < 3) { } else if (strength < 3) {
bars = [`bg-[#FFBA18]`, `bg-[#FFBA18]`, `bg-[#F0F0F3]`]; bars = [`bg-[#DC3E42]`, `bg-[#F0F0F3]`, `bg-[#F0F0F3]`];
text = "Password is weak"; text = "Password is weak";
textColor = `text-[#FFBA18]`; textColor = `text-[#F0F0F3]`;
} else { } else {
bars = [`bg-[#3E9B4F]`, `bg-[#3E9B4F]`, `bg-[#3E9B4F]`]; bars = [`bg-[#3E9B4F]`, `bg-[#3E9B4F]`, `bg-[#3E9B4F]`];
text = "Password is strong"; text = "Password is strong";

View File

@ -148,7 +148,10 @@ export const CreateWorkspace: React.FC<Props> = (props) => {
</div> </div>
<form className="w-full mx-auto mt-2 space-y-4" onSubmit={handleSubmit(handleCreateWorkspace)}> <form className="w-full mx-auto mt-2 space-y-4" onSubmit={handleSubmit(handleCreateWorkspace)}>
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="name"> <label
className="text-sm text-onboarding-text-300 font-medium after:content-['*'] after:ml-0.5 after:text-red-500"
htmlFor="name"
>
Workspace name Workspace name
</label> </label>
<Controller <Controller
@ -187,7 +190,10 @@ export const CreateWorkspace: React.FC<Props> = (props) => {
{errors.name && <span className="text-sm text-red-500">{errors.name.message}</span>} {errors.name && <span className="text-sm text-red-500">{errors.name.message}</span>}
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="slug"> <label
className="text-sm text-onboarding-text-300 font-medium after:content-['*'] after:ml-0.5 after:text-red-500"
htmlFor="slug"
>
Workspace URL Workspace URL
</label> </label>
<Controller <Controller
@ -224,7 +230,10 @@ export const CreateWorkspace: React.FC<Props> = (props) => {
</div> </div>
<hr className="w-full border-onboarding-border-100" /> <hr className="w-full border-onboarding-border-100" />
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="organization_size"> <label
className="text-sm text-onboarding-text-300 font-medium after:content-['*'] after:ml-0.5 after:text-red-500"
htmlFor="organization_size"
>
Company size Company size
</label> </label>
<div className="w-full"> <div className="w-full">

View File

@ -157,6 +157,7 @@ const InviteMemberInput: React.FC<InviteMemberFormProps> = (props) => {
hasError={Boolean(errors.emails?.[index]?.email)} hasError={Boolean(errors.emails?.[index]?.email)}
placeholder={placeholderEmails[index % placeholderEmails.length]} placeholder={placeholderEmails[index % placeholderEmails.length]}
className="w-full border-onboarding-border-100 text-xs placeholder:text-onboarding-text-400 sm:text-sm" className="w-full border-onboarding-border-100 text-xs placeholder:text-onboarding-text-400 sm:text-sm"
autoComplete="off"
/> />
)} )}
/> />

View File

@ -333,7 +333,10 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="first_name"> <label
className="text-sm text-onboarding-text-300 font-medium after:content-['*'] after:ml-0.5 after:text-red-500"
htmlFor="first_name"
>
First name First name
</label> </label>
<Controller <Controller
@ -364,7 +367,10 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
{errors.first_name && <span className="text-sm text-red-500">{errors.first_name.message}</span>} {errors.first_name && <span className="text-sm text-red-500">{errors.first_name.message}</span>}
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="last_name"> <label
className="text-sm text-onboarding-text-300 font-medium after:content-['*'] after:ml-0.5 after:text-red-500"
htmlFor="last_name"
>
Last name Last name
</label> </label>
<Controller <Controller
@ -485,7 +491,10 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
{profileSetupStep !== EProfileSetupSteps.USER_DETAILS && ( {profileSetupStep !== EProfileSetupSteps.USER_DETAILS && (
<> <>
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="role"> <label
className="text-sm text-onboarding-text-300 font-medium after:content-['*'] after:ml-0.5 after:text-red-500"
htmlFor="role"
>
What role are you working on? Choose one. What role are you working on? Choose one.
</label> </label>
<Controller <Controller
@ -513,7 +522,10 @@ export const ProfileSetup: React.FC<Props> = observer((props) => {
{errors.role && <span className="text-sm text-red-500">{errors.role.message}</span>} {errors.role && <span className="text-sm text-red-500">{errors.role.message}</span>}
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="use_case"> <label
className="text-sm text-onboarding-text-300 font-medium after:content-['*'] after:ml-0.5 after:text-red-500"
htmlFor="use_case"
>
What is your domain expertise? Choose one. What is your domain expertise? Choose one.
</label> </label>
<Controller <Controller

View File

@ -22,7 +22,6 @@ export enum EAuthSteps {
export enum EErrorAlertType { export enum EErrorAlertType {
BANNER_ALERT = "BANNER_ALERT", BANNER_ALERT = "BANNER_ALERT",
TOAST_ALERT = "TOAST_ALERT",
INLINE_FIRST_NAME = "INLINE_FIRST_NAME", INLINE_FIRST_NAME = "INLINE_FIRST_NAME",
INLINE_EMAIL = "INLINE_EMAIL", INLINE_EMAIL = "INLINE_EMAIL",
INLINE_PASSWORD = "INLINE_PASSWORD", INLINE_PASSWORD = "INLINE_PASSWORD",
@ -32,42 +31,52 @@ export enum EErrorAlertType {
export enum EAuthenticationErrorCodes { export enum EAuthenticationErrorCodes {
// Global // Global
INSTANCE_NOT_CONFIGURED = "5000", INSTANCE_NOT_CONFIGURED = "5000",
SIGNUP_DISABLED = "5001", INVALID_EMAIL = "5005",
INVALID_PASSWORD = "5002", // Password strength validation EMAIL_REQUIRED = "5010",
SMTP_NOT_CONFIGURED = "5007", SIGNUP_DISABLED = "5015",
// email check // Password strength
INVALID_EMAIL = "5012", INVALID_PASSWORD = "5020",
EMAIL_REQUIRED = "5013", SMTP_NOT_CONFIGURED = "5025",
// Sign Up // Sign Up
USER_ALREADY_EXIST = "5003", USER_ALREADY_EXIST = "5030",
REQUIRED_EMAIL_PASSWORD_SIGN_UP = "5015", AUTHENTICATION_FAILED_SIGN_UP = "5035",
AUTHENTICATION_FAILED_SIGN_UP = "5006", REQUIRED_EMAIL_PASSWORD_SIGN_UP = "5040",
INVALID_EMAIL_SIGN_UP = "5017", INVALID_EMAIL_SIGN_UP = "5045",
MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED = "5023", INVALID_EMAIL_MAGIC_SIGN_UP = "5050",
INVALID_EMAIL_MAGIC_SIGN_UP = "5019", MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED = "5055",
// Sign In // Sign In
USER_DOES_NOT_EXIST = "5004", USER_DOES_NOT_EXIST = "5060",
REQUIRED_EMAIL_PASSWORD_SIGN_IN = "5014", AUTHENTICATION_FAILED_SIGN_IN = "5065",
AUTHENTICATION_FAILED_SIGN_IN = "5005", REQUIRED_EMAIL_PASSWORD_SIGN_IN = "5070",
INVALID_EMAIL_SIGN_IN = "5016", INVALID_EMAIL_SIGN_IN = "5075",
MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED = "5022", INVALID_EMAIL_MAGIC_SIGN_IN = "5080",
INVALID_EMAIL_MAGIC_SIGN_IN = "5018", MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED = "5085",
// Both Sign in and Sign up // Both Sign in and Sign up for magic
INVALID_MAGIC_CODE = "5008", INVALID_MAGIC_CODE = "5090",
EXPIRED_MAGIC_CODE = "5009", EXPIRED_MAGIC_CODE = "5095",
EMAIL_CODE_ATTEMPT_EXHAUSTED = "5100",
// Oauth // Oauth
GOOGLE_NOT_CONFIGURED = "5010", GOOGLE_NOT_CONFIGURED = "5105",
GITHUB_NOT_CONFIGURED = "5011", GITHUB_NOT_CONFIGURED = "5110",
GOOGLE_OAUTH_PROVIDER_ERROR = "5021", GOOGLE_OAUTH_PROVIDER_ERROR = "5115",
GITHUB_OAUTH_PROVIDER_ERROR = "5020", GITHUB_OAUTH_PROVIDER_ERROR = "5120",
// Reset Password // Reset Password
INVALID_PASSWORD_TOKEN = "5024", INVALID_PASSWORD_TOKEN = "5125",
EXPIRED_PASSWORD_TOKEN = "5025", EXPIRED_PASSWORD_TOKEN = "5130",
// Change password // Change password
INCORRECT_OLD_PASSWORD = "5026", INCORRECT_OLD_PASSWORD = "5135",
INVALID_NEW_PASSWORD = "5027", INVALID_NEW_PASSWORD = "5140",
// set password // set passowrd
PASSWORD_ALREADY_SET = "5028", // used in the onboarding and set password page 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 = { export type TAuthErrorInfo = {
@ -116,7 +125,7 @@ const errorCodeMessages: {
Your account is already registered.&nbsp; Your account is already registered.&nbsp;
<Link <Link
className="underline underline-offset-4 font-medium hover:font-bold transition-all" className="underline underline-offset-4 font-medium hover:font-bold transition-all"
href={`/accounts/sign-in${email ? `?email=${email}` : ``}`} href={`/sign-in${email ? `?email=${email}` : ``}`}
> >
Sign In Sign In
</Link> </Link>
@ -191,6 +200,10 @@ const errorCodeMessages: {
title: `Expired magic code`, title: `Expired magic code`,
message: () => `Expired magic code. Please try again.`, message: () => `Expired magic code. Please try again.`,
}, },
[EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED]: {
title: `Expired magic code`,
message: () => `Expired magic code. Please try again.`,
},
// Oauth // Oauth
[EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED]: { [EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED]: {
@ -235,27 +248,84 @@ const errorCodeMessages: {
title: `Password already set`, title: `Password already set`,
message: () => `Password already set. Please try again.`, 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.&nbsp;
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
Sign In
</Link>
&nbsp;now.
</div>
),
},
[EAuthenticationErrorCodes.ADMIN_USER_DOES_NOT_EXIST]: {
title: `Admin user does not exist`,
message: () => (
<div>
Admin user does not exist.&nbsp;
<Link className="underline underline-offset-4 font-medium hover:font-bold transition-all" href={`/admin`}>
Sign In
</Link>
&nbsp;now.
</div>
),
},
}; };
export const authErrorHandler = ( export const authErrorHandler = (
errorCode: EAuthenticationErrorCodes, errorCode: EAuthenticationErrorCodes,
email?: string | undefined email?: string | undefined
): TAuthErrorInfo | undefined => { ): TAuthErrorInfo | undefined => {
const toastAlertErrorCodes = [ const bannerAlertErrorCodes = [
EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED,
EAuthenticationErrorCodes.INVALID_EMAIL,
EAuthenticationErrorCodes.EMAIL_REQUIRED,
EAuthenticationErrorCodes.SIGNUP_DISABLED, EAuthenticationErrorCodes.SIGNUP_DISABLED,
EAuthenticationErrorCodes.INVALID_PASSWORD, EAuthenticationErrorCodes.INVALID_PASSWORD,
EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED, EAuthenticationErrorCodes.SMTP_NOT_CONFIGURED,
EAuthenticationErrorCodes.INVALID_EMAIL, EAuthenticationErrorCodes.USER_ALREADY_EXIST,
EAuthenticationErrorCodes.EMAIL_REQUIRED,
EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP, EAuthenticationErrorCodes.AUTHENTICATION_FAILED_SIGN_UP,
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP,
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP, EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_UP,
EAuthenticationErrorCodes.MAGIC_SIGN_UP_EMAIL_CODE_REQUIRED,
EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_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.AUTHENTICATION_FAILED_SIGN_IN,
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN,
EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN, EAuthenticationErrorCodes.INVALID_EMAIL_SIGN_IN,
EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN, EAuthenticationErrorCodes.INVALID_EMAIL_MAGIC_SIGN_IN,
EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED,
EAuthenticationErrorCodes.INVALID_MAGIC_CODE, EAuthenticationErrorCodes.INVALID_MAGIC_CODE,
EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE, EAuthenticationErrorCodes.EXPIRED_MAGIC_CODE,
EAuthenticationErrorCodes.EMAIL_CODE_ATTEMPT_EXHAUSTED,
EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED, EAuthenticationErrorCodes.GOOGLE_NOT_CONFIGURED,
EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED, EAuthenticationErrorCodes.GITHUB_NOT_CONFIGURED,
EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR, EAuthenticationErrorCodes.GOOGLE_OAUTH_PROVIDER_ERROR,
@ -265,23 +335,15 @@ export const authErrorHandler = (
EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD, EAuthenticationErrorCodes.INCORRECT_OLD_PASSWORD,
EAuthenticationErrorCodes.INVALID_NEW_PASSWORD, EAuthenticationErrorCodes.INVALID_NEW_PASSWORD,
EAuthenticationErrorCodes.PASSWORD_ALREADY_SET, 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,
]; ];
const bannerAlertErrorCodes = [
EAuthenticationErrorCodes.INSTANCE_NOT_CONFIGURED,
EAuthenticationErrorCodes.USER_ALREADY_EXIST,
EAuthenticationErrorCodes.USER_DOES_NOT_EXIST,
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_UP,
EAuthenticationErrorCodes.REQUIRED_EMAIL_PASSWORD_SIGN_IN,
EAuthenticationErrorCodes.MAGIC_SIGN_IN_EMAIL_CODE_REQUIRED,
];
if (toastAlertErrorCodes.includes(errorCode))
return {
type: EErrorAlertType.TOAST_ALERT,
code: errorCode,
title: errorCodeMessages[errorCode]?.title || "Error",
message: errorCodeMessages[errorCode]?.message(email) || "Something went wrong. Please try again.",
};
if (bannerAlertErrorCodes.includes(errorCode)) if (bannerAlertErrorCodes.includes(errorCode))
return { return {

View File

@ -95,7 +95,7 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
membership.hasPermissionToCurrentWorkspace === undefined membership.hasPermissionToCurrentWorkspace === undefined
) { ) {
return ( return (
<div className="relative flex h-full w-full flex-col items-center justify-center bg-custom-background-90"> <div className="relative flex h-screen w-full flex-col items-center justify-center bg-custom-background-90 ">
<div className="container relative mx-auto flex h-full w-full flex-col overflow-hidden overflow-y-auto px-5 py-14 md:px-0"> <div className="container relative mx-auto flex h-full w-full flex-col overflow-hidden overflow-y-auto px-5 py-14 md:px-0">
<div className="relative flex flex-shrink-0 items-center justify-between gap-4"> <div className="relative flex flex-shrink-0 items-center justify-between gap-4">
<div className="z-10 flex-shrink-0 bg-custom-background-90 py-4"> <div className="z-10 flex-shrink-0 bg-custom-background-90 py-4">

View File

@ -84,7 +84,7 @@ export const AuthenticationWrapper: FC<TAuthenticationWrapper> = observer((props
if (pageType === EPageTypes.ONBOARDING) { if (pageType === EPageTypes.ONBOARDING) {
if (!currentUser?.id) { if (!currentUser?.id) {
router.push("/accounts/sign-in"); router.push("/sign-in");
return <></>; return <></>;
} else { } else {
if (currentUser && currentUserProfile?.id && currentUserProfile?.is_onboarded) { if (currentUser && currentUserProfile?.id && currentUserProfile?.is_onboarded) {
@ -97,7 +97,7 @@ export const AuthenticationWrapper: FC<TAuthenticationWrapper> = observer((props
if (pageType === EPageTypes.SET_PASSWORD) { if (pageType === EPageTypes.SET_PASSWORD) {
if (!currentUser?.id) { if (!currentUser?.id) {
router.push("/accounts/sign-in"); router.push("/sign-in");
return <></>; return <></>;
} else { } else {
if ( if (
@ -121,7 +121,7 @@ export const AuthenticationWrapper: FC<TAuthenticationWrapper> = observer((props
return <></>; return <></>;
} }
} else { } else {
router.push("/accounts/sign-in"); router.push("/sign-in");
return <></>; return <></>;
} }
} }

View File

@ -176,7 +176,7 @@ const ForgotPasswordPage: NextPageWithLayout = () => {
> >
{resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"} {resendTimerCode > 0 ? `Resend in ${resendTimerCode} seconds` : "Send reset link"}
</Button> </Button>
<Link href="/accounts/sign-in" className={cn("w-full", getButtonStyling("link-neutral", "lg"))}> <Link href="/sign-in" className={cn("w-full", getButtonStyling("link-neutral", "lg"))}>
Back to sign in Back to sign in
</Link> </Link>
</form> </form>

View File

@ -48,7 +48,7 @@ const HomePage: NextPageWithLayout = observer(() => {
<div className="text-center text-sm font-medium text-onboarding-text-300"> <div className="text-center text-sm font-medium text-onboarding-text-300">
Already have an account?{" "} Already have an account?{" "}
<Link <Link
href="/accounts/sign-in" href="/sign-in"
onClick={() => captureEvent(NAVIGATE_TO_SIGNIN, {})} onClick={() => captureEvent(NAVIGATE_TO_SIGNIN, {})}
className="font-semibold text-custom-primary-100 hover:underline" className="font-semibold text-custom-primary-100 hover:underline"
> >

View File

@ -641,3 +641,12 @@ div.web-view-spinner div.bar12 {
border-left: 5px solid rgb(var(--color-primary-100)) !important; border-left: 5px solid rgb(var(--color-primary-100)) !important;
background: rgb(var(--color-background-80)); background: rgb(var(--color-background-80));
} }
/* By applying below class, the autofilled text in form fields will not have the default autofill background color and styles applied by WebKit browsers */
.disable-autofill-style:-webkit-autofill,
.disable-autofill-style:-webkit-autofill:hover,
.disable-autofill-style:-webkit-autofill:focus,
.disable-autofill-style:-webkit-autofill:active {
-webkit-background-clip: text;
}