mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-19 01:40:15 +01:00
feat: Auth and User System (#205)
* server-side JWT auth implementation * move oauth routes and strategies, fix bugs * backend modifications for wiring up the frontend login and reg forms * Add frontend data services for login and registration * Add login and registration forms * Implment auth context, functional client side auth * protect routes with jwt auth * finish local strategy (using local storage) * Start setting up google auth * disable token refresh, remove old auth middleware * refactor client, add ApiErrorBoundary context * disable google and facebook strategies * fix: fix presets not displaying specific to user * fix: fix issue with browser refresh * fix: casing issue with User.js (#11) * delete user.js to be renamed * fix: fix casing issue with User.js * comment out api error watcher temporarily * fix: issue with api error watcher (#12) * delete user.js to be renamed * fix: fix casing issue with User.js * comment out api error watcher temporarily * feat: add google auth social login * fix: make google login url dynamic based on dev/prod * fix: bug where UI is briefly displayed before redirecting to login * fix: fix cookie expires value for local auth * Update README.md * Update LOCAL_INSTALL structure * Add local testing instructions * Only load google strategy if client id and secret are provided * Update .env.example files with new params * fix issue with not redirecting to register form * only show google login button if value is set in .env * cleanup log messages * Add label to button for google login on login form * doc: fix client/server url values in .env.example * feat: add error message details to registration failure * Restore preventing paste on confirm password * auto-login user after registering * feat: forgot password (#24) * make login/reg pages look like openai's * add password reset data services * new form designs similar to openai, add password reset pages * add api's for password reset * email utils for password reset * remove bcrypt salt rounds from process.env * refactor: restructure api auth code, consolidate routes (#25) * add api's for password reset * remove bcrypt salt rounds from process.env * refactor: consolidate auth routes, use controller pattern * refactor: code cleanup * feat: migrate data to first user (#26) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes after refactor (#27) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: issue with auto-login when logging out then logging in with new browser window (#28) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: fix issue with auto-login in new tab * doc: Update README and .env.example files with user system information (#29) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: fix issue with auto-login in new tab * doc: update README and .env.example files * Fixup: LOCAL_INSTALL.md PS instructions (#200) (#30) Co-authored-by: alfredo-f <alfredo.fomitchenko@mail.polimi.it> * feat: send user with completion to protect against abuse (#31) * Fixup: LOCAL_INSTALL.md PS instructions (#200) * server-side JWT auth implementation * move oauth routes and strategies, fix bugs * backend modifications for wiring up the frontend login and reg forms * Add frontend data services for login and registration * Add login and registration forms * Implment auth context, functional client side auth * protect routes with jwt auth * finish local strategy (using local storage) * Start setting up google auth * disable token refresh, remove old auth middleware * refactor client, add ApiErrorBoundary context * disable google and facebook strategies * fix: fix presets not displaying specific to user * fix: fix issue with browser refresh * fix: casing issue with User.js (#11) * delete user.js to be renamed * fix: fix casing issue with User.js * comment out api error watcher temporarily * feat: add google auth social login * fix: make google login url dynamic based on dev/prod * fix: bug where UI is briefly displayed before redirecting to login * fix: fix cookie expires value for local auth * Only load google strategy if client id and secret are provided * Update .env.example files with new params * fix issue with not redirecting to register form * only show google login button if value is set in .env * cleanup log messages * Add label to button for google login on login form * doc: fix client/server url values in .env.example * feat: add error message details to registration failure * Restore preventing paste on confirm password * auto-login user after registering * feat: forgot password (#24) * make login/reg pages look like openai's * add password reset data services * new form designs similar to openai, add password reset pages * add api's for password reset * email utils for password reset * remove bcrypt salt rounds from process.env * refactor: restructure api auth code, consolidate routes (#25) * add api's for password reset * remove bcrypt salt rounds from process.env * refactor: consolidate auth routes, use controller pattern * refactor: code cleanup * feat: migrate data to first user (#26) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes after refactor (#27) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: issue with auto-login when logging out then logging in with new browser window (#28) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: fix issue with auto-login in new tab * doc: Update README and .env.example files with user system information (#29) * refactor: use /api for auth routes * fix: use user id instead of username * feat: migrate data to first user on register * fix: fix social login routes * fix: fix issue with auto-login in new tab * doc: update README and .env.example files * Send user id to openai to protect against abuse * add meilisearch to gitignore * Remove webpack --------- Co-authored-by: alfredo-f <alfredo.fomitchenko@mail.polimi.it> --------- Co-authored-by: Danny Avila <110412045+danny-avila@users.noreply.github.com> Co-authored-by: Alfredo Fomitchenko <alfredo.fomitchenko@mail.polimi.it>
This commit is contained in:
parent
65543eb084
commit
dac19038a3
68 changed files with 3968 additions and 3394 deletions
175
client/src/hooks/AuthContext.tsx
Normal file
175
client/src/hooks/AuthContext.tsx
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
import { useState, useCallback, useEffect, useMemo, ReactNode, createContext, useContext } from 'react';
|
||||
import {
|
||||
TUser,
|
||||
TLoginResponse,
|
||||
setTokenHeader,
|
||||
useLoginUserMutation,
|
||||
useLogoutUserMutation,
|
||||
useGetUserQuery,
|
||||
useRefreshTokenMutation,
|
||||
TLoginUser
|
||||
} from '~/data-provider';
|
||||
import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import store from '~/store';
|
||||
|
||||
export type TAuthContext = {
|
||||
user: TUser | undefined,
|
||||
token: string | undefined,
|
||||
isAuthenticated: boolean,
|
||||
isLoading: boolean,
|
||||
error: string | undefined,
|
||||
login: (data: TLoginUser) => void,
|
||||
logout: () => void
|
||||
};
|
||||
|
||||
export type TUserContext = {
|
||||
user?: TUser | undefined,
|
||||
token: string | undefined,
|
||||
isAuthenticated: boolean,
|
||||
redirect?: string
|
||||
};
|
||||
|
||||
const AuthContext = createContext <TAuthContext | undefined>(undefined);
|
||||
|
||||
const AuthContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [user, setUser] = useState<TUser | undefined>(undefined);
|
||||
const [token, setToken] = useState <string | undefined>(undefined);
|
||||
const [error, setError] = useState <string | undefined>(undefined);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
const loginUser = useLoginUserMutation();
|
||||
const logoutUser = useLogoutUserMutation();
|
||||
const userQuery = useGetUserQuery({ enabled: !!token });
|
||||
const refreshToken = useRefreshTokenMutation();
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const { newConversation } = store.useConversation();
|
||||
|
||||
const setUserContext = (userContext: TUserContext) => {
|
||||
const { token, isAuthenticated, user, redirect } = userContext;
|
||||
if(user) {
|
||||
setUser(user);
|
||||
}
|
||||
setToken(token);
|
||||
setTokenHeader(token);
|
||||
setIsAuthenticated(isAuthenticated);
|
||||
if (redirect) {
|
||||
navigate(redirect);
|
||||
}
|
||||
};
|
||||
|
||||
const getCookieValue = key => {
|
||||
let keyValue = document.cookie.match('(^|;) ?' + key + '=([^;]*)(;|$)');
|
||||
return keyValue ? keyValue[2] : null;
|
||||
};
|
||||
|
||||
const login = (data: TLoginUser) => {
|
||||
loginUser.mutate(data, {
|
||||
onSuccess: (data: TLoginResponse) => {
|
||||
const { user, token } = data;
|
||||
setUserContext({ token, isAuthenticated: true, user, redirect: '/chat/new' });
|
||||
},
|
||||
onError: error => {
|
||||
setError(error.message);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
document.cookie.split(';').forEach(c => {
|
||||
document.cookie = c
|
||||
.replace(/^ +/, '')
|
||||
.replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/');
|
||||
});
|
||||
logoutUser.mutate(undefined, {
|
||||
onSuccess: () => {
|
||||
setUserContext({ token: undefined, isAuthenticated: false, user: undefined, redirect: '/login' });
|
||||
},
|
||||
onError: error => {
|
||||
setError(error.message);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (userQuery.data) {
|
||||
setUser(userQuery.data);
|
||||
}
|
||||
else if (userQuery.isError) {
|
||||
setError(userQuery.error.message);
|
||||
navigate('/login');
|
||||
}
|
||||
if (error && isAuthenticated) {
|
||||
setError(undefined);
|
||||
}
|
||||
if (!token || !isAuthenticated) {
|
||||
const tokenFromCookie = getCookieValue('token');
|
||||
if (tokenFromCookie) {
|
||||
// debugger;
|
||||
setUserContext({ token: tokenFromCookie, isAuthenticated: true, user: userQuery.data, redirect: '/chat/new' })
|
||||
}
|
||||
else {
|
||||
navigate('/login');
|
||||
}
|
||||
}
|
||||
}, [token, isAuthenticated, userQuery.data, userQuery.isError]);
|
||||
|
||||
// const silentRefresh = useCallback(() => {
|
||||
// refreshToken.mutate(undefined, {
|
||||
// onSuccess: (data: TLoginResponse) => {
|
||||
// const { user, token } = data;
|
||||
// setUserContext({ token, isAuthenticated: true, user });
|
||||
// },
|
||||
// onError: error => {
|
||||
// setError(error.message);
|
||||
// }
|
||||
// });
|
||||
// setTimeout(silentRefresh, 5 * 60 * 1000);
|
||||
// }, [setUserContext]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loginUser.isLoading || logoutUser.isLoading) {
|
||||
setIsLoading(true);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [loginUser.isLoading, logoutUser.isLoading]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (token)
|
||||
// silentRefresh();
|
||||
// }, [token, silentRefresh]);
|
||||
|
||||
// Make the provider update only when it should
|
||||
const memoedValue = useMemo(
|
||||
() => ({
|
||||
user,
|
||||
token,
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
error,
|
||||
login,
|
||||
logout
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[user, isLoading, error, isAuthenticated, token]
|
||||
);
|
||||
|
||||
return <AuthContext.Provider value={memoedValue}>{children}</AuthContext.Provider>;
|
||||
};
|
||||
|
||||
const useAuthContext = () => {
|
||||
const context = useContext(AuthContext);
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('useAuthContext should be used inside AuthProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
export { AuthContextProvider, useAuthContext };
|
||||
Loading…
Add table
Add a link
Reference in a new issue