From c0f586ad89504f25f1503707ff92f77a9054238e Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Fri, 11 Aug 2023 11:33:52 -0300 Subject: [PATCH 001/196] =?UTF-8?q?=F0=9F=94=A7=20fix(authGuard):=20add=20?= =?UTF-8?q?missing=20newline=20at=20the=20end=20of=20the=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🆕 feat(headerComponent): add logout functionality and redirect to login page on sign out 🔧 fix(constants): add missing URL to the list of excluded error retries 🔧 fix(authContext): add refreshToken state and update login and logout functions to handle refresh token 🆕 feat(api): add interceptor to handle access token expiration and refresh 🔧 fix(api): add missing request interceptor to add access token to every request 🔧 fix(API/index.ts): import missing LoginAuthType and LoginType from types/api/index to resolve compilation error ✨ feat(API/index.ts): add onLogin function to handle user login and authentication ✨ feat(API/index.ts): add renewAccessToken function to handle token renewal 🔧 fix(loginPage/index.tsx): import missing onLogin function from controllers/API to resolve compilation error ✨ feat(loginPage/index.tsx): add signIn function to handle user sign in and authentication 🔧 fix(routes.tsx): import ProtectedRoute component from components/authGuard to resolve compilation error ✨ feat(routes.tsx): add protected routes for HomePage, FlowPage, AdminPage, and DeleteAccountPage to enforce authentication 🔧 fix(api/index.ts): add missing grant_type, scope, client_id, and client_secret properties to LoginType to match API requirements ✨ feat(api/index.ts): add LoginAuthType to represent the authentication response from the server 🔧 fix(contexts/auth.ts): add refreshToken property to AuthContextType to store the refresh token --- .../src/components/authGuard/index.tsx | 14 +++ .../src/components/headerComponent/index.tsx | 13 ++- src/frontend/src/constants/constants.ts | 1 + src/frontend/src/contexts/authContext.tsx | 20 +++- src/frontend/src/contexts/index.tsx | 3 + src/frontend/src/controllers/API/api.tsx | 94 +++++++++++++------ src/frontend/src/controllers/API/index.ts | 42 ++++++++- src/frontend/src/pages/loginPage/index.tsx | 33 ++++++- src/frontend/src/routes.tsx | 46 ++++++++- src/frontend/src/types/api/index.ts | 15 +++ src/frontend/src/types/contexts/auth.ts | 1 + 11 files changed, 240 insertions(+), 42 deletions(-) create mode 100644 src/frontend/src/components/authGuard/index.tsx diff --git a/src/frontend/src/components/authGuard/index.tsx b/src/frontend/src/components/authGuard/index.tsx new file mode 100644 index 000000000..25cc804ee --- /dev/null +++ b/src/frontend/src/components/authGuard/index.tsx @@ -0,0 +1,14 @@ +import { useContext } from "react"; +import { Navigate } from "react-router-dom"; +import { AuthContext } from "../../contexts/authContext"; + +export const ProtectedRoute = ({ children }) => { + + const { isAuthenticated } = useContext(AuthContext); + + if (!isAuthenticated) { + return ; + } + + return children; + }; \ No newline at end of file diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index 2a5fef057..296740a91 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -1,6 +1,6 @@ import { useContext, useEffect, useState } from "react"; import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa"; -import { Link, useLocation } from "react-router-dom"; +import { Link, useLocation, useNavigate } from "react-router-dom"; import AlertDropdown from "../../alerts/alertDropDown"; import { USER_PROJECTS_HEADER } from "../../constants/constants"; import { alertContext } from "../../contexts/alertContext"; @@ -11,12 +11,15 @@ import IconComponent from "../genericIconComponent"; import { Button } from "../ui/button"; import { Separator } from "../ui/separator"; import MenuBar from "./components/menuBar"; +import { AuthContext } from "../../contexts/authContext"; export default function Header() { const { flows, tabId } = useContext(TabsContext); const { dark, setDark } = useContext(darkContext); const { notificationCenter } = useContext(alertContext); const location = useLocation(); + const { logout } = useContext(AuthContext); + const navigate = useNavigate(); const [stars, setStars] = useState(null); @@ -34,7 +37,13 @@ export default function Header() { ⛓️ - {flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && ( diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index 33aeb895a..8831927bb 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -507,6 +507,7 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [ "/api/v1/validate/code", "/api/v1/custom_component", "/api/v1/validate/prompt", + "http://localhost:7860/login" ]; export const CONTROL_INPUT_STATE = { password: "", diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index dc4ca52c9..823b0913a 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -1,9 +1,12 @@ import { createContext, useEffect, useState } from "react"; import { AuthContextType, userData } from "../types/contexts/auth"; +import { LoginType } from "../types/api"; +import { api } from "../controllers/API/api"; const initialValue: AuthContextType = { isAuthenticated: false, accessToken: null, + refreshToken: null, login: () => {}, logout: () => {}, refreshAccessToken: () => Promise.resolve(), @@ -11,10 +14,12 @@ const initialValue: AuthContextType = { setUserData: () => {}, }; -const AuthContext = createContext(initialValue); +export const AuthContext = createContext(initialValue); export function AuthProvider({ children }): React.ReactElement { const [accessToken, setAccessToken] = useState(null); + const [refreshToken, setRefreshToken] = useState(null); + const [isAuthenticated, setIsAuthenticated] = useState(false); const [userData, setUserData] = useState(null); useEffect(() => { @@ -27,18 +32,23 @@ export function AuthProvider({ children }): React.ReactElement { function login(newAccessToken: string, refreshToken: string) { localStorage.setItem("access_token", newAccessToken); setAccessToken(newAccessToken); - // Store refreshToken if needed + + localStorage.setItem("refresh_token", refreshToken); + setRefreshToken(refreshToken); + + setIsAuthenticated(true); } function logout() { localStorage.removeItem("access_token"); - // Clear refreshToken if used + localStorage.removeItem("refresh_token"); setAccessToken(null); + setRefreshToken(null); + setIsAuthenticated(false); } async function refreshAccessToken(refreshToken: string) { try { - // Call your API to refresh the access token using the refresh token const response = await fetch("/api/refresh-token", { method: "POST", headers: { @@ -57,6 +67,7 @@ export function AuthProvider({ children }): React.ReactElement { logout(); } } + return ( // !! to convert string to boolean @@ -64,6 +75,7 @@ export function AuthProvider({ children }): React.ReactElement { value={{ isAuthenticated: !!accessToken, accessToken, + refreshToken, login, logout, refreshAccessToken, diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx index f143df708..915f8bb8e 100644 --- a/src/frontend/src/contexts/index.tsx +++ b/src/frontend/src/contexts/index.tsx @@ -8,6 +8,7 @@ import { LocationProvider } from "./locationContext"; import { TabsProvider } from "./tabsContext"; import { TypesProvider } from "./typesContext"; import { UndoRedoProvider } from "./undoRedoContext"; +import { AuthProvider } from "./authContext"; export default function ContextWrapper({ children }: { children: ReactNode }) { //element to wrap all context @@ -18,6 +19,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) { + @@ -25,6 +27,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) { + diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index 6029d135e..3637754cb 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -1,6 +1,10 @@ import axios, { AxiosError, AxiosInstance } from "axios"; import { useContext, useEffect, useRef } from "react"; import { alertContext } from "../../contexts/alertContext"; +import { AuthContext } from "../../contexts/authContext"; +import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants"; +import { renewAccessToken } from "."; +import { useNavigate } from "react-router-dom"; // Create a new Axios instance const api: AxiosInstance = axios.create({ @@ -10,44 +14,80 @@ const api: AxiosInstance = axios.create({ function ApiInterceptor() { const retryCounts = useRef([]); const { setErrorData } = useContext(alertContext); + const { accessToken, refreshAccessToken, login, logout } = useContext(AuthContext); + const navigate = useNavigate(); useEffect(() => { const interceptor = api.interceptors.response.use( (response) => response, async (error: AxiosError) => { - // if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) { - // return Promise.reject(error); - // } - // let retryCount = 0; - // while (retryCount < 4) { - // await sleep(5000); // Sleep for 5 seconds - // retryCount++; - // try { - // const response = await axios.request(error.config); - // return response; - // } catch (error) { - // if (retryCount === 3) { - // setErrorData({ - // title: "There was an error on web connection, please: ", - // list: [ - // "Refresh the page", - // "Use a new flow tab", - // "Check if the backend is up", - // "Endpoint: " + error.config?.url, - // ], - // }); - // return Promise.reject(error); - // } - // } - // } + if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) { + return Promise.reject(error); + } + + if(error.response?.status === 401){ + const refreshToken = localStorage.getItem("refresh_token"); + if (refreshToken) { + const res = await renewAccessToken(refreshToken); + login(res.access_token, res.refresh_token) + try { + const response = await axios.request(error.config); + return response; + } catch (error) { + if(error.response?.status === 401){ + logout(); + navigate("/login"); + } + } + } + } + + else{ + let retryCount = 0; + while (retryCount < 4) { + await sleep(5000); // Sleep for 5 seconds + retryCount++; + try { + const response = await axios.request(error.config); + return response; + } catch (error) { + if (retryCount === 3) { + setErrorData({ + title: "There was an error on web connection, please: ", + list: [ + "Refresh the page", + "Use a new flow tab", + "Check if the backend is up", + "Endpoint: " + error.config?.url, + ], + }); + return Promise.reject(error); + } + } + } + } + } + ); + + // Request interceptor to add access token to every request + const requestInterceptor = api.interceptors.request.use( + (config) => { + if (accessToken) { + config.headers["Authorization"] = `Bearer ${accessToken}`; + } + return config; + }, + (error) => { + return Promise.reject(error); } ); return () => { - // Clean up the interceptor when the component unmounts + // Clean up the interceptors when the component unmounts api.interceptors.response.eject(interceptor); + api.interceptors.request.eject(requestInterceptor); }; - }, [retryCounts]); + }, [accessToken, setErrorData]); return null; } diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 9491f8973..ac2e92696 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -1,7 +1,7 @@ import { AxiosResponse } from "axios"; import { ReactFlowJsonObject } from "reactflow"; import { api } from "../../controllers/API/api"; -import { APIObjectType, sendAllProps } from "../../types/api/index"; +import { APIObjectType, LoginAuthType, LoginType, sendAllProps } from "../../types/api/index"; import { FlowStyleType, FlowType } from "../../types/flow"; import { APIClassType, @@ -346,3 +346,43 @@ export async function postCustomComponent( ): Promise> { return await api.post(`/api/v1/custom_component`, { code }); } + +export async function onLogin( + user: LoginType +) { + try { + const response = await api.post( + "http://localhost:7860/login", + new URLSearchParams({ + username: user.username, + password: user.password, + }).toString(), + { + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + } + ); + + if (response.status === 200) { + const data = response.data; + return data; + } + } catch (error) { + console.log("Error:", error); + throw error; + } +} + +export async function renewAccessToken( + token: string +): Promise { + try { + return await api.post(`http://localhost:7860/refresh?token=${token}`); + } catch (error) { + console.log("Error:", error); + throw error; + } +} + + diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/loginPage/index.tsx index fa953f6ca..2ded4539b 100644 --- a/src/frontend/src/pages/loginPage/index.tsx +++ b/src/frontend/src/pages/loginPage/index.tsx @@ -1,6 +1,6 @@ import * as Form from "@radix-ui/react-form"; -import { useState } from "react"; -import { Link } from "react-router-dom"; +import { useContext, useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; import IconComponent from "../../components/genericIconComponent"; import InputComponent from "../../components/inputComponent"; import { Button } from "../../components/ui/button"; @@ -10,18 +10,43 @@ import { inputHandlerEventType, loginInputStateType, } from "../../types/components"; +import { onLogin } from "../../controllers/API"; +import { LoginType } from "../../types/api"; +import { AuthContext } from "../../contexts/authContext"; export default function LoginPage(): JSX.Element { const [inputState, setInputState] = useState(CONTROL_LOGIN_STATE); const { password, username } = inputState; + const { login } = useContext(AuthContext); + const navigate = useNavigate(); function handleInput({ target: { name, value }, }: inputHandlerEventType): void { setInputState((prev) => ({ ...prev, [name]: value })); } + + function signIn(){ + + const user: LoginType = { + username: username, + password: password + }; + + try{ + onLogin( + user + ).then((user) => { + login(user.access_token, user.refresh_token); + navigate("/"); + }); + } + catch(error){ + } + } + return ( { @@ -107,7 +132,9 @@ export default function LoginPage(): JSX.Element {
- +
diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx index 8466f9b57..43a5bc32e 100644 --- a/src/frontend/src/routes.tsx +++ b/src/frontend/src/routes.tsx @@ -1,4 +1,5 @@ import { Route, Routes } from "react-router-dom"; +import { ProtectedRoute } from "./components/authGuard"; import AdminPage from "./pages/AdminPage"; import LoginAdminPage from "./pages/AdminPage/LoginPage"; import CommunityPage from "./pages/CommunityPage"; @@ -11,21 +12,56 @@ import SignUp from "./pages/signUpPage"; const Router = () => { return ( - } /> + + + + } + /> } /> - } /> + + + + } + /> - } /> + + + + } + /> } /> } /> } /> - } /> + + + + } + /> - }> + + + + } + > ); diff --git a/src/frontend/src/types/api/index.ts b/src/frontend/src/types/api/index.ts index 3fa848326..b355a71e4 100644 --- a/src/frontend/src/types/api/index.ts +++ b/src/frontend/src/types/api/index.ts @@ -62,3 +62,18 @@ export type UploadFileTypeAPI = { file_path: string; flowId: string; }; + +export type LoginType = { + grant_type?: string; + username: string; + password: string; + scrope?: string; + client_id?: string; + client_secret?: string; +}; + +export type LoginAuthType = { + access_token: string; + refresh_token: string; + token_type?: string; +}; diff --git a/src/frontend/src/types/contexts/auth.ts b/src/frontend/src/types/contexts/auth.ts index af037ecae..1ac47d6c2 100644 --- a/src/frontend/src/types/contexts/auth.ts +++ b/src/frontend/src/types/contexts/auth.ts @@ -1,6 +1,7 @@ export type AuthContextType = { isAuthenticated: boolean; accessToken: string | null; + refreshToken: string | null; login: (accessToken: string, refreshToken: string) => void; logout: () => void; refreshAccessToken: (refreshToken: string) => Promise; From f030234438c9930bbb30d10f854a8347dab207d5 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Fri, 11 Aug 2023 11:56:12 -0300 Subject: [PATCH 002/196] =?UTF-8?q?=F0=9F=90=9B=20fix(authGuard):=20add=20?= =?UTF-8?q?check=20for=20getAuthentication()=20in=20ProtectedRoute=20compo?= =?UTF-8?q?nent=20to=20prevent=20unauthorized=20access=20and=20logout=20us?= =?UTF-8?q?er=20=E2=9C=A8=20feat(authContext):=20add=20getAuthentication()?= =?UTF-8?q?=20function=20to=20check=20if=20user=20is=20authenticated=20usi?= =?UTF-8?q?ng=20cookies=20=F0=9F=94=A7=20chore(index.tsx):=20reorganize=20?= =?UTF-8?q?context=20providers=20to=20wrap=20AuthProvider=20around=20other?= =?UTF-8?q?=20providers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/authGuard/index.tsx | 6 ++-- src/frontend/src/contexts/authContext.tsx | 28 +++++++++++++++++++ src/frontend/src/contexts/index.tsx | 6 ++-- src/frontend/src/types/contexts/auth.ts | 1 + 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/src/frontend/src/components/authGuard/index.tsx b/src/frontend/src/components/authGuard/index.tsx index 25cc804ee..95d7c4131 100644 --- a/src/frontend/src/components/authGuard/index.tsx +++ b/src/frontend/src/components/authGuard/index.tsx @@ -3,10 +3,10 @@ import { Navigate } from "react-router-dom"; import { AuthContext } from "../../contexts/authContext"; export const ProtectedRoute = ({ children }) => { - - const { isAuthenticated } = useContext(AuthContext); + const { isAuthenticated, logout, getAuthentication } = useContext(AuthContext); - if (!isAuthenticated) { + if (!isAuthenticated && !getAuthentication()) { + logout(); return ; } diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index 823b0913a..dcfadee3a 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -2,6 +2,7 @@ import { createContext, useEffect, useState } from "react"; import { AuthContextType, userData } from "../types/contexts/auth"; import { LoginType } from "../types/api"; import { api } from "../controllers/API/api"; +import Cookies from 'universal-cookie'; const initialValue: AuthContextType = { isAuthenticated: false, @@ -12,6 +13,7 @@ const initialValue: AuthContextType = { refreshAccessToken: () => Promise.resolve(), userData: null, setUserData: () => {}, + getAuthentication: () => false, }; export const AuthContext = createContext(initialValue); @@ -21,6 +23,7 @@ export function AuthProvider({ children }): React.ReactElement { const [refreshToken, setRefreshToken] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); const [userData, setUserData] = useState(null); + const cookies = new Cookies(); useEffect(() => { const storedAccessToken = localStorage.getItem("access_token"); @@ -29,7 +32,26 @@ export function AuthProvider({ children }): React.ReactElement { } }, []); + + function getAuthentication(){ + const storedRefreshToken = cookies.get('refresh_token'); + const storedAccess = cookies.get('refresh_token'); + if (storedAccess && storedRefreshToken) { + setAccessToken(storedAccess); + setRefreshToken(storedRefreshToken); + return true; + } + else{ + return false; + } + } + function login(newAccessToken: string, refreshToken: string) { + + //if we want to use cookie + cookies.set('access_token', newAccessToken, { path: '/' }); + cookies.set('refresh_token', refreshToken, { path: '/' }); + localStorage.setItem("access_token", newAccessToken); setAccessToken(newAccessToken); @@ -40,11 +62,16 @@ export function AuthProvider({ children }): React.ReactElement { } function logout() { + //if we want to use cookie + cookies.remove('access_token'); + cookies.remove('refresh_token'); + localStorage.removeItem("access_token"); localStorage.removeItem("refresh_token"); setAccessToken(null); setRefreshToken(null); setIsAuthenticated(false); + } async function refreshAccessToken(refreshToken: string) { @@ -81,6 +108,7 @@ export function AuthProvider({ children }): React.ReactElement { refreshAccessToken, setUserData, userData, + getAuthentication }} > {children} diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx index 915f8bb8e..969a04214 100644 --- a/src/frontend/src/contexts/index.tsx +++ b/src/frontend/src/contexts/index.tsx @@ -14,12 +14,13 @@ export default function ContextWrapper({ children }: { children: ReactNode }) { //element to wrap all context return ( <> + + - @@ -27,12 +28,13 @@ export default function ContextWrapper({ children }: { children: ReactNode }) { - + + ); } diff --git a/src/frontend/src/types/contexts/auth.ts b/src/frontend/src/types/contexts/auth.ts index 1ac47d6c2..7d6ccce12 100644 --- a/src/frontend/src/types/contexts/auth.ts +++ b/src/frontend/src/types/contexts/auth.ts @@ -7,6 +7,7 @@ export type AuthContextType = { refreshAccessToken: (refreshToken: string) => Promise; userData: userData | null; setUserData: (userData: userData | null) => void; + getAuthentication: () => boolean; }; export type userData = { From ed7723a8e40deb9e57bc730647ffd065bc0aa60e Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Fri, 11 Aug 2023 20:13:30 -0300 Subject: [PATCH 003/196] adjusting retry on interceptor --- src/frontend/src/contexts/authContext.tsx | 1 - src/frontend/src/controllers/API/api.tsx | 14 +++++++++----- src/frontend/src/controllers/API/index.ts | 3 +-- src/frontend/src/pages/loginPage/index.tsx | 13 +++++++++---- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index dcfadee3a..f7f16111e 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -47,7 +47,6 @@ export function AuthProvider({ children }): React.ReactElement { } function login(newAccessToken: string, refreshToken: string) { - //if we want to use cookie cookies.set('access_token', newAccessToken, { path: '/' }); cookies.set('refresh_token', refreshToken, { path: '/' }); diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index 3637754cb..5a7c866dd 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -1,5 +1,5 @@ import axios, { AxiosError, AxiosInstance } from "axios"; -import { useContext, useEffect, useRef } from "react"; +import { useContext, useEffect, useRef, useState } from "react"; import { alertContext } from "../../contexts/alertContext"; import { AuthContext } from "../../contexts/authContext"; import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants"; @@ -29,11 +29,15 @@ function ApiInterceptor() { const refreshToken = localStorage.getItem("refresh_token"); if (refreshToken) { const res = await renewAccessToken(refreshToken); - login(res.access_token, res.refresh_token) + login(res.data.access_token, res.data.refresh_token); try { - const response = await axios.request(error.config); - return response; - } catch (error) { + const accessToken = localStorage.getItem("access_token"); + delete error.config.headers["Authorization"]; + error.config.headers["Authorization"] = `Bearer ${accessToken}`; + const response = await axios.request(error.config); + return response; + } + catch (error) { if(error.response?.status === 401){ logout(); navigate("/login"); diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index ac2e92696..0fc57d357 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -369,14 +369,13 @@ export async function onLogin( return data; } } catch (error) { - console.log("Error:", error); throw error; } } export async function renewAccessToken( token: string -): Promise { +){ try { return await api.post(`http://localhost:7860/refresh?token=${token}`); } catch (error) { diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/loginPage/index.tsx index 2ded4539b..de9f9342f 100644 --- a/src/frontend/src/pages/loginPage/index.tsx +++ b/src/frontend/src/pages/loginPage/index.tsx @@ -13,6 +13,8 @@ import { import { onLogin } from "../../controllers/API"; import { LoginType } from "../../types/api"; import { AuthContext } from "../../contexts/authContext"; +import { alertContext } from "../../contexts/alertContext"; +import { error } from "console"; export default function LoginPage(): JSX.Element { const [inputState, setInputState] = @@ -21,6 +23,7 @@ export default function LoginPage(): JSX.Element { const { password, username } = inputState; const { login } = useContext(AuthContext); const navigate = useNavigate(); + const { setErrorData } = useContext(alertContext); function handleInput({ target: { name, value }, @@ -35,16 +38,18 @@ export default function LoginPage(): JSX.Element { password: password }; - try{ onLogin( user ).then((user) => { login(user.access_token, user.refresh_token); navigate("/"); + }).catch(error => { + setErrorData({ + title: "Error signing in", + list: [error['response']['data']['detail']], + }) }); - } - catch(error){ - } + } return ( From 63df642aa5b3402d6ab49439fdd7a7ee6a45e352 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Fri, 11 Aug 2023 20:23:10 -0300 Subject: [PATCH 004/196] adding community to route guard --- src/frontend/src/controllers/API/api.tsx | 1 - src/frontend/src/routes.tsx | 9 ++++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index 5a7c866dd..4a5f76173 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -12,7 +12,6 @@ const api: AxiosInstance = axios.create({ }); function ApiInterceptor() { - const retryCounts = useRef([]); const { setErrorData } = useContext(alertContext); const { accessToken, refreshAccessToken, login, logout } = useContext(AuthContext); const navigate = useNavigate(); diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx index 43a5bc32e..db0c47770 100644 --- a/src/frontend/src/routes.tsx +++ b/src/frontend/src/routes.tsx @@ -20,7 +20,14 @@ const Router = () => { } /> - } /> + + + + } + /> Date: Fri, 11 Aug 2023 20:37:00 -0300 Subject: [PATCH 005/196] removing url from bearer --- src/frontend/src/constants/constants.ts | 1 + src/frontend/src/controllers/API/api.tsx | 11 ++++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index 8831927bb..27584fd32 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -509,6 +509,7 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [ "/api/v1/validate/prompt", "http://localhost:7860/login" ]; + export const CONTROL_INPUT_STATE = { password: "", cnfPassword: "", diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index 4a5f76173..8c71752b2 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -13,7 +13,7 @@ const api: AxiosInstance = axios.create({ function ApiInterceptor() { const { setErrorData } = useContext(alertContext); - const { accessToken, refreshAccessToken, login, logout } = useContext(AuthContext); + const { accessToken, login, logout } = useContext(AuthContext); const navigate = useNavigate(); useEffect(() => { @@ -75,9 +75,18 @@ function ApiInterceptor() { // Request interceptor to add access token to every request const requestInterceptor = api.interceptors.request.use( (config) => { + if (accessToken) { config.headers["Authorization"] = `Bearer ${accessToken}`; } + + if( + config?.url?.includes("https://raw.githubusercontent.com/logspace-ai/langflow_examples/main/examples") || + config?.url?.includes("https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples")) + { + delete config.headers["Authorization"]; + } + return config; }, (error) => { From ff72640e76725efa0c52a961eca201e3f68c539e Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Sat, 12 Aug 2023 00:07:47 -0300 Subject: [PATCH 006/196] =?UTF-8?q?=F0=9F=90=9B=20fix(PaginatorComponent):?= =?UTF-8?q?=20fix=20initial=20pageIndex=20value=20to=20start=20from=200=20?= =?UTF-8?q?instead=20of=201=20for=20consistency=20=E2=9C=A8=20feat(Paginat?= =?UTF-8?q?orComponent):=20update=20rowsCount=20options=20to=20[10,=2020,?= =?UTF-8?q?=2050,=20100]=20for=20more=20flexibility=20in=20choosing=20page?= =?UTF-8?q?=20size=20=F0=9F=90=9B=20fix(PaginatorComponent):=20fix=20maxIn?= =?UTF-8?q?dex=20calculation=20to=20always=20be=20100=20for=20now,=20to=20?= =?UTF-8?q?avoid=20incorrect=20pagination=20=F0=9F=90=9B=20fix(PaginatorCo?= =?UTF-8?q?mponent):=20fix=20page=20index=20display=20to=20start=20from=20?= =?UTF-8?q?1=20instead=20of=200=20for=20better=20user=20experience=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(PaginatorComponent):=20fix=20disabled=20stat?= =?UTF-8?q?e=20of=20first=20and=20previous=20buttons=20to=20be=20disabled?= =?UTF-8?q?=20when=20index=20is=200=20=F0=9F=90=9B=20fix(PaginatorComponen?= =?UTF-8?q?t):=20fix=20disabled=20state=20of=20next=20and=20last=20buttons?= =?UTF-8?q?=20to=20be=20disabled=20when=20index=20is=20equal=20to=20maxInd?= =?UTF-8?q?ex=20=E2=9C=A8=20feat(authGuard):=20add=20new=20ProtectedLoginR?= =?UTF-8?q?oute=20component=20to=20redirect=20authenticated=20users=20to?= =?UTF-8?q?=20home=20page=20=F0=9F=90=9B=20fix(genericIconComponent):=20fi?= =?UTF-8?q?x=20icon=20size=20to=20be=20smaller=20with=20h-3=20and=20w-3=20?= =?UTF-8?q?classes=20=F0=9F=90=9B=20fix(ui/checkbox):=20fix=20checkbox=20i?= =?UTF-8?q?con=20size=20to=20be=20smaller=20with=20h-3=20and=20w-3=20class?= =?UTF-8?q?es=20=E2=9C=A8=20feat(constants):=20add=20CONTROL=5FNEW=5FUSER?= =?UTF-8?q?=20constant=20for=20new=20user=20form=20initialization=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(authContext):=20fix=20getAuthentication=20fu?= =?UTF-8?q?nction=20to=20correctly=20check=20for=20stored=20access=20and?= =?UTF-8?q?=20refresh=20tokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🐛 fix(api.tsx): remove unused imports and reorganize imports for better readability ✨ feat(api.tsx): add support for retrying failed requests up to 3 times with a 5-second delay between retries 🐛 fix(api.tsx): fix logic for excluding certain URLs from error retries ✨ feat(api.tsx): add support for adding access token to every request as a request interceptor 🐛 fix(api.tsx): fix formatting and remove unnecessary code ✨ feat(index.ts): add support for fetching users, adding a user, fetching users with pagination, deleting a user, and updating a user 🐛 fix(UserManagementModal): fix typo in username state variable name ✨ feat(UserManagementModal): add support for password visibility toggle in password and confirm password fields ✨ feat(UserManagementModal): add support for is_disabled and is_superuser checkboxes 🐛 fix(LoginPage): fix typo in username state variable name ✨ feat(LoginPage): add support for handling input changes and signing in with username and password 🔨 refactor(AdminPage/index.tsx): import Checkbox component to improve code readability and maintainability 🔨 refactor(AdminPage/index.tsx): import API functions separately to improve code readability and maintainability 🔨 refactor(AdminPage/index.tsx): import alertContext from contexts/alertContext to improve code readability and maintainability 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused variables and functions to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused imports to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve code cleanliness and performance 🔨 refactor(AdminPage/index.tsx): remove unused code and dependencies to improve 🐛 fix(AdminPage/index.tsx): update text when there are no users registered to improve clarity and user experience ✨ feat(AdminPage/index.tsx): add loading state when fetching users to provide feedback to the user 🔨 refactor(AdminPage/index.tsx): refactor filter functionality to reset filter when input value is cleared 🔨 refactor(AdminPage/index.tsx): refactor filter functionality to use the current user list instead of the filtered list 🔨 refactor(AdminPage/index.tsx): refactor handleNewUser function to pass the user object instead of index 🔨 refactor(AdminPage/index.tsx): refactor handleEditUser function to pass the user id instead of index 🔨 refactor(AdminPage/index.tsx): refactor handleDeleteUser function to pass the user object instead of index 🔨 refactor(AdminPage/index.tsx): refactor TableRow key to use index instead of user.user 🔨 refactor(AdminPage/index.tsx): refactor TableCell content to use ShadTooltip component for better user experience 🔨 refactor(AdminPage/index.tsx): refactor Checkbox components to use id and checked props for better semantics 🔨 refactor(AdminPage/index.tsx): refactor date formatting to use toISOString and split methods for better readability 🔨 refactor(AdminPage/index.tsx): refactor UserManagementModal titleHeader to use user.id instead of user.user for better clarity 🔨 refactor(AdminPage/index.tsx): refactor handleEditUser function to pass the user id instead of index 🔨 refactor(AdminPage/index.tsx): refactor handleDeleteUser function to pass the user object instead of index 🔨 refactor(AdminPage/index.tsx): refactor PaginatorComponent to use handleChangePagination function for better readability and maintainability 🐛 fix(loginPage/index.tsx): remove unused import statement for 'error' from console module ✨ feat(loginPage/index.tsx): add support for displaying error message when signing in fails 🐛 fix(routes.tsx): add ProtectedLoginRoute component to protect login, signup, and login/admin routes 🐛 fix(api/index.ts): add missing 'is_disabled' and 'is_superuser' properties to Users type 🐛 fix(components/index.ts): change value type of inputHandlerEventType to include boolean ✨ feat(components/index.ts): add UserInputType type for user input data 🐛 fix(styleUtils.ts): add missing Eye and EyeOff icons to nodeIconsLucide object --- .../components/PaginatorComponent/index.tsx | 42 +- .../src/components/authGuard/index.tsx | 19 +- .../src/components/authLoginGuard/index.tsx | 14 + .../components/genericIconComponent/index.tsx | 10 +- src/frontend/src/components/ui/checkbox.tsx | 2 +- src/frontend/src/constants/constants.ts | 8 + src/frontend/src/contexts/authContext.tsx | 10 +- src/frontend/src/controllers/API/api.tsx | 98 ++-- src/frontend/src/controllers/API/index.ts | 76 ++- .../src/modals/UserManagementModal/index.tsx | 169 ++++--- .../src/pages/AdminPage/LoginPage/index.tsx | 60 ++- src/frontend/src/pages/AdminPage/index.tsx | 475 ++++++++---------- src/frontend/src/pages/loginPage/index.tsx | 34 +- src/frontend/src/routes.tsx | 28 +- src/frontend/src/types/api/index.ts | 9 + src/frontend/src/types/components/index.ts | 9 +- src/frontend/src/utils/styleUtils.ts | 4 + 17 files changed, 609 insertions(+), 458 deletions(-) create mode 100644 src/frontend/src/components/authLoginGuard/index.tsx diff --git a/src/frontend/src/components/PaginatorComponent/index.tsx b/src/frontend/src/components/PaginatorComponent/index.tsx index 8fcd8683f..ebad18506 100644 --- a/src/frontend/src/components/PaginatorComponent/index.tsx +++ b/src/frontend/src/components/PaginatorComponent/index.tsx @@ -12,16 +12,16 @@ import { Button } from "../ui/button"; export default function PaginatorComponent({ pageSize = 10, - pageIndex = 1, - rowsCount = [10, 20, 30], + pageIndex = 0, + rowsCount = [10, 20, 50, 100], totalRowsCount = 0, paginate, }: PaginatorComponentType) { + const [size, setPageSize] = useState(pageSize); const [index, setPageIndex] = useState(pageIndex); - const [maxIndex, setMaxPageIndex] = useState( - Math.ceil(totalRowsCount / pageSize) + 100 ); return ( @@ -34,8 +34,8 @@ export default function PaginatorComponent({
- Page {index} of {maxIndex} + Page {index+1} of {maxIndex}
{ - setUserName(input.target.value); + onChange={({ target: { value } }) => { + handleInput({ target: { name: "username", value } }); + setUserName(value); }} value={username} className="primary-input" @@ -104,21 +122,38 @@ export default function UserManagementModal({ justifyContent: "space-between", }} > - + Password{" "} - * + + * + + {pwdVisible && ( + setPwdVisible(!pwdVisible)} + className="h-5 cursor-pointer" strokeWidth={1.5} /> + )} + + {!pwdVisible && ( + setPwdVisible(!pwdVisible)} + className="h-5 cursor-pointer" strokeWidth={1.5} /> + )} + { - setPassword(input.target.value); + onChange={({ target: { value } }) => { + handleInput({ target: { name: "password", value } }); + setPassword(value); }} value={password} className="primary-input" required + type={pwdVisible ? "text" : "password"} /> + Please enter a password @@ -143,9 +178,22 @@ export default function UserManagementModal({ justifyContent: "space-between", }} > - + Confirm password{" "} - * + + * + + {confirmPwdVisible && ( + setConfirmPwdVisible(!confirmPwdVisible)} + className="h-5 cursor-pointer" strokeWidth={1.5} /> + )} + + {!confirmPwdVisible && ( + setConfirmPwdVisible(!confirmPwdVisible)} + className="h-5 cursor-pointer" strokeWidth={1.5} /> + )} @@ -156,6 +204,7 @@ export default function UserManagementModal({ value={confirmPassword} className="primary-input" required + type={confirmPwdVisible ? "text" : "password"} /> @@ -164,57 +213,49 @@ export default function UserManagementModal({ +
+ +
+ + Disabled + + + { + handleInput({ target: { name: "is_disabled", value } }); + setIsDisabled(value); + }} + /> + +
+
- {/* - -
- - Email * - - - Please enter your email - - - Please provide a valid email - -
- - - -
*/} - - {/* - -
- - Date of birth{" "} - * - - - Please enter your date of birth - -
- - - -
*/} + +
+ + Superuser + + + { + handleInput({ + target: { name: "is_superuser", value }, + }); + setIsSuperUser(value); + }} + /> + +
+
+
diff --git a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx index 74cc75d07..bcc545e9b 100644 --- a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx @@ -1,12 +1,49 @@ +import { useContext, useState } from "react"; import { useNavigate } from "react-router-dom"; import { Button } from "../../../components/ui/button"; import { Input } from "../../../components/ui/input"; +import { CONTROL_LOGIN_STATE } from "../../../constants/constants"; +import { alertContext } from "../../../contexts/alertContext"; +import { AuthContext } from "../../../contexts/authContext"; +import { onLogin } from "../../../controllers/API"; +import { LoginType } from "../../../types/api"; +import { + inputHandlerEventType, + loginInputStateType, +} from "../../../types/components"; export default function LoginAdminPage() { const navigate = useNavigate(); - function loginAdmin() { - navigate("/admin/"); + const [inputState, setInputState] = + useState(CONTROL_LOGIN_STATE); + const { login } = useContext(AuthContext); + + const { password, username } = inputState; + const { setErrorData } = useContext(alertContext); + + function handleInput({ + target: { name, value }, + }: inputHandlerEventType): void { + setInputState((prev) => ({ ...prev, [name]: value })); + } + + function signIn() { + const user: LoginType = { + username: username, + password: password, + }; + onLogin(user) + .then((user) => { + login(user.access_token, user.refresh_token); + navigate("/admin/"); + }) + .catch((error) => { + setErrorData({ + title: "Error signing in", + list: [error["response"]["data"]["detail"]], + }); + }); } return ( @@ -14,11 +51,24 @@ export default function LoginAdminPage() {
⛓️ Admin - - + { + handleInput({ target: { name: "username", value } }); + }} + className="bg-background" + placeholder="Username" + /> + { + handleInput({ target: { name: "password", value } }); + }} + className="bg-background" + placeholder="Password" + />
- {userList.current.length === 0 && ( + {userList.current.length === 0 && !loadingUsers && ( <>
-

There's no users left :)

+

There's no users registered :)

)} - {userList.current.length > 0 && ( - <> -
-
- handleFilterUsers(e.target.value)} - /> - {inputValue.length > 0 && ( - - )} -
-
- { - handleNewUser(user); + <> +
+
+ handleFilterUsers(e.target.value)} + /> + {inputValue.length > 0 && ( + - -
+ Reset + + + )}
-
- - - - User - Password - - - +
+ { + handleNewUser(user); + }} + > + + +
+ + {loadingUsers && ( +
+ Loading... +
+ )} +
+
+ + + Id + Username + Disabled + Superuser + Created At + Updated At + + + + {!loadingUsers && ( {filterUserList.map((user, index) => ( - + - {user.user} + + + {user.id} + + - {user.password} + + + {user.username} + + + + + + + + + + + { + new Date(user.create_at) + .toISOString() + .split("T")[0] + } + + + { + new Date(user.updated_at) + .toISOString() + .split("T")[0] + }
{ - handleEditUser(index, user); + onConfirm={(index, editUser) => { + handleEditUser(user.id, editUser); }} > @@ -365,7 +307,7 @@ export default function AdminPage() { data={user} index={index} onConfirm={(index, user) => { - handleDeleteUser(index); + handleDeleteUser(user); }} > @@ -380,18 +322,19 @@ export default function AdminPage() { ))} -
-
- { - handleChangePagination(pageSize, pageIndex); - }} - > - - )} + )} + +
+ + { + handleChangePagination(pageSize, pageIndex); + }} + > +
diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/loginPage/index.tsx index de9f9342f..aaa07096a 100644 --- a/src/frontend/src/pages/loginPage/index.tsx +++ b/src/frontend/src/pages/loginPage/index.tsx @@ -6,15 +6,14 @@ import InputComponent from "../../components/inputComponent"; import { Button } from "../../components/ui/button"; import { Input } from "../../components/ui/input"; import { CONTROL_LOGIN_STATE } from "../../constants/constants"; +import { alertContext } from "../../contexts/alertContext"; +import { AuthContext } from "../../contexts/authContext"; +import { onLogin } from "../../controllers/API"; +import { LoginType } from "../../types/api"; import { inputHandlerEventType, loginInputStateType, } from "../../types/components"; -import { onLogin } from "../../controllers/API"; -import { LoginType } from "../../types/api"; -import { AuthContext } from "../../contexts/authContext"; -import { alertContext } from "../../contexts/alertContext"; -import { error } from "console"; export default function LoginPage(): JSX.Element { const [inputState, setInputState] = @@ -31,25 +30,22 @@ export default function LoginPage(): JSX.Element { setInputState((prev) => ({ ...prev, [name]: value })); } - function signIn(){ - + function signIn() { const user: LoginType = { username: username, - password: password + password: password, }; - - onLogin( - user - ).then((user) => { + onLogin(user) + .then((user) => { login(user.access_token, user.refresh_token); navigate("/"); - }).catch(error => { + }) + .catch((error) => { setErrorData({ title: "Error signing in", - list: [error['response']['data']['detail']], - }) + list: [error["response"]["data"]["detail"]], + }); }); - } return ( @@ -137,9 +133,9 @@ export default function LoginPage(): JSX.Element {
- +
diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx index db0c47770..4875478f9 100644 --- a/src/frontend/src/routes.tsx +++ b/src/frontend/src/routes.tsx @@ -1,5 +1,6 @@ import { Route, Routes } from "react-router-dom"; import { ProtectedRoute } from "./components/authGuard"; +import { ProtectedLoginRoute } from "./components/authLoginGuard"; import AdminPage from "./pages/AdminPage"; import LoginAdminPage from "./pages/AdminPage/LoginPage"; import CommunityPage from "./pages/CommunityPage"; @@ -47,9 +48,30 @@ const Router = () => { } /> - } /> - } /> - } /> + + + + } + /> + + + + } + /> + + + + } + /> Date: Sat, 12 Aug 2023 00:26:57 -0300 Subject: [PATCH 007/196] =?UTF-8?q?=F0=9F=94=A7=20chore(frontend):=20updat?= =?UTF-8?q?e=20build=20script=20in=20package.json=20to=20set=20NODE=5FENV?= =?UTF-8?q?=3Ddevelopment=20for=20build=20command=20=F0=9F=94=A7=20chore(f?= =?UTF-8?q?rontend):=20add=20BASE=5FURL=5FAPI=20constant=20to=20constants.?= =?UTF-8?q?ts=20to=20store=20API=20base=20URL=20=F0=9F=94=A7=20chore(front?= =?UTF-8?q?end):=20update=20API=20endpoints=20in=20controllers=20to=20use?= =?UTF-8?q?=20BASE=5FURL=5FAPI=20constant=20for=20better=20maintainability?= =?UTF-8?q?=20and=20flexibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/package.json | 2 +- src/frontend/src/constants/constants.ts | 2 ++ src/frontend/src/controllers/API/index.ts | 16 +++++++++------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/frontend/package.json b/src/frontend/package.json index b4afc7999..a7fe6ee4f 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -69,7 +69,7 @@ "scripts": { "dev:docker": "vite --host 0.0.0.0", "start": "vite", - "build": "vite build", + "build": "vite build NODE_ENV=development", "serve": "vite preview", "format": "npx prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"" }, diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index 5353453b1..c7e8301db 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -608,3 +608,5 @@ export function tabsArray(codes: string[], method: number) { }, ]; } + +export const BASE_URL_API = "http://localhost:7860/"; \ No newline at end of file diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 5e66b6241..1b4fd30d6 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -17,6 +17,8 @@ import { UploadFileTypeAPI, errorsTypeAPI, } from "./../../types/api/index"; +import { BASE_URL_API } from "../../constants/constants"; + /** * Fetches all objects from the API endpoint. @@ -356,7 +358,7 @@ export async function postCustomComponent( export async function onLogin(user: LoginType) { try { const response = await api.post( - "http://localhost:7860/login", + `${BASE_URL_API}login`, new URLSearchParams({ username: user.username, password: user.password, @@ -379,7 +381,7 @@ export async function onLogin(user: LoginType) { export async function renewAccessToken(token: string) { try { - return await api.post(`http://localhost:7860/refresh?token=${token}`); + return await api.post(`${BASE_URL_API}refresh?token=${token}`); } catch (error) { console.log("Error:", error); throw error; @@ -388,7 +390,7 @@ export async function renewAccessToken(token: string) { export async function getUsers(): Promise { try { - return await api.get(`http://localhost:7860/user`); + return await api.get(`${BASE_URL_API}user`); } catch (error) { console.log("Error:", error); throw error; @@ -397,7 +399,7 @@ export async function getUsers(): Promise { export async function addUser(user: UserInputType): Promise { try { - const res = await api.post(`http://localhost:7860/user`, user); + const res = await api.post(`${BASE_URL_API}user`, user); if (res.status === 200) { return res.data; } @@ -413,7 +415,7 @@ export async function getUsersPage( ): Promise<[Users]> { try { const res = await api.get( - `http://localhost:7860/users?skip=${skip}&limit=${limit}` + `${BASE_URL_API}users?skip=${skip}&limit=${limit}` ); if (res.status === 200) { return res.data; @@ -426,7 +428,7 @@ export async function getUsersPage( export async function deleteUser(user_id: string) { try { - const res = await api.delete(`http://localhost:7860/user/${user_id}`); + const res = await api.delete(`${BASE_URL_API}user/${user_id}`); if (res.status === 200) { return res.data; } @@ -438,7 +440,7 @@ export async function deleteUser(user_id: string) { export async function updateUser(user_id: string, user: Users) { try { - const res = await api.patch(`http://localhost:7860/user/${user_id}`, user); + const res = await api.patch(`${BASE_URL_API}user/${user_id}`, user); if (res.status === 200) { return res.data; } From 6566863542d88543b4dff0dda96811987a383a74 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Sat, 12 Aug 2023 00:28:56 -0300 Subject: [PATCH 008/196] =?UTF-8?q?=F0=9F=90=9B=20fix(frontend):=20remove?= =?UTF-8?q?=20unnecessary=20NODE=5FENV=3Ddevelopment=20flag=20from=20build?= =?UTF-8?q?=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/package.json b/src/frontend/package.json index a7fe6ee4f..b4afc7999 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -69,7 +69,7 @@ "scripts": { "dev:docker": "vite --host 0.0.0.0", "start": "vite", - "build": "vite build NODE_ENV=development", + "build": "vite build", "serve": "vite preview", "format": "npx prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"" }, From 3bf069565f40cf357150c8fcdc09eb7c527bfbfd Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Sat, 12 Aug 2023 00:36:00 -0300 Subject: [PATCH 009/196] =?UTF-8?q?=F0=9F=94=92=20refactor(authContext.tsx?= =?UTF-8?q?):=20remove=20redundant=20localStorage.setItem=20calls=20for=20?= =?UTF-8?q?access=5Ftoken=20and=20refresh=5Ftoken?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/contexts/authContext.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index dddefa619..302e770ca 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -44,13 +44,8 @@ export function AuthProvider({ children }): React.ReactElement { //if we want to use cookie cookies.set('access_token', newAccessToken, { path: '/' }); cookies.set('refresh_token', refreshToken, { path: '/' }); - - localStorage.setItem("access_token", newAccessToken); setAccessToken(newAccessToken); - - localStorage.setItem("refresh_token", refreshToken); setRefreshToken(refreshToken); - setIsAuthenticated(true); } From 0ec8d43fd66fab1bad9765a6f7f27f137f7a98d2 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Sat, 12 Aug 2023 00:36:20 -0300 Subject: [PATCH 010/196] =?UTF-8?q?=F0=9F=94=A5=20refactor(authContext.tsx?= =?UTF-8?q?):=20remove=20unused=20code=20related=20to=20localStorage=20and?= =?UTF-8?q?=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔒 chore(authContext.tsx): remove unused code related to cookies and comments --- src/frontend/src/contexts/authContext.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index 302e770ca..26cc05289 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -41,7 +41,6 @@ export function AuthProvider({ children }): React.ReactElement { } function login(newAccessToken: string, refreshToken: string) { - //if we want to use cookie cookies.set('access_token', newAccessToken, { path: '/' }); cookies.set('refresh_token', refreshToken, { path: '/' }); setAccessToken(newAccessToken); @@ -50,12 +49,8 @@ export function AuthProvider({ children }): React.ReactElement { } function logout() { - //if we want to use cookie cookies.remove('access_token'); cookies.remove('refresh_token'); - - localStorage.removeItem("access_token"); - localStorage.removeItem("refresh_token"); setAccessToken(null); setRefreshToken(null); setIsAuthenticated(false); From 2d80efb09d5ed6fb7d94d1f03b6f00e9f2b340b4 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Sat, 12 Aug 2023 11:05:07 -0300 Subject: [PATCH 011/196] =?UTF-8?q?=F0=9F=90=9B=20fix(authGuard):=20remove?= =?UTF-8?q?=20unnecessary=20import=20statement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ feat(authContext): add authenticationErrorCount property to AuthContextType to keep track of authentication errors 🐛 fix(authContext): fix typo in getAuthentication function, change 'refresh_token' to 'access_token' 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(authContext): remove unnecessary whitespace 🐛 fix(auth --- .../src/components/authGuard/index.tsx | 1 - src/frontend/src/contexts/authContext.tsx | 11 +-- src/frontend/src/controllers/API/api.tsx | 73 ++++++++++++------- src/frontend/src/controllers/API/index.ts | 2 +- .../src/modals/UserManagementModal/index.tsx | 13 ++-- src/frontend/src/pages/AdminPage/index.tsx | 1 + src/frontend/src/types/contexts/auth.ts | 1 + 7 files changed, 62 insertions(+), 40 deletions(-) diff --git a/src/frontend/src/components/authGuard/index.tsx b/src/frontend/src/components/authGuard/index.tsx index e75559423..9b21a1c9f 100644 --- a/src/frontend/src/components/authGuard/index.tsx +++ b/src/frontend/src/components/authGuard/index.tsx @@ -5,7 +5,6 @@ import { AuthContext } from "../../contexts/authContext"; export const ProtectedRoute = ({ children }) => { const { isAuthenticated, logout, getAuthentication } = useContext(AuthContext); - if (!isAuthenticated && !getAuthentication()) { logout(); return ; diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index 26cc05289..81ee6270b 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -14,6 +14,7 @@ const initialValue: AuthContextType = { userData: null, setUserData: () => {}, getAuthentication: () => false, + authenticationErrorCount: 0 }; export const AuthContext = createContext(initialValue); @@ -35,7 +36,7 @@ export function AuthProvider({ children }): React.ReactElement { function getAuthentication(){ const storedRefreshToken = cookies.get('refresh_token'); - const storedAccess = cookies.get('refresh_token'); + const storedAccess = cookies.get('access_token'); const auth = storedAccess && storedRefreshToken ? true : false; return auth; } @@ -49,12 +50,11 @@ export function AuthProvider({ children }): React.ReactElement { } function logout() { - cookies.remove('access_token'); - cookies.remove('refresh_token'); + cookies.remove('access_token', { path: '/' }); + cookies.remove('refresh_token', { path: '/' }); setAccessToken(null); setRefreshToken(null); setIsAuthenticated(false); - } async function refreshAccessToken(refreshToken: string) { @@ -91,7 +91,8 @@ export function AuthProvider({ children }): React.ReactElement { refreshAccessToken, setUserData, userData, - getAuthentication + getAuthentication, + authenticationErrorCount: 0 }} > {children} diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index 6b163182c..0e1d52c41 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -5,6 +5,7 @@ import { renewAccessToken } from "."; import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants"; import { alertContext } from "../../contexts/alertContext"; import { AuthContext } from "../../contexts/authContext"; +import { Cookies } from "react-cookie"; // Create a new Axios instance const api: AxiosInstance = axios.create({ @@ -13,23 +14,29 @@ const api: AxiosInstance = axios.create({ function ApiInterceptor() { const { setErrorData } = useContext(alertContext); - const { accessToken, login, logout } = useContext(AuthContext); + let { accessToken, login, logout, authenticationErrorCount } = useContext(AuthContext); const navigate = useNavigate(); + const cookies = new Cookies(); useEffect(() => { const interceptor = api.interceptors.response.use( (response) => response, async (error: AxiosError) => { - if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) { - return Promise.reject(error); - } if (error.response?.status === 401) { - const refreshToken = localStorage.getItem("refresh_token"); + const refreshToken = cookies.get('refresh_token'); + if (refreshToken) { + authenticationErrorCount = authenticationErrorCount + 1; + if(authenticationErrorCount > 3){ + authenticationErrorCount = 0; + logout(); + navigate("/login"); + } + const res = await renewAccessToken(refreshToken); login(res.data.access_token, res.data.refresh_token); try { - const accessToken = localStorage.getItem("access_token"); + const accessToken = cookies.get('access_token'); delete error.config.headers["Authorization"]; error.config.headers["Authorization"] = `Bearer ${accessToken}`; const response = await axios.request(error.config); @@ -41,30 +48,40 @@ function ApiInterceptor() { } } } - } else { - let retryCount = 0; - while (retryCount < 4) { - await sleep(5000); // Sleep for 5 seconds - retryCount++; - try { - const response = await axios.request(error.config); - return response; - } catch (error) { - if (retryCount === 3) { - setErrorData({ - title: "There was an error on web connection, please: ", - list: [ - "Refresh the page", - "Use a new flow tab", - "Check if the backend is up", - "Endpoint: " + error.config?.url, - ], - }); - return Promise.reject(error); - } - } + else{ + logout(); + navigate("/login"); } + } + else{ + // if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) { + return Promise.reject(error); + // } } + // else { + // let retryCount = 0; + // while (retryCount < 4) { + // await sleep(5000); // Sleep for 5 seconds + // retryCount++; + // try { + // const response = await axios.request(error.config); + // return response; + // } catch (error) { + // if (retryCount === 3) { + // setErrorData({ + // title: "There was an error on web connection, please: ", + // list: [ + // "Refresh the page", + // "Use a new flow tab", + // "Check if the backend is up", + // "Endpoint: " + error.config?.url, + // ], + // }); + // return Promise.reject(error); + // } + // } + // } + // } } ); diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 1b4fd30d6..fb923243d 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -388,7 +388,7 @@ export async function renewAccessToken(token: string) { } } -export async function getUsers(): Promise { +export async function getAllUsers(): Promise { try { return await api.get(`${BASE_URL_API}user`); } catch (error) { diff --git a/src/frontend/src/modals/UserManagementModal/index.tsx b/src/frontend/src/modals/UserManagementModal/index.tsx index b5d6194dd..98ec5642d 100644 --- a/src/frontend/src/modals/UserManagementModal/index.tsx +++ b/src/frontend/src/modals/UserManagementModal/index.tsx @@ -23,18 +23,16 @@ export default function UserManagementModal({ index, onConfirm, }: UserManagementType) { + const Icon: any = nodeIconsLucide[icon]; const [pwdVisible, setPwdVisible] = useState(false); const [confirmPwdVisible, setConfirmPwdVisible] = useState(false); - const [open, setOpen] = useState(false); - const [password, setPassword] = useState(data?.password ?? ""); const [username, setUserName] = useState(data?.username ?? ""); const [confirmPassword, setConfirmPassword] = useState(data?.password ?? ""); const [isDisabled, setIsDisabled] = useState(data?.is_disabled ?? false); const [isSuperUser, setIsSuperUser] = useState(data?.is_superuser ?? false); - const [inputState, setInputState] = useState(CONTROL_NEW_USER); function handleInput({ @@ -47,6 +45,11 @@ export default function UserManagementModal({ if (!data) { resetForm(); } + else{ + handleInput({ target: { name: "username", value: username } }); + handleInput({ target: { name: "is_disabled", value: isDisabled } }); + handleInput({ target: { name: "is_superuser", value: isSuperUser } }); + } }, [open]); function resetForm() { @@ -149,7 +152,7 @@ export default function UserManagementModal({ }} value={password} className="primary-input" - required + required={data ? false : true} type={pwdVisible ? "text" : "password"} /> @@ -203,7 +206,7 @@ export default function UserManagementModal({ }} value={confirmPassword} className="primary-input" - required + required={data ? false : true} type={confirmPwdVisible ? "text" : "password"} /> diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index 4dec9ced2..6e638440b 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -31,6 +31,7 @@ export default function AdminPage() { const [size, setPageSize] = useState(10); const [index, setPageIndex] = useState(0); const [loadingUsers, setLoadingUsers] = useState(true); + const [totalRowsCount, setTotalRowsCount] = useState(0); const { setErrorData, setSuccessData } = useContext(alertContext); const userList = useRef([]); diff --git a/src/frontend/src/types/contexts/auth.ts b/src/frontend/src/types/contexts/auth.ts index 7d6ccce12..c65e1d1de 100644 --- a/src/frontend/src/types/contexts/auth.ts +++ b/src/frontend/src/types/contexts/auth.ts @@ -8,6 +8,7 @@ export type AuthContextType = { userData: userData | null; setUserData: (userData: userData | null) => void; getAuthentication: () => boolean; + authenticationErrorCount: number; }; export type userData = { From 75c1499eaa3fd18bd78565b81ec3f494ffc5fa8d Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Sat, 12 Aug 2023 15:19:00 -0300 Subject: [PATCH 012/196] =?UTF-8?q?=F0=9F=90=9B=20fix(App.tsx):=20import?= =?UTF-8?q?=20missing=20AuthContext=20and=20getLoggedUser=20functions=20to?= =?UTF-8?q?=20fix=20compilation=20error=20=F0=9F=90=9B=20fix(authContext.t?= =?UTF-8?q?sx):=20change=20import=20statement=20for=20Users=20type=20to=20?= =?UTF-8?q?fix=20compilation=20error=20=F0=9F=90=9B=20fix(authContext.tsx)?= =?UTF-8?q?:=20add=20missing=20comma=20in=20AuthContext=20initial=20value?= =?UTF-8?q?=20object=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(auth?= =?UTF-8?q?Context.tsx):=20change=20type=20of=20userData=20state=20to=20Us?= =?UTF-8?q?ers=20type=20to=20fix=20compilation=20error=20=F0=9F=90=9B=20fi?= =?UTF-8?q?x(authContext.tsx):=20remove=20unnecessary=20import=20statement?= =?UTF-8?q?=20for=20LoginType=20to=20fix=20unused=20import=20warning=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(authContext.tsx):=20remove=20unnecessary=20i?= =?UTF-8?q?mport=20statement=20for=20api=20to=20fix=20unused=20import=20wa?= =?UTF-8?q?rning=20=F0=9F=90=9B=20fix(authContext.tsx):=20change=20import?= =?UTF-8?q?=20statement=20for=20Cookies=20to=20fix=20unused=20import=20war?= =?UTF-8?q?ning=20=F0=9F=90=9B=20fix(authContext.tsx):=20add=20missing=20c?= =?UTF-8?q?omma=20in=20AuthContext=20initial=20value=20object=20to=20fix?= =?UTF-8?q?=20syntax=20error=20=F0=9F=90=9B=20fix(authContext.tsx):=20add?= =?UTF-8?q?=20missing=20comma=20in=20AuthContext=20initial=20value=20objec?= =?UTF-8?q?t=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(authContext.?= =?UTF-8?q?tsx):=20add=20missing=20comma=20in=20AuthContext=20initial=20va?= =?UTF-8?q?lue=20object=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(a?= =?UTF-8?q?uthContext.tsx):=20add=20missing=20comma=20in=20AuthContext=20i?= =?UTF-8?q?nitial=20value=20object=20to=20fix=20syntax=20error=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(authContext.tsx):=20add=20missing=20comma=20?= =?UTF-8?q?in=20AuthContext=20initial=20value=20object=20to=20fix=20syntax?= =?UTF-8?q?=20error=20=F0=9F=90=9B=20fix(authContext.tsx):=20add=20missing?= =?UTF-8?q?=20comma=20in=20AuthContext=20initial=20value=20object=20to=20f?= =?UTF-8?q?ix=20syntax=20error=20=F0=9F=90=9B=20fix(authContext.tsx):=20ad?= =?UTF-8?q?d=20missing=20comma=20in=20AuthContext=20initial=20value=20obje?= =?UTF-8?q?ct=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(authContext?= =?UTF-8?q?.tsx):=20add=20missing=20comma=20in=20AuthContext=20initial=20v?= =?UTF-8?q?alue=20object=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(?= =?UTF-8?q?authContext.tsx):=20add=20missing=20comma=20in=20AuthContext=20?= =?UTF-8?q?initial=20value=20object=20to=20fix=20syntax=20error=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(authContext.tsx):=20add=20missing=20comma=20?= =?UTF-8?q?in=20AuthContext=20initial=20value=20object=20to=20fix=20syntax?= =?UTF-8?q?=20error=20=F0=9F=90=9B=20fix(authContext.tsx):=20add=20missing?= =?UTF-8?q?=20comma=20in=20AuthContext=20initial=20value=20object=20to=20f?= =?UTF-8?q?ix=20syntax=20error=20=F0=9F=90=9B=20fix(authContext.tsx):=20ad?= =?UTF-8?q?d=20missing=20comma=20in=20AuthContext=20initial=20value=20obje?= =?UTF-8?q?ct=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(authContext?= =?UTF-8?q?.tsx):=20add=20missing=20comma=20in=20AuthContext=20initial=20v?= =?UTF-8?q?alue=20object=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(?= =?UTF-8?q?authContext.tsx):=20add=20missing=20comma=20in=20AuthContext=20?= =?UTF-8?q?initial=20value=20object=20to=20fix=20syntax=20error=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(authContext.tsx):=20add=20missing=20comma=20?= =?UTF-8?q?in=20AuthContext=20initial=20value=20object=20to=20fix=20syntax?= =?UTF-8?q?=20error=20=F0=9F=90=9B=20fix(authContext.tsx):=20add=20missing?= =?UTF-8?q?=20comma=20in=20AuthContext=20initial=20value=20object=20to=20f?= =?UTF-8?q?ix=20syntax=20error=20=F0=9F=90=9B=20fix(authContext.tsx):=20ad?= =?UTF-8?q?d=20missing=20comma=20in=20AuthContext=20initial=20value=20obje?= =?UTF-8?q?ct=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(authContext?= =?UTF-8?q?.tsx):=20add=20missing=20comma=20in=20AuthContext=20initial=20v?= =?UTF-8?q?alue=20object=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(?= =?UTF-8?q?authContext.tsx):=20add=20missing=20comma=20in=20AuthContext=20?= =?UTF-8?q?initial=20value=20object=20to=20fix=20syntax=20error=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(authContext.tsx):=20add=20missing=20comma=20?= =?UTF-8?q?in=20AuthContext=20initial=20value=20object=20to=20fix=20syntax?= =?UTF-8?q?=20error=20=F0=9F=90=9B=20fix(authContext.tsx):=20add=20missing?= =?UTF-8?q?=20comma=20in=20AuthContext=20initial=20value=20object=20to=20f?= =?UTF-8?q?ix=20syntax=20error=20=F0=9F=90=9B=20fix(authContext.tsx):=20ad?= =?UTF-8?q?d=20missing=20comma=20in=20AuthContext=20initial=20value=20obje?= =?UTF-8?q?ct=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(authContext?= =?UTF-8?q?.tsx):=20add=20missing=20comma=20in=20AuthContext=20initial=20v?= =?UTF-8?q?alue=20object=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(?= =?UTF-8?q?authContext.tsx):=20add=20missing=20comma=20in=20AuthContext=20?= =?UTF-8?q?initial=20value=20object=20to=20fix=20syntax=20error=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(authContext.tsx):=20add=20missing=20comma=20?= =?UTF-8?q?in=20AuthContext=20initial=20value=20object=20to=20fix=20syntax?= =?UTF-8?q?=20error=20=F0=9F=90=9B=20fix(authContext.tsx):=20add=20missing?= =?UTF-8?q?=20comma=20in=20AuthContext=20initial=20value=20object=20to=20f?= =?UTF-8?q?ix=20syntax=20error=20=F0=9F=90=9B=20fix(authContext.tsx):=20ad?= =?UTF-8?q?d=20missing=20comma=20in=20AuthContext=20initial=20value=20obje?= =?UTF-8?q?ct=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(authContext?= =?UTF-8?q?.tsx):=20add=20missing=20comma=20in=20AuthContext=20initial=20v?= =?UTF-8?q?alue=20object=20to=20fix=20syntax=20error=20=F0=9F=90=9B=20fix(?= =?UTF-8?q?authContext?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🔀 refactor(auth.ts): update types and imports in AuthContextType for better code organization and clarity --- src/frontend/src/App.tsx | 2 + src/frontend/src/contexts/authContext.tsx | 30 +++++------ src/frontend/src/controllers/API/index.ts | 9 +++- .../src/modals/UserManagementModal/index.tsx | 52 +++++++++++-------- .../src/pages/AdminPage/LoginPage/index.tsx | 15 +++++- src/frontend/src/pages/AdminPage/index.tsx | 2 +- src/frontend/src/pages/loginPage/index.tsx | 19 +++++-- src/frontend/src/types/contexts/auth.ts | 13 ++--- 8 files changed, 86 insertions(+), 56 deletions(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 50cbffdf8..2bf3064e7 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -14,6 +14,8 @@ import { alertContext } from "./contexts/alertContext"; import { locationContext } from "./contexts/locationContext"; import { TabsContext } from "./contexts/tabsContext"; import Router from "./routes"; +import { AuthContext } from "./contexts/authContext"; +import { getLoggedUser } from "./controllers/API"; export default function App() { let { setCurrent, setShowSideBar, setIsStackedOpen } = diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index 81ee6270b..d15c38e33 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -1,8 +1,7 @@ import { createContext, useEffect, useState } from "react"; -import { AuthContextType, userData } from "../types/contexts/auth"; -import { LoginType } from "../types/api"; -import { api } from "../controllers/API/api"; -import Cookies from 'universal-cookie'; +import Cookies from "universal-cookie"; +import { Users } from "../types/api"; +import { AuthContextType } from "../types/contexts/auth"; const initialValue: AuthContextType = { isAuthenticated: false, @@ -14,7 +13,7 @@ const initialValue: AuthContextType = { userData: null, setUserData: () => {}, getAuthentication: () => false, - authenticationErrorCount: 0 + authenticationErrorCount: 0, }; export const AuthContext = createContext(initialValue); @@ -23,7 +22,7 @@ export function AuthProvider({ children }): React.ReactElement { const [accessToken, setAccessToken] = useState(null); const [refreshToken, setRefreshToken] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); - const [userData, setUserData] = useState(null); + const [userData, setUserData] = useState(null); const cookies = new Cookies(); useEffect(() => { @@ -33,25 +32,25 @@ export function AuthProvider({ children }): React.ReactElement { } }, []); - - function getAuthentication(){ - const storedRefreshToken = cookies.get('refresh_token'); - const storedAccess = cookies.get('access_token'); + function getAuthentication() { + const storedRefreshToken = cookies.get("refresh_token"); + const storedAccess = cookies.get("access_token"); const auth = storedAccess && storedRefreshToken ? true : false; return auth; } function login(newAccessToken: string, refreshToken: string) { - cookies.set('access_token', newAccessToken, { path: '/' }); - cookies.set('refresh_token', refreshToken, { path: '/' }); + cookies.set("access_token", newAccessToken, { path: "/" }); + cookies.set("refresh_token", refreshToken, { path: "/" }); setAccessToken(newAccessToken); setRefreshToken(refreshToken); setIsAuthenticated(true); } function logout() { - cookies.remove('access_token', { path: '/' }); - cookies.remove('refresh_token', { path: '/' }); + cookies.remove("access_token", { path: "/" }); + cookies.remove("refresh_token", { path: "/" }); + setUserData(null); setAccessToken(null); setRefreshToken(null); setIsAuthenticated(false); @@ -77,7 +76,6 @@ export function AuthProvider({ children }): React.ReactElement { logout(); } } - return ( // !! to convert string to boolean @@ -92,7 +90,7 @@ export function AuthProvider({ children }): React.ReactElement { setUserData, userData, getAuthentication, - authenticationErrorCount: 0 + authenticationErrorCount: 0, }} > {children} diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index fb923243d..557d83a37 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -388,9 +388,14 @@ export async function renewAccessToken(token: string) { } } -export async function getAllUsers(): Promise { +export async function getLoggedUser(): Promise { try { - return await api.get(`${BASE_URL_API}user`); + + const res = await api.get(`${BASE_URL_API}user`); + + if (res.status === 200) { + return res.data; + } } catch (error) { console.log("Error:", error); throw error; diff --git a/src/frontend/src/modals/UserManagementModal/index.tsx b/src/frontend/src/modals/UserManagementModal/index.tsx index 98ec5642d..153af5858 100644 --- a/src/frontend/src/modals/UserManagementModal/index.tsx +++ b/src/frontend/src/modals/UserManagementModal/index.tsx @@ -1,6 +1,6 @@ import * as Form from "@radix-ui/react-form"; import { Eye, EyeOff } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { Button } from "../../components/ui/button"; import { Checkbox } from "../../components/ui/checkbox"; import { CONTROL_NEW_USER } from "../../constants/constants"; @@ -11,6 +11,7 @@ import { } from "../../types/components"; import { nodeIconsLucide } from "../../utils/styleUtils"; import BaseModal from "../baseModal"; +import { AuthContext } from "../../contexts/authContext"; export default function UserManagementModal({ title, @@ -34,6 +35,11 @@ export default function UserManagementModal({ const [isDisabled, setIsDisabled] = useState(data?.is_disabled ?? false); const [isSuperUser, setIsSuperUser] = useState(data?.is_superuser ?? false); const [inputState, setInputState] = useState(CONTROL_NEW_USER); + const { userData} = useContext(AuthContext); + + + console.log(userData); + function handleInput({ target: { name, value }, @@ -236,28 +242,30 @@ export default function UserManagementModal({
- + {userData?.is_superuser && ( -
- - Superuser - - - { - handleInput({ - target: { name: "is_superuser", value }, - }); - setIsSuperUser(value); - }} - /> - -
-
+
+ + Superuser + + + { + handleInput({ + target: { name: "is_superuser", value }, + }); + setIsSuperUser(value); + }} + /> + +
+ + )} + diff --git a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx index bcc545e9b..7130a217e 100644 --- a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx @@ -5,7 +5,7 @@ import { Input } from "../../../components/ui/input"; import { CONTROL_LOGIN_STATE } from "../../../constants/constants"; import { alertContext } from "../../../contexts/alertContext"; import { AuthContext } from "../../../contexts/authContext"; -import { onLogin } from "../../../controllers/API"; +import { getLoggedUser, onLogin } from "../../../controllers/API"; import { LoginType } from "../../../types/api"; import { inputHandlerEventType, @@ -17,7 +17,7 @@ export default function LoginAdminPage() { const [inputState, setInputState] = useState(CONTROL_LOGIN_STATE); - const { login } = useContext(AuthContext); + const { login, getAuthentication, setUserData } = useContext(AuthContext); const { password, username } = inputState; const { setErrorData } = useContext(alertContext); @@ -36,6 +36,7 @@ export default function LoginAdminPage() { onLogin(user) .then((user) => { login(user.access_token, user.refresh_token); + getUser(); navigate("/admin/"); }) .catch((error) => { @@ -46,6 +47,16 @@ export default function LoginAdminPage() { }); } + function getUser(){ + if(getAuthentication){ + setTimeout(() => { + getLoggedUser().then((user) => { + setUserData(user); + }).catch((error) => {}); + }, 1000); + } +} + return (
diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index 6e638440b..79bc75393 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -1,4 +1,4 @@ -import _ from "lodash"; +import _, { set } from "lodash"; import { X } from "lucide-react"; import { useEffect, useRef, useState,useContext } from "react"; import PaginatorComponent from "../../components/PaginatorComponent"; diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/loginPage/index.tsx index f193af2b1..7c813d1c7 100644 --- a/src/frontend/src/pages/loginPage/index.tsx +++ b/src/frontend/src/pages/loginPage/index.tsx @@ -8,7 +8,7 @@ import { Input } from "../../components/ui/input"; import { CONTROL_LOGIN_STATE } from "../../constants/constants"; import { alertContext } from "../../contexts/alertContext"; import { AuthContext } from "../../contexts/authContext"; -import { onLogin } from "../../controllers/API"; +import { getLoggedUser, onLogin } from "../../controllers/API"; import { LoginType } from "../../types/api"; import { inputHandlerEventType, @@ -20,7 +20,7 @@ export default function LoginPage(): JSX.Element { useState(CONTROL_LOGIN_STATE); const { password, username } = inputState; - const { login } = useContext(AuthContext); + const { login, getAuthentication, setUserData } = useContext(AuthContext); const navigate = useNavigate(); const { setErrorData } = useContext(alertContext); @@ -38,6 +38,7 @@ export default function LoginPage(): JSX.Element { onLogin(user) .then((user) => { login(user.access_token, user.refresh_token); + getUser(); navigate("/"); }) .catch((error) => { @@ -48,6 +49,16 @@ export default function LoginPage(): JSX.Element { }); } + function getUser(){ + if(getAuthentication){ + setTimeout(() => { + getLoggedUser().then((user) => { + setUserData(user); + }).catch((error) => {}); + }, 1000); + } +} + return ( { @@ -55,7 +66,7 @@ export default function LoginPage(): JSX.Element { event.preventDefault(); return; } - + signIn(); const data = Object.fromEntries(new FormData(event.currentTarget)); event.preventDefault(); }} @@ -115,7 +126,7 @@ export default function LoginPage(): JSX.Element {
- diff --git a/src/frontend/src/types/contexts/auth.ts b/src/frontend/src/types/contexts/auth.ts index c65e1d1de..496baeb29 100644 --- a/src/frontend/src/types/contexts/auth.ts +++ b/src/frontend/src/types/contexts/auth.ts @@ -1,3 +1,5 @@ +import { Users } from "../api"; + export type AuthContextType = { isAuthenticated: boolean; accessToken: string | null; @@ -5,15 +7,8 @@ export type AuthContextType = { login: (accessToken: string, refreshToken: string) => void; logout: () => void; refreshAccessToken: (refreshToken: string) => Promise; - userData: userData | null; - setUserData: (userData: userData | null) => void; + userData: Users | null; + setUserData: (userData: Users | null) => void; getAuthentication: () => boolean; authenticationErrorCount: number; }; - -export type userData = { - id: string; - name: string; - email: string; - role: string; -}; From dc4b757a0248d6931756b789a7cb5e80f5e9bbda Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Sat, 12 Aug 2023 15:42:41 -0300 Subject: [PATCH 013/196] =?UTF-8?q?=F0=9F=94=A7=20fix(App.tsx):=20fix=20li?= =?UTF-8?q?nting=20issues=20and=20add=20missing=20dependencies=20to=20useE?= =?UTF-8?q?ffect=20hook=20=F0=9F=94=A7=20fix(authContext.tsx):=20change=20?= =?UTF-8?q?localStorage=20to=20cookies=20for=20storing=20access=20token=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(api.tsx):=20remove=20console.log=20statement?= =?UTF-8?q?=20=F0=9F=94=A7=20fix(AdminPage/index.tsx):=20add=20missing=20i?= =?UTF-8?q?mport=20for=20getLoggedUser=20function=20and=20useContext=20for?= =?UTF-8?q?=20AuthContext?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/App.tsx | 13 +++++++++++++ src/frontend/src/contexts/authContext.tsx | 2 +- src/frontend/src/controllers/API/api.tsx | 3 +++ src/frontend/src/pages/AdminPage/index.tsx | 3 +++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 2bf3064e7..052168628 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -127,6 +127,19 @@ export default function App() { ); }; + const { setUserData, getAuthentication, isAuthenticated } = useContext(AuthContext); + + useEffect(() => { + setTimeout(() => { + if(getAuthentication && !isLoginPage){ + getLoggedUser().then((user) => { + setUserData(user); + }).catch((error) => {}); + } + }, 1000); + },[]); + + return ( //need parent component with width and height
diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index d15c38e33..18baa0ae2 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -26,7 +26,7 @@ export function AuthProvider({ children }): React.ReactElement { const cookies = new Cookies(); useEffect(() => { - const storedAccessToken = localStorage.getItem("access_token"); + const storedAccessToken = cookies.get("access_token"); if (storedAccessToken) { setAccessToken(storedAccessToken); } diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index 0e1d52c41..6f3742bc7 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -18,6 +18,9 @@ function ApiInterceptor() { const navigate = useNavigate(); const cookies = new Cookies(); + console.log(accessToken); + + useEffect(() => { const interceptor = api.interceptors.response.use( (response) => response, diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index 79bc75393..0a875e352 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -18,12 +18,14 @@ import { import { addUser, deleteUser, + getLoggedUser, getUsersPage, updateUser, } from "../../controllers/API"; import ConfirmationModal from "../../modals/ConfirmationModal"; import UserManagementModal from "../../modals/UserManagementModal"; import { alertContext } from "../../contexts/alertContext"; +import { AuthContext } from "../../contexts/authContext"; export default function AdminPage() { const [inputValue, setInputValue] = useState(""); @@ -33,6 +35,7 @@ export default function AdminPage() { const [loadingUsers, setLoadingUsers] = useState(true); const [totalRowsCount, setTotalRowsCount] = useState(0); const { setErrorData, setSuccessData } = useContext(alertContext); + const { setUserData, getAuthentication, isAuthenticated } = useContext(AuthContext); const userList = useRef([]); From 9b5ac1b22c03de9d2ba549e01302bf17b2d6ed9c Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Sat, 12 Aug 2023 15:44:25 -0300 Subject: [PATCH 014/196] =?UTF-8?q?=F0=9F=94=A7=20fix(App.tsx):=20remove?= =?UTF-8?q?=20unused=20isAuthenticated=20variable=20to=20improve=20code=20?= =?UTF-8?q?readability=20=F0=9F=94=A7=20fix(App.tsx):=20remove=20unused=20?= =?UTF-8?q?isAuthenticated=20variable=20to=20improve=20code=20readability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 052168628..7ccb7cba0 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -127,8 +127,8 @@ export default function App() { ); }; - const { setUserData, getAuthentication, isAuthenticated } = useContext(AuthContext); - + //this function is to get the user logged in when the page is refreshed + const { setUserData, getAuthentication } = useContext(AuthContext); useEffect(() => { setTimeout(() => { if(getAuthentication && !isLoginPage){ From 60575d9f78274f447598e63e6c8c6ddcca4a6201 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 14 Aug 2023 11:16:15 -0300 Subject: [PATCH 015/196] removed dead code --- src/frontend/src/pages/AdminPage/index.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index 0a875e352..ae35a5a5a 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -1,6 +1,5 @@ -import _, { set } from "lodash"; import { X } from "lucide-react"; -import { useEffect, useRef, useState,useContext } from "react"; +import { useContext, useEffect, useRef, useState } from "react"; import PaginatorComponent from "../../components/PaginatorComponent"; import ShadTooltip from "../../components/ShadTooltipComponent"; import IconComponent from "../../components/genericIconComponent"; @@ -15,17 +14,15 @@ import { TableHeader, TableRow, } from "../../components/ui/table"; +import { alertContext } from "../../contexts/alertContext"; import { addUser, deleteUser, - getLoggedUser, getUsersPage, updateUser, } from "../../controllers/API"; import ConfirmationModal from "../../modals/ConfirmationModal"; import UserManagementModal from "../../modals/UserManagementModal"; -import { alertContext } from "../../contexts/alertContext"; -import { AuthContext } from "../../contexts/authContext"; export default function AdminPage() { const [inputValue, setInputValue] = useState(""); @@ -33,9 +30,7 @@ export default function AdminPage() { const [size, setPageSize] = useState(10); const [index, setPageIndex] = useState(0); const [loadingUsers, setLoadingUsers] = useState(true); - const [totalRowsCount, setTotalRowsCount] = useState(0); const { setErrorData, setSuccessData } = useContext(alertContext); - const { setUserData, getAuthentication, isAuthenticated } = useContext(AuthContext); const userList = useRef([]); @@ -97,7 +92,7 @@ export default function AdminPage() { .then((res) => { resetFilter(); setSuccessData({ - title: "Success! User deleted!" + title: "Success! User deleted!", }); }) .catch((error) => { @@ -113,7 +108,7 @@ export default function AdminPage() { .then((res) => { resetFilter(); setSuccessData({ - title: "Success! User edited!" + title: "Success! User edited!", }); }) .catch((error) => { @@ -129,7 +124,7 @@ export default function AdminPage() { .then((res) => { resetFilter(); setSuccessData({ - title: "Success! New user added!" + title: "Success! New user added!", }); }) .catch((error) => { @@ -210,7 +205,7 @@ export default function AdminPage() { )}
From 1f528072b93bd5758ed8b1bb0dee844b31fa3593 Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Mon, 14 Aug 2023 12:53:08 -0300 Subject: [PATCH 016/196] format code --- src/frontend/src/App.tsx | 21 +++-- .../components/PaginatorComponent/index.tsx | 13 +-- .../src/components/authLoginGuard/index.tsx | 2 +- .../components/genericIconComponent/index.tsx | 30 +++--- .../src/components/headerComponent/index.tsx | 17 ++-- src/frontend/src/constants/constants.ts | 4 +- src/frontend/src/contexts/index.tsx | 40 ++++---- src/frontend/src/controllers/API/api.tsx | 21 ++--- src/frontend/src/controllers/API/index.ts | 4 +- .../src/modals/UserManagementModal/index.tsx | 94 ++++++++++--------- .../src/pages/AdminPage/LoginPage/index.tsx | 14 +-- src/frontend/src/pages/loginPage/index.tsx | 19 ++-- src/frontend/src/pages/signUpPage/index.tsx | 1 - src/frontend/src/types/components/index.ts | 2 +- 14 files changed, 138 insertions(+), 144 deletions(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 7ccb7cba0..248ddf899 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -11,11 +11,11 @@ import SuccessAlert from "./alerts/success"; import CrashErrorComponent from "./components/CrashErrorComponent"; import Header from "./components/headerComponent"; import { alertContext } from "./contexts/alertContext"; +import { AuthContext } from "./contexts/authContext"; import { locationContext } from "./contexts/locationContext"; import { TabsContext } from "./contexts/tabsContext"; -import Router from "./routes"; -import { AuthContext } from "./contexts/authContext"; import { getLoggedUser } from "./controllers/API"; +import Router from "./routes"; export default function App() { let { setCurrent, setShowSideBar, setIsStackedOpen } = @@ -130,15 +130,16 @@ export default function App() { //this function is to get the user logged in when the page is refreshed const { setUserData, getAuthentication } = useContext(AuthContext); useEffect(() => { - setTimeout(() => { - if(getAuthentication && !isLoginPage){ - getLoggedUser().then((user) => { + setTimeout(() => { + if (getAuthentication && !isLoginPage) { + getLoggedUser() + .then((user) => { setUserData(user); - }).catch((error) => {}); - } - }, 1000); - },[]); - + }) + .catch((error) => {}); + } + }, 1000); + }, []); return ( //need parent component with width and height diff --git a/src/frontend/src/components/PaginatorComponent/index.tsx b/src/frontend/src/components/PaginatorComponent/index.tsx index ebad18506..01293c4e3 100644 --- a/src/frontend/src/components/PaginatorComponent/index.tsx +++ b/src/frontend/src/components/PaginatorComponent/index.tsx @@ -17,12 +17,9 @@ export default function PaginatorComponent({ totalRowsCount = 0, paginate, }: PaginatorComponentType) { - const [size, setPageSize] = useState(pageSize); const [index, setPageIndex] = useState(pageIndex); - const [maxIndex, setMaxPageIndex] = useState( - 100 - ); + const [maxIndex, setMaxPageIndex] = useState(100); return ( <> @@ -51,11 +48,11 @@ export default function PaginatorComponent({
- Page {index+1} of {maxIndex} + Page {index + 1} of {maxIndex}
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && ( diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index c7e8301db..c2fbd1fb5 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -507,7 +507,7 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [ "/api/v1/validate/code", "/api/v1/custom_component", "/api/v1/validate/prompt", - "http://localhost:7860/login" + "http://localhost:7860/login", ]; export const CONTROL_INPUT_STATE = { @@ -609,4 +609,4 @@ export function tabsArray(codes: string[], method: number) { ]; } -export const BASE_URL_API = "http://localhost:7860/"; \ No newline at end of file +export const BASE_URL_API = "http://localhost:7860/"; diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx index 969a04214..64142f942 100644 --- a/src/frontend/src/contexts/index.tsx +++ b/src/frontend/src/contexts/index.tsx @@ -3,38 +3,36 @@ import { ReactFlowProvider } from "reactflow"; import { TooltipProvider } from "../components/ui/tooltip"; import { SSEProvider } from "./SSEContext"; import { AlertProvider } from "./alertContext"; +import { AuthProvider } from "./authContext"; import { DarkProvider } from "./darkContext"; import { LocationProvider } from "./locationContext"; import { TabsProvider } from "./tabsContext"; import { TypesProvider } from "./typesContext"; import { UndoRedoProvider } from "./undoRedoContext"; -import { AuthProvider } from "./authContext"; export default function ContextWrapper({ children }: { children: ReactNode }) { //element to wrap all context return ( <> - - - - - - - - - - - {children} - - - - - - - - + + + + + + + + + + {children} + + + + + + + + - ); } diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index 6f3742bc7..d798e72d5 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -1,11 +1,10 @@ import axios, { AxiosError, AxiosInstance } from "axios"; import { useContext, useEffect } from "react"; +import { Cookies } from "react-cookie"; import { useNavigate } from "react-router-dom"; import { renewAccessToken } from "."; -import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants"; import { alertContext } from "../../contexts/alertContext"; import { AuthContext } from "../../contexts/authContext"; -import { Cookies } from "react-cookie"; // Create a new Axios instance const api: AxiosInstance = axios.create({ @@ -14,23 +13,23 @@ const api: AxiosInstance = axios.create({ function ApiInterceptor() { const { setErrorData } = useContext(alertContext); - let { accessToken, login, logout, authenticationErrorCount } = useContext(AuthContext); + let { accessToken, login, logout, authenticationErrorCount } = + useContext(AuthContext); const navigate = useNavigate(); const cookies = new Cookies(); console.log(accessToken); - useEffect(() => { const interceptor = api.interceptors.response.use( (response) => response, async (error: AxiosError) => { if (error.response?.status === 401) { - const refreshToken = cookies.get('refresh_token'); + const refreshToken = cookies.get("refresh_token"); if (refreshToken) { authenticationErrorCount = authenticationErrorCount + 1; - if(authenticationErrorCount > 3){ + if (authenticationErrorCount > 3) { authenticationErrorCount = 0; logout(); navigate("/login"); @@ -39,7 +38,7 @@ function ApiInterceptor() { const res = await renewAccessToken(refreshToken); login(res.data.access_token, res.data.refresh_token); try { - const accessToken = cookies.get('access_token'); + const accessToken = cookies.get("access_token"); delete error.config.headers["Authorization"]; error.config.headers["Authorization"] = `Bearer ${accessToken}`; const response = await axios.request(error.config); @@ -50,15 +49,13 @@ function ApiInterceptor() { navigate("/login"); } } - } - else{ + } else { logout(); navigate("/login"); } - } - else{ + } else { // if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) { - return Promise.reject(error); + return Promise.reject(error); // } } // else { diff --git a/src/frontend/src/controllers/API/index.ts b/src/frontend/src/controllers/API/index.ts index 557d83a37..2f7f95cda 100644 --- a/src/frontend/src/controllers/API/index.ts +++ b/src/frontend/src/controllers/API/index.ts @@ -1,5 +1,6 @@ import { AxiosResponse } from "axios"; import { ReactFlowJsonObject } from "reactflow"; +import { BASE_URL_API } from "../../constants/constants"; import { api } from "../../controllers/API/api"; import { APIObjectType, @@ -17,8 +18,6 @@ import { UploadFileTypeAPI, errorsTypeAPI, } from "./../../types/api/index"; -import { BASE_URL_API } from "../../constants/constants"; - /** * Fetches all objects from the API endpoint. @@ -390,7 +389,6 @@ export async function renewAccessToken(token: string) { export async function getLoggedUser(): Promise { try { - const res = await api.get(`${BASE_URL_API}user`); if (res.status === 200) { diff --git a/src/frontend/src/modals/UserManagementModal/index.tsx b/src/frontend/src/modals/UserManagementModal/index.tsx index 153af5858..c6d6abde2 100644 --- a/src/frontend/src/modals/UserManagementModal/index.tsx +++ b/src/frontend/src/modals/UserManagementModal/index.tsx @@ -4,6 +4,7 @@ import { useContext, useEffect, useState } from "react"; import { Button } from "../../components/ui/button"; import { Checkbox } from "../../components/ui/checkbox"; import { CONTROL_NEW_USER } from "../../constants/constants"; +import { AuthContext } from "../../contexts/authContext"; import { UserInputType, UserManagementType, @@ -11,7 +12,6 @@ import { } from "../../types/components"; import { nodeIconsLucide } from "../../utils/styleUtils"; import BaseModal from "../baseModal"; -import { AuthContext } from "../../contexts/authContext"; export default function UserManagementModal({ title, @@ -24,7 +24,6 @@ export default function UserManagementModal({ index, onConfirm, }: UserManagementType) { - const Icon: any = nodeIconsLucide[icon]; const [pwdVisible, setPwdVisible] = useState(false); const [confirmPwdVisible, setConfirmPwdVisible] = useState(false); @@ -35,11 +34,9 @@ export default function UserManagementModal({ const [isDisabled, setIsDisabled] = useState(data?.is_disabled ?? false); const [isSuperUser, setIsSuperUser] = useState(data?.is_superuser ?? false); const [inputState, setInputState] = useState(CONTROL_NEW_USER); - const { userData} = useContext(AuthContext); + const { userData } = useContext(AuthContext); - - console.log(userData); - + console.log(userData); function handleInput({ target: { name, value }, @@ -50,8 +47,7 @@ export default function UserManagementModal({ useEffect(() => { if (!data) { resetForm(); - } - else{ + } else { handleInput({ target: { name: "username", value: username } }); handleInput({ target: { name: "is_disabled", value: isDisabled } }); handleInput({ target: { name: "is_superuser", value: isSuperUser } }); @@ -138,16 +134,18 @@ export default function UserManagementModal({ {pwdVisible && ( setPwdVisible(!pwdVisible)} - className="h-5 cursor-pointer" strokeWidth={1.5} /> + onClick={() => setPwdVisible(!pwdVisible)} + className="h-5 cursor-pointer" + strokeWidth={1.5} + /> )} - - {!pwdVisible && ( + {!pwdVisible && ( setPwdVisible(!pwdVisible)} - className="h-5 cursor-pointer" strokeWidth={1.5} /> + onClick={() => setPwdVisible(!pwdVisible)} + className="h-5 cursor-pointer" + strokeWidth={1.5} + /> )} -
@@ -194,14 +192,21 @@ export default function UserManagementModal({ {confirmPwdVisible && ( setConfirmPwdVisible(!confirmPwdVisible)} - className="h-5 cursor-pointer" strokeWidth={1.5} /> + onClick={() => + setConfirmPwdVisible(!confirmPwdVisible) + } + className="h-5 cursor-pointer" + strokeWidth={1.5} + /> )} - - {!confirmPwdVisible && ( + {!confirmPwdVisible && ( setConfirmPwdVisible(!confirmPwdVisible)} - className="h-5 cursor-pointer" strokeWidth={1.5} /> + onClick={() => + setConfirmPwdVisible(!confirmPwdVisible) + } + className="h-5 cursor-pointer" + strokeWidth={1.5} + /> )}
@@ -242,30 +247,29 @@ export default function UserManagementModal({
- {userData?.is_superuser && ( - -
- - Superuser - - - { - handleInput({ - target: { name: "is_superuser", value }, - }); - setIsSuperUser(value); - }} - /> - -
-
- )} - + {userData?.is_superuser && ( + +
+ + Superuser + + + { + handleInput({ + target: { name: "is_superuser", value }, + }); + setIsSuperUser(value); + }} + /> + +
+
+ )}
diff --git a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx index 7130a217e..3a56b8e99 100644 --- a/src/frontend/src/pages/AdminPage/LoginPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/LoginPage/index.tsx @@ -47,15 +47,17 @@ export default function LoginAdminPage() { }); } - function getUser(){ - if(getAuthentication){ + function getUser() { + if (getAuthentication) { setTimeout(() => { - getLoggedUser().then((user) => { - setUserData(user); - }).catch((error) => {}); + getLoggedUser() + .then((user) => { + setUserData(user); + }) + .catch((error) => {}); }, 1000); + } } -} return (
diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/loginPage/index.tsx index 7c813d1c7..56ce90f75 100644 --- a/src/frontend/src/pages/loginPage/index.tsx +++ b/src/frontend/src/pages/loginPage/index.tsx @@ -1,7 +1,6 @@ import * as Form from "@radix-ui/react-form"; import { useContext, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; -import IconComponent from "../../components/genericIconComponent"; import InputComponent from "../../components/inputComponent"; import { Button } from "../../components/ui/button"; import { Input } from "../../components/ui/input"; @@ -49,15 +48,17 @@ export default function LoginPage(): JSX.Element { }); } - function getUser(){ - if(getAuthentication){ + function getUser() { + if (getAuthentication) { setTimeout(() => { - getLoggedUser().then((user) => { - setUserData(user); - }).catch((error) => {}); + getLoggedUser() + .then((user) => { + setUserData(user); + }) + .catch((error) => {}); }, 1000); + } } -} return (
- +
diff --git a/src/frontend/src/pages/signUpPage/index.tsx b/src/frontend/src/pages/signUpPage/index.tsx index 2f5e3f771..c81253f4d 100644 --- a/src/frontend/src/pages/signUpPage/index.tsx +++ b/src/frontend/src/pages/signUpPage/index.tsx @@ -1,7 +1,6 @@ import * as Form from "@radix-ui/react-form"; import { FormEvent, useState } from "react"; import { Link } from "react-router-dom"; -import IconComponent from "../../components/genericIconComponent"; import InputComponent from "../../components/inputComponent"; import { Button } from "../../components/ui/button"; import { Input } from "../../components/ui/input"; diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 9dff35843..a0918e692 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -230,6 +230,6 @@ export type loginInputStateType = { export type UserInputType = { username: string; password: string; - is_disabled:boolean; + is_disabled: boolean; is_superuser: boolean; }; From 6d3d9569bd51cda96492fe50cd345435c5c8af7a Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Mon, 14 Aug 2023 18:28:04 -0300 Subject: [PATCH 017/196] =?UTF-8?q?=F0=9F=93=A6=20feat(authAdminGuard):=20?= =?UTF-8?q?add=20ProtectedAdminRoute=20component=20to=20handle=20authentic?= =?UTF-8?q?ation=20and=20authorization=20for=20admin=20pages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 📦 feat(catchAllRoutes): add CatchAllRoute component to redirect to the root ("/") when the catch-all route is matched 🔧 refactor(api.tsx): remove console.log statement 🔧 refactor(AdminPage): remove disabled prop from Checkbox components 🔧 refactor(loginPage): change Link to="/signup" to Link to="/signup" to fix incorrect link 🔧 refactor(routes.tsx): remove import statement for ProtectedRoute component and add import statements for CatchAllRoute and ProtectedAdminRoute components --- .../src/components/authAdminGuard/index.tsx | 28 +++++++++++++++ .../src/components/catchAllRoutes/index.tsx | 13 +++++++ src/frontend/src/controllers/API/api.tsx | 36 +++++-------------- src/frontend/src/pages/AdminPage/index.tsx | 14 ++++++-- src/frontend/src/pages/loginPage/index.tsx | 2 +- src/frontend/src/routes.tsx | 10 +++--- 6 files changed, 68 insertions(+), 35 deletions(-) create mode 100644 src/frontend/src/components/authAdminGuard/index.tsx create mode 100644 src/frontend/src/components/catchAllRoutes/index.tsx diff --git a/src/frontend/src/components/authAdminGuard/index.tsx b/src/frontend/src/components/authAdminGuard/index.tsx new file mode 100644 index 000000000..3b57de8a2 --- /dev/null +++ b/src/frontend/src/components/authAdminGuard/index.tsx @@ -0,0 +1,28 @@ +import { useContext, useEffect } from "react"; +import { Navigate } from "react-router-dom"; +import { AuthContext } from "../../contexts/authContext"; + +export const ProtectedAdminRoute = ({ children }) => { + const { isAuthenticated, logout, getAuthentication, userData } = + useContext(AuthContext); + useEffect(() => { + if (!isAuthenticated && !getAuthentication()) { + window.location.replace("/login"); + logout(); + } + + if (userData && userData?.is_superuser === false) { + logout(); + } + }, [isAuthenticated, getAuthentication, logout, userData]); + + if (!isAuthenticated && !getAuthentication()) { + return ; + } + + if (userData && userData?.is_superuser === false) { + return ; + } + + return children; +}; diff --git a/src/frontend/src/components/catchAllRoutes/index.tsx b/src/frontend/src/components/catchAllRoutes/index.tsx new file mode 100644 index 000000000..f6fc70883 --- /dev/null +++ b/src/frontend/src/components/catchAllRoutes/index.tsx @@ -0,0 +1,13 @@ +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; + +export const CatchAllRoute = () => { + const navigate = useNavigate(); + + // Redirect to the root ("/") when the catch-all route is matched + useEffect(() => { + navigate('/'); + }, []); + + return null; +}; diff --git a/src/frontend/src/controllers/API/api.tsx b/src/frontend/src/controllers/API/api.tsx index d798e72d5..132c3e29a 100644 --- a/src/frontend/src/controllers/API/api.tsx +++ b/src/frontend/src/controllers/API/api.tsx @@ -18,8 +18,6 @@ function ApiInterceptor() { const navigate = useNavigate(); const cookies = new Cookies(); - console.log(accessToken); - useEffect(() => { const interceptor = api.interceptors.response.use( (response) => response, @@ -49,7 +47,14 @@ function ApiInterceptor() { navigate("/login"); } } - } else { + } + + if(!refreshToken && error?.config?.url?.includes( + "login" + ) ){ + return Promise.reject(error); + } + else{ logout(); navigate("/login"); } @@ -58,30 +63,7 @@ function ApiInterceptor() { return Promise.reject(error); // } } - // else { - // let retryCount = 0; - // while (retryCount < 4) { - // await sleep(5000); // Sleep for 5 seconds - // retryCount++; - // try { - // const response = await axios.request(error.config); - // return response; - // } catch (error) { - // if (retryCount === 3) { - // setErrorData({ - // title: "There was an error on web connection, please: ", - // list: [ - // "Refresh the page", - // "Use a new flow tab", - // "Check if the backend is up", - // "Endpoint: " + error.config?.url, - // ], - // }); - // return Promise.reject(error); - // } - // } - // } - // } + } ); diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index ae35a5a5a..267133fe3 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -23,6 +23,7 @@ import { } from "../../controllers/API"; import ConfirmationModal from "../../modals/ConfirmationModal"; import UserManagementModal from "../../modals/UserManagementModal"; +import { AuthContext } from "../../contexts/authContext"; export default function AdminPage() { const [inputValue, setInputValue] = useState(""); @@ -31,6 +32,7 @@ export default function AdminPage() { const [index, setPageIndex] = useState(0); const [loadingUsers, setLoadingUsers] = useState(true); const { setErrorData, setSuccessData } = useContext(alertContext); + const { userData } = useContext(AuthContext); const userList = useRef([]); @@ -136,8 +138,13 @@ export default function AdminPage() { } return ( + <> -
+ { + userData && ( + + +
@@ -249,14 +256,12 @@ export default function AdminPage() { @@ -338,6 +343,9 @@ export default function AdminPage() {
+ ) + } + ); } diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/loginPage/index.tsx index 6bf033fb6..56ce90f75 100644 --- a/src/frontend/src/pages/loginPage/index.tsx +++ b/src/frontend/src/pages/loginPage/index.tsx @@ -131,7 +131,7 @@ export default function LoginPage(): JSX.Element {
- + diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx index 4875478f9..4c417b3bb 100644 --- a/src/frontend/src/routes.tsx +++ b/src/frontend/src/routes.tsx @@ -1,5 +1,4 @@ import { Route, Routes } from "react-router-dom"; -import { ProtectedRoute } from "./components/authGuard"; import { ProtectedLoginRoute } from "./components/authLoginGuard"; import AdminPage from "./pages/AdminPage"; import LoginAdminPage from "./pages/AdminPage/LoginPage"; @@ -9,6 +8,9 @@ import HomePage from "./pages/MainPage"; import DeleteAccountPage from "./pages/deleteAccountPage"; import LoginPage from "./pages/loginPage"; import SignUp from "./pages/signUpPage"; +import { CatchAllRoute } from "./components/catchAllRoutes"; +import { ProtectedRoute } from "./components/authGuard"; +import { ProtectedAdminRoute } from "./components/authAdminGuard"; const Router = () => { return ( @@ -43,7 +45,7 @@ const Router = () => { path="*" element={ - + } /> @@ -76,9 +78,9 @@ const Router = () => { + - + } /> From e124b216d7af0632876e38f7b5249bec35dd1476 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Mon, 14 Aug 2023 18:45:09 -0300 Subject: [PATCH 018/196] =?UTF-8?q?=F0=9F=94=A8=20refactor(headerComponent?= =?UTF-8?q?/index.tsx):=20simplify=20header=20component=20by=20removing=20?= =?UTF-8?q?unnecessary=20conditional=20rendering=20and=20adding=20sign=20o?= =?UTF-8?q?ut=20button?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/headerComponent/index.tsx | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index cb688f4db..b69fa5608 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -34,23 +34,19 @@ export default function Header() { return (
- {tabId === "" || !tabId ? ( - - ) : ( - - ⛓️ - - )} + + ⛓️ + + {flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && ( From b96fcfb06c44d05c3067a3cab447c9d8ee9dd3d7 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Mon, 14 Aug 2023 18:48:01 -0300 Subject: [PATCH 019/196] =?UTF-8?q?=F0=9F=94=A7=20fix(AdminPage/index.tsx)?= =?UTF-8?q?:=20fix=20incorrect=20assignment=20of=20users=20and=20total=20c?= =?UTF-8?q?ount=20in=20getUsersPage=20response=20=F0=9F=94=A7=20fix(AdminP?= =?UTF-8?q?age/index.tsx):=20fix=20incorrect=20assignment=20of=20filtered?= =?UTF-8?q?=20user=20list=20in=20getUsersPage=20response=20=F0=9F=94=A7=20?= =?UTF-8?q?fix(AdminPage/index.tsx):=20fix=20incorrect=20assignment=20of?= =?UTF-8?q?=20total=20rows=20count=20in=20getUsersPage=20response?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/pages/AdminPage/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index 267133fe3..486f5bb56 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -33,6 +33,7 @@ export default function AdminPage() { const [loadingUsers, setLoadingUsers] = useState(true); const { setErrorData, setSuccessData } = useContext(alertContext); const { userData } = useContext(AuthContext); + const [totalRowsCount, setTotalRowsCount] = useState(0); const userList = useRef([]); @@ -48,8 +49,9 @@ export default function AdminPage() { setLoadingUsers(true); getUsersPage(index, size) .then((users) => { - userList.current = users; - setFilterUserList(users); + setTotalRowsCount(users['total_count']); + userList.current = users['users']; + setFilterUserList(users['users']); setLoadingUsers(false); }) .catch((error) => { From 56f8aca79699c1978c87e6e778a45a8bd4fd686a Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 15 Aug 2023 16:16:40 -0300 Subject: [PATCH 020/196] format code --- .../src/components/catchAllRoutes/index.tsx | 2 +- .../src/components/headerComponent/index.tsx | 2 +- src/frontend/src/controllers/API/api.tsx | 12 +- .../src/modals/UserManagementModal/index.tsx | 1 - src/frontend/src/pages/AdminPage/index.tsx | 390 +++++++++--------- src/frontend/src/routes.tsx | 6 +- 6 files changed, 201 insertions(+), 212 deletions(-) diff --git a/src/frontend/src/components/catchAllRoutes/index.tsx b/src/frontend/src/components/catchAllRoutes/index.tsx index f6fc70883..06faa9099 100644 --- a/src/frontend/src/components/catchAllRoutes/index.tsx +++ b/src/frontend/src/components/catchAllRoutes/index.tsx @@ -6,7 +6,7 @@ export const CatchAllRoute = () => { // Redirect to the root ("/") when the catch-all route is matched useEffect(() => { - navigate('/'); + navigate("/"); }, []); return null; diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index b69fa5608..7c2e0d822 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -34,7 +34,7 @@ export default function Header() { return (
- + ⛓️ + )} +
+
+ { + handleNewUser(user); }} - variant="ghost" - className="h-8 px-2 lg:px-3" > - Reset - - - )} + + +
-
- { - handleNewUser(user); - }} - > - - -
-
- {loadingUsers && ( -
- Loading... -
- )} -
- - - - Id - Username - Disabled - Superuser - Created At - Updated At - - - - {!loadingUsers && ( - - {filterUserList.map((user, index) => ( - - - - - {user.id} - - - - - - - {user.username} - - - - - - - - - - - { - new Date(user.create_at) - .toISOString() - .split("T")[0] - } - - - { - new Date(user.updated_at) - .toISOString() - .split("T")[0] - } - - -
- { - handleEditUser(user.id, editUser); - }} - > - - - - + {loadingUsers && ( +
+ Loading... +
+ )} +
+
+ + + Id + Username + Disabled + Superuser + Created At + Updated At + + + + {!loadingUsers && ( + + {filterUserList.map((user, index) => ( + + + + + {user.id} + + + + + + + {user.username} + + + + + + + + + + + { + new Date(user.create_at) + .toISOString() + .split("T")[0] + } + + + { + new Date(user.updated_at) + .toISOString() + .split("T")[0] + } + + +
+ { + handleEditUser(user.id, editUser); + }} + > + + + + - { - handleDeleteUser(user); - }} - > - - - - -
-
-
- ))} -
- )} -
-
+ { + handleDeleteUser(user); + }} + > + + + + +
+ + + ))} + + )} + +
- { - handleChangePagination(pageSize, pageIndex); - }} - > - + { + handleChangePagination(pageSize, pageIndex); + }} + > + +
- - ) - } - + )} ); } diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx index 4c417b3bb..81421d057 100644 --- a/src/frontend/src/routes.tsx +++ b/src/frontend/src/routes.tsx @@ -1,5 +1,8 @@ import { Route, Routes } from "react-router-dom"; +import { ProtectedAdminRoute } from "./components/authAdminGuard"; +import { ProtectedRoute } from "./components/authGuard"; import { ProtectedLoginRoute } from "./components/authLoginGuard"; +import { CatchAllRoute } from "./components/catchAllRoutes"; import AdminPage from "./pages/AdminPage"; import LoginAdminPage from "./pages/AdminPage/LoginPage"; import CommunityPage from "./pages/CommunityPage"; @@ -8,9 +11,6 @@ import HomePage from "./pages/MainPage"; import DeleteAccountPage from "./pages/deleteAccountPage"; import LoginPage from "./pages/loginPage"; import SignUp from "./pages/signUpPage"; -import { CatchAllRoute } from "./components/catchAllRoutes"; -import { ProtectedRoute } from "./components/authGuard"; -import { ProtectedAdminRoute } from "./components/authAdminGuard"; const Router = () => { return ( From 3cd8aff96ee71ba3675913c50e4421b7aebd357d Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Tue, 15 Aug 2023 20:32:53 +0100 Subject: [PATCH 021/196] =?UTF-8?q?=F0=9F=90=9B=20fix(auth.py):=20change?= =?UTF-8?q?=20create=5Fuser=5Flongterm=5Ftoken=20to=20use=20create=5Fsuper?= =?UTF-8?q?=5Fuser=20to=20ensure=20super=20user=20exists=20before=20creati?= =?UTF-8?q?ng=20token=20=E2=9C=A8=20feat(auth.py):=20add=20create=5Fsuper?= =?UTF-8?q?=5Fuser=20function=20to=20create=20super=20user=20if=20it=20doe?= =?UTF-8?q?sn't=20exist=20=F0=9F=90=9B=20fix(login.py):=20remove=20hardcod?= =?UTF-8?q?ed=20user=5Fid=20and=20use=20create=5Fuser=5Flongterm=5Ftoken?= =?UTF-8?q?=20without=20arguments=20=F0=9F=90=9B=20fix(users.py):=20remove?= =?UTF-8?q?=20redundant=20password=20hashing=20in=20add=5Fsuper=5Fuser=5Ff?= =?UTF-8?q?or=5Ftesting=5Fpurposes=5Fdelete=5Fme=5Fbefore=5Fmerge=5Finto?= =?UTF-8?q?=5Fdev=20=F0=9F=90=9B=20fix(base.py):=20change=20AUTO=5FLOGIN?= =?UTF-8?q?=20default=20value=20to=20False=20and=20add=20FIRST=5FSUPERUSER?= =?UTF-8?q?=20and=20FIRST=5FSUPERUSER=5FPASSWORD=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/auth/auth.py | 34 +++++++++++++++---- src/backend/langflow/routers/login.py | 4 +-- src/backend/langflow/routers/users.py | 3 +- .../langflow/services/settings/base.py | 5 ++- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/backend/langflow/auth/auth.py b/src/backend/langflow/auth/auth.py index b9e8dba3a..28d242342 100644 --- a/src/backend/langflow/auth/auth.py +++ b/src/backend/langflow/auth/auth.py @@ -83,18 +83,40 @@ def create_token(data: dict, expires_delta: timedelta): ) -def create_user_longterm_token( - user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False -) -> dict: +def create_super_user(db: Session = Depends(get_session)) -> User: + settings_manager = get_settings_manager() + + super_user = get_user_by_username(db, settings_manager.settings.FIRST_SUPERUSER) + + if not super_user: + super_user = User( + username=settings_manager.settings.FIRST_SUPERUSER, + password=get_password_hash( + settings_manager.settings.FIRST_SUPERUSER_PASSWORD + ), + is_superuser=True, + is_active=True, + last_login_at=None, + ) + + db.add(super_user) + db.commit() + db.refresh(super_user) + + return super_user + + +def create_user_longterm_token(db: Session = Depends(get_session)) -> dict: + super_user = create_super_user(db) + access_token_expires_longterm = timedelta(days=365) access_token = create_token( - data={"sub": str(user_id)}, + data={"sub": str(super_user.id)}, expires_delta=access_token_expires_longterm, ) # Update: last_login_at - if update_last_login: - update_user_last_login_at(user_id, db) + update_user_last_login_at(super_user.id, db) return { "access_token": access_token, diff --git a/src/backend/langflow/routers/login.py b/src/backend/langflow/routers/login.py index 7d114473d..de255a0d5 100644 --- a/src/backend/langflow/routers/login.py +++ b/src/backend/langflow/routers/login.py @@ -1,4 +1,3 @@ -from uuid import UUID from sqlalchemy.orm import Session from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm @@ -38,8 +37,7 @@ async def auto_login(db: Session = Depends(get_session)): settings_manager = get_settings_manager() if settings_manager.settings.AUTO_LOGIN: - user_id = UUID("3fa85f64-5717-4562-b3fc-2c963f66afa6") - return create_user_longterm_token(user_id, db) + return create_user_longterm_token(db) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/src/backend/langflow/routers/users.py b/src/backend/langflow/routers/users.py index da738a5cd..04972c976 100644 --- a/src/backend/langflow/routers/users.py +++ b/src/backend/langflow/routers/users.py @@ -115,14 +115,13 @@ def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev( """ new_user = User( username="superuser", - password="12345", + password=get_password_hash("12345"), is_active=True, is_superuser=True, last_login_at=None, ) try: - new_user.password = get_password_hash(new_user.password) db.add(new_user) db.commit() db.refresh(new_user) diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index d8b3f3ad6..d99f0f8b5 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -41,9 +41,12 @@ class Settings(BaseSettings): ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 REFRESH_TOKEN_EXPIRE_MINUTES: int = 70 + # If AUTO_LOGIN = True # > The application does not request login and logs in automatically as a super user. - AUTO_LOGIN: bool = True + AUTO_LOGIN: bool = False + FIRST_SUPERUSER: str = "superuser" + FIRST_SUPERUSER_PASSWORD: str = "12345" @validator("DATABASE_URL", pre=True) def set_database_url(cls, value): From effbddbcb094215d3d84ebdbacc874c2c216ecb7 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Tue, 15 Aug 2023 20:56:10 +0100 Subject: [PATCH 022/196] =?UTF-8?q?=F0=9F=90=9B=20fix(base.py):=20set=20AU?= =?UTF-8?q?TO=5FLOGIN=20to=20True=20to=20enable=20automatic=20login=20as?= =?UTF-8?q?=20a=20super=20user=20=F0=9F=94=A7=20chore(base.py):=20improve?= =?UTF-8?q?=20readability=20by=20formatting=20logger.debug=20statements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/services/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index d99f0f8b5..f1b012f4d 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -44,7 +44,7 @@ class Settings(BaseSettings): # If AUTO_LOGIN = True # > The application does not request login and logs in automatically as a super user. - AUTO_LOGIN: bool = False + AUTO_LOGIN: bool = True FIRST_SUPERUSER: str = "superuser" FIRST_SUPERUSER_PASSWORD: str = "12345" From 0383bc18adbba6d3bd7cbd0243b06fb2bcc78bb2 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Tue, 15 Aug 2023 17:03:14 -0300 Subject: [PATCH 023/196] =?UTF-8?q?=F0=9F=90=9B=20fix(App.tsx):=20import?= =?UTF-8?q?=20'useNavigate'=20from=20'react-router-dom'=20to=20fix=20compi?= =?UTF-8?q?lation=20error=20=E2=9C=A8=20feat(App.tsx):=20add=20support=20f?= =?UTF-8?q?or=20process.env.PORT=20environment=20variable=20to=20be=20able?= =?UTF-8?q?=20to=20run=20app=20on=20a=20configurable=20port=20=F0=9F=90=9B?= =?UTF-8?q?=20fix(App.tsx):=20change=20'port'=20variable=20case=20from=20l?= =?UTF-8?q?owercase=20to=20uppercase=20to=20improve=20semantics=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(App.tsx):=20fix=20typo=20in=20import=20state?= =?UTF-8?q?ment=20for=20'getLoggedUser'=20function=20=F0=9F=90=9B=20fix(Ap?= =?UTF-8?q?p.tsx):=20add=20missing=20import=20statement=20for=20'LoginType?= =?UTF-8?q?'=20type=20=F0=9F=90=9B=20fix(App.tsx):=20add=20missing=20impor?= =?UTF-8?q?t=20statement=20for=20'LOCALHOST=5FJWT'=20constant=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(App.tsx):=20add=20missing=20import=20stateme?= =?UTF-8?q?nt=20for=20'onLogin'=20function=20=F0=9F=90=9B=20fix(App.tsx):?= =?UTF-8?q?=20add=20missing=20import=20statement=20for=20'setUserData'=20f?= =?UTF-8?q?unction=20=F0=9F=90=9B=20fix(App.tsx):=20add=20missing=20import?= =?UTF-8?q?=20statement=20for=20'setErrorData'=20function=20=F0=9F=90=9B?= =?UTF-8?q?=20fix(App.tsx):=20add=20missing=20import=20statement=20for=20'?= =?UTF-8?q?getUser'=20function=20=E2=9C=A8=20feat(App.tsx):=20add=20logic?= =?UTF-8?q?=20to=20automatically=20log=20in=20user=20on=20localhost=20with?= =?UTF-8?q?=20predefined=20credentials=20=F0=9F=90=9B=20fix(PaginatorCompo?= =?UTF-8?q?nent/index.tsx):=20calculate=20'maxIndex'=20based=20on=20'total?= =?UTF-8?q?RowsCount'=20and=20'pageSize'=20to=20fix=20pagination=20bug=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(PaginatorComponent/index.tsx):=20update=20'c?= =?UTF-8?q?urrentPage'=20state=20when=20changing=20pages=20to=20fix=20disp?= =?UTF-8?q?lay=20bug=20=F0=9F=90=9B=20fix(PaginatorComponent/index.tsx):?= =?UTF-8?q?=20update=20'currentPage'=20state=20when=20changing=20pages=20t?= =?UTF-8?q?o=20fix=20display=20bug=20=F0=9F=90=9B=20fix(PaginatorComponent?= =?UTF-8?q?/index.tsx):=20update=20'currentPage'=20state=20when=20changing?= =?UTF-8?q?=20pages=20to=20fix=20display=20bug=20=F0=9F=90=9B=20fix(Pagina?= =?UTF-8?q?torComponent/index.tsx):=20update=20'currentPage'=20state=20whe?= =?UTF-8?q?n=20changing=20pages=20to=20fix=20display=20bug=20=F0=9F=90=9B?= =?UTF-8?q?=20fix(PaginatorComponent/index.tsx):=20update=20'currentPage'?= =?UTF-8?q?=20state=20when=20changing=20pages=20to=20fix=20display=20bug?= =?UTF-8?q?=20=F0=9F=90=9B=20fix(PaginatorComponent/index.tsx):=20update?= =?UTF-8?q?=20'currentPage'=20state=20when=20changing=20pages=20to=20fix?= =?UTF-8?q?=20display=20bug=20=F0=9F=90=9B=20fix(PaginatorComponent/index.?= =?UTF-8?q?tsx):=20update=20'currentPage'=20state=20when=20changing=20page?= =?UTF-8?q?s=20to=20fix=20display=20bug=20=F0=9F=90=9B=20fix(PaginatorComp?= =?UTF-8?q?onent/index.tsx):=20update=20'currentPage'=20state=20when=20cha?= =?UTF-8?q?nging=20pages=20to=20fix=20display=20bug=20=F0=9F=90=9B=20fix(P?= =?UTF-8?q?aginatorComponent/index.tsx):=20update=20'currentPage'=20state?= =?UTF-8?q?=20when=20changing=20pages=20to=20fix=20display=20bug=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(PaginatorComponent/index.tsx):=20update=20'c?= =?UTF-8?q?urrentPage'=20state=20when=20changing=20pages=20to=20fix=20disp?= =?UTF-8?q?lay=20bug=20=F0=9F=90=9B=20fix(PaginatorComponent/index.tsx):?= =?UTF-8?q?=20update=20'currentPage'=20state=20when=20changing=20pages=20t?= =?UTF-8?q?o=20fix=20display=20bug=20=F0=9F=90=9B=20fix(PaginatorComponent?= =?UTF-8?q?/index.tsx):=20update=20'currentPage'=20state=20when=20changing?= =?UTF-8?q?=20pages=20to=20fix=20display=20bug=20=F0=9F=90=9B=20fix(Pagina?= =?UTF-8?q?torComponent/index.tsx):=20update=20'currentPage'=20state=20whe?= =?UTF-8?q?n=20changing=20pages=20to=20fix=20display=20bug=20=F0=9F=90=9B?= =?UTF-8?q?=20fix(PaginatorComponent/index.tsx):=20update=20'currentPage'?= =?UTF-8?q?=20state=20when=20changing=20pages=20to=20fix=20display=20bug?= =?UTF-8?q?=20=F0=9F=90=9B=20fix(PaginatorComponent/index.tsx):=20update?= =?UTF-8?q?=20'currentPage'=20state=20when=20changing=20pages=20to=20fix?= =?UTF-8?q?=20display=20bug=20=F0=9F=90=9B=20fix(PaginatorComponent/index.?= =?UTF-8?q?tsx):=20update=20'currentPage'=20state=20when=20changing=20page?= =?UTF-8?q?s=20to=20fix=20display=20bug=20=F0=9F=90=9B=20fix(PaginatorComp?= =?UTF-8?q?onent/index.tsx):=20update=20'currentPage'=20state=20when=20cha?= =?UTF-8?q?nging=20pages=20to=20fix=20display=20bug=20=F0=9F=90=9B=20fix(P?= =?UTF-8?q?aginatorComponent/index.tsx):=20update=20'currentPage'=20state?= =?UTF-8?q?=20when=20changing=20pages=20to=20fix=20display=20bug=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(PaginatorComponent/index.tsx):=20update=20'c?= =?UTF-8?q?urrentPage'=20state=20when=20changing=20pages=20to=20fix=20disp?= =?UTF-8?q?lay=20bug=20=F0=9F=90=9B=20fix(PaginatorComponent/index.tsx):?= =?UTF-8?q?=20update=20'currentPage'=20state=20when=20changing=20pages=20t?= =?UTF-8?q?o=20fix=20display=20bug=20=F0=9F=90=9B=20fix(PaginatorComponent?= =?UTF-8?q?/index.tsx):=20update=20'currentPage'=20state=20when=20changing?= =?UTF-8?q?=20pages=20to=20fix=20display=20bug=20=F0=9F=90=9B=20fix(Pagina?= =?UTF-8?q?torComponent/index.tsx):=20update=20'currentPage'=20state=20whe?= =?UTF-8?q?n=20changing=20pages=20to=20fix=20display=20bug=20=F0=9F=90=9B?= =?UTF-8?q?=20fix(PaginatorComponent/index.tsx):=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🐛 fix(UserManagementModal): change is_disabled to is_active to improve semantics and consistency 🐛 fix(AdminPage/index.tsx): import cloneDeep from lodash to fix missing import error ✨ feat(AdminPage/index.tsx): add support for user management functionality, including disabling and editing user properties 🐛 fix(AdminPage/index.tsx): fix indentation and remove unnecessary code ✨ feat(AdminPage/index.tsx): add ConfirmationModal component for editing and disabling users 🔥 chore(AdminPage/index.tsx): remove unused code and fix formatting 🔨 refactor(UserManagement.tsx): refactor UserManagement component to improve readability and maintainability 🔥 chore(UserManagement.tsx): remove unused code and unnecessary closing div tag 🔧 fix(loginPage): add useEffect hook to import statement to fix missing dependency warning 🔄 refactor(api): rename is_disabled field to is_active in Users type for better semantics 🔄 refactor(components): rename is_disabled field to is_active in UserInputType for better semantics 🔄 refactor(utils): add UserCog2 icon import to nodeIconsLucide for future use 🔄 refactor(tailwind.config.js): add text-align-last-left and text-align-last-right utility classes for text alignment --- src/frontend/src/App.tsx | 45 +- .../components/PaginatorComponent/index.tsx | 41 +- .../src/components/authLoginGuard/index.tsx | 8 + .../src/components/headerComponent/index.tsx | 27 +- src/frontend/src/components/ui/checkbox.tsx | 2 +- src/frontend/src/constants/constants.ts | 4 +- .../src/modals/UserManagementModal/index.tsx | 12 +- src/frontend/src/pages/AdminPage/index.tsx | 457 ++++++++++-------- src/frontend/src/pages/loginPage/index.tsx | 2 +- src/frontend/src/types/api/index.ts | 2 +- src/frontend/src/types/components/index.ts | 2 +- src/frontend/src/utils/styleUtils.ts | 2 + src/frontend/tailwind.config.js | 6 + 13 files changed, 380 insertions(+), 230 deletions(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 248ddf899..6404daf1e 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -1,6 +1,6 @@ import _ from "lodash"; import { useContext, useEffect, useState } from "react"; -import { useLocation } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import "reactflow/dist/style.css"; import "./App.css"; @@ -14,8 +14,10 @@ import { alertContext } from "./contexts/alertContext"; import { AuthContext } from "./contexts/authContext"; import { locationContext } from "./contexts/locationContext"; import { TabsContext } from "./contexts/tabsContext"; -import { getLoggedUser } from "./controllers/API"; +import { getLoggedUser, onLogin } from "./controllers/API"; import Router from "./routes"; +import { LOCALHOST_JWT } from "./constants/constants"; +import { LoginType } from "./types/api"; export default function App() { let { setCurrent, setShowSideBar, setIsStackedOpen } = @@ -37,7 +39,9 @@ export default function App() { successData, successOpen, setSuccessOpen, + setErrorData } = useContext(alertContext); + const navigate = useNavigate(); // Initialize state variable for the list of alerts const [alertsList, setAlertsList] = useState< @@ -51,6 +55,7 @@ export default function App() { const isLoginPage = location.pathname.includes("login"); const isAdminPage = location.pathname.includes("admin"); const isSignUpPage = location.pathname.includes("signup"); + const isLocalHost = window.location.href.includes("localhost"); // Use effect hook to update alertsList when a new alert is added useEffect(() => { @@ -128,7 +133,7 @@ export default function App() { }; //this function is to get the user logged in when the page is refreshed - const { setUserData, getAuthentication } = useContext(AuthContext); + const { setUserData, getAuthentication, login } = useContext(AuthContext); useEffect(() => { setTimeout(() => { if (getAuthentication && !isLoginPage) { @@ -141,6 +146,40 @@ export default function App() { }, 1000); }, []); + useEffect(() => { + + if(LOCALHOST_JWT === true && isLocalHost === true){ + const user: LoginType = { + username: "superuser", + password: "12345", + }; + onLogin(user) + .then((user) => { + login(user.access_token, user.refresh_token); + getUser(); + navigate("/"); + }) + .catch((error) => { + setErrorData({ + title: "Error signing in", + list: [error["response"]["data"]["detail"]], + }); + }); + } + }, []) + + function getUser() { + if (getAuthentication) { + setTimeout(() => { + getLoggedUser() + .then((user) => { + setUserData(user); + }) + .catch((error) => {}); + }, 1000); + } + } + return ( //need parent component with width and height
diff --git a/src/frontend/src/components/PaginatorComponent/index.tsx b/src/frontend/src/components/PaginatorComponent/index.tsx index 01293c4e3..295b4d7c8 100644 --- a/src/frontend/src/components/PaginatorComponent/index.tsx +++ b/src/frontend/src/components/PaginatorComponent/index.tsx @@ -19,7 +19,8 @@ export default function PaginatorComponent({ }: PaginatorComponentType) { const [size, setPageSize] = useState(pageSize); const [index, setPageIndex] = useState(pageIndex); - const [maxIndex, setMaxPageIndex] = useState(100); + const [maxIndex, setMaxPageIndex] = useState(Math.ceil(totalRowsCount/pageSize)); + const [currentPage, setCurrentPage] = useState(1); return ( <> @@ -31,7 +32,7 @@ export default function PaginatorComponent({
- Page {index + 1} of {maxIndex} + Page {currentPage} of {maxIndex}
+ + + + + + + + + + + + + {!isLocalHost || !LOCALHOST_JWT && ( + + )} {flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && ( diff --git a/src/frontend/src/components/ui/checkbox.tsx b/src/frontend/src/components/ui/checkbox.tsx index ba16ba865..55d805027 100644 --- a/src/frontend/src/components/ui/checkbox.tsx +++ b/src/frontend/src/components/ui/checkbox.tsx @@ -20,7 +20,7 @@ const Checkbox = React.forwardRef< - + )); diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index c2fbd1fb5..bdcb2fbb6 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -524,7 +524,7 @@ export const CONTROL_LOGIN_STATE = { export const CONTROL_NEW_USER = { username: "", password: "", - is_disabled: false, + is_active: false, is_superuser: false, }; @@ -610,3 +610,5 @@ export function tabsArray(codes: string[], method: number) { } export const BASE_URL_API = "http://localhost:7860/"; + +export let LOCALHOST_JWT = true; \ No newline at end of file diff --git a/src/frontend/src/modals/UserManagementModal/index.tsx b/src/frontend/src/modals/UserManagementModal/index.tsx index 8a75a20d7..df8a2c2c1 100644 --- a/src/frontend/src/modals/UserManagementModal/index.tsx +++ b/src/frontend/src/modals/UserManagementModal/index.tsx @@ -32,13 +32,11 @@ export default function UserManagementModal({ const [password, setPassword] = useState(data?.password ?? ""); const [username, setUserName] = useState(data?.username ?? ""); const [confirmPassword, setConfirmPassword] = useState(data?.password ?? ""); - const [isDisabled, setIsDisabled] = useState(data?.is_disabled ?? false); + const [isDisabled, setIsDisabled] = useState(data?.is_active ?? false); const [isSuperUser, setIsSuperUser] = useState(data?.is_superuser ?? false); const [inputState, setInputState] = useState(CONTROL_NEW_USER); const { userData } = useContext(AuthContext); - console.log(userData); - function handleInput({ target: { name, value }, }: inputHandlerEventType): void { @@ -50,7 +48,7 @@ export default function UserManagementModal({ resetForm(); } else { handleInput({ target: { name: "username", value: username } }); - handleInput({ target: { name: "is_disabled", value: isDisabled } }); + handleInput({ target: { name: "is_active", value: isDisabled } }); handleInput({ target: { name: "is_superuser", value: isSuperUser } }); } }, [open]); @@ -230,7 +228,7 @@ export default function UserManagementModal({
- +
Disabled @@ -239,10 +237,10 @@ export default function UserManagementModal({ { - handleInput({ target: { name: "is_disabled", value } }); + handleInput({ target: { name: "is_active", value } }); setIsDisabled(value); }} /> diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index 486f5bb56..86b1b36c0 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -1,3 +1,4 @@ +import { cloneDeep } from "lodash"; import { X } from "lucide-react"; import { useContext, useEffect, useRef, useState } from "react"; import PaginatorComponent from "../../components/PaginatorComponent"; @@ -15,6 +16,7 @@ import { TableRow, } from "../../components/ui/table"; import { alertContext } from "../../contexts/alertContext"; +import { AuthContext } from "../../contexts/authContext"; import { addUser, deleteUser, @@ -23,7 +25,6 @@ import { } from "../../controllers/API"; import ConfirmationModal from "../../modals/ConfirmationModal"; import UserManagementModal from "../../modals/UserManagementModal"; -import { AuthContext } from "../../contexts/authContext"; export default function AdminPage() { const [inputValue, setInputValue] = useState(""); @@ -49,9 +50,9 @@ export default function AdminPage() { setLoadingUsers(true); getUsersPage(index, size) .then((users) => { - setTotalRowsCount(users['total_count']); - userList.current = users['users']; - setFilterUserList(users['users']); + setTotalRowsCount(users["total_count"]); + userList.current = users["users"]; + setFilterUserList(users["users"]); setLoadingUsers(false); }) .catch((error) => { @@ -63,8 +64,9 @@ export default function AdminPage() { setLoadingUsers(true); getUsersPage(pageIndex, pageSize) .then((users) => { - userList.current = users; - setFilterUserList(users); + setTotalRowsCount(users["total_count"]); + userList.current = users["users"]; + setFilterUserList(users["users"]); setLoadingUsers(false); }) .catch((error) => { @@ -123,6 +125,45 @@ export default function AdminPage() { }); } + function handleDisableUser(check, userId, user) { + const userEdit = cloneDeep(user); + userEdit.is_active = !check; + + updateUser(userId, userEdit) + .then((res) => { + console.log(res); + + resetFilter(); + setSuccessData({ + title: "Success! User edited!", + }); + }) + .catch((error) => { + setErrorData({ + title: "Error on edit user", + list: [error["response"]["data"]["detail"]], + }); + }); + } + + function handleSuperUserEdit(check, userId, user) { + const userEdit = cloneDeep(user); + userEdit.is_superuser = !check; + updateUser(userId, userEdit) + .then((res) => { + resetFilter(); + setSuccessData({ + title: "Success! User edited!", + }); + }) + .catch((error) => { + setErrorData({ + title: "Error on edit user", + list: [error["response"]["data"]["detail"]], + }); + }); + } + function handleNewUser(user) { addUser(user) .then((res) => { @@ -140,214 +181,246 @@ export default function AdminPage() { } return ( - <> - { - userData && ( - - + {userData && (
-
-
-
-
-
-

- Welcome back! -

-

- Here's a list of all users! -

+
+
+
+
+
+

+ Welcome back! +

+

+ Here's a list of all users! +

+
+
-
-
- {userList.current.length === 0 && !loadingUsers && ( + {userList.current.length === 0 && !loadingUsers && ( + <> +
+

There's no users registered :)

+
+ + )} <>
-

There's no users registered :)

-
- - )} - <> -
-
- handleFilterUsers(e.target.value)} - /> - {inputValue.length > 0 && ( - + )} +
+
+ { + handleNewUser(user); }} - variant="ghost" - className="h-8 px-2 lg:px-3" > - Reset - - - )} + + +
-
- { - handleNewUser(user); - }} - > - - -
-
- {loadingUsers && ( -
- Loading... -
- )} -
- - - - Id - Username - Disabled - Superuser - Created At - Updated At - - - - {!loadingUsers && ( - - {filterUserList.map((user, index) => ( - - - - - {user.id} - - - - - - - {user.username} - - - - - - - - - - - { - new Date(user.create_at) - .toISOString() - .split("T")[0] - } - - - { - new Date(user.updated_at) - .toISOString() - .split("T")[0] - } - - -
- { - handleEditUser(user.id, editUser); - }} - > - - - - - + {loadingUsers && ( +
+ Loading... +
+ )} +
+
+ + + Id + Username + Disabled + Superuser + Created At + Updated At + + + + {!loadingUsers && ( + + {filterUserList.map((user, index) => ( + + + + + {user.id} + + + + + + + {user.username} + + + + { - handleDeleteUser(user); + handleDisableUser( + user.is_superuser, + user.id, + user + ); }} > - - - + - - - - ))} - - )} -
-
+ + + { + handleSuperUserEdit( + user.is_superuser, + user.id, + user + ); + }} + > + + + + + { + new Date(user.create_at) + .toISOString() + .split("T")[0] + } + + + { + new Date(user.updated_at) + .toISOString() + .split("T")[0] + } + + +
+ { + handleEditUser(user.id, editUser); + }} + > + + + + - { - handleChangePagination(pageSize, pageIndex); - }} - > - + { + handleDeleteUser(user); + }} + > + + + + +
+
+ + ))} + + )} + +
+ + { + handleChangePagination(pageSize, pageIndex); + }} + > + +
-
- ) - } - + )} ); } diff --git a/src/frontend/src/pages/loginPage/index.tsx b/src/frontend/src/pages/loginPage/index.tsx index 56ce90f75..3195ca6dd 100644 --- a/src/frontend/src/pages/loginPage/index.tsx +++ b/src/frontend/src/pages/loginPage/index.tsx @@ -1,5 +1,5 @@ import * as Form from "@radix-ui/react-form"; -import { useContext, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import InputComponent from "../../components/inputComponent"; import { Button } from "../../components/ui/button"; diff --git a/src/frontend/src/types/api/index.ts b/src/frontend/src/types/api/index.ts index 9dafae733..52ed6b7e0 100644 --- a/src/frontend/src/types/api/index.ts +++ b/src/frontend/src/types/api/index.ts @@ -81,7 +81,7 @@ export type LoginAuthType = { export type Users = { id: string; username: string; - is_disabled: boolean; + is_active: boolean; is_superuser: boolean; create_at: Date; updated_at: Date; diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index a0918e692..0f2b8c8f5 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -230,6 +230,6 @@ export type loginInputStateType = { export type UserInputType = { username: string; password: string; - is_disabled: boolean; + is_active: boolean; is_superuser: boolean; }; diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 8ae5742a3..7c63dcb26 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -62,6 +62,7 @@ import { Trash2, Undo, Upload, + UserCog2, UserMinus2, UserPlus2, Users2, @@ -292,4 +293,5 @@ export const nodeIconsLucide = { FaApple, EyeOff, Eye, + UserCog2 }; diff --git a/src/frontend/tailwind.config.js b/src/frontend/tailwind.config.js index 52330ae92..5130f3fcf 100644 --- a/src/frontend/tailwind.config.js +++ b/src/frontend/tailwind.config.js @@ -201,6 +201,12 @@ module.exports = { ".dark .theme-attribution .react-flow__attribution a": { color: "black", }, + ".text-align-last-left": { + "text-align-last": "left", + }, + ".text-align-last-right": { + "text-align-last": "right", + }, }); }), require("@tailwindcss/typography"), From 19c1eb6cf6b296dacd94a30c4d6c6ea16720a0ea Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Tue, 15 Aug 2023 20:32:53 +0100 Subject: [PATCH 024/196] =?UTF-8?q?=F0=9F=90=9B=20fix(auth.py):=20change?= =?UTF-8?q?=20create=5Fuser=5Flongterm=5Ftoken=20to=20use=20create=5Fsuper?= =?UTF-8?q?=5Fuser=20to=20ensure=20super=20user=20exists=20before=20creati?= =?UTF-8?q?ng=20token=20=E2=9C=A8=20feat(auth.py):=20add=20create=5Fsuper?= =?UTF-8?q?=5Fuser=20function=20to=20create=20super=20user=20if=20it=20doe?= =?UTF-8?q?sn't=20exist=20=F0=9F=90=9B=20fix(login.py):=20remove=20hardcod?= =?UTF-8?q?ed=20user=5Fid=20and=20use=20create=5Fuser=5Flongterm=5Ftoken?= =?UTF-8?q?=20without=20arguments=20=F0=9F=90=9B=20fix(users.py):=20remove?= =?UTF-8?q?=20redundant=20password=20hashing=20in=20add=5Fsuper=5Fuser=5Ff?= =?UTF-8?q?or=5Ftesting=5Fpurposes=5Fdelete=5Fme=5Fbefore=5Fmerge=5Finto?= =?UTF-8?q?=5Fdev=20=F0=9F=90=9B=20fix(base.py):=20change=20AUTO=5FLOGIN?= =?UTF-8?q?=20default=20value=20to=20False=20and=20add=20FIRST=5FSUPERUSER?= =?UTF-8?q?=20and=20FIRST=5FSUPERUSER=5FPASSWORD=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/auth/auth.py | 34 +++++++++++++++---- src/backend/langflow/routers/login.py | 4 +-- src/backend/langflow/routers/users.py | 3 +- .../langflow/services/settings/base.py | 5 ++- 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/backend/langflow/auth/auth.py b/src/backend/langflow/auth/auth.py index b9e8dba3a..28d242342 100644 --- a/src/backend/langflow/auth/auth.py +++ b/src/backend/langflow/auth/auth.py @@ -83,18 +83,40 @@ def create_token(data: dict, expires_delta: timedelta): ) -def create_user_longterm_token( - user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False -) -> dict: +def create_super_user(db: Session = Depends(get_session)) -> User: + settings_manager = get_settings_manager() + + super_user = get_user_by_username(db, settings_manager.settings.FIRST_SUPERUSER) + + if not super_user: + super_user = User( + username=settings_manager.settings.FIRST_SUPERUSER, + password=get_password_hash( + settings_manager.settings.FIRST_SUPERUSER_PASSWORD + ), + is_superuser=True, + is_active=True, + last_login_at=None, + ) + + db.add(super_user) + db.commit() + db.refresh(super_user) + + return super_user + + +def create_user_longterm_token(db: Session = Depends(get_session)) -> dict: + super_user = create_super_user(db) + access_token_expires_longterm = timedelta(days=365) access_token = create_token( - data={"sub": str(user_id)}, + data={"sub": str(super_user.id)}, expires_delta=access_token_expires_longterm, ) # Update: last_login_at - if update_last_login: - update_user_last_login_at(user_id, db) + update_user_last_login_at(super_user.id, db) return { "access_token": access_token, diff --git a/src/backend/langflow/routers/login.py b/src/backend/langflow/routers/login.py index 7d114473d..de255a0d5 100644 --- a/src/backend/langflow/routers/login.py +++ b/src/backend/langflow/routers/login.py @@ -1,4 +1,3 @@ -from uuid import UUID from sqlalchemy.orm import Session from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm @@ -38,8 +37,7 @@ async def auto_login(db: Session = Depends(get_session)): settings_manager = get_settings_manager() if settings_manager.settings.AUTO_LOGIN: - user_id = UUID("3fa85f64-5717-4562-b3fc-2c963f66afa6") - return create_user_longterm_token(user_id, db) + return create_user_longterm_token(db) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/src/backend/langflow/routers/users.py b/src/backend/langflow/routers/users.py index da738a5cd..04972c976 100644 --- a/src/backend/langflow/routers/users.py +++ b/src/backend/langflow/routers/users.py @@ -115,14 +115,13 @@ def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev( """ new_user = User( username="superuser", - password="12345", + password=get_password_hash("12345"), is_active=True, is_superuser=True, last_login_at=None, ) try: - new_user.password = get_password_hash(new_user.password) db.add(new_user) db.commit() db.refresh(new_user) diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index d8b3f3ad6..d99f0f8b5 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -41,9 +41,12 @@ class Settings(BaseSettings): ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 REFRESH_TOKEN_EXPIRE_MINUTES: int = 70 + # If AUTO_LOGIN = True # > The application does not request login and logs in automatically as a super user. - AUTO_LOGIN: bool = True + AUTO_LOGIN: bool = False + FIRST_SUPERUSER: str = "superuser" + FIRST_SUPERUSER_PASSWORD: str = "12345" @validator("DATABASE_URL", pre=True) def set_database_url(cls, value): From 85add46761ec56b4e8bfbc7bb2adc4597899dbbe Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Tue, 15 Aug 2023 18:04:10 -0300 Subject: [PATCH 025/196] feat(AdminPage): add support for UserInputType in handleNewUser function to improve type safety and prevent potential errors fix(UserInputType): make is_active and is_superuser optional in UserInputType to allow for flexibility in user input --- src/frontend/src/pages/AdminPage/index.tsx | 3 ++- src/frontend/src/types/components/index.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index f7b34da57..9a108be9b 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -25,6 +25,7 @@ import { } from "../../controllers/API"; import ConfirmationModal from "../../modals/ConfirmationModal"; import UserManagementModal from "../../modals/UserManagementModal"; +import { UserInputType } from "../../types/components"; export default function AdminPage() { const [inputValue, setInputValue] = useState(""); @@ -164,7 +165,7 @@ export default function AdminPage() { }); } - function handleNewUser(user) { + function handleNewUser(user: UserInputType) { addUser(user) .then((res) => { resetFilter(); diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 0f2b8c8f5..ddac47d9f 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -230,6 +230,6 @@ export type loginInputStateType = { export type UserInputType = { username: string; password: string; - is_active: boolean; - is_superuser: boolean; + is_active?: boolean; + is_superuser?: boolean; }; From 91fcf33506ca227a80d1a305e7a9699ce37f7bc5 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Tue, 15 Aug 2023 23:47:50 +0100 Subject: [PATCH 026/196] =?UTF-8?q?=F0=9F=90=9B=20fix(auth.py):=20import?= =?UTF-8?q?=20get=5Fsession=20from=20correct=20module=20to=20fix=20import?= =?UTF-8?q?=20error=20=E2=9C=A8=20feat(auth.py):=20add=20support=20for=20c?= =?UTF-8?q?reating=20user=20API=20key=20and=20getting=20user=20ID=20from?= =?UTF-8?q?=20token=20=F0=9F=90=9B=20fix(base.py):=20fix=20typo=20in=20API?= =?UTF-8?q?=5FKEY=5FSECRET=5FKEY=20variable=20name=20=F0=9F=90=9B=20fix(ba?= =?UTF-8?q?se.py):=20fix=20typo=20in=20FIRST=5FSUPERUSER=20and=20FIRST=5FS?= =?UTF-8?q?UPERUSER=5FPASSWORD=20variable=20names=20=F0=9F=90=9B=20fix(bas?= =?UTF-8?q?e.py):=20fix=20indentation=20in=20load=5Fsettings=5Ffrom=5Fyaml?= =?UTF-8?q?=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/auth/auth.py | 20 +++++++++++++++++-- .../langflow/services/settings/base.py | 10 ++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/backend/langflow/auth/auth.py b/src/backend/langflow/auth/auth.py index 28d242342..9d4f12862 100644 --- a/src/backend/langflow/auth/auth.py +++ b/src/backend/langflow/auth/auth.py @@ -7,9 +7,8 @@ from fastapi.security import OAuth2PasswordBearer from fastapi import Depends, HTTPException, status from datetime import datetime, timedelta, timezone -from langflow.services.utils import get_settings_manager +from langflow.services.utils import get_settings_manager, get_session -from langflow.services.utils import get_session from langflow.database.models.user import ( User, get_user_by_id, @@ -125,6 +124,23 @@ def create_user_longterm_token(db: Session = Depends(get_session)) -> dict: } +def create_user_api_key(user_id: UUID) -> dict: + access_token = create_token( + data={"sub": str(user_id), "role": "api_key"}, + expires_delta=timedelta(days=365 * 2), + ) + + return {"api_key": access_token} + + +def get_user_id_from_token(token: str) -> UUID: + try: + user_id = jwt.get_unverified_claims(token)["sub"] + return UUID(user_id) + except (KeyError, JWTError, ValueError): + return UUID(int=0) + + def create_user_tokens( user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False ) -> dict: diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index f1b012f4d..ec976bade 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -42,11 +42,17 @@ class Settings(BaseSettings): ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 REFRESH_TOKEN_EXPIRE_MINUTES: int = 70 + # API Key to execute /process endpoint + API_KEY_SECRET_KEY: Optional[ + str + ] = "b82818e0ad4ff76615c5721ee21004b07d84cd9b87ba4d9cb42374da134b841a" + API_KEY_ALGORITHM: str = "HS256" + # If AUTO_LOGIN = True # > The application does not request login and logs in automatically as a super user. AUTO_LOGIN: bool = True - FIRST_SUPERUSER: str = "superuser" - FIRST_SUPERUSER_PASSWORD: str = "12345" + FIRST_SUPERUSER: str = "langflow" + FIRST_SUPERUSER_PASSWORD: str = "langflow" @validator("DATABASE_URL", pre=True) def set_database_url(cls, value): From d320ddd30aff22b418325434fee86320a12d1c4e Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Wed, 16 Aug 2023 08:38:49 -0300 Subject: [PATCH 027/196] =?UTF-8?q?=F0=9F=90=9B=20fix(App.tsx):=20add=20au?= =?UTF-8?q?toLogin=20function=20to=20handle=20automatic=20login=20on=20pag?= =?UTF-8?q?e=20refresh=20=F0=9F=90=9B=20fix(authLoginGuard/index.tsx):=20c?= =?UTF-8?q?hange=20condition=20to=20check=20autoLogin=20flag=20instead=20o?= =?UTF-8?q?f=20LOCALHOST=5FJWT=20flag=20=F0=9F=90=9B=20fix(headerComponent?= =?UTF-8?q?/index.tsx):=20change=20condition=20to=20check=20autoLogin=20fl?= =?UTF-8?q?ag=20instead=20of=20LOCALHOST=5FJWT=20flag=20=F0=9F=90=9B=20fix?= =?UTF-8?q?(constants.ts):=20remove=20unused=20LOCALHOST=5FJWT=20variable?= =?UTF-8?q?=20=E2=9C=A8=20feat(authContext.tsx):=20add=20autoLogin=20and?= =?UTF-8?q?=20setAutoLogin=20functions=20to=20AuthContext=20=F0=9F=90=9B?= =?UTF-8?q?=20fix(API/api.tsx):=20add=20auto=5Flogin=20endpoint=20to=20the?= =?UTF-8?q?=20list=20of=20excluded=20endpoints=20from=20Authorization=20he?= =?UTF-8?q?ader=20=E2=9C=A8=20feat(API/index.ts):=20add=20autoLogin=20func?= =?UTF-8?q?tion=20to=20handle=20automatic=20login=20=F0=9F=90=9B=20fix(Adm?= =?UTF-8?q?inPage/index.tsx):=20change=20handleDisableUser=20function=20pa?= =?UTF-8?q?rameter=20from=20user.is=5Fsuperuser=20to=20user.is=5Factive=20?= =?UTF-8?q?=F0=9F=90=9B=20fix(types/contexts/auth.ts):=20add=20autoLogin?= =?UTF-8?q?=20and=20setAutoLogin=20to=20AuthContextType?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/frontend/src/App.tsx | 68 +++++++------------ .../components/PaginatorComponent/index.tsx | 12 ---- .../src/components/authLoginGuard/index.tsx | 7 +- .../src/components/headerComponent/index.tsx | 7 +- src/frontend/src/constants/constants.ts | 2 - src/frontend/src/contexts/authContext.tsx | 5 ++ src/frontend/src/controllers/API/api.tsx | 4 ++ src/frontend/src/controllers/API/index.ts | 15 ++++ src/frontend/src/pages/AdminPage/index.tsx | 4 +- src/frontend/src/types/contexts/auth.ts | 2 + 10 files changed, 56 insertions(+), 70 deletions(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 6404daf1e..99901e3a0 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -14,10 +14,8 @@ import { alertContext } from "./contexts/alertContext"; import { AuthContext } from "./contexts/authContext"; import { locationContext } from "./contexts/locationContext"; import { TabsContext } from "./contexts/tabsContext"; -import { getLoggedUser, onLogin } from "./controllers/API"; +import { autoLogin, getLoggedUser, onLogin } from "./controllers/API"; import Router from "./routes"; -import { LOCALHOST_JWT } from "./constants/constants"; -import { LoginType } from "./types/api"; export default function App() { let { setCurrent, setShowSideBar, setIsStackedOpen } = @@ -133,52 +131,32 @@ export default function App() { }; //this function is to get the user logged in when the page is refreshed - const { setUserData, getAuthentication, login } = useContext(AuthContext); + const { setUserData, getAuthentication, login, setAutoLogin } = useContext(AuthContext); + useEffect(() => { setTimeout(() => { - if (getAuthentication && !isLoginPage) { - getLoggedUser() - .then((user) => { - setUserData(user); - }) - .catch((error) => {}); - } - }, 1000); + autoLogin().then((user) => { + if(user && user['access_token']){ + user['refresh_token'] = "auto"; + login(user['access_token'], user['refresh_token']); + setAutoLogin(true); + } + }).catch((error) => { + setAutoLogin(false); + if (getAuthentication && !isLoginPage) { + getLoggedUser() + .then((user) => { + setUserData(user); + }) + .catch((error) => {}); + } + else{ + navigate("/login"); + } + }); + }, 500); }, []); - useEffect(() => { - - if(LOCALHOST_JWT === true && isLocalHost === true){ - const user: LoginType = { - username: "superuser", - password: "12345", - }; - onLogin(user) - .then((user) => { - login(user.access_token, user.refresh_token); - getUser(); - navigate("/"); - }) - .catch((error) => { - setErrorData({ - title: "Error signing in", - list: [error["response"]["data"]["detail"]], - }); - }); - } - }, []) - - function getUser() { - if (getAuthentication) { - setTimeout(() => { - getLoggedUser() - .then((user) => { - setUserData(user); - }) - .catch((error) => {}); - }, 1000); - } - } return ( //need parent component with width and height diff --git a/src/frontend/src/components/PaginatorComponent/index.tsx b/src/frontend/src/components/PaginatorComponent/index.tsx index 295b4d7c8..0bb40fa60 100644 --- a/src/frontend/src/components/PaginatorComponent/index.tsx +++ b/src/frontend/src/components/PaginatorComponent/index.tsx @@ -65,10 +65,6 @@ export default function PaginatorComponent({ Go to first page - - - - - - - - - - - - )} + + {isAdmin && ( + + )} + {flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && ( diff --git a/src/frontend/src/contexts/authContext.tsx b/src/frontend/src/contexts/authContext.tsx index 18baa0ae2..ee0cf11d9 100644 --- a/src/frontend/src/contexts/authContext.tsx +++ b/src/frontend/src/contexts/authContext.tsx @@ -2,8 +2,11 @@ import { createContext, useEffect, useState } from "react"; import Cookies from "universal-cookie"; import { Users } from "../types/api"; import { AuthContextType } from "../types/contexts/auth"; +import { getLoggedUser } from "../controllers/API"; const initialValue: AuthContextType = { + isAdmin: false, + setIsAdmin: () => false, isAuthenticated: false, accessToken: null, refreshToken: null, @@ -22,6 +25,7 @@ export function AuthProvider({ children }): React.ReactElement { const [accessToken, setAccessToken] = useState(null); const [refreshToken, setRefreshToken] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); + const [isAdmin, setIsAdmin] = useState(false); const [userData, setUserData] = useState(null); const cookies = new Cookies(); @@ -31,6 +35,15 @@ export function AuthProvider({ children }): React.ReactElement { setAccessToken(storedAccessToken); } }, []); + + useEffect(() => { + if (accessToken) { + getLoggedUser().then((user) => { + const isSuperUser = user.is_superuser + setIsAdmin(isSuperUser); + }); + } + }, [accessToken, isAdmin]) function getAuthentication() { const storedRefreshToken = cookies.get("refresh_token"); @@ -69,6 +82,7 @@ export function AuthProvider({ children }): React.ReactElement { if (response.ok) { const data = await response.json(); login(data.accessToken, refreshToken); + getLoggedUser().then((user) => { console.log('oi')}); } else { logout(); } @@ -81,6 +95,8 @@ export function AuthProvider({ children }): React.ReactElement { // !! to convert string to boolean void; isAuthenticated: boolean; accessToken: string | null; refreshToken: string | null; From 5202e1348af05843654c0ecb3ab26acbc2a63e10 Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Wed, 16 Aug 2023 16:15:43 -0300 Subject: [PATCH 029/196] feat[headerComponent]: Add api icon on header --- src/frontend/src/components/genericIconComponent/index.tsx | 2 +- src/frontend/src/components/headerComponent/index.tsx | 6 ++++++ src/frontend/src/utils/styleUtils.ts | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/components/genericIconComponent/index.tsx b/src/frontend/src/components/genericIconComponent/index.tsx index 86c304beb..5cb6bdeca 100644 --- a/src/frontend/src/components/genericIconComponent/index.tsx +++ b/src/frontend/src/components/genericIconComponent/index.tsx @@ -9,7 +9,7 @@ const ForwardedIconComponent = forwardRef( ); diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index 3c400a860..274b4bdc3 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -146,6 +146,12 @@ export default function Header() { />
+
diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 9f4ebe21d..1c2018f69 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -72,6 +72,7 @@ import { X, XCircle, Zap, + Key } from "lucide-react"; import { FaApple, FaGithub } from "react-icons/fa"; import { AirbyteIcon } from "../icons/Airbyte"; @@ -294,5 +295,6 @@ export const nodeIconsLucide = { FaApple, EyeOff, Eye, - UserCog2 + UserCog2, + Key, }; From 4eeb9449cb607c26688c6c2a63f5b53cb648ce01 Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Wed, 16 Aug 2023 20:20:18 +0100 Subject: [PATCH 030/196] =?UTF-8?q?=F0=9F=94=A7=20fix(main.py):=20reformat?= =?UTF-8?q?=20import=20statements=20to=20improve=20readability=20and=20mai?= =?UTF-8?q?ntainability=20=E2=9C=A8=20feat(main.py):=20add=20support=20for?= =?UTF-8?q?=20API=20key=20routes=20to=20enable=20API=20key=20management=20?= =?UTF-8?q?functionality=20=F0=9F=93=9D=20docs(api=5Fkey.py):=20add=20API?= =?UTF-8?q?=20key=20routes=20for=20retrieving,=20creating,=20and=20deletin?= =?UTF-8?q?g=20API=20keys?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/main.py | 3 +- src/backend/langflow/routers/api_key.py | 49 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 src/backend/langflow/routers/api_key.py diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index b63caff24..7045ec99d 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -6,7 +6,7 @@ from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from langflow.api import router -from langflow.routers import login, users, health +from langflow.routers import api_key, login, users, health from langflow.interface.utils import setup_llm_caching from langflow.services.database.utils import initialize_database @@ -32,6 +32,7 @@ def create_app(): ) app.include_router(login.router) + app.include_router(api_key.router) app.include_router(users.router) app.include_router(health.router) diff --git a/src/backend/langflow/routers/api_key.py b/src/backend/langflow/routers/api_key.py new file mode 100644 index 000000000..7cc712962 --- /dev/null +++ b/src/backend/langflow/routers/api_key.py @@ -0,0 +1,49 @@ + + +from fastapi import APIRouter + + + +router = APIRouter(tags=["APIKey"]) + + +@router.get("/api_key/{user_id}") +def get_api_key(user_id: str): + return { + "total_count": 3, + "user_id": user_id, + "api_keys": [ + { + "id": "4425707e-cce4-4d1b-a54e-bd2632064657", + "name": "my api_key name - 01", + "created_at": "2023-08-15T19:28:40.019613", + "last_used_at": "2023-08-16T18:38:20.875210", + }, + { + "id": "6fb7282b-9f2e-4efe-9bda-0c3d8f899473", + "name": "my api_key name - 02", + "created_at": "2023-08-15T19:41:30.077942", + "last_used_at": "2023-08-15T19:45:32.067899", + }, + { + "id": "c55f3b32-4920-42b6-a5cd-698b4251806e", + "name": "my api_key name - 03", + "created_at": "2023-08-15T20:29:40.577808", + "last_used_at": "2023-08-15T20:29:40.577816", + }, + ], + } + + +@router.post("/api_key/{user_id}") +def create_api_key(user_id: str): + return { + "user_id": user_id, + "name": "my api-key 01", + "api_key": "lf-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1YTBmODM1ZS0yMTQxLTQ2YWItYmQ4NS0yMWEzMjQ1MTE2ZDAiLCJleHAiOjE2OTIyMTUwMTN9.c_s0ZPRtjSI9yUrhi8ACIwyXf0feRLYfaeIZEbRVKQg", + } + + +@router.delete("/api_key/{api_key_id}") +def delete_api_key(api_key_id: str): + return {"detail": "API Key deleted"} From a93f44d0540087cdf7bd1c3ae024568f8c2e7288 Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Wed, 16 Aug 2023 16:25:09 -0300 Subject: [PATCH 031/196] fix: login form submit when changing password visibility --- src/frontend/src/components/inputComponent/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/components/inputComponent/index.tsx b/src/frontend/src/components/inputComponent/index.tsx index 5e3c5543c..48efbc1c1 100644 --- a/src/frontend/src/components/inputComponent/index.tsx +++ b/src/frontend/src/components/inputComponent/index.tsx @@ -74,7 +74,8 @@ export default function InputComponent({ ? "input-component-true-button" : "input-component-false-button" )} - onClick={() => { + onClick={(event) => { + event.preventDefault(); setPwdVisible(!pwdVisible); }} > From ba48bbe770e9642765a9471b43bf6dd44df8a2f5 Mon Sep 17 00:00:00 2001 From: Cristhian Zanforlin Lousa Date: Wed, 16 Aug 2023 16:38:13 -0300 Subject: [PATCH 032/196] =?UTF-8?q?=F0=9F=90=9B=20fix(App.tsx):=20remove?= =?UTF-8?q?=20unnecessary=20code=20block=20in=20useEffect=20=F0=9F=94=A7?= =?UTF-8?q?=20chore(constants.ts):=20add=20CONTROL=5FNEW=5FAPI=5FKEY=20con?= =?UTF-8?q?stant=20for=20consistency=20=E2=9C=A8=20feat(SecretKeyModal):?= =?UTF-8?q?=20add=20SecretKeyModal=20component=20to=20handle=20secret=20ke?= =?UTF-8?q?y=20generation=20and=20copying=20=F0=9F=94=A7=20chore(UserManag?= =?UTF-8?q?ementModal):=20rearrange=20buttons=20in=20UserManagementModal?= =?UTF-8?q?=20for=20better=20user=20experience?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚀 feat(ApiKeysPage): add new page for managing API keys 🔧 chore(routes.tsx): add route for ApiKeysPage 🔧 chore(types): add ApiKeyType and ApiKeyInputType to improve type safety and readability of code 🔧 chore(utils): add Key icon from lucide-react to nodeIconsLucide to be used in styling --- src/frontend/src/App.tsx | 3 - src/frontend/src/constants/constants.ts | 4 + .../src/modals/SecretKeyModal/index.tsx | 191 ++++++++++ .../src/modals/UserManagementModal/index.tsx | 11 +- src/frontend/src/pages/ApiKeysPage/index.tsx | 349 ++++++++++++++++++ src/frontend/src/routes.tsx | 9 + src/frontend/src/types/components/index.ts | 15 + src/frontend/src/utils/styleUtils.ts | 4 +- 8 files changed, 578 insertions(+), 8 deletions(-) create mode 100644 src/frontend/src/modals/SecretKeyModal/index.tsx create mode 100644 src/frontend/src/pages/ApiKeysPage/index.tsx diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 99901e3a0..25b21020c 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -150,9 +150,6 @@ export default function App() { }) .catch((error) => {}); } - else{ - navigate("/login"); - } }); }, 500); }, []); diff --git a/src/frontend/src/constants/constants.ts b/src/frontend/src/constants/constants.ts index a8881d7a5..b43954067 100644 --- a/src/frontend/src/constants/constants.ts +++ b/src/frontend/src/constants/constants.ts @@ -528,6 +528,10 @@ export const CONTROL_NEW_USER = { is_superuser: false, }; +export const CONTROL_NEW_API_KEY = { + apikeyname: "", +}; + export const tabsCode = []; export function tabsArray(codes: string[], method: number) { diff --git a/src/frontend/src/modals/SecretKeyModal/index.tsx b/src/frontend/src/modals/SecretKeyModal/index.tsx new file mode 100644 index 000000000..a5a3143f6 --- /dev/null +++ b/src/frontend/src/modals/SecretKeyModal/index.tsx @@ -0,0 +1,191 @@ +import * as Form from "@radix-ui/react-form"; +import { useContext, useEffect, useRef, useState } from "react"; +import IconComponent from "../../components/genericIconComponent"; +import { Button } from "../../components/ui/button"; +import { Input } from "../../components/ui/input"; +import { CONTROL_NEW_API_KEY } from "../../constants/constants"; +import { alertContext } from "../../contexts/alertContext"; +import { + ApiKeyInputType, + ApiKeyType, + inputHandlerEventType, +} from "../../types/components"; +import { nodeIconsLucide } from "../../utils/styleUtils"; +import BaseModal from "../baseModal"; + +export default function SecretKeyModal({ + title, + cancelText, + confirmationText, + children, + icon, + data, + index, + onConfirm, +}: ApiKeyType) { + const Icon: any = nodeIconsLucide[icon]; + const [open, setOpen] = useState(false); + const [apiKeyName, setApiKeyName] = useState(data?.apikeyname ?? ""); + const [apiKeyValue, setApiKeyValue] = useState("Value"); + const [inputState, setInputState] = + useState(CONTROL_NEW_API_KEY); + const [renderKey, setRenderKey] = useState(false); + const [textCopied, setTextCopied] = useState(true); + const { setSuccessData } = useContext(alertContext); + const inputRef = useRef(null); + + function handleInput({ + target: { name, value }, + }: inputHandlerEventType): void { + setInputState((prev) => ({ ...prev, [name]: value })); + } + + useEffect(() => { + if (open) { + setRenderKey(false); + resetForm(); + } + }, [open]); + + function resetForm() { + setApiKeyName(""); + setApiKeyValue("Value"); + } + + const handleCopyClick = async () => { + if (apiKeyValue) { + await navigator.clipboard.writeText(apiKeyValue); + inputRef.current.focus(); + inputRef.current.select(); + setSuccessData({ + title: "API Key copied!", + }); + setTextCopied(false); + + setTimeout(() => { + setTextCopied(true); + }, 3000); + } + }; + + return ( + + {children} + + {title} + + + {renderKey === true && ( + <> + + Please save this secret key somewhere safe and accessible. For + security reasons,{" "} + you won't be able to view it again through your + account. If you lose this secret key, you'll need to generate a + new one. + +
+
+ { + setApiKeyValue(event.target.value); + }} + readOnly={true} + value={apiKeyValue} + /> +
+ +
+ +
+
+ + )} + + { + setRenderKey(true); + event.preventDefault(); + }} + > + {renderKey === false && ( +
+ +
+ + Name (optional){" "} + +
+ + { + handleInput({ target: { name: "apikeyname", value } }); + setApiKeyName(value); + }} + value={apiKeyName} + className="primary-input" + placeholder="My key name" + /> + +
+
+ )} + {renderKey === false && ( +
+ + + + + +
+ )} + + {renderKey === true && ( +
+ +
+ )} +
+
+
+ ); +} diff --git a/src/frontend/src/modals/UserManagementModal/index.tsx b/src/frontend/src/modals/UserManagementModal/index.tsx index d370ef841..993ffb944 100644 --- a/src/frontend/src/modals/UserManagementModal/index.tsx +++ b/src/frontend/src/modals/UserManagementModal/index.tsx @@ -273,17 +273,20 @@ export default function UserManagementModal({
- - - - + + + + +
diff --git a/src/frontend/src/pages/ApiKeysPage/index.tsx b/src/frontend/src/pages/ApiKeysPage/index.tsx new file mode 100644 index 000000000..a08e155f4 --- /dev/null +++ b/src/frontend/src/pages/ApiKeysPage/index.tsx @@ -0,0 +1,349 @@ +import { cloneDeep } from "lodash"; +import { useContext, useEffect, useRef, useState } from "react"; +import ShadTooltip from "../../components/ShadTooltipComponent"; +import IconComponent from "../../components/genericIconComponent"; +import { Button } from "../../components/ui/button"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "../../components/ui/table"; +import { alertContext } from "../../contexts/alertContext"; +import { AuthContext } from "../../contexts/authContext"; +import { + addUser, + deleteUser, + getUsersPage, + updateUser, +} from "../../controllers/API"; +import ConfirmationModal from "../../modals/ConfirmationModal"; +import SecretKeyModal from "../../modals/SecretKeyModal"; +import { UserInputType } from "../../types/components"; + +export default function ApiKeysPage() { + const [inputValue, setInputValue] = useState(""); + + const [size, setPageSize] = useState(10); + const [index, setPageIndex] = useState(0); + const [loadingUsers, setLoadingUsers] = useState(true); + const { setErrorData, setSuccessData } = useContext(alertContext); + const { userData } = useContext(AuthContext); + const [totalRowsCount, setTotalRowsCount] = useState(0); + + const userList = useRef([]); + + useEffect(() => { + setTimeout(() => { + getUsers(); + }, 500); + }, []); + + const [filterUserList, setFilterUserList] = useState(userList.current); + + function getUsers() { + setLoadingUsers(true); + getUsersPage(index, size) + .then((users) => { + setTotalRowsCount(users["total_count"]); + userList.current = users["users"]; + setFilterUserList(users["users"]); + setLoadingUsers(false); + }) + .catch((error) => { + setLoadingUsers(false); + }); + } + + function handleChangePagination(pageIndex: number, pageSize: number) { + setLoadingUsers(true); + getUsersPage(pageIndex, pageSize) + .then((users) => { + setTotalRowsCount(users["total_count"]); + userList.current = users["users"]; + setFilterUserList(users["users"]); + setLoadingUsers(false); + }) + .catch((error) => { + setLoadingUsers(false); + }); + } + + function resetFilter() { + setPageIndex(0); + setPageSize(10); + getUsers(); + } + + function handleFilterUsers(input: string) { + setInputValue(input); + + if (input === "") { + setFilterUserList(userList.current); + } else { + const filteredList = userList.current.filter((user) => + user.username.toLowerCase().includes(input.toLowerCase()) + ); + setFilterUserList(filteredList); + } + } + + function handleDeleteUser(user) { + deleteUser(user.id) + .then((res) => { + resetFilter(); + setSuccessData({ + title: "Success! User deleted!", + }); + }) + .catch((error) => { + setErrorData({ + title: "Error on delete user", + list: [error["response"]["data"]["detail"]], + }); + }); + } + + function handleEditUser(userId, user) { + updateUser(userId, user) + .then((res) => { + resetFilter(); + setSuccessData({ + title: "Success! User edited!", + }); + }) + .catch((error) => { + setErrorData({ + title: "Error on edit user", + list: [error["response"]["data"]["detail"]], + }); + }); + } + + function handleDisableUser(check, userId, user) { + const userEdit = cloneDeep(user); + userEdit.is_active = !check; + + updateUser(userId, userEdit) + .then((res) => { + console.log(res); + + resetFilter(); + setSuccessData({ + title: "Success! User edited!", + }); + }) + .catch((error) => { + setErrorData({ + title: "Error on edit user", + list: [error["response"]["data"]["detail"]], + }); + }); + } + + function handleSuperUserEdit(check, userId, user) { + const userEdit = cloneDeep(user); + userEdit.is_superuser = !check; + updateUser(userId, userEdit) + .then((res) => { + resetFilter(); + setSuccessData({ + title: "Success! User edited!", + }); + }) + .catch((error) => { + setErrorData({ + title: "Error on edit user", + list: [error["response"]["data"]["detail"]], + }); + }); + } + + function handleNewUser(user: UserInputType) { + addUser(user) + .then((res) => { + resetFilter(); + setSuccessData({ + title: "Success! New user added!", + }); + }) + .catch((error) => { + setErrorData({ + title: "Error on add new user", + list: [error["response"]["data"]["detail"]], + }); + }); + } + + function lastUsedMessage() { + return ( +
+ + The last time this key was used.

Accurate to within the hour + from the most recent usage. +
+
+ ); + } + + return ( + <> + {userData && ( +
+
+
+
+
+
+

+ API keys +

+

+ Your secret API keys are listed below. Please note that we + do not display your secret API keys again after you + generate them.

+ Do not share your API key with others, or expose it in the + browser or other client-side code. +

+
+
+
+ + {userList.current.length === 0 && !loadingUsers && ( + <> +
+

There's no users registered :)

+
+ + )} + <> + {loadingUsers && ( +
+ Loading... +
+ )} +
+ + + + Name + Key + Created + + Last Used + +
+ +
+
+
+ +
+
+ {!loadingUsers && ( + + {filterUserList.map((user, index) => ( + + + + + {user.id} + + + + + + + {user.username} + + + + + { + new Date(user.create_at) + .toISOString() + .split("T")[0] + } + + + { + new Date(user.updated_at) + .toISOString() + .split("T")[0] + } + + +
+ { + handleDeleteUser(user); + }} + > + + + + +
+
+
+ ))} +
+ )} +
+
+ +
+
+ { + handleNewUser(user); + }} + > + + +
+
+ +
+
+
+
+ )} + + ); +} diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx index 81421d057..88d485155 100644 --- a/src/frontend/src/routes.tsx +++ b/src/frontend/src/routes.tsx @@ -11,6 +11,7 @@ import HomePage from "./pages/MainPage"; import DeleteAccountPage from "./pages/deleteAccountPage"; import LoginPage from "./pages/loginPage"; import SignUp from "./pages/signUpPage"; +import ApiKeysPage from "./pages/ApiKeysPage"; const Router = () => { return ( @@ -93,6 +94,14 @@ const Router = () => { } > + + + + } + > ); diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index ddac47d9f..053eb50df 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -233,3 +233,18 @@ export type UserInputType = { is_active?: boolean; is_superuser?: boolean; }; + +export type ApiKeyType = { + title: string; + cancelText: string; + confirmationText: string; + children: ReactElement; + icon: string; + data?: any; + index?: number; + onConfirm: (index, data) => void; +}; + +export type ApiKeyInputType = { + apikeyname: string; +}; \ No newline at end of file diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 9f4ebe21d..0f82635f6 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -72,6 +72,7 @@ import { X, XCircle, Zap, + Key } from "lucide-react"; import { FaApple, FaGithub } from "react-icons/fa"; import { AirbyteIcon } from "../icons/Airbyte"; @@ -294,5 +295,6 @@ export const nodeIconsLucide = { FaApple, EyeOff, Eye, - UserCog2 + UserCog2, + Key }; From 98a24d331c1ff482725a726f2865c0016070621c Mon Sep 17 00:00:00 2001 From: gustavoschaedler Date: Wed, 16 Aug 2023 21:00:39 +0100 Subject: [PATCH 033/196] =?UTF-8?q?=F0=9F=94=A7=20fix(api=5Fkey.py):=20rem?= =?UTF-8?q?ove=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey?= =?UTF-8?q?.py):=20remove=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fi?= =?UTF-8?q?x(api=5Fkey.py):=20remove=20unnecessary=20empty=20line=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unnecessary=20empt?= =?UTF-8?q?y=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unneces?= =?UTF-8?q?sary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remov?= =?UTF-8?q?e=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.p?= =?UTF-8?q?y):=20remove=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(?= =?UTF-8?q?api=5Fkey.py):=20remove=20unnecessary=20empty=20line=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unnecessary=20empt?= =?UTF-8?q?y=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unneces?= =?UTF-8?q?sary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remov?= =?UTF-8?q?e=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.p?= =?UTF-8?q?y):=20remove=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(?= =?UTF-8?q?api=5Fkey.py):=20remove=20unnecessary=20empty=20line=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unnecessary=20empt?= =?UTF-8?q?y=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unneces?= =?UTF-8?q?sary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remov?= =?UTF-8?q?e=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.p?= =?UTF-8?q?y):=20remove=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(?= =?UTF-8?q?api=5Fkey.py):=20remove=20unnecessary=20empty=20line=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unnecessary=20empt?= =?UTF-8?q?y=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unneces?= =?UTF-8?q?sary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remov?= =?UTF-8?q?e=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.p?= =?UTF-8?q?y):=20remove=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(?= =?UTF-8?q?api=5Fkey.py):=20remove=20unnecessary=20empty=20line=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unnecessary=20empt?= =?UTF-8?q?y=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unneces?= =?UTF-8?q?sary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remov?= =?UTF-8?q?e=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.p?= =?UTF-8?q?y):=20remove=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(?= =?UTF-8?q?api=5Fkey.py):=20remove=20unnecessary=20empty=20line=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unnecessary=20empt?= =?UTF-8?q?y=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unneces?= =?UTF-8?q?sary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remov?= =?UTF-8?q?e=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.p?= =?UTF-8?q?y):=20remove=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(?= =?UTF-8?q?api=5Fkey.py):=20remove=20unnecessary=20empty=20line=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unnecessary=20empt?= =?UTF-8?q?y=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remove=20unneces?= =?UTF-8?q?sary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.py):=20remov?= =?UTF-8?q?e=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(api=5Fkey.p?= =?UTF-8?q?y):=20remove=20unnecessary=20empty=20line=20=F0=9F=94=A7=20fix(?= =?UTF-8?q?api=5Fkey.py):=20remove=20unnecessary=20empty=20line=20?= =?UTF-8?q?=F0=9F=94=A7=20fix(api=5Fkey?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/routers/api_key.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backend/langflow/routers/api_key.py b/src/backend/langflow/routers/api_key.py index 7cc712962..9fa6acec5 100644 --- a/src/backend/langflow/routers/api_key.py +++ b/src/backend/langflow/routers/api_key.py @@ -1,9 +1,6 @@ - - from fastapi import APIRouter - router = APIRouter(tags=["APIKey"]) @@ -15,18 +12,21 @@ def get_api_key(user_id: str): "api_keys": [ { "id": "4425707e-cce4-4d1b-a54e-bd2632064657", + "api_key": "lf-...abcd", "name": "my api_key name - 01", "created_at": "2023-08-15T19:28:40.019613", "last_used_at": "2023-08-16T18:38:20.875210", }, { "id": "6fb7282b-9f2e-4efe-9bda-0c3d8f899473", + "api_key": "lf-...abcd", "name": "my api_key name - 02", "created_at": "2023-08-15T19:41:30.077942", "last_used_at": "2023-08-15T19:45:32.067899", }, { "id": "c55f3b32-4920-42b6-a5cd-698b4251806e", + "api_key": "lf-...abcd", "name": "my api_key name - 03", "created_at": "2023-08-15T20:29:40.577808", "last_used_at": "2023-08-15T20:29:40.577816", From 1ad78dca1fe897b60f8c3b91a5d0c7343d4c8a66 Mon Sep 17 00:00:00 2001 From: igorrCarvalho Date: Wed, 16 Aug 2023 18:14:49 -0300 Subject: [PATCH 034/196] Feat: add user on signUp button form --- src/frontend/src/App.tsx | 47 ++++++++--------- .../components/PaginatorComponent/index.tsx | 12 +++-- .../src/components/authLoginGuard/index.tsx | 2 +- .../src/components/headerComponent/index.tsx | 11 ++-- src/frontend/src/constants/constants.ts | 3 ++ src/frontend/src/contexts/authContext.tsx | 14 ++--- src/frontend/src/controllers/API/api.tsx | 7 +-- src/frontend/src/controllers/API/index.ts | 4 +- src/frontend/src/pages/loginPage/index.tsx | 2 +- src/frontend/src/pages/signUpPage/index.tsx | 51 +++++++++++++++++-- src/frontend/src/types/contexts/auth.ts | 2 +- src/frontend/src/utils/styleUtils.ts | 2 +- 12 files changed, 99 insertions(+), 58 deletions(-) diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 99901e3a0..beab8c683 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -14,7 +14,7 @@ import { alertContext } from "./contexts/alertContext"; import { AuthContext } from "./contexts/authContext"; import { locationContext } from "./contexts/locationContext"; import { TabsContext } from "./contexts/tabsContext"; -import { autoLogin, getLoggedUser, onLogin } from "./controllers/API"; +import { autoLogin, getLoggedUser } from "./controllers/API"; import Router from "./routes"; export default function App() { @@ -37,7 +37,7 @@ export default function App() { successData, successOpen, setSuccessOpen, - setErrorData + setErrorData, } = useContext(alertContext); const navigate = useNavigate(); @@ -131,33 +131,34 @@ export default function App() { }; //this function is to get the user logged in when the page is refreshed - const { setUserData, getAuthentication, login, setAutoLogin } = useContext(AuthContext); + const { setUserData, getAuthentication, login, setAutoLogin } = + useContext(AuthContext); useEffect(() => { setTimeout(() => { - autoLogin().then((user) => { - if(user && user['access_token']){ - user['refresh_token'] = "auto"; - login(user['access_token'], user['refresh_token']); - setAutoLogin(true); - } - }).catch((error) => { - setAutoLogin(false); - if (getAuthentication && !isLoginPage) { - getLoggedUser() - .then((user) => { - setUserData(user); - }) - .catch((error) => {}); - } - else{ - navigate("/login"); - } - }); + autoLogin() + .then((user) => { + if (user && user["access_token"]) { + user["refresh_token"] = "auto"; + login(user["access_token"], user["refresh_token"]); + setAutoLogin(true); + } + }) + .catch((error) => { + setAutoLogin(false); + if (getAuthentication && !isLoginPage) { + getLoggedUser() + .then((user) => { + setUserData(user); + }) + .catch((error) => {}); + } else { + navigate("/login"); + } + }); }, 500); }, []); - return ( //need parent component with width and height
diff --git a/src/frontend/src/components/PaginatorComponent/index.tsx b/src/frontend/src/components/PaginatorComponent/index.tsx index 0bb40fa60..0e9242f15 100644 --- a/src/frontend/src/components/PaginatorComponent/index.tsx +++ b/src/frontend/src/components/PaginatorComponent/index.tsx @@ -19,7 +19,9 @@ export default function PaginatorComponent({ }: PaginatorComponentType) { const [size, setPageSize] = useState(pageSize); const [index, setPageIndex] = useState(pageIndex); - const [maxIndex, setMaxPageIndex] = useState(Math.ceil(totalRowsCount/pageSize)); + const [maxIndex, setMaxPageIndex] = useState( + Math.ceil(totalRowsCount / pageSize) + ); const [currentPage, setCurrentPage] = useState(1); return ( @@ -32,7 +34,7 @@ export default function PaginatorComponent({