diff --git a/frontend/src/features/Auth/atoms.ts b/frontend/src/features/Auth/atoms.ts new file mode 100644 index 0000000..0446606 --- /dev/null +++ b/frontend/src/features/Auth/atoms.ts @@ -0,0 +1,8 @@ +import { atom } from 'jotai'; +import { AUTH_TOKEN_LOCAL_STORAGE_KEY } from './consts'; + +export const AUTH_TOKEN_ATOM = atom(localStorage.getItem(AUTH_TOKEN_LOCAL_STORAGE_KEY)); + +export const TESTNET_USERNAME_ATOM = atom(''); + +export const IS_AUTH_READY_ATOM = atom(false); diff --git a/frontend/src/features/Auth/components/ProtectedRoute/ProtectedRoute.tsx b/frontend/src/features/Auth/components/ProtectedRoute/ProtectedRoute.tsx index 49323f5..86ebb6a 100644 --- a/frontend/src/features/Auth/components/ProtectedRoute/ProtectedRoute.tsx +++ b/frontend/src/features/Auth/components/ProtectedRoute/ProtectedRoute.tsx @@ -1,11 +1,11 @@ import { Navigate, useLocation } from 'react-router'; import { useAccount } from '@gear-js/react-hooks'; import { ProtectedRouteProps } from './ProtectedRoute.interface'; -import { useAuth } from '../../hooks'; import { LOGIN, NOT_AUTHORIZED } from '@/App.routes'; +import { AUTH_TOKEN_LOCAL_STORAGE_KEY } from '../../consts'; function ProtectedRoute({ children }: ProtectedRouteProps) { - const { authToken } = useAuth(); + const authToken = localStorage.getItem(AUTH_TOKEN_LOCAL_STORAGE_KEY); const { account } = useAccount(); const location = useLocation(); @@ -13,7 +13,7 @@ function ProtectedRoute({ children }: ProtectedRouteProps) { return ; } - if (!authToken && !account) { + if (!authToken) { return ; } diff --git a/frontend/src/features/Auth/consts.ts b/frontend/src/features/Auth/consts.ts index 733fc1d..4d78444 100644 --- a/frontend/src/features/Auth/consts.ts +++ b/frontend/src/features/Auth/consts.ts @@ -1,11 +1,5 @@ -import { atom } from 'jotai'; +export const AUTH_API_ADDRESS = process.env.REACT_APP_AUTH_API_ADDRESS as string; -const AUTH_API_ADDRESS = process.env.REACT_APP_AUTH_API_ADDRESS as string; +export const AUTH_MESSAGE = 'VARA'; -const AUTH_MESSAGE = 'VARA'; - -const AUTH_TOKEN_LOCAL_STORAGE_KEY = 'authToken'; - -const AUTH_TOKEN_ATOM = atom(localStorage.getItem(AUTH_TOKEN_LOCAL_STORAGE_KEY)); - -export { AUTH_API_ADDRESS, AUTH_MESSAGE, AUTH_TOKEN_LOCAL_STORAGE_KEY, AUTH_TOKEN_ATOM }; +export const AUTH_TOKEN_LOCAL_STORAGE_KEY = 'authToken'; diff --git a/frontend/src/features/Auth/hooks.ts b/frontend/src/features/Auth/hooks.ts index bb54edb..4f690e8 100644 --- a/frontend/src/features/Auth/hooks.ts +++ b/frontend/src/features/Auth/hooks.ts @@ -3,21 +3,25 @@ import { useAtom } from 'jotai'; import { useAlert, useAccount, Account } from '@gear-js/react-hooks'; import { useEffect } from 'react'; import { web3FromAddress } from '@polkadot/extension-dapp'; -import { AUTH_MESSAGE, AUTH_TOKEN_ATOM, AUTH_TOKEN_LOCAL_STORAGE_KEY } from './consts'; +import { AUTH_MESSAGE, AUTH_TOKEN_LOCAL_STORAGE_KEY } from './consts'; +import { AUTH_TOKEN_ATOM, IS_AUTH_READY_ATOM, TESTNET_USERNAME_ATOM } from './atoms'; import { fetchAuth, post } from './utils'; import { AuthResponse, ISignInError, SignInResponse } from './types'; - import { NOT_AUTHORIZED, PLAY } from '@/App.routes'; function useAuth() { - const { login, logout } = useAccount(); + const { account, login, logout } = useAccount(); const alert = useAlert(); const [authToken, setAuthToken] = useAtom(AUTH_TOKEN_ATOM); + const [isAuthReady, setIsAuthReady] = useAtom(IS_AUTH_READY_ATOM); + const [testnameUsername, setTestnameUsername] = useAtom(TESTNET_USERNAME_ATOM); const navigate = useNavigate(); const location = useLocation(); const from = location.state?.from?.pathname || PLAY; + const isAwaitingVerification = account && !authToken; + // eslint-disable-next-line @typescript-eslint/no-shadow const signIn = async (account: Account) => { const { address } = account; @@ -44,14 +48,16 @@ function useAuth() { } setAuthToken(null); + setTestnameUsername(''); await login(account); navigate(NOT_AUTHORIZED, { replace: true }); } else { const data: SignInResponse = await res.json(); - const { accessToken } = data; + const { accessToken, username } = data; await login(account); setAuthToken(accessToken); + setTestnameUsername(username || ''); navigate(from, { replace: true }); } } catch (e) { @@ -65,30 +71,46 @@ function useAuth() { }; const auth = () => { - if (!authToken) return; + const localStorageToken = localStorage.getItem(AUTH_TOKEN_LOCAL_STORAGE_KEY); + + if (!localStorageToken) { + setIsAuthReady(true); + if (!isAwaitingVerification) logout(); + return; + } - fetchAuth('auth/me', 'PUT', authToken).catch(({ message }: Error) => { - signOut(); - alert.error(message); - }); + fetchAuth('auth/me', 'PUT', localStorageToken) + .then(({ username }) => { + setAuthToken(localStorageToken); + setTestnameUsername(username || ''); + }) + .catch(({ message }: Error) => { + signOut(); + localStorage.removeItem(AUTH_TOKEN_LOCAL_STORAGE_KEY); + alert.error(message); + }) + .finally(() => setIsAuthReady(true)); }; - return { authToken, signIn, signOut, auth }; + return { authToken, signIn, signOut, auth, isAuthReady, isAwaitingVerification, testnameUsername }; } function useAuthSync() { - const { authToken, auth } = useAuth(); + const { isAccountReady } = useAccount(); + const { authToken, isAuthReady, auth } = useAuth(); useEffect(() => { + if (!isAccountReady) return; auth(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [authToken]); useEffect(() => { + if (!isAuthReady) return; if (!authToken) return localStorage.removeItem(AUTH_TOKEN_LOCAL_STORAGE_KEY); localStorage.setItem(AUTH_TOKEN_LOCAL_STORAGE_KEY, authToken); - }, [authToken]); + }, [isAuthReady, authToken]); } export { useAuth, useAuthSync }; diff --git a/frontend/src/features/Auth/types.ts b/frontend/src/features/Auth/types.ts index 82f553b..cacad06 100644 --- a/frontend/src/features/Auth/types.ts +++ b/frontend/src/features/Auth/types.ts @@ -1,11 +1,13 @@ type SignInResponse = { accessToken: string; + username: string; }; type AuthResponse = { email: string; id: string; publicKey: string; + username: string; }; export type { SignInResponse, AuthResponse };