Skip to content

Commit eb74d32

Browse files
Merge pull request #30 from developmentseed/feature/auth
Initial auth implementation
2 parents f5624cf + 7c90d28 commit eb74d32

16 files changed

+598
-153
lines changed

Diff for: package-lock.json

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: packages/client/.env

+7-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
# App metadata
22
APP_TITLE=STAC Manager
33
APP_DESCRIPTION=Plugin based STAC editor
4+
## Don't set the public url here. Check the README.md file for more information
5+
# PUBLIC_URL= Do not set here
46

57
# Api variables
68
REACT_APP_STAC_BROWSER=
79
REACT_APP_STAC_API=
810

11+
## Keycloak auth variables
12+
REACT_APP_KEYCLOAK_URL=
13+
REACT_APP_KEYCLOAK_CLIENT_ID=
14+
REACT_APP_KEYCLOAK_REALM=
15+
916
## Theming
1017
# REACT_APP_THEME_PRIMARY_COLOR='#6A5ACD'
1118
# REACT_APP_THEME_SECONDARY_COLOR='#048A81'
12-
13-
## Don't set the public url here. Check the README.md file for more information
14-
# PUBLIC_URL= Do not set here

Diff for: packages/client/README.md

+10-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,18 @@ Some client options are controlled by environment variables. These are:
1414
## Title and description of the app for metadata
1515
APP_TITLE
1616
APP_DESCRIPTION
17+
## If the app is being served in from a subfolder, the domain url must be set.
1718
PUBLIC_URL
1819
1920
# API
20-
## If the app is being served in from a subfolder, the domain url must be set.
21-
PUBLIC_URL
2221
REACT_APP_STAC_BROWSER
2322
REACT_APP_STAC_API
2423
24+
# Auth
25+
REACT_APP_KEYCLOAK_URL
26+
REACT_APP_KEYCLOAK_CLIENT_ID
27+
REACT_APP_KEYCLOAK_REALM
28+
2529
# Theming
2630
REACT_APP_THEME_PRIMARY_COLOR
2731
REACT_APP_THEME_SECONDARY_COLOR
@@ -40,6 +44,10 @@ You must provide a value for the `REACT_APP_STAC_API` environment variable. This
4044

4145
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`.
4246

47+
**Auth**
48+
The client uses Keycloack for authentication, which is disabled by default. To
49+
enable it you must provide values for the `REACT_APP_KEYCLOAK_*` environment variables. These can be obtained through the Keycloak server.
50+
4351
### Theming
4452

4553
The Stac manager client allows for simple theming to give the instance a different look and feel.

Diff for: packages/client/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,13 @@
6969
"@turf/bbox": "^7.1.0",
7070
"@turf/bbox-polygon": "^7.1.0",
7171
"@types/jest": "^29.5.14",
72+
"@types/keycloak-js": "^2.5.4",
7273
"@types/mapbox__mapbox-gl-draw": "^1.4.8",
7374
"@types/react": "^18.3.12",
7475
"@types/react-dom": "^18.3.1",
7576
"formik": "^2.4.6",
7677
"framer-motion": "^10.16.5",
78+
"keycloak-js": "^26.1.4",
7779
"mapbox-gl-draw-rectangle-mode": "^1.0.4",
7880
"maplibre-gl": "^3.6.2",
7981
"polished": "^4.3.1",

Diff for: packages/client/src/App.tsx

+158-112
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,177 @@
11
import React from 'react';
2-
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
32
import {
4-
ChakraProvider,
53
Box,
64
Container,
75
Flex,
86
Heading,
97
Text,
108
Badge,
119
Divider,
10+
Fade,
1211
Image
1312
} from '@chakra-ui/react';
14-
import { StacApiProvider } from '@developmentseed/stac-react';
15-
import { PluginConfigProvider } from '@stac-manager/data-core';
13+
import { keyframes } from '@emotion/react';
14+
import { Route, Routes } from 'react-router-dom';
15+
import {
16+
CollecticonCog,
17+
CollecticonHeart
18+
} from '@devseed-ui/collecticons-chakra';
1619

17-
import theme from './theme/theme';
18-
import { MainNavigation } from './components';
19-
import Home from './pages/Home';
20-
import CollectionList from './pages/CollectionList';
21-
import { CollectionForm } from './pages/CollectionForm';
22-
import ItemDetail from './pages/ItemDetail';
23-
import NotFound from './pages/NotFound';
24-
import CollectionDetail from './pages/CollectionDetail';
25-
import Sandbox from './pages/Sandbox';
26-
import { config } from './plugin-system/config';
20+
import { RequireAuth } from '$components/auth/RequireAuth';
21+
import MainNavigation from '$components/MainNavigation';
22+
import Home from '$pages/Home';
23+
import CollectionList from '$pages/CollectionList';
24+
import { CollectionForm } from '$pages/CollectionForm';
25+
import ItemDetail from '$pages/ItemDetail';
26+
import NotFound from '$pages/NotFound';
27+
import CollectionDetail from '$pages/CollectionDetail';
28+
import Sandbox from '$pages/Sandbox';
2729

28-
const publicUrl = process.env.PUBLIC_URL || '';
30+
import { useKeycloak } from './auth/Context';
31+
import SmartLink from '$components/SmartLink';
2932

30-
let basename: string | undefined;
31-
if (publicUrl) {
32-
try {
33-
basename = new URL(publicUrl).pathname;
34-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
35-
} catch (error) {
36-
// no-op
33+
const rotate = keyframes`
34+
from {
35+
transform: rotate(0deg);
3736
}
38-
}
37+
to {
38+
transform: rotate(360deg);
39+
}
40+
`;
41+
42+
const rotate2 = keyframes`
43+
from {
44+
transform: rotate(22.5deg);
45+
}
46+
to {
47+
transform: rotate(382.5deg);
48+
}
49+
`;
50+
51+
export function App() {
52+
const { initStatus } = useKeycloak();
3953

40-
export const App = () => (
41-
<ChakraProvider theme={theme}>
42-
<StacApiProvider apiUrl={process.env.REACT_APP_STAC_API!}>
43-
<PluginConfigProvider config={config}>
44-
<Router basename={basename}>
45-
<Container
46-
maxW='container.xl'
47-
minH='100vh'
48-
display='flex'
49-
flexDirection='column'
54+
const isLoading = initStatus === 'loading';
55+
56+
return (
57+
<>
58+
<Fade in={isLoading} unmountOnExit>
59+
<Flex
60+
minW='100vw'
61+
minH='100vh'
62+
bg='white'
63+
align='center'
64+
justify='center'
65+
>
66+
<CollecticonCog
67+
size='5em'
68+
color='base.300'
69+
animation={`${rotate} 4s linear infinite`}
70+
/>
71+
<CollecticonCog
72+
ml={-2}
73+
size='5em'
74+
color='base.300'
75+
animation={`${rotate2} 4s linear infinite reverse`}
76+
/>
77+
</Flex>
78+
</Fade>
79+
{!isLoading && (
80+
<Container
81+
maxW='container.lg'
82+
minH='100vh'
83+
display='flex'
84+
flexDirection='column'
85+
gap={4}
86+
>
87+
<Flex
88+
as='header'
5089
gap={4}
90+
alignItems='center'
91+
justifyContent='space-between'
92+
py={8}
5193
>
52-
<Flex
53-
as='header'
54-
gap={4}
55-
alignItems='center'
56-
justifyContent='space-between'
57-
py={8}
58-
>
59-
<Flex gap={4} alignItems='center'>
60-
<Image
61-
src={`${publicUrl}/meta/icon-512.png`}
62-
width={8}
63-
aspectRatio={1}
64-
borderRadius='md'
65-
/>
66-
<Divider
67-
orientation='vertical'
68-
borderColor='base.200a'
69-
h='1rem'
70-
borderLeftWidth='2px'
71-
/>
72-
<Heading as='p' size='sm'>
73-
STAC Manager
74-
</Heading>
75-
</Flex>
76-
77-
<MainNavigation />
94+
<Flex gap={4} alignItems='center'>
95+
<Image
96+
src={`${process.env.PUBLIC_URL || ''}/meta/icon-512.png`}
97+
width={8}
98+
aspectRatio={1}
99+
borderRadius='md'
100+
/>
101+
<Divider
102+
orientation='vertical'
103+
borderColor='base.200a'
104+
h='1rem'
105+
borderLeftWidth='2px'
106+
/>
107+
<Heading as='p' size='sm'>
108+
STAC Manager
109+
</Heading>
78110
</Flex>
79-
<Box as='main'>
80-
<Routes>
81-
<Route path='/' element={<Home />} />
82-
<Route path='/collections/' element={<CollectionList />} />
83-
<Route path='/collections/new/' element={<CollectionForm />} />
84-
<Route
85-
path='/collections/:collectionId/'
86-
element={<CollectionDetail />}
87-
/>
88-
<Route
89-
path='/collections/:collectionId/edit/'
90-
element={<CollectionForm />}
91-
/>
92-
<Route
93-
path='/collections/:collectionId/items/:itemId/'
94-
element={<ItemDetail />}
95-
/>
96-
<Route path='/sandbox' element={<Sandbox />} />
97-
<Route path='*' element={<NotFound />} />
98-
</Routes>
99-
</Box>
100-
<Flex
101-
as='footer'
102-
gap={4}
103-
alignItems='center'
104-
justifyContent='space-between'
105-
mt='auto'
106-
py={8}
107-
>
108-
<Flex gap={4} alignItems='center'>
109-
<Text as='span'>
110-
Powered by{' '}
111-
<strong>
112-
STAC Manager{' '}
113-
<Badge bg='base.400a' color='surface.500' px='0.375rem'>
114-
{process.env.APP_VERSION}
115-
</Badge>
116-
</strong>{' '}
117-
</Text>
118-
<Divider
119-
orientation='vertical'
120-
borderColor='base.200a'
121-
h='1em'
122-
/>
123-
{new Date().getFullYear()}
124-
</Flex>
125-
</Flex>
126-
</Container>
127-
</Router>
128-
</PluginConfigProvider>
129-
</StacApiProvider>
130-
</ChakraProvider>
131-
);
111+
112+
<MainNavigation />
113+
</Flex>
114+
<Box as='main'>
115+
<Routes>
116+
<Route path='/' element={<Home />} />
117+
<Route path='/collections/' element={<CollectionList />} />
118+
<Route
119+
path='/collections/new/'
120+
element={<RequireAuth Component={CollectionForm} />}
121+
/>
122+
<Route
123+
path='/collections/:collectionId/'
124+
element={<CollectionDetail />}
125+
/>
126+
<Route
127+
path='/collections/:collectionId/edit/'
128+
element={<RequireAuth Component={CollectionForm} />}
129+
/>
130+
<Route
131+
path='/collections/:collectionId/items/:itemId/'
132+
element={<ItemDetail />}
133+
/>
134+
<Route path='/sandbox' element={<Sandbox />} />
135+
<Route path='*' element={<NotFound />} />
136+
</Routes>
137+
</Box>
138+
<AppFooter />
139+
</Container>
140+
)}
141+
</>
142+
);
143+
}
144+
145+
function AppFooter() {
146+
return (
147+
<Flex
148+
as='footer'
149+
gap={4}
150+
alignItems='center'
151+
justifyContent='space-between'
152+
mt='auto'
153+
p={4}
154+
>
155+
<Flex gap={4} alignItems='center' width='100%'>
156+
<Text as='span'>
157+
Powered by{' '}
158+
<strong>
159+
STAC Manager{' '}
160+
<Badge bg='base.400a' color='surface.500' px='0.375rem'>
161+
{process.env.APP_VERSION}
162+
</Badge>
163+
</strong>{' '}
164+
</Text>
165+
<Divider orientation='vertical' borderColor='base.200a' h='1em' />
166+
{new Date().getFullYear()}
167+
<Text as='span' ml='auto'>
168+
Made with <CollecticonHeart meaningful title='love' /> by{' '}
169+
<SmartLink to='https://developmentseed.org' color='inherit'>
170+
Development Seed
171+
</SmartLink>
172+
.
173+
</Text>
174+
</Flex>
175+
</Flex>
176+
);
177+
}

0 commit comments

Comments
 (0)