diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx
index e0038e2ae..db31c4c17 100644
--- a/src/frontend/src/App.tsx
+++ b/src/frontend/src/App.tsx
@@ -1,7 +1,6 @@
import { useContext, useEffect, useState } from "react";
import { Cookies } from "react-cookie";
import { ErrorBoundary } from "react-error-boundary";
-import { useNavigate } from "react-router-dom";
import "reactflow/dist/style.css";
import "./App.css";
import AlertDisplayArea from "./alerts/displayArea";
@@ -11,10 +10,14 @@ import LoadingComponent from "./components/loadingComponent";
import {
FETCH_ERROR_DESCRIPION,
FETCH_ERROR_MESSAGE,
- LANGFLOW_AUTO_LOGIN_OPTION,
+ LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS,
+ LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV,
} from "./constants/constants";
import { AuthContext } from "./contexts/authContext";
-import { useAutoLogin } from "./controllers/API/queries/auth";
+import {
+ useAutoLogin,
+ useRefreshAccessToken,
+} from "./controllers/API/queries/auth";
import { useGetHealthQuery } from "./controllers/API/queries/health";
import { useGetGlobalVariables } from "./controllers/API/queries/variables";
import { useGetVersionQuery } from "./controllers/API/queries/version";
@@ -31,21 +34,27 @@ import { useFolderStore } from "./stores/foldersStore";
export default function App() {
useTrackLastVisitedPath();
const isLoading = useFlowsManagerStore((state) => state.isLoading);
- const { login, setUserData, getUser, logout } = useContext(AuthContext);
+ const { login, setUserData, getUser } = useContext(AuthContext);
const setAutoLogin = useAuthStore((state) => state.setAutoLogin);
const setLoading = useAlertStore((state) => state.setLoading);
const refreshStars = useDarkStore((state) => state.refreshStars);
const dark = useDarkStore((state) => state.dark);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
+ const cookies = new Cookies();
+ const logout = useAuthStore((state) => state.logout);
+
+ const refreshToken = cookies.get("refresh_token");
const { mutate: mutateAutoLogin } = useAutoLogin();
useGetVersionQuery();
- const cookies = new Cookies();
const isLoadingFolders = useFolderStore((state) => state.isLoadingFolders);
const { mutate: mutateGetGlobalVariables } = useGetGlobalVariables();
+ const { mutate: mutateRefresh } = useRefreshAccessToken();
+
+ const isLoginPage = location.pathname.includes("login");
const {
data: healthData,
@@ -63,8 +72,6 @@ export default function App() {
}, [dark]);
useEffect(() => {
- const isLoginPage = location.pathname.includes("login");
-
mutateAutoLogin(undefined, {
onSuccess: async (user) => {
if (user && user["access_token"]) {
@@ -74,31 +81,45 @@ export default function App() {
setUserData(user);
setAutoLogin(true);
fetchAllData();
+ // mutateRefresh({ refresh_token: refreshToken });
}
},
onError: (error) => {
if (error.name !== "CanceledError") {
setAutoLogin(false);
- if (
- cookies.get(LANGFLOW_AUTO_LOGIN_OPTION) === "auto" &&
- isAuthenticated
- ) {
- logout();
- return;
- }
-
- if (isAuthenticated && !isLoginPage) {
- getUser();
- fetchAllData();
- } else {
- setLoading(false);
- useFlowsManagerStore.setState({ isLoading: false });
+ if (!isLoginPage) {
+ if (!isAuthenticated) {
+ setLoading(false);
+ useFlowsManagerStore.setState({ isLoading: false });
+ logout();
+ } else {
+ mutateRefresh({ refresh_token: refreshToken });
+ fetchAllData();
+ getUser();
+ }
}
}
},
});
}, []);
+ useEffect(() => {
+ const envRefreshTime = LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV;
+ const automaticRefreshTime = LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS;
+
+ const accessTokenTimer = isNaN(envRefreshTime)
+ ? automaticRefreshTime
+ : envRefreshTime;
+
+ const intervalId = setInterval(() => {
+ if (isAuthenticated && !isLoginPage) {
+ mutateRefresh({ refresh_token: refreshToken });
+ }
+ }, accessTokenTimer * 1000);
+
+ return () => clearInterval(intervalId);
+ }, [isLoginPage]);
+
const fetchAllData = async () => {
setTimeout(async () => {
await Promise.all([refreshStars(), fetchData()]);
diff --git a/src/frontend/src/components/authAdminGuard/index.tsx b/src/frontend/src/components/authAdminGuard/index.tsx
index a08e1f4a9..e2966c873 100644
--- a/src/frontend/src/components/authAdminGuard/index.tsx
+++ b/src/frontend/src/components/authAdminGuard/index.tsx
@@ -4,11 +4,14 @@ import { Navigate } from "react-router-dom";
import { AuthContext } from "../../contexts/authContext";
export const ProtectedAdminRoute = ({ children }) => {
- const { logout, userData } = useContext(AuthContext);
+ const { userData } = useContext(AuthContext);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const autoLogin = useAuthStore((state) => state.autoLogin);
const isAdmin = useAuthStore((state) => state.isAdmin);
- if (!isAuthenticated) {
+ const isLoginPage = location.pathname.includes("login");
+ const logout = useAuthStore((state) => state.logout);
+
+ if (!isAuthenticated && !isLoginPage) {
logout();
} else if ((userData && !isAdmin) || autoLogin) {
return ;
diff --git a/src/frontend/src/components/authGuard/index.tsx b/src/frontend/src/components/authGuard/index.tsx
index 8bfacbd6c..ea3e6f12a 100644
--- a/src/frontend/src/components/authGuard/index.tsx
+++ b/src/frontend/src/components/authGuard/index.tsx
@@ -1,14 +1,13 @@
import { LANGFLOW_AUTO_LOGIN_OPTION } from "@/constants/constants";
import useAuthStore from "@/stores/authStore";
-import { useContext } from "react";
-import { AuthContext } from "../../contexts/authContext";
export const ProtectedRoute = ({ children }) => {
- const { logout } = useContext(AuthContext);
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const hasToken = !!localStorage.getItem(LANGFLOW_AUTO_LOGIN_OPTION);
+ const isLoginPage = location.pathname.includes("login");
+ const logout = useAuthStore((state) => state.logout);
- if (!isAuthenticated && hasToken) {
+ if (!isAuthenticated && hasToken && !isLoginPage) {
logout();
} else {
return children;
diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts
index a4d74462c..7b8663ed9 100644
--- a/src/frontend/src/constants/constants.ts
+++ b/src/frontend/src/constants/constants.ts
@@ -874,3 +874,8 @@ export const TABS_ORDER = [
export const LANGFLOW_ACCESS_TOKEN = "access_token_lf";
export const LANGFLOW_API_TOKEN = "apikey_tkn_lflw";
export const LANGFLOW_AUTO_LOGIN_OPTION = "auto_login_lf";
+
+export const LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS = 60 * 60 - 60 * 60 * 0.1;
+export const LANGFLOW_ACCESS_TOKEN_EXPIRE_SECONDS_ENV =
+ Number(process.env.ACCESS_TOKEN_EXPIRE_SECONDS) -
+ Number(process.env.ACCESS_TOKEN_EXPIRE_SECONDS) * 0.1;
diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx
index 735b912bb..52beddf9b 100644
--- a/src/frontend/src/contexts/authContext.tsx
+++ b/src/frontend/src/contexts/authContext.tsx
@@ -18,7 +18,6 @@ import { AuthContextType } from "../types/contexts/auth";
const initialValue: AuthContextType = {
accessToken: null,
login: () => {},
- logout: () => new Promise(() => {}),
userData: null,
setUserData: () => {},
authenticationErrorCount: 0,
@@ -90,29 +89,6 @@ export function AuthProvider({ children }): React.ReactElement {
getUser();
}
- async function logout() {
- if (autoLogin) {
- return;
- }
- try {
- await requestLogout();
- cookies.remove(LANGFLOW_API_TOKEN, { path: "/" });
- cookies.remove(LANGFLOW_AUTO_LOGIN_OPTION, { path: "/" });
- setIsAdmin(false);
- setUserData(null);
- setAccessToken(null);
- setIsAuthenticated(false);
- setIsAuthenticated(false);
- setAllFlows([]);
- setSelectedFolder(null);
- navigate("/login");
- setIsLoading(false);
- } catch (error) {
- console.error(error);
- throw error;
- }
- }
-
function storeApiKey(apikey: string) {
setApiKey(apikey);
}
@@ -123,7 +99,6 @@ export function AuthProvider({ children }): React.ReactElement {
value={{
accessToken,
login,
- logout,
setUserData,
userData,
authenticationErrorCount: 0,
diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx
index 66b7334ae..1a4156a1b 100644
--- a/src/frontend/src/controllers/API/api.tsx
+++ b/src/frontend/src/controllers/API/api.tsx
@@ -25,6 +25,7 @@ function ApiInterceptor() {
const { mutate: mutationLogout } = useLogout();
const { mutate: mutationRenewAccessToken } = useRefreshAccessToken();
const logout = useAuthStore((state) => state.logout);
+ const isLoginPage = location.pathname.includes("login");
useEffect(() => {
const interceptor = api.interceptors.response.use(
@@ -122,6 +123,8 @@ function ApiInterceptor() {
}, [accessToken, setErrorData]);
function checkErrorCount() {
+ if (isLoginPage) return;
+
authenticationErrorCount = authenticationErrorCount + 1;
if (authenticationErrorCount > 3) {
@@ -141,7 +144,7 @@ function ApiInterceptor() {
}
async function tryToRenewAccessToken(error: AxiosError) {
- if (window.location.pathname.includes("/login")) return;
+ if (isLoginPage) return;
mutationRenewAccessToken(
{},
{
diff --git a/src/frontend/src/controllers/API/queries/auth/use-post-logout.ts b/src/frontend/src/controllers/API/queries/auth/use-post-logout.ts
index df0005f57..80aa0c15a 100644
--- a/src/frontend/src/controllers/API/queries/auth/use-post-logout.ts
+++ b/src/frontend/src/controllers/API/queries/auth/use-post-logout.ts
@@ -1,11 +1,6 @@
import useAuthStore from "@/stores/authStore";
-import {
- changeUser,
- resetPasswordType,
- useMutationFunctionType,
-} from "@/types/api";
-import { UseMutationResult } from "@tanstack/react-query";
-import { useNavigate } from "react-router-dom";
+import { useMutationFunctionType } from "@/types/api";
+
import { api } from "../../api";
import { getURL } from "../../helpers/constants";
import { UseRequestProcessor } from "../../services/request-processor";
@@ -14,7 +9,6 @@ export const useLogout: useMutationFunctionType = (
options?,
) => {
const { mutate } = UseRequestProcessor();
- const navigate = useNavigate();
const logout = useAuthStore((state) => state.logout);
async function logoutUser(): Promise {
@@ -29,7 +23,6 @@ export const useLogout: useMutationFunctionType = (
const mutation = mutate(["useLogout"], logoutUser, {
onSuccess: () => {
logout();
- navigate("/login");
},
onError: (error) => {
console.error(error);
diff --git a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx
index e0119ca6a..442126e3c 100644
--- a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx
+++ b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx
@@ -1,5 +1,7 @@
import { useLoginUser } from "@/controllers/API/queries/auth";
import { useGetGlobalVariables } from "@/controllers/API/queries/variables";
+import useFlowsManagerStore from "@/stores/flowsManagerStore";
+import { useFolderStore } from "@/stores/foldersStore";
import { useContext, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Button } from "../../../components/ui/button";
@@ -24,6 +26,9 @@ export default function LoginAdminPage() {
const { mutate: mutateGetGlobalVariables } = useGetGlobalVariables();
+ const setAllFlows = useFlowsManagerStore((state) => state.setAllFlows);
+ const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder);
+
const { password, username } = inputState;
const setErrorData = useAlertStore((state) => state.setErrorData);
function handleInput({
@@ -42,6 +47,9 @@ export default function LoginAdminPage() {
mutate(user, {
onSuccess: (res) => {
+ setAllFlows([]);
+ setSelectedFolder(null);
+
setLoading(true);
login(res.access_token, "login");
diff --git a/src/frontend/src/pages/LoginPage/index.tsx b/src/frontend/src/pages/LoginPage/index.tsx
index 7c8fece04..ac526da2b 100644
--- a/src/frontend/src/pages/LoginPage/index.tsx
+++ b/src/frontend/src/pages/LoginPage/index.tsx
@@ -1,4 +1,5 @@
import { useLoginUser } from "@/controllers/API/queries/auth";
+import { useFolderStore } from "@/stores/foldersStore";
import * as Form from "@radix-ui/react-form";
import { useContext, useState } from "react";
import { Link, useNavigate } from "react-router-dom";
@@ -25,6 +26,8 @@ export default function LoginPage(): JSX.Element {
const navigate = useNavigate();
const setErrorData = useAlertStore((state) => state.setErrorData);
const setLoading = useFlowsManagerStore((state) => state.setIsLoading);
+ const setAllFlows = useFlowsManagerStore((state) => state.setAllFlows);
+ const setSelectedFolder = useFolderStore((state) => state.setSelectedFolder);
function handleInput({
target: { name, value },
@@ -42,7 +45,9 @@ export default function LoginPage(): JSX.Element {
mutate(user, {
onSuccess: (data) => {
- console.log("admin page");
+ setAllFlows([]);
+ setSelectedFolder(null);
+
setLoading(true);
login(data.access_token, "login");
navigate("/admin/");
diff --git a/src/frontend/src/stores/authStore.ts b/src/frontend/src/stores/authStore.ts
index 1d53d8cdc..4dcd917c1 100644
--- a/src/frontend/src/stores/authStore.ts
+++ b/src/frontend/src/stores/authStore.ts
@@ -26,9 +26,9 @@ const useAuthStore = create((set, get) => ({
set({ authenticationErrorCount }),
logout: async () => {
- const setAllFlows = useFlowsManagerStore.getState().setAllFlows;
- const setSelectedFolder = useFolderStore.getState().setSelectedFolder;
- cookies.remove("apikey_tkn_lflw", { path: "/" });
+ get().setIsAuthenticated(false);
+ get().setIsAdmin(false);
+
set({
isAdmin: false,
userData: null,
@@ -37,8 +37,8 @@ const useAuthStore = create((set, get) => ({
autoLogin: false,
apiKey: null,
});
- setAllFlows([]);
- setSelectedFolder(null);
+
+ window.location.href = "/login";
},
// getUser: () => {
// const setLoading = useAlertStore.getState().setLoading;
diff --git a/src/frontend/src/types/contexts/auth.ts b/src/frontend/src/types/contexts/auth.ts
index 32dbef825..8bbaac0aa 100644
--- a/src/frontend/src/types/contexts/auth.ts
+++ b/src/frontend/src/types/contexts/auth.ts
@@ -3,7 +3,6 @@ import { Users } from "../api";
export type AuthContextType = {
accessToken: string | null;
login: (accessToken: string, autoLogin: string) => void;
- logout: () => Promise;
userData: Users | null;
setUserData: (userData: Users | null) => void;
authenticationErrorCount: number;
diff --git a/src/frontend/vite.config.mts b/src/frontend/vite.config.mts
index c52b05777..24e4c1795 100644
--- a/src/frontend/vite.config.mts
+++ b/src/frontend/vite.config.mts
@@ -32,6 +32,10 @@ export default defineConfig(({ mode }) => {
},
define: {
"process.env.BACKEND_URL": JSON.stringify(process.env.BACKEND_URL),
+ "process.env.ACCESS_TOKEN_EXPIRE_SECONDS": JSON.stringify(
+ process.env.ACCESS_TOKEN_EXPIRE_SECONDS,
+ ),
+ "process.env.CI": JSON.stringify(process.env.CI),
},
plugins: [react(), svgr(), tsconfigPaths()],
server: {