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
46 changed files with 250 additions and 172 deletions
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 prefix = parseInt(process.env.NEXT_PUBLIC_DEPLOY_WITH_NGINX || "0") === 0 ? "/" : "/god-mode/";
|
||||
const prefix = "/god-mode/";
|
||||
|
||||
return (
|
||||
<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";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
// layouts
|
||||
import { DefaultLayout } from "@/layouts";
|
||||
// components
|
||||
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 router = useRouter();
|
||||
const LoginPage = () => (
|
||||
<>
|
||||
<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]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader title="Plane - God Mode" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RootPage;
|
||||
export default LoginPage;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export const HelpSection: FC = () => {
|
|||
// refs
|
||||
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 (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const NewUserPopup: React.FC = observer(() => {
|
|||
// 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 (
|
||||
|
|
|
|||
|
|
@ -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>(rootStore);
|
||||
|
||||
|
|
|
|||
|
|
@ -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<IAuthWrapper> = 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 (
|
||||
<div className="relative flex h-screen w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
|
||||
if (userStatus && userStatus?.status === EUserStatus.ERROR)
|
||||
return (
|
||||
<div className="relative flex h-screen w-screen items-center justify-center">
|
||||
Something went wrong. please try again later
|
||||
</div>
|
||||
);
|
||||
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 <></>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<TInstanceWrapper> = 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 (
|
||||
<div className="relative flex h-screen w-full items-center justify-center">
|
||||
<Spinner />
|
||||
</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")
|
||||
return (
|
||||
<DefaultLayout withoutBackground>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ export interface IUserStore {
|
|||
currentUser: IUser | undefined;
|
||||
// fetch actions
|
||||
fetchCurrentUser: () => Promise<IUser>;
|
||||
signOut: () => Promise<void>;
|
||||
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();
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue