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 { selectFeatureVisibility } from "xServices/entitlements/entitlementsSelectors"
import { XServiceContext } from "xServices/StateContext"
import { AuthAndFrame } from "./components/AuthAndFrame/AuthAndFrame"
import { NavbarLayout } from "./components/NavbarLayout/NavbarLayout"
import { RequireAuth } from "./components/RequireAuth/RequireAuth"
import { SettingsLayout } from "./components/SettingsLayout/SettingsLayout"
import { DeploySettingsLayout } from "components/DeploySettingsLayout/DeploySettingsLayout"
@@ -131,374 +131,309 @@ export const AppRouter: FC = () => {
return (
<Suspense fallback={<FullScreenLoader />}>
<Routes>
<Route
index
element={
<RequireAuth>
<IndexPage />
</RequireAuth>
}
/>
<Route path="login" element={<LoginPage />} />
<Route path="setup" element={<SetupPage />} />
<Route
path="cli-auth"
element={
<RequireAuth>
<CliAuthenticationPage />
</RequireAuth>
}
/>
<Route
path="gitauth"
element={
<RequireAuth>
<GitAuthPage />
</RequireAuth>
}
/>
<Route
path="workspaces"
element={
<AuthAndFrame>
<WorkspacesPage />
</AuthAndFrame>
}
/>
{/* Authenticated routes */}
<Route element={<RequireAuth />}>
<Route index element={<IndexPage />} />
<Route path="cli-auth" element={<CliAuthenticationPage />} />
<Route path="gitauth" element={<GitAuthPage />} />
<Route path="starter-templates">
<Route
index
path="workspaces"
element={
<AuthAndFrame>
<StarterTemplatesPage />
</AuthAndFrame>
<NavbarLayout>
<WorkspacesPage />
</NavbarLayout>
}
/>
<Route
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 path="starter-templates">
<Route
index
element={
<AuthAndFrame>
<TemplateLayout>
<TemplateSummaryPage />
</TemplateLayout>
</AuthAndFrame>
<NavbarLayout>
<StarterTemplatesPage />
</NavbarLayout>
}
/>
<Route
path="permissions"
path=":exampleId"
element={
<AuthAndFrame>
<TemplateLayout>
<TemplatePermissionsPage />
</TemplateLayout>
</AuthAndFrame>
<NavbarLayout>
<StarterTemplatePage />
</NavbarLayout>
}
/>
</Route>
<Route path="templates">
<Route
path="workspace"
index
element={
<RequireAuth>
<CreateWorkspacePage />
</RequireAuth>
<NavbarLayout>
<TemplatesPage />
</NavbarLayout>
}
/>
<Route
path="settings"
element={
<RequireAuth>
<TemplateSettingsPage />
</RequireAuth>
}
/>
<Route path="versions">
<Route path="new" element={<CreateTemplatePage />} />
<Route path=":template">
<Route
path=":version"
index
element={
<AuthAndFrame>
<TemplateVersionPage />
</AuthAndFrame>
<NavbarLayout>
<TemplateLayout>
<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 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 path="users">
<Route
index
element={
<AuthAndFrame>
<WorkspacePage />
</AuthAndFrame>
<NavbarLayout>
<UsersLayout>
<UsersPage />
</UsersLayout>
</NavbarLayout>
}
/>
<Route path="create" element={<CreateUserPage />} />
</Route>
<Route path="/groups">
<Route
path="schedule"
index
element={
<RequireAuth>
<WorkspaceSchedulePage />
</RequireAuth>
<NavbarLayout>
<UsersLayout>
<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
path="terminal"
index
element={
<RequireAuth>
<TerminalPage />
</RequireAuth>
<NavbarLayout>
<RequirePermission
isFeatureVisible={
featureVisibility[FeatureNames.AuditLog] &&
Boolean(permissions?.viewAuditLog)
}
>
<AuditPage />
</RequirePermission>
</NavbarLayout>
}
/>
</Route>
<Route path="/settings/deployment">
<Route
path="builds/:buildNumber"
path="general"
element={
<AuthAndFrame>
<WorkspaceBuildPage />
</AuthAndFrame>
<NavbarLayout>
<RequirePermission
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
path="change-version"
path="account"
element={
<RequireAuth>
<WorkspaceChangeVersionPage />
</RequireAuth>
<NavbarLayout>
<SettingsLayout>
<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>
@@ -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 { useActor } from "@xstate/react"
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 { Navbar } from "../Navbar/Navbar"
import { RequireAuth } from "../RequireAuth/RequireAuth"
import { UpdateCheckBanner } from "components/UpdateCheckBanner/UpdateCheckBanner"
import { Margins } from "components/Margins/Margins"
interface AuthAndFrameProps {
children: JSX.Element
}
/**
* Wraps page in RequireAuth and renders it between Navbar and Footer
*/
export const AuthAndFrame: FC<AuthAndFrameProps> = ({ children }) => {
export const NavbarLayout: FC<PropsWithChildren> = ({ children }) => {
const styles = useStyles()
const xServices = useContext(XServiceContext)
const [authState] = useActor(xServices.authXService)
@@ -32,25 +24,23 @@ export const AuthAndFrame: FC<AuthAndFrameProps> = ({ children }) => {
}, [authState, updateCheckSend])
return (
<RequireAuth>
<div className={styles.site}>
<Navbar />
{updateCheckState.context.show && (
<div className={styles.updateCheckBanner}>
<Margins>
<UpdateCheckBanner
updateCheck={updateCheckState.context.updateCheck}
error={updateCheckState.context.error}
onDismiss={() => updateCheckSend("DISMISS")}
/>
</Margins>
</div>
)}
<div className={styles.siteContent}>
<Suspense fallback={<Loader />}>{children}</Suspense>
<div className={styles.site}>
<Navbar />
{updateCheckState.context.show && (
<div className={styles.updateCheckBanner}>
<Margins>
<UpdateCheckBanner
updateCheck={updateCheckState.context.updateCheck}
error={updateCheckState.context.error}
onDismiss={() => updateCheckSend("DISMISS")}
/>
</Margins>
</div>
)}
<div className={styles.siteContent}>
<Suspense fallback={<Loader />}>{children}</Suspense>
</div>
</RequireAuth>
</div>
)
}
@@ -1,17 +1,12 @@
import { useActor } from "@xstate/react"
import { useContext, FC, PropsWithChildren } from "react"
import { useContext, FC } from "react"
import { Navigate, useLocation } from "react-router"
import { Outlet } from "react-router-dom"
import { embedRedirect } from "../../util/redirect"
import { XServiceContext } from "../../xServices/StateContext"
import { FullScreenLoader } from "../Loader/FullScreenLoader"
export interface RequireAuthProps {
children: JSX.Element
}
export const RequireAuth: FC<PropsWithChildren<RequireAuthProps>> = ({
children,
}) => {
export const RequireAuth: FC = () => {
const xServices = useContext(XServiceContext)
const [authState] = useActor(xServices.authXService)
const location = useLocation()
@@ -25,6 +20,6 @@ export const RequireAuth: FC<PropsWithChildren<RequireAuthProps>> = ({
} else if (authState.hasTag("loading")) {
return <FullScreenLoader />
} else {
return children
return <Outlet />
}
}
+3 -4
View File
@@ -64,10 +64,9 @@ export function renderWithAuth(
<I18nextProvider i18n={i18n}>
<ThemeProvider theme={dark}>
<Routes>
<Route
path={path ?? route}
element={<RequireAuth>{ui}</RequireAuth>}
/>
<Route element={<RequireAuth />}>
<Route path={path ?? route} element={ui} />
</Route>
{routes}
</Routes>
</ThemeProvider>