packages/app/src/account-provider.tsx (82 lines of code) (raw):

/** * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as React from "react"; import Backdrop from '@mui/material/Backdrop'; import CircularProgress from '@mui/material/CircularProgress'; import { Navigate, useLocation } from "react-router-dom"; import { useFlashMessage } from './flash-message-provider.tsx'; interface AccountContextType { authenticated: false, registryHref?: string, login?: string, ott?: string; logout: () => Promise<void>; } const AccountContext = React.createContext<AccountContextType>(null!); export function AccountProvider({ children }: { children: React.ReactNode }) { const [authenticated, setAuthenticated] = React.useState<any>(false); const [registryHref, setRegistryHref] = React.useState<any>(null); const [login, setLogin] = React.useState<any>(null); const [loading, setLoading] = React.useState<any>(true); const [initialLoad, setInitialLoad] = React.useState<boolean>(true); const [ott, setOtt] = React.useState<any>(undefined); const flash = useFlashMessage(); const logout = async () => { await fetch('/logout', { method: 'POST' }); setLogin(null); setAuthenticated(false); setRegistryHref(null); setOtt(false); }; const value = { authenticated, registryHref, login, ott, setOtt, logout }; React.useEffect(() => { if (!initialLoad) return; setInitialLoad(false); // Store ott used during authentication: let tempOtt; if (window.location.search) { const match = window.location.search.match(/ott=(?<ott>[^&]+)/); if (match && match.groups) { tempOtt = match.groups.ott; } } // Check if user is logged in, if so allow protected routes // to be accessed: async function fetchAccount() { const resp = await fetch(tempOtt ? `/_/account?ott=${tempOtt}` : '/_/account'); const json = await resp.json(); if (json.authenticated) { setLogin(json.login); setAuthenticated(json.authenticated); setRegistryHref(json.registryHref); } if (json.ott) { setOtt(json.ott); } if (json.flash) { const flashObject = JSON.parse(json.flash); flash.set(flashObject.severity, flashObject.message); } setLoading(false); } fetchAccount(); }, [flash, initialLoad]); if (!loading) { return <AccountContext.Provider value={value}>{children}</AccountContext.Provider>; } else { return ( <Backdrop sx={{ color: '#fff', zIndex: (theme) => theme.zIndex.drawer + 1 }} open={true} > <CircularProgress color="inherit" /> </Backdrop> ); } } export function useAccount() { return React.useContext(AccountContext); } export function RequireAccount({ children }: { children: JSX.Element }) { const account = useAccount(); const location = useLocation(); if (!account.authenticated) { // Redirect them to the /login page, but save the current location they were // trying to go to when they were redirected. This allows us to send them // along to that page after they login, which is a nicer user experience // than dropping them off on the home page. return <Navigate to="/_/login" state={{ from: location }} />; } return children; }