Skip to content

Commit 86545b0

Browse files
authored
Add optional auth tooling (#8)
1 parent 2a63bf3 commit 86545b0

File tree

11 files changed

+179
-16
lines changed

11 files changed

+179
-16
lines changed

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,8 @@ VSI_CACHE_SIZE=536870912
3838
MOSAIC_CONCURRENCY=1
3939
EOAPI_RASTER_ENABLE_MOSAIC_SEARCH=TRUE
4040

41+
# AUTH
42+
EOAPI_AUTH_CLIENT_ID=my-client-id
43+
EOAPI_AUTH_OPENID_CONFIGURATION_URL=https://cognito-idp.us-east-1.amazonaws.com/<user pool id>/.well-known/openid-configuration
44+
EOAPI_AUTH_USE_PKCE=true
45+
SB_authConfig={ "type": "openIdConnect", "openIdConnectUrl": "https://cognito-idp.us-east-1.amazonaws.com/<user pool id>/.well-known/openid-configuration", "oidcOptions": { "client_id": "stac-browser" } }

.github/workflows/ci.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ jobs:
5252
# see https://github.com/developmentseed/tipg/issues/37
5353
- name: Restart the Vector service
5454
run: |
55-
docker compose stop vector
56-
docker compose up -d vector
55+
docker compose restart vector
5756
5857
- name: Sleep for 10 seconds
5958
run: sleep 10s

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
version: "3"
2-
31
services:
42
# change to official image when available https://github.com/radiantearth/stac-browser/pull/386
53
stac-browser:
4+
# build: https://github.com/radiantearth/stac-browser.git
5+
# TODO: Rm when https://github.com/radiantearth/stac-browser/pull/461 is merged
66
build:
77
context: dockerfiles
88
dockerfile: Dockerfile.browser

dockerfiles/Dockerfile.browser

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ RUN rm config.js
1212
RUN npm install
1313
# replace the default config.js with our config file
1414
COPY ./browser_config.js ./config.js
15-
RUN \[ "${DYNAMIC_CONFIG}" == "true" \] && sed -i 's/<!-- <script defer="defer" src=".\/config.js"><\/script> -->/<script defer="defer" src=".\/config.js"><\/script>/g' public/index.html
15+
RUN \[ "${DYNAMIC_CONFIG}" == "true" \] && sed -i "s|<!-- <script defer=\"defer\" src=\"/config.js\"></script> -->|<script defer=\"defer\" src=\"${pathPrefix}config.js\"></script>|g" public/index.html
1616
RUN npm run build
1717

1818

@@ -31,4 +31,4 @@ EXPOSE 8085
3131
STOPSIGNAL SIGTERM
3232

3333
# override entrypoint, which calls nginx-entrypoint underneath
34-
COPY --from=build-step /app/docker/docker-entrypoint.sh ./docker-entrypoint.d/40-stac-browser-entrypoint.sh
34+
ADD ./docker-entrypoint.sh ./docker-entrypoint.d/40-stac-browser-entrypoint.sh

dockerfiles/docker-entrypoint.sh

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# TODO: Rm when https://github.com/radiantearth/stac-browser/pull/461 is merged
2+
# echo a string, handling different types
3+
safe_echo() {
4+
# $1 = value
5+
if [ -z "$1" ]; then
6+
echo -n "null"
7+
elif printf '%s\n' "$1" | grep -qE '\n.+\n$'; then
8+
echo -n "\`$1\`"
9+
else
10+
echo -n "'$1'"
11+
fi
12+
}
13+
14+
# handle boolean
15+
bool() {
16+
# $1 = value
17+
case "$1" in
18+
true | TRUE | yes | t | True)
19+
echo -n true
20+
;;
21+
false | FALSE | no | n | False)
22+
echo -n false
23+
;;
24+
*)
25+
echo "Err: Unknown boolean value \"$1\"" >&2
26+
exit 1
27+
;;
28+
esac
29+
}
30+
31+
# handle array values
32+
array() {
33+
# $1 = value
34+
# $2 = arraytype
35+
if [ -z "$1" ]; then
36+
echo -n "[]"
37+
else
38+
case "$2" in
39+
string)
40+
echo -n "['$(echo "$1" | sed "s/,/', '/g")']"
41+
;;
42+
*)
43+
echo -n "[$1]"
44+
;;
45+
esac
46+
fi
47+
}
48+
49+
# handle object values
50+
object() {
51+
# $1 = value
52+
if [ -z "$1" ]; then
53+
echo -n "null"
54+
else
55+
echo -n "$1"
56+
fi
57+
}
58+
59+
config_schema=$(cat /etc/nginx/conf.d/config.schema.json)
60+
61+
# Iterate over environment variables with "SB_" prefix
62+
env -0 | cut -f1 -d= | tr '\0' '\n' | grep "^SB_" | {
63+
echo "window.STAC_BROWSER_CONFIG = {"
64+
while IFS='=' read -r name; do
65+
# Strip the prefix
66+
argname="${name#SB_}"
67+
# Read the variable's value
68+
value="$(eval "echo \"\$$name\"")"
69+
70+
# Get the argument type from the schema
71+
argtype="$(echo "$config_schema" | jq -r ".properties.$argname.type[0]")"
72+
arraytype="$(echo "$config_schema" | jq -r ".properties.$argname.items.type[0]")"
73+
74+
# Encode key/value
75+
echo -n " $argname: "
76+
case "$argtype" in
77+
string)
78+
safe_echo "$value"
79+
;;
80+
boolean)
81+
bool "$value"
82+
;;
83+
integer | number | object)
84+
object "$value"
85+
;;
86+
array)
87+
array "$value" "$arraytype"
88+
;;
89+
*)
90+
safe_echo "$value"
91+
;;
92+
esac
93+
echo ","
94+
done
95+
echo "}"
96+
} >/usr/share/nginx/html/config.js

runtimes/eoapi/raster/eoapi/raster/app.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import jinja2
99
import pystac
10+
from eoapi.auth_utils import OpenIdConnectAuth, OpenIdConnectSettings
1011
from fastapi import Depends, FastAPI, Query
1112
from psycopg import OperationalError
1213
from psycopg.rows import dict_row
@@ -38,12 +39,15 @@
3839
from titiler.pgstac.reader import PgSTACReader
3940

4041
from . import __version__ as eoapi_raster_version
41-
from . import config, logs
42+
from .config import ApiSettings
43+
from .logs import init_logging
44+
45+
settings = ApiSettings()
46+
auth_settings = OpenIdConnectSettings()
4247

43-
settings = config.ApiSettings()
4448

4549
# Logs
46-
logs.init_logging(
50+
init_logging(
4751
debug=settings.debug,
4852
loggers={
4953
"botocore.credentials": {
@@ -95,6 +99,10 @@ async def lifespan(app: FastAPI):
9599
docs_url="/api.html",
96100
root_path=settings.root_path,
97101
lifespan=lifespan,
102+
swagger_ui_init_oauth={
103+
"clientId": auth_settings.client_id,
104+
"usePkceWithAuthorizationCodeGrant": auth_settings.use_pkce,
105+
},
98106
)
99107
add_exception_handlers(app, DEFAULT_STATUS_CODES)
100108
add_exception_handlers(app, MOSAIC_STATUS_CODES)
@@ -404,3 +412,16 @@ def landing(request: Request):
404412
"urlparams": str(request.url.query),
405413
},
406414
)
415+
416+
417+
# Add dependencies to routes
418+
if auth_settings.openid_configuration_url:
419+
oidc_auth = OpenIdConnectAuth.from_settings(auth_settings)
420+
421+
restricted_prefixes = ["/collections", "/searches"]
422+
for route in app.routes:
423+
if any(
424+
route.path.startswith(f"{app.root_path}{prefix}")
425+
for prefix in restricted_prefixes
426+
):
427+
oidc_auth.apply_auth_dependencies(route, required_token_scopes=[])

runtimes/eoapi/raster/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ dependencies = [
2424
"titiler.extensions",
2525
"starlette-cramjam>=0.3,<0.4",
2626
"importlib_resources>=1.1.0;python_version<'3.9'",
27+
"eoapi.auth-utils>=0.2.0",
2728
]
2829

2930
[project.optional-dependencies]

runtimes/eoapi/stac/eoapi/stac/app.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import logging
44
from contextlib import asynccontextmanager
55

6+
from eoapi.auth_utils import OpenIdConnectAuth, OpenIdConnectSettings
67
from fastapi import FastAPI
78
from fastapi.responses import ORJSONResponse
89
from stac_fastapi.api.app import StacApi
@@ -34,7 +35,9 @@
3435
from starlette.templating import Jinja2Templates
3536
from starlette_cramjam.middleware import CompressionMiddleware
3637

37-
from . import config, extension, logs
38+
from .config import ApiSettings
39+
from .extension import TiTilerExtension
40+
from .logs import init_logging
3841

3942
try:
4043
from importlib.resources import files as resources_files # type: ignore
@@ -45,11 +48,12 @@
4548

4649
templates = Jinja2Templates(directory=str(resources_files(__package__) / "templates")) # type: ignore
4750

48-
api_settings = config.ApiSettings()
51+
api_settings = ApiSettings()
52+
auth_settings = OpenIdConnectSettings()
4953
settings = Settings(enable_response_models=True)
5054

5155
# Logs
52-
logs.init_logging(debug=api_settings.debug)
56+
init_logging(debug=api_settings.debug)
5357
logger = logging.getLogger(__name__)
5458

5559
# Extensions
@@ -66,7 +70,7 @@
6670
"filter": FilterExtension(client=FiltersClient()),
6771
"bulk_transactions": BulkTransactionExtension(client=BulkTransactionsClient()),
6872
"titiler": (
69-
extension.TiTilerExtension(titiler_endpoint=api_settings.titiler_endpoint)
73+
TiTilerExtension(titiler_endpoint=api_settings.titiler_endpoint)
7074
if api_settings.titiler_endpoint
7175
else None
7276
),
@@ -129,6 +133,10 @@ async def lifespan(app: FastAPI):
129133
openapi_url="/api",
130134
docs_url="/api.html",
131135
redoc_url=None,
136+
swagger_ui_init_oauth={
137+
"clientId": auth_settings.client_id,
138+
"usePkceWithAuthorizationCodeGrant": auth_settings.use_pkce,
139+
},
132140
),
133141
title=api_settings.name,
134142
description=api_settings.name,
@@ -155,3 +163,15 @@ async def viewer_page(request: Request):
155163
},
156164
media_type="text/html",
157165
)
166+
167+
168+
if auth_settings.openid_configuration_url:
169+
oidc_auth = OpenIdConnectAuth.from_settings(auth_settings)
170+
171+
restricted_prefixes = ["/collections", "/search"]
172+
for route in app.routes:
173+
if any(
174+
route.path.startswith(f"{app.root_path}{prefix}")
175+
for prefix in restricted_prefixes
176+
):
177+
oidc_auth.apply_auth_dependencies(route, required_token_scopes=[])

runtimes/eoapi/stac/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ dependencies = [
2424
"starlette-cramjam>=0.3,<0.4",
2525
"importlib_resources>=1.1.0;python_version<'3.9'",
2626
"psycopg_pool",
27+
"eoapi.auth-utils>=0.2.0",
2728
]
2829

2930
[project.optional-dependencies]

runtimes/eoapi/vector/eoapi/vector/app.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from contextlib import asynccontextmanager
55

66
import jinja2
7+
from eoapi.auth_utils import OpenIdConnectAuth, OpenIdConnectSettings
78
from fastapi import FastAPI, Request
89
from starlette.middleware.cors import CORSMiddleware
910
from starlette.templating import Jinja2Templates
@@ -16,7 +17,8 @@
1617
from tipg.settings import PostgresSettings
1718

1819
from . import __version__ as eoapi_vector_version
19-
from . import config, logs
20+
from .config import ApiSettings
21+
from .logs import init_logging
2022

2123
try:
2224
from importlib.resources import files as resources_files # type: ignore
@@ -27,11 +29,12 @@
2729

2830
CUSTOM_SQL_DIRECTORY = resources_files(__package__) / "sql"
2931

30-
settings = config.ApiSettings()
32+
settings = ApiSettings()
3133
postgres_settings = PostgresSettings()
34+
auth_settings = OpenIdConnectSettings()
3235

3336
# Logs
34-
logs.init_logging(
37+
init_logging(
3538
debug=settings.debug,
3639
loggers={
3740
"botocore.credentials": {
@@ -88,6 +91,10 @@ async def lifespan(app: FastAPI):
8891
docs_url="/api.html",
8992
lifespan=lifespan,
9093
root_path=settings.root_path,
94+
swagger_ui_init_oauth={
95+
"clientId": auth_settings.client_id,
96+
"usePkceWithAuthorizationCodeGrant": auth_settings.use_pkce,
97+
},
9198
)
9299

93100
# add eoapi_vector templates and tipg templates
@@ -168,3 +175,15 @@ async def refresh(request: Request):
168175
)
169176

170177
return request.app.state.collection_catalog
178+
179+
180+
if auth_settings.openid_configuration_url:
181+
oidc_auth = OpenIdConnectAuth.from_settings(auth_settings)
182+
183+
restricted_prefixes = ["/collections"]
184+
for route in app.routes:
185+
if any(
186+
route.path.startswith(f"{app.root_path}{prefix}")
187+
for prefix in restricted_prefixes
188+
):
189+
oidc_auth.apply_auth_dependencies(route, required_token_scopes=[])

runtimes/eoapi/vector/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ classifiers = [
2121
dynamic = ["version"]
2222
dependencies = [
2323
"tipg==0.7.1",
24+
"eoapi.auth-utils>=0.2.0",
2425
]
2526

2627
[project.optional-dependencies]

0 commit comments

Comments
 (0)