Skip to content

Commit

Permalink
fix: authentication redirection and UI (#4432)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
3 people committed May 10, 2024
1 parent 2d1201c commit 88ebda4
Show file tree
Hide file tree
Showing 49 changed files with 1,334 additions and 539 deletions.
4 changes: 2 additions & 2 deletions admin/Dockerfile.admin
Expand Up @@ -32,7 +32,7 @@ ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG 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

ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
Expand Down Expand Up @@ -62,7 +62,7 @@ ENV NEXT_PUBLIC_API_BASE_URL=$NEXT_PUBLIC_API_BASE_URL
ARG 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

ARG NEXT_PUBLIC_ADMIN_BASE_PATH="/god-mode"
Expand Down
46 changes: 46 additions & 0 deletions admin/components/common/empty-state.tsx
@@ -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>
);
1 change: 1 addition & 0 deletions admin/components/common/index.ts
Expand Up @@ -4,3 +4,4 @@ export * from "./controller-input";
export * from "./copy-field";
export * from "./password-strength-meter";
export * from "./banner";
export * from "./empty-state";
8 changes: 7 additions & 1 deletion admin/lib/wrappers/instance-wrapper.tsx
Expand Up @@ -13,6 +13,7 @@ import { InstanceNotReady } from "@/components/instance";
import { useInstance } from "@/hooks/store";
// helpers
import { EInstancePageType } from "@/helpers";
import { EmptyState } from "@/components/common";

type TInstanceWrapper = {
children: ReactNode;
Expand Down Expand Up @@ -41,7 +42,12 @@ export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
);

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


Expand Down
Expand Up @@ -77,7 +77,13 @@ def initiate(self):
current_attempt = data["current_attempt"] + 1

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 = {
"current_attempt": current_attempt,
Expand Down
35 changes: 26 additions & 9 deletions apiserver/plane/authentication/utils/host.py
Expand Up @@ -5,21 +5,38 @@
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"""

if is_admin and settings.ADMIN_BASE_URL:
return settings.ADMIN_BASE_URL

if is_space and settings.SPACE_BASE_URL:
return settings.SPACE_BASE_URL

return (
# Calculate the base origin from request
base_origin = str(
request.META.get("HTTP_ORIGIN")
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()}"""
)

# 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):
return str(request.META.get("REMOTE_ADDR"))
9 changes: 7 additions & 2 deletions apiserver/plane/authentication/utils/login.py
Expand Up @@ -5,12 +5,17 @@
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)
device_info = {
"user_agent": request.META.get("HTTP_USER_AGENT", ""),
"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.save()
Expand Down
38 changes: 19 additions & 19 deletions apiserver/plane/authentication/views/app/email.py
Expand Up @@ -42,8 +42,8 @@ def post(self, request):
params["next_path"] = str(next_path)
# Base URL join
url = urljoin(
base_host(request=request),
"accounts/sign-in?" + urlencode(params),
base_host(request=request, is_app=True),
"sign-in?" + urlencode(params),
)
return HttpResponseRedirect(url)

Expand All @@ -66,8 +66,8 @@ def post(self, request):
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request),
"accounts/sign-in?" + urlencode(params),
base_host(request=request, is_app=True),
"sign-in?" + urlencode(params),
)
return HttpResponseRedirect(url)

Expand All @@ -85,8 +85,8 @@ def post(self, request):
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request),
"accounts/sign-in?" + urlencode(params),
base_host(request=request, is_app=True),
"sign-in?" + urlencode(params),
)
return HttpResponseRedirect(url)

Expand All @@ -100,8 +100,8 @@ def post(self, request):
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request),
"accounts/sign-in?" + urlencode(params),
base_host(request=request, is_app=True),
"sign-in?" + urlencode(params),
)
return HttpResponseRedirect(url)

Expand All @@ -111,7 +111,7 @@ def post(self, request):
)
user = provider.authenticate()
# 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_project_invitations(user=user)
# Get the redirection path
Expand All @@ -121,15 +121,15 @@ def post(self, request):
path = get_redirection_path(user=user)

# redirect to referer path
url = urljoin(base_host(request=request), path)
url = urljoin(base_host(request=request, is_app=True), path)
return HttpResponseRedirect(url)
except AuthenticationException as e:
params = e.get_error_dict()
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request),
"accounts/sign-in?" + urlencode(params),
base_host(request=request, is_app=True),
"sign-in?" + urlencode(params),
)
return HttpResponseRedirect(url)

Expand All @@ -152,7 +152,7 @@ def post(self, request):
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request),
base_host(request=request, is_app=True),
"?" + urlencode(params),
)
return HttpResponseRedirect(url)
Expand All @@ -173,7 +173,7 @@ def post(self, request):
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request),
base_host(request=request, is_app=True),
"?" + urlencode(params),
)
return HttpResponseRedirect(url)
Expand All @@ -192,7 +192,7 @@ def post(self, request):
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request),
base_host(request=request, is_app=True),
"?" + urlencode(params),
)
return HttpResponseRedirect(url)
Expand All @@ -207,7 +207,7 @@ def post(self, request):
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request),
base_host(request=request, is_app=True),
"?" + urlencode(params),
)
return HttpResponseRedirect(url)
Expand All @@ -218,7 +218,7 @@ def post(self, request):
)
user = provider.authenticate()
# 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_project_invitations(user=user)
# Get the redirection path
Expand All @@ -227,14 +227,14 @@ def post(self, request):
else:
path = get_redirection_path(user=user)
# redirect to referer path
url = urljoin(base_host(request=request), path)
url = urljoin(base_host(request=request, is_app=True), path)
return HttpResponseRedirect(url)
except AuthenticationException as e:
params = e.get_error_dict()
if next_path:
params["next_path"] = str(next_path)
url = urljoin(
base_host(request=request),
base_host(request=request, is_app=True),
"?" + urlencode(params),
)
return HttpResponseRedirect(url)

0 comments on commit 88ebda4

Please sign in to comment.