Skip to content

Initial auth implementation #30

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Apr 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions packages/client/.env
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# App metadata
APP_TITLE=STAC Manager
APP_DESCRIPTION=Plugin based STAC editor
## Don't set the public url here. Check the README.md file for more information
# PUBLIC_URL= Do not set here

# Api variables
REACT_APP_STAC_BROWSER=
REACT_APP_STAC_API=

## Keycloak auth variables
REACT_APP_KEYCLOAK_URL=
REACT_APP_KEYCLOAK_CLIENT_ID=
REACT_APP_KEYCLOAK_REALM=

## Theming
# REACT_APP_THEME_PRIMARY_COLOR='#6A5ACD'
# REACT_APP_THEME_SECONDARY_COLOR='#048A81'

## Don't set the public url here. Check the README.md file for more information
# PUBLIC_URL= Do not set here
12 changes: 10 additions & 2 deletions packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@ Some client options are controlled by environment variables. These are:
## Title and description of the app for metadata
APP_TITLE
APP_DESCRIPTION
## If the app is being served in from a subfolder, the domain url must be set.
PUBLIC_URL

# API
## If the app is being served in from a subfolder, the domain url must be set.
PUBLIC_URL
REACT_APP_STAC_BROWSER
REACT_APP_STAC_API

# Auth
REACT_APP_KEYCLOAK_URL
REACT_APP_KEYCLOAK_CLIENT_ID
REACT_APP_KEYCLOAK_REALM

# Theming
REACT_APP_THEME_PRIMARY_COLOR
REACT_APP_THEME_SECONDARY_COLOR
Expand All @@ -40,6 +44,10 @@ You must provide a value for the `REACT_APP_STAC_API` environment variable. This

If the `REACT_APP_STAC_BROWSER` environment variable is not set, [Radiant Earth's STAC Browser](https://radiantearth.github.io/stac-browser/) will be used by default, which will connect to the STAC API specified in `REACT_APP_STAC_API`.

**Auth**
The client uses Keycloack for authentication, which is disabled by default. To
enable it you must provide values for the `REACT_APP_KEYCLOAK_*` environment variables. These can be obtained through the Keycloak server.

### Theming

The Stac manager client allows for simple theming to give the instance a different look and feel.
Expand Down
2 changes: 2 additions & 0 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,13 @@
"@turf/bbox": "^7.1.0",
"@turf/bbox-polygon": "^7.1.0",
"@types/jest": "^29.5.14",
"@types/keycloak-js": "^2.5.4",
"@types/mapbox__mapbox-gl-draw": "^1.4.8",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"formik": "^2.4.6",
"framer-motion": "^10.16.5",
"keycloak-js": "^26.1.4",
"mapbox-gl-draw-rectangle-mode": "^1.0.4",
"maplibre-gl": "^3.6.2",
"polished": "^4.3.1",
Expand Down
270 changes: 158 additions & 112 deletions packages/client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,131 +1,177 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import {
ChakraProvider,
Box,
Container,
Flex,
Heading,
Text,
Badge,
Divider,
Fade,
Image
} from '@chakra-ui/react';
import { StacApiProvider } from '@developmentseed/stac-react';
import { PluginConfigProvider } from '@stac-manager/data-core';
import { keyframes } from '@emotion/react';
import { Route, Routes } from 'react-router-dom';
import {
CollecticonCog,
CollecticonHeart
} from '@devseed-ui/collecticons-chakra';

import theme from './theme/theme';
import { MainNavigation } from './components';
import Home from './pages/Home';
import CollectionList from './pages/CollectionList';
import { CollectionForm } from './pages/CollectionForm';
import ItemDetail from './pages/ItemDetail';
import NotFound from './pages/NotFound';
import CollectionDetail from './pages/CollectionDetail';
import Sandbox from './pages/Sandbox';
import { config } from './plugin-system/config';
import { RequireAuth } from '$components/auth/RequireAuth';
import MainNavigation from '$components/MainNavigation';
import Home from '$pages/Home';
import CollectionList from '$pages/CollectionList';
import { CollectionForm } from '$pages/CollectionForm';
import ItemDetail from '$pages/ItemDetail';
import NotFound from '$pages/NotFound';
import CollectionDetail from '$pages/CollectionDetail';
import Sandbox from '$pages/Sandbox';

const publicUrl = process.env.PUBLIC_URL || '';
import { useKeycloak } from './auth/Context';
import SmartLink from '$components/SmartLink';

let basename: string | undefined;
if (publicUrl) {
try {
basename = new URL(publicUrl).pathname;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (error) {
// no-op
const rotate = keyframes`
from {
transform: rotate(0deg);
}
}
to {
transform: rotate(360deg);
}
`;

const rotate2 = keyframes`
from {
transform: rotate(22.5deg);
}
to {
transform: rotate(382.5deg);
}
`;

export function App() {
const { initStatus } = useKeycloak();

export const App = () => (
<ChakraProvider theme={theme}>
<StacApiProvider apiUrl={process.env.REACT_APP_STAC_API!}>
<PluginConfigProvider config={config}>
<Router basename={basename}>
<Container
maxW='container.xl'
minH='100vh'
display='flex'
flexDirection='column'
const isLoading = initStatus === 'loading';

return (
<>
<Fade in={isLoading} unmountOnExit>
<Flex
minW='100vw'
minH='100vh'
bg='white'
align='center'
justify='center'
>
<CollecticonCog
size='5em'
color='base.300'
animation={`${rotate} 4s linear infinite`}
/>
<CollecticonCog
ml={-2}
size='5em'
color='base.300'
animation={`${rotate2} 4s linear infinite reverse`}
/>
</Flex>
</Fade>
{!isLoading && (
<Container
maxW='container.lg'
minH='100vh'
display='flex'
flexDirection='column'
gap={4}
>
<Flex
as='header'
gap={4}
alignItems='center'
justifyContent='space-between'
py={8}
>
<Flex
as='header'
gap={4}
alignItems='center'
justifyContent='space-between'
py={8}
>
<Flex gap={4} alignItems='center'>
<Image
src={`${publicUrl}/meta/icon-512.png`}
width={8}
aspectRatio={1}
borderRadius='md'
/>
<Divider
orientation='vertical'
borderColor='base.200a'
h='1rem'
borderLeftWidth='2px'
/>
<Heading as='p' size='sm'>
STAC Manager
</Heading>
</Flex>

<MainNavigation />
<Flex gap={4} alignItems='center'>
<Image
src={`${process.env.PUBLIC_URL || ''}/meta/icon-512.png`}
width={8}
aspectRatio={1}
borderRadius='md'
/>
<Divider
orientation='vertical'
borderColor='base.200a'
h='1rem'
borderLeftWidth='2px'
/>
<Heading as='p' size='sm'>
STAC Manager
</Heading>
</Flex>
<Box as='main'>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/collections/' element={<CollectionList />} />
<Route path='/collections/new/' element={<CollectionForm />} />
<Route
path='/collections/:collectionId/'
element={<CollectionDetail />}
/>
<Route
path='/collections/:collectionId/edit/'
element={<CollectionForm />}
/>
<Route
path='/collections/:collectionId/items/:itemId/'
element={<ItemDetail />}
/>
<Route path='/sandbox' element={<Sandbox />} />
<Route path='*' element={<NotFound />} />
</Routes>
</Box>
<Flex
as='footer'
gap={4}
alignItems='center'
justifyContent='space-between'
mt='auto'
py={8}
>
<Flex gap={4} alignItems='center'>
<Text as='span'>
Powered by{' '}
<strong>
STAC Manager{' '}
<Badge bg='base.400a' color='surface.500' px='0.375rem'>
{process.env.APP_VERSION}
</Badge>
</strong>{' '}
</Text>
<Divider
orientation='vertical'
borderColor='base.200a'
h='1em'
/>
{new Date().getFullYear()}
</Flex>
</Flex>
</Container>
</Router>
</PluginConfigProvider>
</StacApiProvider>
</ChakraProvider>
);

<MainNavigation />
</Flex>
<Box as='main'>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/collections/' element={<CollectionList />} />
<Route
path='/collections/new/'
element={<RequireAuth Component={CollectionForm} />}
/>
<Route
path='/collections/:collectionId/'
element={<CollectionDetail />}
/>
<Route
path='/collections/:collectionId/edit/'
element={<RequireAuth Component={CollectionForm} />}
/>
<Route
path='/collections/:collectionId/items/:itemId/'
element={<ItemDetail />}
/>
<Route path='/sandbox' element={<Sandbox />} />
<Route path='*' element={<NotFound />} />
</Routes>
</Box>
<AppFooter />
</Container>
)}
</>
);
}

function AppFooter() {
return (
<Flex
as='footer'
gap={4}
alignItems='center'
justifyContent='space-between'
mt='auto'
p={4}
>
<Flex gap={4} alignItems='center' width='100%'>
<Text as='span'>
Powered by{' '}
<strong>
STAC Manager{' '}
<Badge bg='base.400a' color='surface.500' px='0.375rem'>
{process.env.APP_VERSION}
</Badge>
</strong>{' '}
</Text>
<Divider orientation='vertical' borderColor='base.200a' h='1em' />
{new Date().getFullYear()}
<Text as='span' ml='auto'>
Made with <CollecticonHeart meaningful title='love' /> by{' '}
<SmartLink to='https://developmentseed.org' color='inherit'>
Development Seed
</SmartLink>
.
</Text>
</Flex>
</Flex>
);
}
Loading