forked from github/plane
fix: auth redirection issues in the web, space and admin apps (#4414)
* fix: login redirection * dev: log the user out when deactivating the account * dev: update redirect uris for google and github * fix: redirection url and invitation api and add redirection to god mode in nginx * dev: add reset password redirection * dev: update nginx headers * dev: fix setup sh and env example and put validation for use minio when fetching project covers * dev: stabilize dev setup * fix: handled redirection error in web, space, and admin apps * fix: resovled build errors --------- Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
This commit is contained in:
parent
692f570258
commit
58bf056ddb
12
admin/Dockerfile.dev
Normal file
12
admin/Dockerfile.dev
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM node:18-alpine
|
||||||
|
RUN apk add --no-cache libc6-compat
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN yarn global add turbo
|
||||||
|
RUN yarn install
|
||||||
|
EXPOSE 3000
|
||||||
|
VOLUME [ "/app/node_modules", "/app/admin/node_modules" ]
|
||||||
|
CMD ["yarn", "dev", "--filter=admin"]
|
@ -15,7 +15,7 @@ interface RootLayoutProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RootLayout = ({ children, ...pageProps }: RootLayoutProps) => {
|
const RootLayout = ({ children, ...pageProps }: RootLayoutProps) => {
|
||||||
const prefix = parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0") === 0 ? "/" : "/god-mode/";
|
const prefix = "/god-mode/";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
// lib
|
|
||||||
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
|
||||||
// helpers
|
|
||||||
import { EAuthenticationPageType, EInstancePageType } from "@/helpers";
|
|
||||||
|
|
||||||
interface LoginLayoutProps {
|
|
||||||
children: ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const LoginLayout = ({ children }: LoginLayoutProps) => (
|
|
||||||
<InstanceWrapper pageType={EInstancePageType.POST_SETUP}>
|
|
||||||
<AuthWrapper authType={EAuthenticationPageType.NOT_AUTHENTICATED}>{children}</AuthWrapper>
|
|
||||||
</InstanceWrapper>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default LoginLayout;
|
|
@ -1,18 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
// layouts
|
|
||||||
import { DefaultLayout } from "@/layouts";
|
|
||||||
// components
|
|
||||||
import { PageHeader } from "@/components/core";
|
|
||||||
import { InstanceSignInForm } from "./components";
|
|
||||||
|
|
||||||
const LoginPage = () => (
|
|
||||||
<>
|
|
||||||
<PageHeader title="Setup - God Mode" />
|
|
||||||
<DefaultLayout>
|
|
||||||
<InstanceSignInForm />
|
|
||||||
</DefaultLayout>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default LoginPage;
|
|
@ -1,20 +1,26 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
// layouts
|
||||||
import { useRouter } from "next/navigation";
|
import { DefaultLayout } from "@/layouts";
|
||||||
// components
|
// components
|
||||||
import { PageHeader } from "@/components/core";
|
import { PageHeader } from "@/components/core";
|
||||||
|
import { InstanceSignInForm } from "@/components/login";
|
||||||
|
// lib
|
||||||
|
import { AuthWrapper, InstanceWrapper } from "@/lib/wrappers";
|
||||||
|
// helpers
|
||||||
|
import { EAuthenticationPageType, EInstancePageType } from "@/helpers";
|
||||||
|
|
||||||
const RootPage = () => {
|
const LoginPage = () => (
|
||||||
const router = useRouter();
|
<>
|
||||||
|
<PageHeader title="Login - God Mode" />
|
||||||
|
<InstanceWrapper pageType={EInstancePageType.POST_SETUP}>
|
||||||
|
<AuthWrapper authType={EAuthenticationPageType.NOT_AUTHENTICATED}>
|
||||||
|
<DefaultLayout>
|
||||||
|
<InstanceSignInForm />
|
||||||
|
</DefaultLayout>
|
||||||
|
</AuthWrapper>
|
||||||
|
</InstanceWrapper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => router.push("/login"), [router]);
|
export default LoginPage;
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageHeader title="Plane - God Mode" />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RootPage;
|
|
||||||
|
@ -36,7 +36,7 @@ export const HelpSection: FC = () => {
|
|||||||
// refs
|
// refs
|
||||||
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
|
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
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/`}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -19,7 +19,7 @@ export const NewUserPopup: React.FC = observer(() => {
|
|||||||
// theme
|
// theme
|
||||||
const { resolvedTheme } = nextUseTheme();
|
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 <></>;
|
if (!isNewUserPopup) return <></>;
|
||||||
return (
|
return (
|
||||||
|
@ -4,7 +4,7 @@ import { ReactElement, createContext } from "react";
|
|||||||
// mobx store
|
// mobx store
|
||||||
import { RootStore } from "@/store/root-store";
|
import { RootStore } from "@/store/root-store";
|
||||||
|
|
||||||
let rootStore = new RootStore();
|
export let rootStore = new RootStore();
|
||||||
|
|
||||||
export const StoreContext = createContext<RootStore>(rootStore);
|
export const StoreContext = createContext<RootStore>(rootStore);
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, ReactNode } from "react";
|
import { FC, ReactNode } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { Spinner } from "@plane/ui";
|
import { Spinner } from "@plane/ui";
|
||||||
// hooks
|
// hooks
|
||||||
import { useInstance, useUser } from "@/hooks";
|
import { useInstance, useUser } from "@/hooks";
|
||||||
// helpers
|
// helpers
|
||||||
import { EAuthenticationPageType, EUserStatus } from "@/helpers";
|
import { EAuthenticationPageType } from "@/helpers";
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
export interface IAuthWrapper {
|
export interface IAuthWrapper {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -16,41 +16,41 @@ export interface IAuthWrapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AuthWrapper: FC<IAuthWrapper> = observer((props) => {
|
export const AuthWrapper: FC<IAuthWrapper> = observer((props) => {
|
||||||
|
const router = useRouter();
|
||||||
|
// props
|
||||||
const { children, authType = EAuthenticationPageType.AUTHENTICATED } = props;
|
const { children, authType = EAuthenticationPageType.AUTHENTICATED } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const { instance, fetchInstanceAdmins } = useInstance();
|
const { instance } = useInstance();
|
||||||
const { isLoading, userStatus, currentUser, fetchCurrentUser } = useUser();
|
const { isLoading, currentUser, fetchCurrentUser } = useUser();
|
||||||
|
|
||||||
useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
|
const { isLoading: isSWRLoading } = useSWR("CURRENT_USER_DETAILS", () => fetchCurrentUser(), {
|
||||||
shouldRetryOnError: false,
|
|
||||||
});
|
|
||||||
useSWR("INSTANCE_ADMINS", () => fetchInstanceAdmins(), {
|
|
||||||
shouldRetryOnError: false,
|
shouldRetryOnError: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isLoading)
|
if (isSWRLoading || isLoading)
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-screen w-full items-center justify-center">
|
<div className="relative flex h-screen w-full items-center justify-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (userStatus && userStatus?.status === EUserStatus.ERROR)
|
if (authType === EAuthenticationPageType.NOT_AUTHENTICATED) {
|
||||||
return (
|
if (currentUser === undefined) return <>{children}</>;
|
||||||
<div className="relative flex h-screen w-screen items-center justify-center">
|
else {
|
||||||
Something went wrong. please try again later
|
router.push("/general/");
|
||||||
</div>
|
return <></>;
|
||||||
);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ([EAuthenticationPageType.AUTHENTICATED, EAuthenticationPageType.NOT_AUTHENTICATED].includes(authType)) {
|
if (authType === EAuthenticationPageType.AUTHENTICATED) {
|
||||||
if (authType === EAuthenticationPageType.NOT_AUTHENTICATED) {
|
if (currentUser) return <>{children}</>;
|
||||||
if (currentUser === undefined) return <>{children}</>;
|
else {
|
||||||
else redirect("/general/");
|
if (instance && instance?.instance?.is_setup_done) {
|
||||||
} else {
|
router.push("/");
|
||||||
if (currentUser) return <>{children}</>;
|
return <></>;
|
||||||
else {
|
} else {
|
||||||
if (instance?.instance?.is_setup_done) redirect("/login/");
|
router.push("/setup/");
|
||||||
else redirect("/setup/");
|
return <></>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import { InstanceNotReady } from "@/components/instance";
|
|||||||
// hooks
|
// hooks
|
||||||
import { useInstance } from "@/hooks";
|
import { useInstance } from "@/hooks";
|
||||||
// helpers
|
// helpers
|
||||||
import { EInstancePageType, EInstanceStatus } from "@/helpers";
|
import { EInstancePageType } from "@/helpers";
|
||||||
|
|
||||||
type TInstanceWrapper = {
|
type TInstanceWrapper = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -24,26 +24,19 @@ export const InstanceWrapper: FC<TInstanceWrapper> = observer((props) => {
|
|||||||
const searchparams = useSearchParams();
|
const searchparams = useSearchParams();
|
||||||
const authEnabled = searchparams.get("auth_enabled") || "1";
|
const authEnabled = searchparams.get("auth_enabled") || "1";
|
||||||
// hooks
|
// 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,
|
revalidateOnFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isLoading)
|
if (isSWRLoading || isLoading)
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-screen w-full items-center justify-center">
|
<div className="relative flex h-screen w-full items-center justify-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (instanceStatus && instanceStatus?.status === EInstanceStatus.ERROR)
|
|
||||||
return (
|
|
||||||
<div className="relative flex h-screen w-screen items-center justify-center">
|
|
||||||
Something went wrong. please try again later
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (instance?.instance?.is_setup_done === false && authEnabled === "1")
|
if (instance?.instance?.is_setup_done === false && authEnabled === "1")
|
||||||
return (
|
return (
|
||||||
<DefaultLayout withoutBackground>
|
<DefaultLayout withoutBackground>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
|
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
|
||||||
|
// store
|
||||||
|
import { rootStore } from "@/lib/store-context";
|
||||||
|
|
||||||
export abstract class APIService {
|
export abstract class APIService {
|
||||||
protected baseURL: string;
|
protected baseURL: string;
|
||||||
@ -18,7 +20,8 @@ export abstract class APIService {
|
|||||||
this.axiosInstance.interceptors.response.use(
|
this.axiosInstance.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(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);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -18,8 +18,10 @@ export class RootStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resetOnSignOut() {
|
resetOnSignOut() {
|
||||||
this.theme = new ThemeStore(this);
|
localStorage.setItem("theme", "system");
|
||||||
|
|
||||||
this.instance = new InstanceStore(this);
|
this.instance = new InstanceStore(this);
|
||||||
this.user = new UserStore(this);
|
this.user = new UserStore(this);
|
||||||
|
this.theme = new ThemeStore(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,8 @@ export interface IUserStore {
|
|||||||
currentUser: IUser | undefined;
|
currentUser: IUser | undefined;
|
||||||
// fetch actions
|
// fetch actions
|
||||||
fetchCurrentUser: () => Promise<IUser>;
|
fetchCurrentUser: () => Promise<IUser>;
|
||||||
signOut: () => Promise<void>;
|
reset: () => void;
|
||||||
|
signOut: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UserStore implements IUserStore {
|
export class UserStore implements IUserStore {
|
||||||
@ -28,8 +29,6 @@ export class UserStore implements IUserStore {
|
|||||||
// services
|
// services
|
||||||
userService;
|
userService;
|
||||||
authService;
|
authService;
|
||||||
// rootStore
|
|
||||||
rootStore;
|
|
||||||
|
|
||||||
constructor(private store: RootStore) {
|
constructor(private store: RootStore) {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
@ -40,10 +39,11 @@ export class UserStore implements IUserStore {
|
|||||||
currentUser: observable,
|
currentUser: observable,
|
||||||
// action
|
// action
|
||||||
fetchCurrentUser: action,
|
fetchCurrentUser: action,
|
||||||
|
reset: action,
|
||||||
|
signOut: action,
|
||||||
});
|
});
|
||||||
this.userService = new UserService();
|
this.userService = new UserService();
|
||||||
this.authService = new AuthService();
|
this.authService = new AuthService();
|
||||||
this.rootStore = store;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,11 +54,20 @@ export class UserStore implements IUserStore {
|
|||||||
try {
|
try {
|
||||||
if (this.currentUser === undefined) this.isLoading = true;
|
if (this.currentUser === undefined) this.isLoading = true;
|
||||||
const currentUser = await this.userService.currentUser();
|
const currentUser = await this.userService.currentUser();
|
||||||
runInAction(() => {
|
if (currentUser) {
|
||||||
this.isUserLoggedIn = true;
|
await this.store.instance.fetchInstanceAdmins();
|
||||||
this.currentUser = currentUser;
|
runInAction(() => {
|
||||||
this.isLoading = false;
|
this.isUserLoggedIn = true;
|
||||||
});
|
this.currentUser = currentUser;
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
runInAction(() => {
|
||||||
|
this.isUserLoggedIn = false;
|
||||||
|
this.currentUser = undefined;
|
||||||
|
this.isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
return currentUser;
|
return currentUser;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.isLoading = false;
|
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 () => {
|
signOut = async () => {
|
||||||
this.rootStore.resetOnSignOut();
|
this.store.resetOnSignOut();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Backend
|
# Backend
|
||||||
# Debug value for api server use it as 0 for production use
|
# Debug value for api server use it as 0 for production use
|
||||||
DEBUG=0
|
DEBUG=0
|
||||||
CORS_ALLOWED_ORIGINS=""
|
CORS_ALLOWED_ORIGINS="http://localhost"
|
||||||
|
|
||||||
# Error logs
|
# Error logs
|
||||||
SENTRY_DSN=""
|
SENTRY_DSN=""
|
||||||
|
@ -602,11 +602,19 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
|||||||
@cache_response(60 * 60 * 24, user=False)
|
@cache_response(60 * 60 * 24, user=False)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
files = []
|
files = []
|
||||||
s3 = boto3.client(
|
if settings.USE_MINIO:
|
||||||
"s3",
|
s3 = boto3.client(
|
||||||
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
"s3",
|
||||||
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
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 = {
|
params = {
|
||||||
"Bucket": settings.AWS_STORAGE_BUCKET_NAME,
|
"Bucket": settings.AWS_STORAGE_BUCKET_NAME,
|
||||||
"Prefix": "static/project-cover/",
|
"Prefix": "static/project-cover/",
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
# Django imports
|
# Django imports
|
||||||
from django.db.models import Case, Count, IntegerField, Q, When
|
from django.db.models import Case, Count, IntegerField, Q, When
|
||||||
|
from django.contrib.auth import logout
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -26,6 +28,7 @@ from plane.db.models import (
|
|||||||
from plane.license.models import Instance, InstanceAdmin
|
from plane.license.models import Instance, InstanceAdmin
|
||||||
from plane.utils.cache import cache_response, invalidate_cache
|
from plane.utils.cache import cache_response, invalidate_cache
|
||||||
from plane.utils.paginator import BasePaginator
|
from plane.utils.paginator import BasePaginator
|
||||||
|
from plane.authentication.utils.host import user_ip
|
||||||
|
|
||||||
|
|
||||||
class UserEndpoint(BaseViewSet):
|
class UserEndpoint(BaseViewSet):
|
||||||
@ -166,7 +169,14 @@ class UserEndpoint(BaseViewSet):
|
|||||||
"workspace_invite": False,
|
"workspace_invite": False,
|
||||||
}
|
}
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
|
# User log out
|
||||||
|
user.last_logout_ip = user_ip(request=request)
|
||||||
|
user.last_logout_time = timezone.now()
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
|
# Logout the user
|
||||||
|
logout(request)
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,12 +7,6 @@ def auth_exception_handler(exc, context):
|
|||||||
response = exception_handler(exc, context)
|
response = exception_handler(exc, context)
|
||||||
# Check if an AuthenticationFailed exception is raised.
|
# Check if an AuthenticationFailed exception is raised.
|
||||||
if isinstance(exc, NotAuthenticated):
|
if isinstance(exc, NotAuthenticated):
|
||||||
# Return 403 if the users me api fails
|
response.status_code = 401
|
||||||
request = context["request"]
|
|
||||||
if request.path == "/api/users/me/":
|
|
||||||
response.status_code = 403
|
|
||||||
# else return 401
|
|
||||||
else:
|
|
||||||
response.status_code = 401
|
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -46,9 +46,7 @@ class GitHubOAuthProvider(OauthAdapter):
|
|||||||
client_id = GITHUB_CLIENT_ID
|
client_id = GITHUB_CLIENT_ID
|
||||||
client_secret = GITHUB_CLIENT_SECRET
|
client_secret = GITHUB_CLIENT_SECRET
|
||||||
|
|
||||||
redirect_uri = (
|
redirect_uri = f"""{"https" if request.is_secure() else "http"}://{request.get_host()}/auth/github/callback/"""
|
||||||
f"{request.scheme}://{request.get_host()}/auth/github/callback/"
|
|
||||||
)
|
|
||||||
url_params = {
|
url_params = {
|
||||||
"client_id": client_id,
|
"client_id": client_id,
|
||||||
"redirect_uri": redirect_uri,
|
"redirect_uri": redirect_uri,
|
||||||
|
@ -43,9 +43,7 @@ class GoogleOAuthProvider(OauthAdapter):
|
|||||||
client_id = GOOGLE_CLIENT_ID
|
client_id = GOOGLE_CLIENT_ID
|
||||||
client_secret = GOOGLE_CLIENT_SECRET
|
client_secret = GOOGLE_CLIENT_SECRET
|
||||||
|
|
||||||
redirect_uri = (
|
redirect_uri = f"""{"https" if request.is_secure() else "http"}://{request.get_host()}/auth/google/callback/"""
|
||||||
f"{request.scheme}://{request.get_host()}/auth/google/callback/"
|
|
||||||
)
|
|
||||||
url_params = {
|
url_params = {
|
||||||
"client_id": client_id,
|
"client_id": client_id,
|
||||||
"scope": self.scope,
|
"scope": self.scope,
|
||||||
|
@ -6,7 +6,7 @@ def base_host(request):
|
|||||||
return (
|
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"{request.scheme}://{request.get_host()}"
|
or f"""{"https" if request.is_secure() else "http"}://{request.get_host()}"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,10 +10,13 @@ def get_redirection_path(user):
|
|||||||
return "onboarding"
|
return "onboarding"
|
||||||
|
|
||||||
# Redirect to the last workspace if the user has last workspace
|
# Redirect to the last workspace if the user has last workspace
|
||||||
if profile.last_workspace_id and Workspace.objects.filter(
|
if (
|
||||||
pk=profile.last_workspace_id,
|
profile.last_workspace_id
|
||||||
workspace_member__member_id=user.id,
|
and Workspace.objects.filter(
|
||||||
workspace_member__is_active=True,
|
pk=profile.last_workspace_id,
|
||||||
|
workspace_member__member_id=user.id,
|
||||||
|
workspace_member__is_active=True,
|
||||||
|
).exists()
|
||||||
):
|
):
|
||||||
workspace = Workspace.objects.filter(
|
workspace = Workspace.objects.filter(
|
||||||
pk=profile.last_workspace_id,
|
pk=profile.last_workspace_id,
|
||||||
|
@ -206,7 +206,7 @@ class ResetPasswordEndpoint(View):
|
|||||||
|
|
||||||
url = urljoin(
|
url = urljoin(
|
||||||
base_host(request=request),
|
base_host(request=request),
|
||||||
"accounts/sign-in?" + urlencode({"success", True}),
|
"accounts/sign-in?" + urlencode({"success": True}),
|
||||||
)
|
)
|
||||||
return HttpResponseRedirect(url)
|
return HttpResponseRedirect(url)
|
||||||
except DjangoUnicodeDecodeError:
|
except DjangoUnicodeDecodeError:
|
||||||
|
@ -31,6 +31,8 @@ MEDIA_URL = "/uploads/"
|
|||||||
MEDIA_ROOT = os.path.join(BASE_DIR, "uploads") # noqa
|
MEDIA_ROOT = os.path.join(BASE_DIR, "uploads") # noqa
|
||||||
|
|
||||||
CORS_ALLOWED_ORIGINS = [
|
CORS_ALLOWED_ORIGINS = [
|
||||||
|
"http://localhost",
|
||||||
|
"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:4000",
|
||||||
|
@ -60,4 +60,4 @@ zxcvbn==4.4.28
|
|||||||
# timezone
|
# timezone
|
||||||
pytz==2024.1
|
pytz==2024.1
|
||||||
# jwt
|
# jwt
|
||||||
jwt==1.3.1
|
PyJWT==2.8.0
|
@ -73,6 +73,20 @@ services:
|
|||||||
- worker
|
- worker
|
||||||
- web
|
- web
|
||||||
|
|
||||||
|
admin:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./admin/Dockerfile.dev
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- dev_env
|
||||||
|
volumes:
|
||||||
|
- ./admin:/app/admin
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
- worker
|
||||||
|
- web
|
||||||
|
|
||||||
api:
|
api:
|
||||||
build:
|
build:
|
||||||
context: ./apiserver
|
context: ./apiserver
|
||||||
@ -167,3 +181,4 @@ services:
|
|||||||
- web
|
- web
|
||||||
- api
|
- api
|
||||||
- space
|
- space
|
||||||
|
- admin
|
||||||
|
@ -2,5 +2,6 @@
|
|||||||
|
|
||||||
export dollar="$"
|
export dollar="$"
|
||||||
export http_upgrade="http_upgrade"
|
export http_upgrade="http_upgrade"
|
||||||
|
export scheme="scheme"
|
||||||
envsubst < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
|
envsubst < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf
|
||||||
exec nginx -g 'daemon off;'
|
exec nginx -g 'daemon off;'
|
||||||
|
@ -15,6 +15,8 @@ http {
|
|||||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||||
add_header Permissions-Policy "interest-cohort=()" always;
|
add_header Permissions-Policy "interest-cohort=()" always;
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
add_header X-Forwarded-Proto "${dollar}scheme";
|
||||||
|
add_header Host "${dollar}host";
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://web:3000/;
|
proxy_pass http://web:3000/;
|
||||||
@ -23,8 +25,8 @@ http {
|
|||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
}
|
}
|
||||||
|
|
||||||
location /god-mode {
|
location /god-mode/ {
|
||||||
proxy_pass http://godmode:3000/;
|
proxy_pass http://admin:3000/god-mode/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
|
@ -15,6 +15,8 @@ http {
|
|||||||
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
add_header Referrer-Policy "no-referrer-when-downgrade" always;
|
||||||
add_header Permissions-Policy "interest-cohort=()" always;
|
add_header Permissions-Policy "interest-cohort=()" always;
|
||||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
add_header X-Forwarded-Proto "${dollar}scheme";
|
||||||
|
add_header Host "${dollar}host";
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://web:3000/;
|
proxy_pass http://web:3000/;
|
||||||
|
2
setup.sh
2
setup.sh
@ -7,6 +7,8 @@ export LC_CTYPE=C
|
|||||||
|
|
||||||
cp ./web/.env.example ./web/.env
|
cp ./web/.env.example ./web/.env
|
||||||
cp ./apiserver/.env.example ./apiserver/.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
|
# Generate the SECRET_KEY that will be used by django
|
||||||
echo "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./apiserver/.env
|
echo "SECRET_KEY=\"$(tr -dc 'a-z0-9' < /dev/urandom | head -c50)\"" >> ./apiserver/.env
|
2
space/.env.example
Normal file
2
space/.env.example
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
NEXT_PUBLIC_APP_URL=
|
||||||
|
NEXT_PUBLIC_API_BASE_URL=
|
@ -1,7 +1,7 @@
|
|||||||
import { useContext } from "react";
|
import { useContext } from "react";
|
||||||
// store
|
// store
|
||||||
import { StoreContext } from "@/lib/store-context";
|
import { StoreContext } from "@/lib/store-context";
|
||||||
import { IUserStore } from "@/store/user/index.store";
|
import { IUserStore } from "@/store/user";
|
||||||
|
|
||||||
export const useUser = (): IUserStore => {
|
export const useUser = (): IUserStore => {
|
||||||
const context = useContext(StoreContext);
|
const context = useContext(StoreContext);
|
||||||
|
@ -2,7 +2,7 @@ import { ReactElement, createContext } from "react";
|
|||||||
// mobx store
|
// mobx store
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
|
|
||||||
let rootStore = new RootStore();
|
export let rootStore = new RootStore();
|
||||||
|
|
||||||
export const StoreContext = createContext<RootStore>(rootStore);
|
export const StoreContext = createContext<RootStore>(rootStore);
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import { StoreProvider } from "@/lib/store-context";
|
|||||||
// wrappers
|
// wrappers
|
||||||
import { InstanceWrapper } from "@/lib/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) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
return (
|
return (
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import axios, { AxiosInstance } from "axios";
|
import axios, { AxiosInstance } from "axios";
|
||||||
|
// store
|
||||||
|
import { rootStore } from "@/lib/store-context";
|
||||||
|
|
||||||
abstract class APIService {
|
abstract class APIService {
|
||||||
protected baseURL: string;
|
protected baseURL: string;
|
||||||
@ -19,7 +21,8 @@ abstract class APIService {
|
|||||||
this.axiosInstance.interceptors.response.use(
|
this.axiosInstance.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(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);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,7 @@ import { enableStaticRendering } from "mobx-react-lite";
|
|||||||
// store imports
|
// store imports
|
||||||
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/index.store";
|
import { IUserStore, UserStore } from "@/store/user";
|
||||||
import { IProfileStore, ProfileStore } from "@/store/user/profile.store";
|
import { IProfileStore, ProfileStore } from "@/store/user/profile.store";
|
||||||
|
|
||||||
import IssueStore, { IIssueStore } from "./issue";
|
import IssueStore, { IIssueStore } from "./issue";
|
||||||
|
@ -2,8 +2,6 @@ import set from "lodash/set";
|
|||||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||||
// types
|
// types
|
||||||
import { IUser } from "@plane/types";
|
import { IUser } from "@plane/types";
|
||||||
// helpers
|
|
||||||
// import { API_BASE_URL } from "@/helpers/common.helper";
|
|
||||||
// services
|
// services
|
||||||
import { AuthService } from "@/services/authentication.service";
|
import { AuthService } from "@/services/authentication.service";
|
||||||
import { UserService } from "@/services/user.service";
|
import { UserService } from "@/services/user.service";
|
||||||
@ -30,6 +28,7 @@ export interface IUserStore {
|
|||||||
// actions
|
// actions
|
||||||
fetchCurrentUser: () => Promise<IUser | undefined>;
|
fetchCurrentUser: () => Promise<IUser | undefined>;
|
||||||
updateCurrentUser: (data: Partial<IUser>) => Promise<IUser | undefined>;
|
updateCurrentUser: (data: Partial<IUser>) => Promise<IUser | undefined>;
|
||||||
|
reset: () => void;
|
||||||
signOut: () => Promise<void>;
|
signOut: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +64,7 @@ export class UserStore implements IUserStore {
|
|||||||
// actions
|
// actions
|
||||||
fetchCurrentUser: action,
|
fetchCurrentUser: action,
|
||||||
updateCurrentUser: action,
|
updateCurrentUser: action,
|
||||||
|
reset: action,
|
||||||
signOut: 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
|
* @description signs out the current user
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
@ -88,7 +88,10 @@ export const AuthRoot: FC<TAuthRoot> = observer((props) => {
|
|||||||
if (authMode === EAuthModes.SIGN_IN) {
|
if (authMode === EAuthModes.SIGN_IN) {
|
||||||
if (response.is_password_autoset) setAuthStep(EAuthSteps.UNIQUE_CODE);
|
if (response.is_password_autoset) setAuthStep(EAuthSteps.UNIQUE_CODE);
|
||||||
else setAuthStep(EAuthSteps.PASSWORD);
|
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) => {
|
.catch((error) => {
|
||||||
const errorhandler = authErrorHandler(error?.error_code.toString(), data?.email || undefined);
|
const errorhandler = authErrorHandler(error?.error_code.toString(), data?.email || undefined);
|
||||||
|
@ -40,15 +40,22 @@ 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, handleStepChange, handleEmailClear, mode } = props;
|
||||||
// states
|
|
||||||
const [passwordFormData, setPasswordFormData] = useState<TPasswordFormValues>({ ...defaultValues, email });
|
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
|
||||||
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
|
|
||||||
const [isPasswordInputFocused, setIsPasswordInputFocused] = useState(false);
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
||||||
// hooks
|
// hooks
|
||||||
const { instance } = useInstance();
|
const { instance } = useInstance();
|
||||||
const { captureEvent } = useEventTracker();
|
const { captureEvent } = useEventTracker();
|
||||||
|
// states
|
||||||
|
const [csrfToken, setCsrfToken] = useState<string | undefined>(undefined);
|
||||||
|
const [passwordFormData, setPasswordFormData] = useState<TPasswordFormValues>({ ...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
|
// derived values
|
||||||
const isSmtpConfigured = instance?.config?.is_smtp_configured;
|
const isSmtpConfigured = instance?.config?.is_smtp_configured;
|
||||||
|
|
||||||
@ -116,9 +123,9 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
|||||||
type="email"
|
type="email"
|
||||||
value={passwordFormData.email}
|
value={passwordFormData.email}
|
||||||
onChange={(e) => handleFormChange("email", e.target.value)}
|
onChange={(e) => handleFormChange("email", e.target.value)}
|
||||||
// hasError={Boolean(errors.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="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
|
||||||
|
disabled
|
||||||
/>
|
/>
|
||||||
{passwordFormData.email.length > 0 && (
|
{passwordFormData.email.length > 0 && (
|
||||||
<XCircle
|
<XCircle
|
||||||
@ -127,6 +134,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</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">
|
||||||
@ -134,7 +142,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
|||||||
</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">
|
||||||
<Input
|
<Input
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword?.password ? "text" : "password"}
|
||||||
name="password"
|
name="password"
|
||||||
value={passwordFormData.password}
|
value={passwordFormData.password}
|
||||||
onChange={(e) => handleFormChange("password", e.target.value)}
|
onChange={(e) => handleFormChange("password", e.target.value)}
|
||||||
@ -144,15 +152,15 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
|||||||
onBlur={() => setIsPasswordInputFocused(false)}
|
onBlur={() => setIsPasswordInputFocused(false)}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
{showPassword ? (
|
{showPassword?.password ? (
|
||||||
<EyeOff
|
<EyeOff
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
onClick={() => setShowPassword(false)}
|
onClick={() => handleShowPassword("password")}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Eye
|
<Eye
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
onClick={() => setShowPassword(true)}
|
onClick={() => handleShowPassword("password")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -165,22 +173,22 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
|
|||||||
</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">
|
||||||
<Input
|
<Input
|
||||||
type={showPassword ? "text" : "password"}
|
type={showPassword?.retypePassword ? "text" : "password"}
|
||||||
name="confirm_password"
|
name="confirm_password"
|
||||||
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="h-[46px] w-full border border-onboarding-border-100 !bg-onboarding-background-200 pr-12 placeholder:text-onboarding-text-400"
|
||||||
/>
|
/>
|
||||||
{showPassword ? (
|
{showPassword?.retypePassword ? (
|
||||||
<EyeOff
|
<EyeOff
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
onClick={() => setShowPassword(false)}
|
onClick={() => handleShowPassword("retypePassword")}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Eye
|
<Eye
|
||||||
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
|
||||||
onClick={() => setShowPassword(true)}
|
onClick={() => handleShowPassword("retypePassword")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -111,10 +111,9 @@ export const AuthUniqueCodeForm: React.FC<Props> = (props) => {
|
|||||||
type="email"
|
type="email"
|
||||||
value={uniqueCodeFormData.email}
|
value={uniqueCodeFormData.email}
|
||||||
onChange={(e) => handleFormChange("email", e.target.value)}
|
onChange={(e) => handleFormChange("email", e.target.value)}
|
||||||
// FIXME:
|
|
||||||
// hasError={Boolean(errors.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="h-[46px] w-full border border-onboarding-border-100 pr-12 placeholder:text-onboarding-text-400"
|
||||||
|
disabled
|
||||||
/>
|
/>
|
||||||
{uniqueCodeFormData.email.length > 0 && (
|
{uniqueCodeFormData.email.length > 0 && (
|
||||||
<XCircle
|
<XCircle
|
||||||
@ -122,6 +121,7 @@ export const AuthUniqueCodeForm: React.FC<Props> = (props) => {
|
|||||||
onClick={handleEmailClear}
|
onClick={handleEmailClear}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<input type="hidden" value={uniqueCodeFormData.email} name="email" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
import { mutate } from "swr";
|
|
||||||
import { Trash2 } from "lucide-react";
|
import { Trash2 } from "lucide-react";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
// hooks
|
// hooks
|
||||||
@ -15,17 +13,14 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DeactivateAccountModal: React.FC<Props> = (props) => {
|
export const DeactivateAccountModal: React.FC<Props> = (props) => {
|
||||||
|
const router = useRouter();
|
||||||
const { isOpen, onClose } = props;
|
const { isOpen, onClose } = props;
|
||||||
|
// hooks
|
||||||
|
const { deactivateAccount, signOut } = useUser();
|
||||||
|
|
||||||
// states
|
// states
|
||||||
const [isDeactivating, setIsDeactivating] = useState(false);
|
const [isDeactivating, setIsDeactivating] = useState(false);
|
||||||
|
|
||||||
const { deactivateAccount } = useUser();
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
const { setTheme } = useTheme();
|
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setIsDeactivating(false);
|
setIsDeactivating(false);
|
||||||
onClose();
|
onClose();
|
||||||
@ -41,8 +36,7 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
|
|||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Account deactivated successfully.",
|
message: "Account deactivated successfully.",
|
||||||
});
|
});
|
||||||
mutate("CURRENT_USER_DETAILS", null);
|
signOut();
|
||||||
setTheme("system");
|
|
||||||
router.push("/");
|
router.push("/");
|
||||||
handleClose();
|
handleClose();
|
||||||
})
|
})
|
||||||
|
@ -2,7 +2,7 @@ import { ReactElement, createContext } from "react";
|
|||||||
// mobx store
|
// mobx store
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
|
|
||||||
let rootStore = new RootStore();
|
export let rootStore = new RootStore();
|
||||||
|
|
||||||
export const StoreContext = createContext<RootStore>(rootStore);
|
export const StoreContext = createContext<RootStore>(rootStore);
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import axios, { AxiosInstance } from "axios";
|
import axios, { AxiosInstance } from "axios";
|
||||||
|
// store
|
||||||
|
import { rootStore } from "@/lib/store-context";
|
||||||
|
|
||||||
export abstract class APIService {
|
export abstract class APIService {
|
||||||
protected baseURL: string;
|
protected baseURL: string;
|
||||||
@ -19,7 +21,11 @@ export abstract class APIService {
|
|||||||
this.axiosInstance.interceptors.response.use(
|
this.axiosInstance.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(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);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -36,6 +36,7 @@ export interface IUserStore {
|
|||||||
updateCurrentUser: (data: Partial<IUser>) => Promise<IUser | undefined>;
|
updateCurrentUser: (data: Partial<IUser>) => Promise<IUser | undefined>;
|
||||||
handleSetPassword: (csrfToken: string, data: { password: string }) => Promise<IUser | undefined>;
|
handleSetPassword: (csrfToken: string, data: { password: string }) => Promise<IUser | undefined>;
|
||||||
deactivateAccount: () => Promise<void>;
|
deactivateAccount: () => Promise<void>;
|
||||||
|
reset: () => void;
|
||||||
signOut: () => Promise<void>;
|
signOut: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +80,7 @@ export class UserStore implements IUserStore {
|
|||||||
updateCurrentUser: action,
|
updateCurrentUser: action,
|
||||||
handleSetPassword: action,
|
handleSetPassword: action,
|
||||||
deactivateAccount: action,
|
deactivateAccount: action,
|
||||||
|
reset: action,
|
||||||
signOut: action,
|
signOut: action,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -191,6 +193,22 @@ export class UserStore implements IUserStore {
|
|||||||
this.store.resetOnSignOut();
|
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
|
* @description signs out the current user
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -2658,7 +2658,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.5.tgz#1e78a3ac2428e6d7e6c05c1665c242023a4601d8"
|
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.5.tgz#1e78a3ac2428e6d7e6c05c1665c242023a4601d8"
|
||||||
integrity sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw==
|
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"
|
version "4.17.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.1.tgz#0fabfcf2f2127ef73b119d98452bd317c4a17eb8"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.1.tgz#0fabfcf2f2127ef73b119d98452bd317c4a17eb8"
|
||||||
integrity sha512-X+2qazGS3jxLAIz5JDXDzglAF3KpijdhFxlf/V1+hEsOUc+HnWi81L/uv/EvGuV90WY+7mPGFCUDGfQC3Gj95Q==
|
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"
|
picocolors "^1.0.0"
|
||||||
source-map-js "^1.2.0"
|
source-map-js "^1.2.0"
|
||||||
|
|
||||||
posthog-js@^1.105.0:
|
posthog-js@^1.131.3:
|
||||||
version "1.131.2"
|
version "1.131.3"
|
||||||
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.131.2.tgz#c82b16a4074f773eaf41df47187fb88cc5aef28c"
|
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.131.3.tgz#bd3e6123dc715f089825a92d3ec62480b7ec0a76"
|
||||||
integrity sha512-un5c5CbDhJ1LRBDgy4I1D5a1++P8/mNl4CS9C5A1z95qIF7iY8OuA6XPW7sIA6tKSdda4PGwfa2Gmfz1nvnywQ==
|
integrity sha512-ds/TADDS+rT/WgUyeW4cJ+X+fX+O1KdkOyssNI/tP90PrFf0IJsck5B42YOLhfz87U2vgTyBaKHkdlMgWuOFog==
|
||||||
dependencies:
|
dependencies:
|
||||||
fflate "^0.4.8"
|
fflate "^0.4.8"
|
||||||
preact "^10.19.3"
|
preact "^10.19.3"
|
||||||
|
Loading…
Reference in New Issue
Block a user