{
// theme
const { resolvedTheme } = nextUseTheme();
- const redirectionLink = `${process.env.NEXT_PUBLIC_APP_URL ? `${process.env.NEXT_PUBLIC_APP_URL}/create-workspace` : `${process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX === "1" ? `/god-mode/` : `/`}`}`;
+ const redirectionLink = `${process.env.NEXT_PUBLIC_APP_URL ? `${process.env.NEXT_PUBLIC_APP_URL}/create-workspace` : `/god-mode/`}`;
if (!isNewUserPopup) return <>>;
return (
diff --git a/admin/lib/store-context.tsx b/admin/lib/store-context.tsx
index 37bba1a71..8893f1a78 100644
--- a/admin/lib/store-context.tsx
+++ b/admin/lib/store-context.tsx
@@ -4,7 +4,7 @@ import { ReactElement, createContext } from "react";
// mobx store
import { RootStore } from "@/store/root-store";
-let rootStore = new RootStore();
+export let rootStore = new RootStore();
export const StoreContext = createContext
(rootStore);
diff --git a/admin/lib/wrappers/auth-wrapper.tsx b/admin/lib/wrappers/auth-wrapper.tsx
index bd3770376..75e7c2acc 100644
--- a/admin/lib/wrappers/auth-wrapper.tsx
+++ b/admin/lib/wrappers/auth-wrapper.tsx
@@ -1,14 +1,14 @@
"use client";
import { FC, ReactNode } from "react";
+import { useRouter } from "next/navigation";
import { observer } from "mobx-react-lite";
import useSWR from "swr";
import { Spinner } from "@plane/ui";
// hooks
import { useInstance, useUser } from "@/hooks";
// helpers
-import { EAuthenticationPageType, EUserStatus } from "@/helpers";
-import { redirect } from "next/navigation";
+import { EAuthenticationPageType } from "@/helpers";
export interface IAuthWrapper {
children: ReactNode;
@@ -16,41 +16,41 @@ export interface IAuthWrapper {
}
export const AuthWrapper: FC = observer((props) => {
+ const router = useRouter();
+ // props
const { children, authType = EAuthenticationPageType.AUTHENTICATED } = props;
// hooks
- const { instance, fetchInstanceAdmins } = useInstance();
- const { isLoading, userStatus, currentUser, fetchCurrentUser } = useUser();
+ const { instance } = useInstance();
+ const { isLoading, currentUser, fetchCurrentUser } = useUser();
- useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
- shouldRetryOnError: false,
- });
- useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins(), {
+ const { isLoading: isSWRLoading } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
shouldRetryOnError: false,
});
- if (isLoading)
+ if (isSWRLoading || isLoading)
return (
);
- if (userStatus && userStatus?.status === EUserStatus.ERROR)
- return (
-
- Something went wrong. please try again later
-
- );
+ if (authType === EAuthenticationPageType.NOT_AUTHENTICATED) {
+ if (currentUser === undefined) return <>{children}>;
+ else {
+ router.push("/general/");
+ return <>>;
+ }
+ }
- if ([EAuthenticationPageType.AUTHENTICATED, EAuthenticationPageType.NOT_AUTHENTICATED].includes(authType)) {
- if (authType === EAuthenticationPageType.NOT_AUTHENTICATED) {
- if (currentUser === undefined) return <>{children}>;
- else redirect("/general/");
- } else {
- if (currentUser) return <>{children}>;
- else {
- if (instance?.instance?.is_setup_done) redirect("/login/");
- else redirect("/setup/");
+ if (authType === EAuthenticationPageType.AUTHENTICATED) {
+ if (currentUser) return <>{children}>;
+ else {
+ if (instance && instance?.instance?.is_setup_done) {
+ router.push("/");
+ return <>>;
+ } else {
+ router.push("/setup/");
+ return <>>;
}
}
}
diff --git a/admin/lib/wrappers/instance-wrapper.tsx b/admin/lib/wrappers/instance-wrapper.tsx
index a4444a208..da02992aa 100644
--- a/admin/lib/wrappers/instance-wrapper.tsx
+++ b/admin/lib/wrappers/instance-wrapper.tsx
@@ -12,7 +12,7 @@ import { InstanceNotReady } from "@/components/instance";
// hooks
import { useInstance } from "@/hooks";
// helpers
-import { EInstancePageType, EInstanceStatus } from "@/helpers";
+import { EInstancePageType } from "@/helpers";
type TInstanceWrapper = {
children: ReactNode;
@@ -24,26 +24,19 @@ export const InstanceWrapper: FC = observer((props) => {
const searchparams = useSearchParams();
const authEnabled = searchparams.get("auth_enabled") || "1";
// hooks
- const { isLoading, instanceStatus, instance, fetchInstanceInfo } = useInstance();
+ const { isLoading, instance, fetchInstanceInfo } = useInstance();
- useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
+ const { isLoading: isSWRLoading } = useSWR("INSTANCE_INFORMATION", () => fetchInstanceInfo(), {
revalidateOnFocus: false,
});
- if (isLoading)
+ if (isSWRLoading || isLoading)
return (
);
- if (instanceStatus && instanceStatus?.status === EInstanceStatus.ERROR)
- return (
-
- Something went wrong. please try again later
-
- );
-
if (instance?.instance?.is_setup_done === false && authEnabled === "1")
return (
diff --git a/admin/services/api.service.ts b/admin/services/api.service.ts
index 9d692a6c6..344cd4f54 100644
--- a/admin/services/api.service.ts
+++ b/admin/services/api.service.ts
@@ -1,4 +1,6 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
+// store
+import { rootStore } from "@/lib/store-context";
export abstract class APIService {
protected baseURL: string;
@@ -18,7 +20,8 @@ export abstract class APIService {
this.axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
- if (error.response && error.response.status === 401) window.location.href = "/login";
+ const store = rootStore;
+ if (error.response && error.response.status === 401 && store.user.currentUser) store.user.reset();
return Promise.reject(error);
}
);
diff --git a/admin/store/root-store.ts b/admin/store/root-store.ts
index 85b2a5a8b..c05cce37f 100644
--- a/admin/store/root-store.ts
+++ b/admin/store/root-store.ts
@@ -18,8 +18,10 @@ export class RootStore {
}
resetOnSignOut() {
- this.theme = new ThemeStore(this);
+ localStorage.setItem("theme", "system");
+
this.instance = new InstanceStore(this);
this.user = new UserStore(this);
+ this.theme = new ThemeStore(this);
}
}
diff --git a/admin/store/user.store.ts b/admin/store/user.store.ts
index 77b81446f..10b5eab81 100644
--- a/admin/store/user.store.ts
+++ b/admin/store/user.store.ts
@@ -16,7 +16,8 @@ export interface IUserStore {
currentUser: IUser | undefined;
// fetch actions
fetchCurrentUser: () => Promise;
- signOut: () => Promise;
+ reset: () => void;
+ signOut: () => void;
}
export class UserStore implements IUserStore {
@@ -28,8 +29,6 @@ export class UserStore implements IUserStore {
// services
userService;
authService;
- // rootStore
- rootStore;
constructor(private store: RootStore) {
makeObservable(this, {
@@ -40,10 +39,11 @@ export class UserStore implements IUserStore {
currentUser: observable,
// action
fetchCurrentUser: action,
+ reset: action,
+ signOut: action,
});
this.userService = new UserService();
this.authService = new AuthService();
- this.rootStore = store;
}
/**
@@ -54,11 +54,20 @@ export class UserStore implements IUserStore {
try {
if (this.currentUser === undefined) this.isLoading = true;
const currentUser = await this.userService.currentUser();
- runInAction(() => {
- this.isUserLoggedIn = true;
- this.currentUser = currentUser;
- this.isLoading = false;
- });
+ if (currentUser) {
+ await this.store.instance.fetchInstanceAdmins();
+ runInAction(() => {
+ this.isUserLoggedIn = true;
+ this.currentUser = currentUser;
+ this.isLoading = false;
+ });
+ } else {
+ runInAction(() => {
+ this.isUserLoggedIn = false;
+ this.currentUser = undefined;
+ this.isLoading = false;
+ });
+ }
return currentUser;
} catch (error: any) {
this.isLoading = false;
@@ -77,7 +86,14 @@ export class UserStore implements IUserStore {
}
};
+ reset = async () => {
+ this.isUserLoggedIn = false;
+ this.currentUser = undefined;
+ this.isLoading = false;
+ this.userStatus = undefined;
+ };
+
signOut = async () => {
- this.rootStore.resetOnSignOut();
+ this.store.resetOnSignOut();
};
}
diff --git a/apiserver/.env.example b/apiserver/.env.example
index d8554f400..52d8d1c50 100644
--- a/apiserver/.env.example
+++ b/apiserver/.env.example
@@ -1,7 +1,7 @@
# Backend
# Debug value for api server use it as 0 for production use
DEBUG=0
-CORS_ALLOWED_ORIGINS=""
+CORS_ALLOWED_ORIGINS="http://localhost"
# Error logs
SENTRY_DSN=""
diff --git a/apiserver/plane/app/views/project/base.py b/apiserver/plane/app/views/project/base.py
index 6017a420f..1d23bd3aa 100644
--- a/apiserver/plane/app/views/project/base.py
+++ b/apiserver/plane/app/views/project/base.py
@@ -602,11 +602,19 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
@cache_response(60 * 60 * 24, user=False)
def get(self, request):
files = []
- s3 = boto3.client(
- "s3",
- aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
- aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
- )
+ if settings.USE_MINIO:
+ s3 = boto3.client(
+ "s3",
+ endpoint_url=settings.AWS_S3_ENDPOINT_URL,
+ aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
+ aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
+ )
+ else:
+ s3 = boto3.client(
+ "s3",
+ aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
+ aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
+ )
params = {
"Bucket": settings.AWS_STORAGE_BUCKET_NAME,
"Prefix": "static/project-cover/",
diff --git a/apiserver/plane/app/views/user/base.py b/apiserver/plane/app/views/user/base.py
index 60823a5a7..9fb514d11 100644
--- a/apiserver/plane/app/views/user/base.py
+++ b/apiserver/plane/app/views/user/base.py
@@ -1,5 +1,7 @@
# Django imports
from django.db.models import Case, Count, IntegerField, Q, When
+from django.contrib.auth import logout
+from django.utils import timezone
# Third party imports
from rest_framework import status
@@ -26,6 +28,7 @@ from plane.db.models import (
from plane.license.models import Instance, InstanceAdmin
from plane.utils.cache import cache_response, invalidate_cache
from plane.utils.paginator import BasePaginator
+from plane.authentication.utils.host import user_ip
class UserEndpoint(BaseViewSet):
@@ -166,7 +169,14 @@ class UserEndpoint(BaseViewSet):
"workspace_invite": False,
}
profile.save()
+
+ # User log out
+ user.last_logout_ip = user_ip(request=request)
+ user.last_logout_time = timezone.now()
user.save()
+
+ # Logout the user
+ logout(request)
return Response(status=status.HTTP_204_NO_CONTENT)
diff --git a/apiserver/plane/authentication/adapter/exception.py b/apiserver/plane/authentication/adapter/exception.py
index 2086046a4..12845ea02 100644
--- a/apiserver/plane/authentication/adapter/exception.py
+++ b/apiserver/plane/authentication/adapter/exception.py
@@ -7,12 +7,6 @@ def auth_exception_handler(exc, context):
response = exception_handler(exc, context)
# Check if an AuthenticationFailed exception is raised.
if isinstance(exc, NotAuthenticated):
- # Return 403 if the users me api fails
- request = context["request"]
- if request.path == "/api/users/me/":
- response.status_code = 403
- # else return 401
- else:
- response.status_code = 401
+ response.status_code = 401
return response
diff --git a/apiserver/plane/authentication/provider/oauth/github.py b/apiserver/plane/authentication/provider/oauth/github.py
index 29e1772b5..b52fecf97 100644
--- a/apiserver/plane/authentication/provider/oauth/github.py
+++ b/apiserver/plane/authentication/provider/oauth/github.py
@@ -46,9 +46,7 @@ class GitHubOAuthProvider(OauthAdapter):
client_id = GITHUB_CLIENT_ID
client_secret = GITHUB_CLIENT_SECRET
- redirect_uri = (
- f"{request.scheme}://{request.get_host()}/auth/github/callback/"
- )
+ redirect_uri = f"""{"https" if request.is_secure() else "http"}://{request.get_host()}/auth/github/callback/"""
url_params = {
"client_id": client_id,
"redirect_uri": redirect_uri,
diff --git a/apiserver/plane/authentication/provider/oauth/google.py b/apiserver/plane/authentication/provider/oauth/google.py
index d0893d23b..5de1ac8e2 100644
--- a/apiserver/plane/authentication/provider/oauth/google.py
+++ b/apiserver/plane/authentication/provider/oauth/google.py
@@ -43,9 +43,7 @@ class GoogleOAuthProvider(OauthAdapter):
client_id = GOOGLE_CLIENT_ID
client_secret = GOOGLE_CLIENT_SECRET
- redirect_uri = (
- f"{request.scheme}://{request.get_host()}/auth/google/callback/"
- )
+ redirect_uri = f"""{"https" if request.is_secure() else "http"}://{request.get_host()}/auth/google/callback/"""
url_params = {
"client_id": client_id,
"scope": self.scope,
diff --git a/apiserver/plane/authentication/utils/host.py b/apiserver/plane/authentication/utils/host.py
index d5a81a249..b9dc7189b 100644
--- a/apiserver/plane/authentication/utils/host.py
+++ b/apiserver/plane/authentication/utils/host.py
@@ -6,7 +6,7 @@ def base_host(request):
return (
request.META.get("HTTP_ORIGIN")
or f"{urlsplit(request.META.get('HTTP_REFERER')).scheme}://{urlsplit(request.META.get('HTTP_REFERER')).netloc}"
- or f"{request.scheme}://{request.get_host()}"
+ or f"""{"https" if request.is_secure() else "http"}://{request.get_host()}"""
)
diff --git a/apiserver/plane/authentication/utils/redirection_path.py b/apiserver/plane/authentication/utils/redirection_path.py
index bf9e15673..12de25cc2 100644
--- a/apiserver/plane/authentication/utils/redirection_path.py
+++ b/apiserver/plane/authentication/utils/redirection_path.py
@@ -10,10 +10,13 @@ def get_redirection_path(user):
return "onboarding"
# Redirect to the last workspace if the user has last workspace
- if profile.last_workspace_id and Workspace.objects.filter(
- pk=profile.last_workspace_id,
- workspace_member__member_id=user.id,
- workspace_member__is_active=True,
+ if (
+ profile.last_workspace_id
+ and Workspace.objects.filter(
+ pk=profile.last_workspace_id,
+ workspace_member__member_id=user.id,
+ workspace_member__is_active=True,
+ ).exists()
):
workspace = Workspace.objects.filter(
pk=profile.last_workspace_id,
diff --git a/apiserver/plane/authentication/views/common.py b/apiserver/plane/authentication/views/common.py
index 22fbb0a5c..a66326b1a 100644
--- a/apiserver/plane/authentication/views/common.py
+++ b/apiserver/plane/authentication/views/common.py
@@ -206,7 +206,7 @@ class ResetPasswordEndpoint(View):
url = urljoin(
base_host(request=request),
- "accounts/sign-in?" + urlencode({"success", True}),
+ "accounts/sign-in?" + urlencode({"success": True}),
)
return HttpResponseRedirect(url)
except DjangoUnicodeDecodeError:
diff --git a/apiserver/plane/settings/local.py b/apiserver/plane/settings/local.py
index 4f67e638b..2290262ae 100644
--- a/apiserver/plane/settings/local.py
+++ b/apiserver/plane/settings/local.py
@@ -31,6 +31,8 @@ MEDIA_URL = "/uploads/"
MEDIA_ROOT = os.path.join(BASE_DIR, "uploads") # noqa
CORS_ALLOWED_ORIGINS = [
+ "http://localhost",
+ "http://127.0.0.1",
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:4000",
diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt
index 05789af59..a6bd2ab50 100644
--- a/apiserver/requirements/base.txt
+++ b/apiserver/requirements/base.txt
@@ -60,4 +60,4 @@ zxcvbn==4.4.28
# timezone
pytz==2024.1
# jwt
-jwt==1.3.1
\ No newline at end of file
+PyJWT==2.8.0
\ No newline at end of file
diff --git a/docker-compose-local.yml b/docker-compose-local.yml
index d79fa54d3..3dce85f3a 100644
--- a/docker-compose-local.yml
+++ b/docker-compose-local.yml
@@ -73,6 +73,20 @@ services:
- worker
- web
+ admin:
+ build:
+ context: .
+ dockerfile: ./admin/Dockerfile.dev
+ restart: unless-stopped
+ networks:
+ - dev_env
+ volumes:
+ - ./admin:/app/admin
+ depends_on:
+ - api
+ - worker
+ - web
+
api:
build:
context: ./apiserver
@@ -167,3 +181,4 @@ services:
- web
- api
- space
+ - admin
diff --git a/nginx/env.sh b/nginx/env.sh
index 7db471eca..dbd59d5b7 100644
--- a/nginx/env.sh
+++ b/nginx/env.sh
@@ -2,5 +2,6 @@
export dollar="$"
export http_upgrade="http_upgrade"
+export scheme="scheme"
envsubst < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
exec nginx -g 'daemon off;'
diff --git a/nginx/nginx.conf.dev b/nginx/nginx.conf.dev
index 872ff6748..d13897166 100644
--- a/nginx/nginx.conf.dev
+++ b/nginx/nginx.conf.dev
@@ -15,6 +15,8 @@ http {
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Permissions-Policy "interest-cohort=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Forwarded-Proto "${dollar}scheme";
+ add_header Host "${dollar}host";
location / {
proxy_pass http://web:3000/;
@@ -23,8 +25,8 @@ http {
proxy_set_header Connection "upgrade";
}
- location /god-mode {
- proxy_pass http://godmode:3000/;
+ location /god-mode/ {
+ proxy_pass http://admin:3000/god-mode/;
}
location /api/ {
diff --git a/nginx/nginx.conf.template b/nginx/nginx.conf.template
index 829c32eed..35b021e81 100644
--- a/nginx/nginx.conf.template
+++ b/nginx/nginx.conf.template
@@ -15,6 +15,8 @@ http {
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Permissions-Policy "interest-cohort=()" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-Forwarded-Proto "${dollar}scheme";
+ add_header Host "${dollar}host";
location / {
proxy_pass http://web:3000/;
diff --git a/setup.sh b/setup.sh
index a1d9bcbe1..838f5bbac 100755
--- a/setup.sh
+++ b/setup.sh
@@ -7,6 +7,8 @@ export LC_CTYPE=C
cp ./web/.env.example ./web/.env
cp ./apiserver/.env.example ./apiserver/.env
+cp ./space/.env.example ./space/.env
+cp ./admin/.env.example ./admin/.env
# Generate the SECRET_KEY that will be used by django
echo "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./apiserver/.env
\ No newline at end of file
diff --git a/space/.env.example b/space/.env.example
new file mode 100644
index 000000000..fbd4ad4f9
--- /dev/null
+++ b/space/.env.example
@@ -0,0 +1,2 @@
+NEXT_PUBLIC_APP_URL=
+NEXT_PUBLIC_API_BASE_URL=
\ No newline at end of file
diff --git a/space/hooks/store/user/use-user.ts b/space/hooks/store/user/use-user.ts
index a99480213..e491d88a2 100644
--- a/space/hooks/store/user/use-user.ts
+++ b/space/hooks/store/user/use-user.ts
@@ -1,7 +1,7 @@
import { useContext } from "react";
// store
import { StoreContext } from "@/lib/store-context";
-import { IUserStore } from "@/store/user/index.store";
+import { IUserStore } from "@/store/user";
export const useUser = (): IUserStore => {
const context = useContext(StoreContext);
diff --git a/space/lib/store-context.tsx b/space/lib/store-context.tsx
index e1493f227..1eff1ddde 100644
--- a/space/lib/store-context.tsx
+++ b/space/lib/store-context.tsx
@@ -2,7 +2,7 @@ import { ReactElement, createContext } from "react";
// mobx store
import { RootStore } from "@/store/root.store";
-let rootStore = new RootStore();
+export let rootStore = new RootStore();
export const StoreContext = createContext(rootStore);
diff --git a/space/pages/_app.tsx b/space/pages/_app.tsx
index 6f326a182..363b61510 100644
--- a/space/pages/_app.tsx
+++ b/space/pages/_app.tsx
@@ -11,7 +11,7 @@ import { StoreProvider } from "@/lib/store-context";
// wrappers
import { InstanceWrapper } from "@/lib/wrappers";
-const prefix = parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0") === 0 ? "/" : "/spaces/";
+const prefix = "/spaces/";
function MyApp({ Component, pageProps }: AppProps) {
return (
diff --git a/space/services/api.service.ts b/space/services/api.service.ts
index 788cd57c6..b6d353ccc 100644
--- a/space/services/api.service.ts
+++ b/space/services/api.service.ts
@@ -1,5 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios, { AxiosInstance } from "axios";
+// store
+import { rootStore } from "@/lib/store-context";
abstract class APIService {
protected baseURL: string;
@@ -19,7 +21,8 @@ abstract class APIService {
this.axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
- if (error.response && error.response.status === 401) window.location.href = "/";
+ const store = rootStore;
+ if (error.response && error.response.status === 401 && store.user.data) store.user.reset();
return Promise.reject(error);
}
);
diff --git a/space/store/root.store.ts b/space/store/root.store.ts
index 8cb4991fc..fa0a25aaf 100644
--- a/space/store/root.store.ts
+++ b/space/store/root.store.ts
@@ -3,7 +3,7 @@ import { enableStaticRendering } from "mobx-react-lite";
// store imports
import { IInstanceStore, InstanceStore } from "@/store/instance.store";
import { IProjectStore, ProjectStore } from "@/store/project";
-import { IUserStore, UserStore } from "@/store/user/index.store";
+import { IUserStore, UserStore } from "@/store/user";
import { IProfileStore, ProfileStore } from "@/store/user/profile.store";
import IssueStore, { IIssueStore } from "./issue";
diff --git a/space/store/user/index.store.ts b/space/store/user/index.ts
similarity index 92%
rename from space/store/user/index.store.ts
rename to space/store/user/index.ts
index d7f905974..e5bfc41c6 100644
--- a/space/store/user/index.store.ts
+++ b/space/store/user/index.ts
@@ -2,8 +2,6 @@ import set from "lodash/set";
import { action, computed, makeObservable, observable, runInAction } from "mobx";
// types
import { IUser } from "@plane/types";
-// helpers
-// import { API_BASE_URL } from "@/helpers/common.helper";
// services
import { AuthService } from "@/services/authentication.service";
import { UserService } from "@/services/user.service";
@@ -30,6 +28,7 @@ export interface IUserStore {
// actions
fetchCurrentUser: () => Promise;
updateCurrentUser: (data: Partial) => Promise;
+ reset: () => void;
signOut: () => Promise;
}
@@ -65,6 +64,7 @@ export class UserStore implements IUserStore {
// actions
fetchCurrentUser: action,
updateCurrentUser: action,
+ reset: action,
signOut: action,
});
}
@@ -153,6 +153,20 @@ export class UserStore implements IUserStore {
}
};
+ /**
+ * @description resets the user store
+ * @returns {void}
+ */
+ reset = (): void => {
+ runInAction(() => {
+ this.isAuthenticated = false;
+ this.isLoading = false;
+ this.error = undefined;
+ this.data = undefined;
+ this.userProfile = new ProfileStore(this.store);
+ });
+ };
+
/**
* @description signs out the current user
* @returns {Promise}
diff --git a/web/components/account/auth-forms/auth-root.tsx b/web/components/account/auth-forms/auth-root.tsx
index c383f190e..1494f91f1 100644
--- a/web/components/account/auth-forms/auth-root.tsx
+++ b/web/components/account/auth-forms/auth-root.tsx
@@ -88,7 +88,10 @@ export const AuthRoot: FC = observer((props) => {
if (authMode === EAuthModes.SIGN_IN) {
if (response.is_password_autoset) setAuthStep(EAuthSteps.UNIQUE_CODE);
else setAuthStep(EAuthSteps.PASSWORD);
- } else setAuthStep(EAuthSteps.PASSWORD);
+ } else {
+ if (instance && instance?.config?.is_smtp_configured) setAuthStep(EAuthSteps.UNIQUE_CODE);
+ else setAuthStep(EAuthSteps.PASSWORD);
+ }
})
.catch((error) => {
const errorhandler = authErrorHandler(error?.error_code.toString(), data?.email || undefined);
diff --git a/web/components/account/auth-forms/password.tsx b/web/components/account/auth-forms/password.tsx
index de3d813e1..37c6d6506 100644
--- a/web/components/account/auth-forms/password.tsx
+++ b/web/components/account/auth-forms/password.tsx
@@ -40,15 +40,22 @@ const authService = new AuthService();
export const AuthPasswordForm: React.FC = observer((props: Props) => {
const { email, handleStepChange, handleEmailClear, mode } = props;
- // states
- const [passwordFormData, setPasswordFormData] = useState({ ...defaultValues, email });
- const [showPassword, setShowPassword] = useState(false);
- const [csrfToken, setCsrfToken] = useState(undefined);
- const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
- const [isSubmitting, setIsSubmitting] = useState(false);
// hooks
const { instance } = useInstance();
const { captureEvent } = useEventTracker();
+ // states
+ const [csrfToken, setCsrfToken] = useState(undefined);
+ const [passwordFormData, setPasswordFormData] = useState({ ...defaultValues, email });
+ const [showPassword, setShowPassword] = useState({
+ password: false,
+ retypePassword: false,
+ });
+ const [isSubmitting, setIsSubmitting] = useState(false);
+ const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
+
+ const handleShowPassword = (key: keyof typeof showPassword) =>
+ setShowPassword((prev) => ({ ...prev, [key]: !prev[key] }));
+
// derived values
const isSmtpConfigured = instance?.config?.is_smtp_configured;
@@ -116,9 +123,9 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => {
type="email"
value={passwordFormData.email}
onChange={(e) => handleFormChange("email", e.target.value)}
- // hasError={Boolean(errors.email)}
placeholder="name@company.com"
className="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
+ disabled
/>
{passwordFormData.email.length > 0 && (
= observer((props: Props) => {
/>
)}
+
diff --git a/web/components/account/deactivate-account-modal.tsx b/web/components/account/deactivate-account-modal.tsx
index a020bd43b..ee5100e36 100644
--- a/web/components/account/deactivate-account-modal.tsx
+++ b/web/components/account/deactivate-account-modal.tsx
@@ -1,7 +1,5 @@
import React, { useState } from "react";
import { useRouter } from "next/router";
-import { useTheme } from "next-themes";
-import { mutate } from "swr";
import { Trash2 } from "lucide-react";
import { Dialog, Transition } from "@headlessui/react";
// hooks
@@ -15,17 +13,14 @@ type Props = {
};
export const DeactivateAccountModal: React.FC
= (props) => {
+ const router = useRouter();
const { isOpen, onClose } = props;
+ // hooks
+ const { deactivateAccount, signOut } = useUser();
// states
const [isDeactivating, setIsDeactivating] = useState(false);
- const { deactivateAccount } = useUser();
-
- const router = useRouter();
-
- const { setTheme } = useTheme();
-
const handleClose = () => {
setIsDeactivating(false);
onClose();
@@ -41,8 +36,7 @@ export const DeactivateAccountModal: React.FC = (props) => {
title: "Success!",
message: "Account deactivated successfully.",
});
- mutate("CURRENT_USER_DETAILS", null);
- setTheme("system");
+ signOut();
router.push("/");
handleClose();
})
diff --git a/web/lib/store-context.tsx b/web/lib/store-context.tsx
index c8d49066d..0170cfe37 100644
--- a/web/lib/store-context.tsx
+++ b/web/lib/store-context.tsx
@@ -2,7 +2,7 @@ import { ReactElement, createContext } from "react";
// mobx store
import { RootStore } from "@/store/root.store";
-let rootStore = new RootStore();
+export let rootStore = new RootStore();
export const StoreContext = createContext(rootStore);
diff --git a/web/services/api.service.ts b/web/services/api.service.ts
index 81f9dd2b8..d5e8f8951 100644
--- a/web/services/api.service.ts
+++ b/web/services/api.service.ts
@@ -1,5 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios, { AxiosInstance } from "axios";
+// store
+import { rootStore } from "@/lib/store-context";
export abstract class APIService {
protected baseURL: string;
@@ -19,7 +21,11 @@ export abstract class APIService {
this.axiosInstance.interceptors.response.use(
(response) => response,
(error) => {
- if (error.response && error.response.status === 401) window.location.href = "/accounts/sign-in";
+ const store = rootStore;
+ if (error.response && error.response.status === 401 && store.user.data) {
+ store.user.reset();
+ store.resetOnSignOut();
+ }
return Promise.reject(error);
}
);
diff --git a/web/store/user/index.ts b/web/store/user/index.ts
index 7010d7097..f22507a12 100644
--- a/web/store/user/index.ts
+++ b/web/store/user/index.ts
@@ -36,6 +36,7 @@ export interface IUserStore {
updateCurrentUser: (data: Partial) => Promise;
handleSetPassword: (csrfToken: string, data: { password: string }) => Promise;
deactivateAccount: () => Promise;
+ reset: () => void;
signOut: () => Promise;
}
@@ -79,6 +80,7 @@ export class UserStore implements IUserStore {
updateCurrentUser: action,
handleSetPassword: action,
deactivateAccount: action,
+ reset: action,
signOut: action,
});
}
@@ -191,6 +193,22 @@ export class UserStore implements IUserStore {
this.store.resetOnSignOut();
};
+ /**
+ * @description resets the user store
+ * @returns {void}
+ */
+ reset = (): void => {
+ runInAction(() => {
+ this.isAuthenticated = false;
+ this.isLoading = false;
+ this.error = undefined;
+ this.data = undefined;
+ this.userProfile = new ProfileStore(this.store);
+ this.userSettings = new UserSettingsStore();
+ this.membership = new UserMembershipStore(this.store);
+ });
+ };
+
/**
* @description signs out the current user
* @returns {Promise}
diff --git a/yarn.lock b/yarn.lock
index e3230e816..c25eddcda 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2658,7 +2658,7 @@
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.5.tgz#1e78a3ac2428e6d7e6c05c1665c242023a4601d8"
integrity sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==
-"@types/lodash@^4.14.202", "@types/lodash@^4.17.0":
+"@types/lodash@^4.14.202", "@types/lodash@^4.17.0", "@types/lodash@^4.17.1":
version "4.17.1"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.1.tgz#0fabfcf2f2127ef73b119d98452bd317c4a17eb8"
integrity sha512-X+2qazGS3jxLAIz5JDXDzglAF3KpijdhFxlf/V1+hEsOUc+HnWi81L/uv/EvGuV90WY+7mPGFCUDGfQC3Gj95Q==
@@ -6935,10 +6935,10 @@ postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.29:
picocolors "^1.0.0"
source-map-js "^1.2.0"
-posthog-js@^1.105.0:
- version "1.131.2"
- resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.131.2.tgz#c82b16a4074f773eaf41df47187fb88cc5aef28c"
- integrity sha512-un5c5CbDhJ1LRBDgy4I1D5a1++P8/mNl4CS9C5A1z95qIF7iY8OuA6XPW7sIA6tKSdda4PGwfa2Gmfz1nvnywQ==
+posthog-js@^1.131.3:
+ version "1.131.3"
+ resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.131.3.tgz#bd3e6123dc715f089825a92d3ec62480b7ec0a76"
+ integrity sha512-ds/TADDS+rT/WgUyeW4cJ+X+fX+O1KdkOyssNI/tP90PrFf0IJsck5B42YOLhfz87U2vgTyBaKHkdlMgWuOFog==
dependencies:
fflate "^0.4.8"
preact "^10.19.3"