refactor: Wrap authenticated routes (#5695)

This commit is contained in:
Bruno Quaresma
2023-01-12 15:52:16 -03:00
committed by GitHub
parent f9f7283e16
commit e6072eff59
5 changed files with 274 additions and 380 deletions
+250 -315
View File
@@ -18,7 +18,7 @@ import { Route, Routes } from "react-router-dom"
import { selectPermissions } from "xServices/auth/authSelectors" import { selectPermissions } from "xServices/auth/authSelectors"
import { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors" import { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors"
import { XServiceContext } from "xServices/StateContext" import { XServiceContext } from "xServices/StateContext"
import { AuthAndFrame } from "./components/AuthAndFrame/AuthAndFrame" import { NavbarLayout } from "./components/NavbarLayout/NavbarLayout"
import { RequireAuth } from "./components/RequireAuth/RequireAuth" import { RequireAuth } from "./components/RequireAuth/RequireAuth"
import { SettingsLayout } from "./components/SettingsLayout/SettingsLayout" import { SettingsLayout } from "./components/SettingsLayout/SettingsLayout"
import { DeploySettingsLayout } from "components/DeploySettingsLayout/DeploySettingsLayout" import { DeploySettingsLayout } from "components/DeploySettingsLayout/DeploySettingsLayout"
@@ -131,374 +131,309 @@ export const AppRouter: FC = () => {
return ( return (
<Suspense fallback={<FullScreenLoader />}> <Suspense fallback={<FullScreenLoader />}>
<Routes> <Routes>
<Route
index
element={
<RequireAuth>
<IndexPage />
</RequireAuth>
}
/>
<Route path="login" element={<LoginPage />} /> <Route path="login" element={<LoginPage />} />
<Route path="setup" element={<SetupPage />} /> <Route path="setup" element={<SetupPage />} />
<Route
path="cli-auth"
element={
<RequireAuth>
<CliAuthenticationPage />
</RequireAuth>
}
/>
<Route
path="gitauth"
element={
<RequireAuth>
<GitAuthPage />
</RequireAuth>
}
/>
<Route {/* Authenticated routes */}
path="workspaces" <Route element={<RequireAuth />}>
element={ <Route index element={<IndexPage />} />
<AuthAndFrame>
<WorkspacesPage /> <Route path="cli-auth" element={<CliAuthenticationPage />} />
</AuthAndFrame> <Route path="gitauth" element={<GitAuthPage />} />
}
/>
<Route path="starter-templates">
<Route <Route
index path="workspaces"
element={ element={
<AuthAndFrame> <NavbarLayout>
<StarterTemplatesPage /> <WorkspacesPage />
</AuthAndFrame> </NavbarLayout>
} }
/> />
<Route <Route path="starter-templates">
path=":exampleId"
element={
<AuthAndFrame>
<StarterTemplatePage />
</AuthAndFrame>
}
></Route>
</Route>
<Route path="templates">
<Route
index
element={
<AuthAndFrame>
<TemplatesPage />
</AuthAndFrame>
}
/>
<Route
path="new"
element={
<RequireAuth>
<CreateTemplatePage />
</RequireAuth>
}
/>
<Route path=":template">
<Route <Route
index index
element={ element={
<AuthAndFrame> <NavbarLayout>
<TemplateLayout> <StarterTemplatesPage />
<TemplateSummaryPage /> </NavbarLayout>
</TemplateLayout>
</AuthAndFrame>
} }
/> />
<Route <Route
path="permissions" path=":exampleId"
element={ element={
<AuthAndFrame> <NavbarLayout>
<TemplateLayout> <StarterTemplatePage />
<TemplatePermissionsPage /> </NavbarLayout>
</TemplateLayout>
</AuthAndFrame>
} }
/> />
</Route>
<Route path="templates">
<Route <Route
path="workspace" index
element={ element={
<RequireAuth> <NavbarLayout>
<CreateWorkspacePage /> <TemplatesPage />
</RequireAuth> </NavbarLayout>
} }
/> />
<Route
path="settings" <Route path="new" element={<CreateTemplatePage />} />
element={
<RequireAuth> <Route path=":template">
<TemplateSettingsPage />
</RequireAuth>
}
/>
<Route path="versions">
<Route <Route
path=":version" index
element={ element={
<AuthAndFrame> <NavbarLayout>
<TemplateVersionPage /> <TemplateLayout>
</AuthAndFrame> <TemplateSummaryPage />
</TemplateLayout>
</NavbarLayout>
} }
/> />
<Route
path="permissions"
element={
<NavbarLayout>
<TemplateLayout>
<TemplatePermissionsPage />
</TemplateLayout>
</NavbarLayout>
}
/>
<Route path="workspace" element={<CreateWorkspacePage />} />
<Route path="settings" element={<TemplateSettingsPage />} />
<Route path="versions">
<Route
path=":version"
element={
<NavbarLayout>
<TemplateVersionPage />
</NavbarLayout>
}
/>
</Route>
</Route> </Route>
</Route> </Route>
</Route>
<Route path="users"> <Route path="users">
<Route
index
element={
<AuthAndFrame>
<UsersLayout>
<UsersPage />
</UsersLayout>
</AuthAndFrame>
}
/>
<Route
path="create"
element={
<RequireAuth>
<CreateUserPage />
</RequireAuth>
}
/>
</Route>
<Route path="/groups">
<Route
index
element={
<AuthAndFrame>
<UsersLayout>
<GroupsPage />
</UsersLayout>
</AuthAndFrame>
}
/>
<Route
path="create"
element={
<RequireAuth>
<CreateGroupPage />
</RequireAuth>
}
/>
<Route
path=":groupId"
element={
<AuthAndFrame>
<GroupPage />
</AuthAndFrame>
}
/>
<Route
path=":groupId/settings"
element={
<RequireAuth>
<SettingsGroupPage />
</RequireAuth>
}
/>
</Route>
<Route path="/audit">
<Route
index
element={
<AuthAndFrame>
<RequirePermission
isFeatureVisible={
featureVisibility[FeatureNames.AuditLog] &&
Boolean(permissions?.viewAuditLog)
}
>
<AuditPage />
</RequirePermission>
</AuthAndFrame>
}
/>
</Route>
<Route path="/settings/deployment">
<Route
path="general"
element={
<AuthAndFrame>
<RequirePermission
isFeatureVisible={Boolean(permissions?.viewDeploymentConfig)}
>
<DeploySettingsLayout>
<GeneralSettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</AuthAndFrame>
}
/>
<Route
path="security"
element={
<AuthAndFrame>
<RequirePermission
isFeatureVisible={Boolean(permissions?.viewDeploymentConfig)}
>
<DeploySettingsLayout>
<SecuritySettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</AuthAndFrame>
}
/>
<Route
path="appearance"
element={
<AuthAndFrame>
<RequirePermission
isFeatureVisible={Boolean(permissions?.viewDeploymentConfig)}
>
<DeploySettingsLayout>
<AppearanceSettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</AuthAndFrame>
}
/>
<Route
path="network"
element={
<AuthAndFrame>
<RequirePermission
isFeatureVisible={Boolean(permissions?.viewDeploymentConfig)}
>
<DeploySettingsLayout>
<NetworkSettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</AuthAndFrame>
}
/>
<Route
path="userauth"
element={
<AuthAndFrame>
<RequirePermission
isFeatureVisible={Boolean(permissions?.viewDeploymentConfig)}
>
<DeploySettingsLayout>
<UserAuthSettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</AuthAndFrame>
}
/>
<Route
path="gitauth"
element={
<AuthAndFrame>
<RequirePermission
isFeatureVisible={Boolean(permissions?.viewDeploymentConfig)}
>
<DeploySettingsLayout>
<GitAuthSettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</AuthAndFrame>
}
/>
</Route>
<Route path="settings">
<Route
path="account"
element={
<AuthAndFrame>
<SettingsLayout>
<AccountPage />
</SettingsLayout>
</AuthAndFrame>
}
/>
<Route
path="security"
element={
<AuthAndFrame>
<SettingsLayout>
<SecurityPage />
</SettingsLayout>
</AuthAndFrame>
}
/>
<Route
path="ssh-keys"
element={
<AuthAndFrame>
<SettingsLayout>
<SSHKeysPage />
</SettingsLayout>
</AuthAndFrame>
}
/>
</Route>
<Route path="/@:username">
<Route path=":workspace">
<Route <Route
index index
element={ element={
<AuthAndFrame> <NavbarLayout>
<WorkspacePage /> <UsersLayout>
</AuthAndFrame> <UsersPage />
</UsersLayout>
</NavbarLayout>
} }
/> />
<Route path="create" element={<CreateUserPage />} />
</Route>
<Route path="/groups">
<Route <Route
path="schedule" index
element={ element={
<RequireAuth> <NavbarLayout>
<WorkspaceSchedulePage /> <UsersLayout>
</RequireAuth> <GroupsPage />
</UsersLayout>
</NavbarLayout>
} }
/> />
<Route path="create" element={<CreateGroupPage />} />
<Route
path=":groupId"
element={
<NavbarLayout>
<GroupPage />
</NavbarLayout>
}
/>
<Route path=":groupId/settings" element={<SettingsGroupPage />} />
</Route>
<Route path="/audit">
<Route <Route
path="terminal" index
element={ element={
<RequireAuth> <NavbarLayout>
<TerminalPage /> <RequirePermission
</RequireAuth> isFeatureVisible={
featureVisibility[FeatureNames.AuditLog] &&
Boolean(permissions?.viewAuditLog)
}
>
<AuditPage />
</RequirePermission>
</NavbarLayout>
} }
/> />
</Route>
<Route path="/settings/deployment">
<Route <Route
path="builds/:buildNumber" path="general"
element={ element={
<AuthAndFrame> <NavbarLayout>
<WorkspaceBuildPage /> <RequirePermission
</AuthAndFrame> isFeatureVisible={Boolean(
permissions?.viewDeploymentConfig,
)}
>
<DeploySettingsLayout>
<GeneralSettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</NavbarLayout>
} }
/> />
<Route
path="security"
element={
<NavbarLayout>
<RequirePermission
isFeatureVisible={Boolean(
permissions?.viewDeploymentConfig,
)}
>
<DeploySettingsLayout>
<SecuritySettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</NavbarLayout>
}
/>
<Route
path="appearance"
element={
<NavbarLayout>
<RequirePermission
isFeatureVisible={Boolean(
permissions?.viewDeploymentConfig,
)}
>
<DeploySettingsLayout>
<AppearanceSettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</NavbarLayout>
}
/>
<Route
path="network"
element={
<NavbarLayout>
<RequirePermission
isFeatureVisible={Boolean(
permissions?.viewDeploymentConfig,
)}
>
<DeploySettingsLayout>
<NetworkSettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</NavbarLayout>
}
/>
<Route
path="userauth"
element={
<NavbarLayout>
<RequirePermission
isFeatureVisible={Boolean(
permissions?.viewDeploymentConfig,
)}
>
<DeploySettingsLayout>
<UserAuthSettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</NavbarLayout>
}
/>
<Route
path="gitauth"
element={
<NavbarLayout>
<RequirePermission
isFeatureVisible={Boolean(
permissions?.viewDeploymentConfig,
)}
>
<DeploySettingsLayout>
<GitAuthSettingsPage />
</DeploySettingsLayout>
</RequirePermission>
</NavbarLayout>
}
/>
</Route>
<Route path="settings">
<Route <Route
path="change-version" path="account"
element={ element={
<RequireAuth> <NavbarLayout>
<WorkspaceChangeVersionPage /> <SettingsLayout>
</RequireAuth> <AccountPage />
</SettingsLayout>
</NavbarLayout>
} }
/> />
<Route
path="security"
element={
<NavbarLayout>
<SettingsLayout>
<SecurityPage />
</SettingsLayout>
</NavbarLayout>
}
/>
<Route
path="ssh-keys"
element={
<NavbarLayout>
<SettingsLayout>
<SSHKeysPage />
</SettingsLayout>
</NavbarLayout>
}
/>
</Route>
<Route path="/@:username">
<Route path=":workspace">
<Route
index
element={
<NavbarLayout>
<WorkspacePage />
</NavbarLayout>
}
/>
<Route path="schedule" element={<WorkspaceSchedulePage />} />
<Route path="terminal" element={<TerminalPage />} />
<Route
path="builds/:buildNumber"
element={
<NavbarLayout>
<WorkspaceBuildPage />
</NavbarLayout>
}
/>
<Route
path="change-version"
element={<WorkspaceChangeVersionPage />}
/>
</Route>
</Route> </Route>
</Route> </Route>
@@ -1,25 +0,0 @@
import { fireEvent, screen } from "@testing-library/react"
import { renderWithAuth } from "testHelpers/renderHelpers"
import { AccountPage } from "pages/UserSettingsPage/AccountPage/AccountPage"
import i18next from "i18next"
const { t } = i18next
describe("AuthAndFrame", () => {
it("sets localStorage key-value when dismissed", async () => {
const localStorageMock = {
...global.localStorage,
getItem: jest.fn(),
}
global.localStorage = localStorageMock
// rendering a random page that is wrapped in AuthAndFrame
return renderWithAuth(<AccountPage />)
fireEvent.click(
screen.getByRole("button", {
name: t("ctas.dismissCta", { ns: "common" }),
}),
)
expect(localStorageMock.getItem).toHaveBeenCalledWith("dismissedVersion")
})
})
@@ -1,21 +1,13 @@
import { makeStyles } from "@material-ui/core/styles" import { makeStyles } from "@material-ui/core/styles"
import { useActor } from "@xstate/react" import { useActor } from "@xstate/react"
import { Loader } from "components/Loader/Loader" import { Loader } from "components/Loader/Loader"
import { FC, Suspense, useContext, useEffect } from "react" import { FC, PropsWithChildren, Suspense, useContext, useEffect } from "react"
import { XServiceContext } from "../../xServices/StateContext" import { XServiceContext } from "../../xServices/StateContext"
import { Navbar } from "../Navbar/Navbar" import { Navbar } from "../Navbar/Navbar"
import { RequireAuth } from "../RequireAuth/RequireAuth"
import { UpdateCheckBanner } from "components/UpdateCheckBanner/UpdateCheckBanner" import { UpdateCheckBanner } from "components/UpdateCheckBanner/UpdateCheckBanner"
import { Margins } from "components/Margins/Margins" import { Margins } from "components/Margins/Margins"
interface AuthAndFrameProps { export const NavbarLayout: FC<PropsWithChildren> = ({ children }) => {
children: JSX.Element
}
/**
* Wraps page in RequireAuth and renders it between Navbar and Footer
*/
export const AuthAndFrame: FC<AuthAndFrameProps> = ({ children }) => {
const styles = useStyles() const styles = useStyles()
const xServices = useContext(XServiceContext) const xServices = useContext(XServiceContext)
const [authState] = useActor(xServices.authXService) const [authState] = useActor(xServices.authXService)
@@ -32,25 +24,23 @@ export const AuthAndFrame: FC<AuthAndFrameProps> = ({ children }) => {
}, [authState, updateCheckSend]) }, [authState, updateCheckSend])
return ( return (
<RequireAuth> <div className={styles.site}>
<div className={styles.site}> <Navbar />
<Navbar /> {updateCheckState.context.show && (
{updateCheckState.context.show && ( <div className={styles.updateCheckBanner}>
<div className={styles.updateCheckBanner}> <Margins>
<Margins> <UpdateCheckBanner
<UpdateCheckBanner updateCheck={updateCheckState.context.updateCheck}
updateCheck={updateCheckState.context.updateCheck} error={updateCheckState.context.error}
error={updateCheckState.context.error} onDismiss={() => updateCheckSend("DISMISS")}
onDismiss={() => updateCheckSend("DISMISS")} />
/> </Margins>
</Margins>
</div>
)}
<div className={styles.siteContent}>
<Suspense fallback={<Loader />}>{children}</Suspense>
</div> </div>
)}
<div className={styles.siteContent}>
<Suspense fallback={<Loader />}>{children}</Suspense>
</div> </div>
</RequireAuth> </div>
) )
} }
@@ -1,17 +1,12 @@
import { useActor } from "@xstate/react" import { useActor } from "@xstate/react"
import { useContext, FC, PropsWithChildren } from "react" import { useContext, FC } from "react"
import { Navigate, useLocation } from "react-router" import { Navigate, useLocation } from "react-router"
import { Outlet } from "react-router-dom"
import { embedRedirect } from "../../util/redirect" import { embedRedirect } from "../../util/redirect"
import { XServiceContext } from "../../xServices/StateContext" import { XServiceContext } from "../../xServices/StateContext"
import { FullScreenLoader } from "../Loader/FullScreenLoader" import { FullScreenLoader } from "../Loader/FullScreenLoader"
export interface RequireAuthProps { export const RequireAuth: FC = () => {
children: JSX.Element
}
export const RequireAuth: FC<PropsWithChildren<RequireAuthProps>> = ({
children,
}) => {
const xServices = useContext(XServiceContext) const xServices = useContext(XServiceContext)
const [authState] = useActor(xServices.authXService) const [authState] = useActor(xServices.authXService)
const location = useLocation() const location = useLocation()
@@ -25,6 +20,6 @@ export const RequireAuth: FC<PropsWithChildren<RequireAuthProps>> = ({
} else if (authState.hasTag("loading")) { } else if (authState.hasTag("loading")) {
return <FullScreenLoader /> return <FullScreenLoader />
} else { } else {
return children return <Outlet />
} }
} }
+3 -4
View File
@@ -64,10 +64,9 @@ export function renderWithAuth(
<I18nextProvider i18n={i18n}> <I18nextProvider i18n={i18n}>
<ThemeProvider theme={dark}> <ThemeProvider theme={dark}>
<Routes> <Routes>
<Route <Route element={<RequireAuth />}>
path={path ?? route} <Route path={path ?? route} element={ui} />
element={<RequireAuth>{ui}</RequireAuth>} </Route>
/>
{routes} {routes}
</Routes> </Routes>
</ThemeProvider> </ThemeProvider>