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:
guru_sainath 2024-05-09 17:46:31 +05:30 committed by GitHub
parent 692f570258
commit 58bf056ddb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
46 changed files with 250 additions and 172 deletions

12
admin/Dockerfile.dev Normal file
View 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"]

View file

@ -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">

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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 (

View file

@ -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);

View file

@ -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 <></>;
}
}
}

View file

@ -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>

View file

@ -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);
}
);

View file

@ -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);
}
}

View file

@ -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();
};
}