preserve Tower Authentik operator login WIP
GeoIntel release gates / Compile, test, contracts and builds (push) Failing after 1m51s
GeoIntel release gates / Python and npm vulnerability policy (push) Failing after 40s
GeoIntel release gates / GIS image, SBOM and container scan (push) Failing after 2m18s

This commit is contained in:
Jens
2026-08-30 03:05:29 +02:00
parent 3627a05bfe
commit 4fdc3aa11b
18 changed files with 744 additions and 201 deletions
+36
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
from datetime import UTC, datetime from datetime import UTC, datetime
from fastapi import APIRouter, Depends, Request, Response, status from fastapi import APIRouter, Depends, Request, Response, status
from fastapi.responses import RedirectResponse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.config import get_settings from app.core.config import get_settings
@@ -10,6 +11,7 @@ from app.core.errors import AppError
from app.db.session import get_db from app.db.session import get_db
from app.schemas.auth import AuthLoginRequest, AuthSession, AuthSessionEnvelope from app.schemas.auth import AuthLoginRequest, AuthSession, AuthSessionEnvelope
from app.services.auth_service import AuthPrincipal, AuthService from app.services.auth_service import AuthPrincipal, AuthService
from app.services.authentik_oidc_service import AuthentikOidcService
from app.services.demo_workflow_service import DemoWorkflowService from app.services.demo_workflow_service import DemoWorkflowService
@@ -18,6 +20,7 @@ COOKIE_NAME = "geointel_session"
def _session_from_principal(principal: AuthPrincipal, *, guest_access_enabled: bool) -> AuthSession: def _session_from_principal(principal: AuthPrincipal, *, guest_access_enabled: bool) -> AuthSession:
settings = get_settings()
return AuthSession( return AuthSession(
authentication_required=True, authentication_required=True,
authenticated=True, authenticated=True,
@@ -25,6 +28,7 @@ def _session_from_principal(principal: AuthPrincipal, *, guest_access_enabled: b
expires_at=datetime.fromtimestamp(principal.expires_at, tz=UTC), expires_at=datetime.fromtimestamp(principal.expires_at, tz=UTC),
role=principal.role, role=principal.role,
guest_access_enabled=guest_access_enabled, guest_access_enabled=guest_access_enabled,
authentik_enabled=AuthentikOidcService(settings).enabled,
guest_project_id=principal.project_id, guest_project_id=principal.project_id,
) )
@@ -32,11 +36,13 @@ def _session_from_principal(principal: AuthPrincipal, *, guest_access_enabled: b
def _session_payload(request: Request) -> AuthSession: def _session_payload(request: Request) -> AuthSession:
settings = get_settings() settings = get_settings()
guest_access_enabled = settings.auth_enabled and settings.guest_access_enabled guest_access_enabled = settings.auth_enabled and settings.guest_access_enabled
authentik_enabled = AuthentikOidcService(settings).enabled
if not settings.auth_enabled: if not settings.auth_enabled:
return AuthSession( return AuthSession(
authentication_required=False, authentication_required=False,
authenticated=True, authenticated=True,
guest_access_enabled=False, guest_access_enabled=False,
authentik_enabled=False,
) )
principal = AuthService.verify_session_token(request.cookies.get(COOKIE_NAME), settings) principal = AuthService.verify_session_token(request.cookies.get(COOKIE_NAME), settings)
if principal is None: if principal is None:
@@ -44,6 +50,7 @@ def _session_payload(request: Request) -> AuthSession:
authentication_required=True, authentication_required=True,
authenticated=False, authenticated=False,
guest_access_enabled=guest_access_enabled, guest_access_enabled=guest_access_enabled,
authentik_enabled=authentik_enabled,
) )
return _session_from_principal( return _session_from_principal(
principal, principal,
@@ -124,6 +131,35 @@ def login(payload: AuthLoginRequest, request: Request, response: Response) -> Au
) )
@router.get("/authentik/start")
def authentik_start(request: Request) -> RedirectResponse:
settings = get_settings()
service = AuthentikOidcService(settings)
try:
location, flow = service.start()
except Exception as exc:
raise AppError(code="AUTHENTIK_UNAVAILABLE", message="Authentik is momenteel niet beschikbaar.", status_code=status.HTTP_503_SERVICE_UNAVAILABLE) from exc
response = RedirectResponse(location, status_code=status.HTTP_302_FOUND)
forwarded_proto = request.headers.get("x-forwarded-proto", "").split(",", 1)[0].strip().lower()
response.set_cookie("geointel_oidc_flow", flow, max_age=600, httponly=True, secure=forwarded_proto == "https" or request.url.scheme == "https", samesite="lax", path=f"{settings.api_prefix}/auth/authentik")
return response
@router.get("/authentik/callback")
def authentik_callback(request: Request, code: str = "", state: str = "") -> RedirectResponse:
settings = get_settings()
service = AuthentikOidcService(settings)
try:
service.finish(code=code, state=state, flow_cookie=request.cookies.get("geointel_oidc_flow", ""))
token = AuthService.create_session_token(settings.auth_username or "operator", settings)
except Exception:
return RedirectResponse(f"{settings.public_base_url.rstrip('/')}?authentik=error", status_code=status.HTTP_302_FOUND)
response = RedirectResponse(settings.public_base_url.rstrip("/") + "/", status_code=status.HTTP_302_FOUND)
_set_session_cookie(request=request, response=response, token=token, max_age=settings.auth_session_ttl_seconds)
response.delete_cookie("geointel_oidc_flow", path=f"{settings.api_prefix}/auth/authentik")
return response
@router.post("/guest", response_model=AuthSessionEnvelope) @router.post("/guest", response_model=AuthSessionEnvelope)
def guest_login( def guest_login(
request: Request, request: Request,
+20
View File
@@ -22,6 +22,11 @@ class Settings(BaseSettings):
auth_username: str | None = Field(default=None, validation_alias="GEOINTEL_AUTH_USERNAME") auth_username: str | None = Field(default=None, validation_alias="GEOINTEL_AUTH_USERNAME")
auth_password_hash: str | None = Field(default=None, validation_alias="GEOINTEL_AUTH_PASSWORD_HASH") auth_password_hash: str | None = Field(default=None, validation_alias="GEOINTEL_AUTH_PASSWORD_HASH")
auth_session_secret: str | None = Field(default=None, validation_alias="GEOINTEL_AUTH_SESSION_SECRET") auth_session_secret: str | None = Field(default=None, validation_alias="GEOINTEL_AUTH_SESSION_SECRET")
authentik_issuer: str | None = Field(default=None, validation_alias="GEOINTEL_AUTHENTIK_ISSUER")
authentik_client_id: str | None = Field(default=None, validation_alias="GEOINTEL_AUTHENTIK_CLIENT_ID")
authentik_client_secret: str | None = Field(default=None, validation_alias="GEOINTEL_AUTHENTIK_CLIENT_SECRET")
authentik_allowed_email: str | None = Field(default=None, validation_alias="GEOINTEL_AUTHENTIK_ALLOWED_EMAIL")
public_base_url: str = Field(default="http://localhost:8000", validation_alias="GEOINTEL_PUBLIC_BASE_URL")
auth_session_ttl_seconds: int = Field( auth_session_ttl_seconds: int = Field(
default=43_200, default=43_200,
ge=900, ge=900,
@@ -470,6 +475,21 @@ class Settings(BaseSettings):
self.guest_display_name = self.guest_display_name.strip() self.guest_display_name = self.guest_display_name.strip()
if not self.guest_display_name: if not self.guest_display_name:
raise ValueError("GEOINTEL_GUEST_DISPLAY_NAME must not be blank") raise ValueError("GEOINTEL_GUEST_DISPLAY_NAME must not be blank")
authentik_values = (
self.authentik_issuer,
self.authentik_client_id,
self.authentik_client_secret,
self.authentik_allowed_email,
)
if any(authentik_values) and not all(authentik_values):
raise ValueError("All GEOINTEL_AUTHENTIK_* values must be configured together")
if all(authentik_values):
if not self.auth_enabled:
raise ValueError("GEOINTEL_AUTH_ENABLED must be true when Authentik is configured")
if not str(self.authentik_issuer).startswith("https://"):
raise ValueError("GEOINTEL_AUTHENTIK_ISSUER must use HTTPS")
if not self.public_base_url.startswith("https://"):
raise ValueError("GEOINTEL_PUBLIC_BASE_URL must use HTTPS for Authentik")
if not self.auth_enabled: if not self.auth_enabled:
return self return self
if not (self.auth_username or "").strip(): if not (self.auth_username or "").strip():
+2
View File
@@ -157,6 +157,8 @@ def create_app() -> FastAPI:
f"{settings.api_prefix}/auth/login", f"{settings.api_prefix}/auth/login",
f"{settings.api_prefix}/auth/guest", f"{settings.api_prefix}/auth/guest",
f"{settings.api_prefix}/auth/logout", f"{settings.api_prefix}/auth/logout",
f"{settings.api_prefix}/auth/authentik/start",
f"{settings.api_prefix}/auth/authentik/callback",
} }
direct_loopback_request = ( direct_loopback_request = (
request.client is not None request.client is not None
+1
View File
@@ -21,6 +21,7 @@ class AuthSession(BaseModel):
expires_at: datetime | None = None expires_at: datetime | None = None
role: Literal["operator", "guest"] | None = None role: Literal["operator", "guest"] | None = None
guest_access_enabled: bool = False guest_access_enabled: bool = False
authentik_enabled: bool = False
guest_project_id: UUID | None = None guest_project_id: UUID | None = None
@@ -0,0 +1,74 @@
from __future__ import annotations
import base64
import hashlib
import json
import secrets
from urllib.parse import urlencode, urlparse
from urllib.request import Request, urlopen
import jwt
from itsdangerous import BadSignature, SignatureExpired, URLSafeTimedSerializer
from app.core.config import Settings
class AuthentikOidcService:
def __init__(self, settings: Settings):
self.settings = settings
self.issuer = (settings.authentik_issuer or "").rstrip("/")
self.serializer = URLSafeTimedSerializer(settings.auth_session_secret or "", salt="geointel-authentik-v1")
@property
def enabled(self) -> bool:
return bool(self.issuer and self.settings.authentik_client_id and self.settings.authentik_client_secret and self.settings.authentik_allowed_email)
@property
def redirect_uri(self) -> str:
return f"{self.settings.public_base_url.rstrip('/')}{self.settings.api_prefix}/auth/authentik/callback"
def _discovery(self) -> dict:
document = self._fetch_json(f"{self.issuer}/.well-known/openid-configuration")
if document.get("issuer", "").rstrip("/") != self.issuer:
raise ValueError("OIDC issuer mismatch")
issuer_origin = urlparse(self.issuer)
for key in ("authorization_endpoint", "token_endpoint", "jwks_uri"):
endpoint = urlparse(document[key])
if endpoint.scheme != "https" or (endpoint.hostname, endpoint.port) != (issuer_origin.hostname, issuer_origin.port):
raise ValueError("Untrusted OIDC endpoint")
return document
@staticmethod
def _fetch_json(url: str, data: dict[str, str] | None = None) -> dict:
encoded = urlencode(data).encode("utf-8") if data is not None else None
request = Request(url, data=encoded, headers={"Accept": "application/json"})
with urlopen(request, timeout=10) as response: # noqa: S310 - URL is validated against the configured HTTPS issuer.
return json.loads(response.read())
def start(self) -> tuple[str, str]:
if not self.enabled:
raise ValueError("Authentik is not configured")
state, nonce, verifier = secrets.token_urlsafe(32), secrets.token_urlsafe(32), secrets.token_urlsafe(48)
flow = self.serializer.dumps({"state": state, "nonce": nonce, "verifier": verifier})
challenge = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode()).digest()).rstrip(b"=").decode()
discovery = self._discovery()
query = urlencode({"client_id": self.settings.authentik_client_id, "redirect_uri": self.redirect_uri, "response_type": "code", "scope": "openid email profile", "state": state, "nonce": nonce, "code_challenge": challenge, "code_challenge_method": "S256"})
return f"{discovery['authorization_endpoint']}?{query}", flow
def finish(self, *, code: str, state: str, flow_cookie: str) -> dict:
try:
flow = self.serializer.loads(flow_cookie, max_age=600)
except (BadSignature, SignatureExpired) as exc:
raise ValueError("Invalid OIDC flow") from exc
if not state or not secrets.compare_digest(state, str(flow.get("state", ""))):
raise ValueError("OIDC state mismatch")
discovery = self._discovery()
token = self._fetch_json(discovery["token_endpoint"], {"grant_type": "authorization_code", "code": code, "redirect_uri": self.redirect_uri, "client_id": self.settings.authentik_client_id or "", "client_secret": self.settings.authentik_client_secret or "", "code_verifier": flow["verifier"]}).get("id_token", "")
claims = jwt.decode(token, jwt.PyJWKClient(discovery["jwks_uri"]).get_signing_key_from_jwt(token).key, algorithms=["RS256"], audience=self.settings.authentik_client_id, issuer=discovery["issuer"], options={"require": ["exp", "iat", "iss", "aud", "sub", "nonce"]})
if claims.get("nonce") != flow["nonce"]:
raise ValueError("OIDC nonce mismatch")
email = str(claims.get("email", "")).strip().lower()
allowed = str(self.settings.authentik_allowed_email or "").strip().lower()
if claims.get("email_verified") is not True or not secrets.compare_digest(email, allowed):
raise ValueError("OIDC identity is not authorized")
return claims
+2
View File
@@ -16,6 +16,8 @@ dependencies = [
"shapely>=2.0.4", "shapely>=2.0.4",
"pyproj>=3.6.1", "pyproj>=3.6.1",
"python-multipart>=0.0.9", "python-multipart>=0.0.9",
"itsdangerous>=2.2.0",
"PyJWT[crypto]>=2.10.1",
"rdflib>=7.1,<8", "rdflib>=7.1,<8",
"alembic>=1.13.2", "alembic>=1.13.2",
] ]
+165 -1
View File
@@ -4,7 +4,7 @@
# #
# pip-compile --extra=dev --extra=gis --generate-hashes --output-file=requirements-ci.lock --strip-extras pyproject.toml # pip-compile --extra=dev --extra=gis --generate-hashes --output-file=requirements-ci.lock --strip-extras pyproject.toml
# #
# geointel-input-sha256: 03c20efedd96474cbe62591b7b70cdad2681688b618bdd76731bd4cfaf85b3d4 # geointel-input-sha256: 84583c5d59b9d8e24665361fc3624e0eb32ed32aac053966cf9bb1f1318b5c1f
affine==2.4.0 \ affine==2.4.0 \
--hash=sha256:8a3df80e2b2378aef598a83c1392efd47967afec4242021a0b06b4c7cbc61a92 \ --hash=sha256:8a3df80e2b2378aef598a83c1392efd47967afec4242021a0b06b4c7cbc61a92 \
--hash=sha256:a24d818d6a836c131976d22f8c27b8d3ca32d0af64c1d8d29deb7bafa4da1eea --hash=sha256:a24d818d6a836c131976d22f8c27b8d3ca32d0af64c1d8d29deb7bafa4da1eea
@@ -41,6 +41,108 @@ certifi==2026.6.17 \
# pyogrio # pyogrio
# pyproj # pyproj
# rasterio # rasterio
cffi==2.1.1 \
--hash=sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e \
--hash=sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66 \
--hash=sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2 \
--hash=sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0 \
--hash=sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6 \
--hash=sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971 \
--hash=sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c \
--hash=sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d \
--hash=sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9 \
--hash=sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517 \
--hash=sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735 \
--hash=sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80 \
--hash=sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f \
--hash=sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1 \
--hash=sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29 \
--hash=sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8 \
--hash=sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c \
--hash=sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e \
--hash=sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48 \
--hash=sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813 \
--hash=sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac \
--hash=sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632 \
--hash=sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6 \
--hash=sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1 \
--hash=sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659 \
--hash=sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688 \
--hash=sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004 \
--hash=sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0 \
--hash=sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062 \
--hash=sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779 \
--hash=sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94 \
--hash=sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50 \
--hash=sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab \
--hash=sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac \
--hash=sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6 \
--hash=sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676 \
--hash=sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1 \
--hash=sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9 \
--hash=sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf \
--hash=sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13 \
--hash=sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e \
--hash=sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e \
--hash=sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973 \
--hash=sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527 \
--hash=sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72 \
--hash=sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890 \
--hash=sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c \
--hash=sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990 \
--hash=sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd \
--hash=sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9 \
--hash=sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94 \
--hash=sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3 \
--hash=sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80 \
--hash=sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41 \
--hash=sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5 \
--hash=sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c \
--hash=sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a \
--hash=sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4 \
--hash=sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e \
--hash=sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6 \
--hash=sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98 \
--hash=sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b \
--hash=sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1 \
--hash=sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03 \
--hash=sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af \
--hash=sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231 \
--hash=sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2 \
--hash=sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3 \
--hash=sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836 \
--hash=sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5 \
--hash=sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399 \
--hash=sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96 \
--hash=sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e \
--hash=sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be \
--hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf \
--hash=sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc \
--hash=sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455 \
--hash=sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0 \
--hash=sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12 \
--hash=sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b \
--hash=sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7 \
--hash=sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692 \
--hash=sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54 \
--hash=sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3 \
--hash=sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b \
--hash=sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be \
--hash=sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d \
--hash=sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358 \
--hash=sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a \
--hash=sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7 \
--hash=sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc \
--hash=sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960 \
--hash=sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125 \
--hash=sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb \
--hash=sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a \
--hash=sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa \
--hash=sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf \
--hash=sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3 \
--hash=sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4 \
--hash=sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264
# via cryptography
click==8.4.2 \ click==8.4.2 \
--hash=sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6 \ --hash=sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6 \
--hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76 --hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76
@@ -57,6 +159,54 @@ cligj==0.7.2 \
--hash=sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27 \ --hash=sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27 \
--hash=sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df --hash=sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df
# via rasterio # via rasterio
cryptography==50.0.1 \
--hash=sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71 \
--hash=sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23 \
--hash=sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6 \
--hash=sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e \
--hash=sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361 \
--hash=sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054 \
--hash=sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f \
--hash=sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6 \
--hash=sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49 \
--hash=sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5 \
--hash=sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149 \
--hash=sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88 \
--hash=sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad \
--hash=sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a \
--hash=sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f \
--hash=sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2 \
--hash=sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20 \
--hash=sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45 \
--hash=sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f \
--hash=sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b \
--hash=sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527 \
--hash=sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3 \
--hash=sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6 \
--hash=sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367 \
--hash=sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0 \
--hash=sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94 \
--hash=sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239 \
--hash=sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b \
--hash=sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a \
--hash=sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9 \
--hash=sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5 \
--hash=sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc \
--hash=sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648 \
--hash=sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986 \
--hash=sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959 \
--hash=sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0 \
--hash=sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17 \
--hash=sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e \
--hash=sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733 \
--hash=sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f \
--hash=sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8 \
--hash=sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf \
--hash=sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671 \
--hash=sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80 \
--hash=sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558 \
--hash=sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef
# via pyjwt
fastapi==0.139.2 \ fastapi==0.139.2 \
--hash=sha256:333145a6891e9b5b3cfceb69baf817e8240cde4d4588ae5a10bf56ffacb6255e \ --hash=sha256:333145a6891e9b5b3cfceb69baf817e8240cde4d4588ae5a10bf56ffacb6255e \
--hash=sha256:b9ad015a835173d59865e2f5d8296fbc2b317bf56a2ba1a5bfbdd03de2fd4b1c --hash=sha256:b9ad015a835173d59865e2f5d8296fbc2b317bf56a2ba1a5bfbdd03de2fd4b1c
@@ -226,6 +376,10 @@ iniconfig==2.3.0 \
--hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \ --hash=sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730 \
--hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
# via pytest # via pytest
itsdangerous==2.2.0 \
--hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \
--hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173
# via geointel-backend (pyproject.toml)
mako==1.3.12 \ mako==1.3.12 \
--hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \ --hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \
--hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a --hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a
@@ -613,6 +767,10 @@ psycopg-binary==3.3.4 \
--hash=sha256:fa1cbc10768a796c96d3243656016bf4e337c81c71097270bb7b0ad6210d9765 \ --hash=sha256:fa1cbc10768a796c96d3243656016bf4e337c81c71097270bb7b0ad6210d9765 \
--hash=sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7 --hash=sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7
# via psycopg # via psycopg
pycparser==3.0 \
--hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
--hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992
# via cffi
pydantic==2.13.4 \ pydantic==2.13.4 \
--hash=sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba \ --hash=sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba \
--hash=sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6 --hash=sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6
@@ -750,6 +908,12 @@ pygments==2.20.0 \
--hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \ --hash=sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f \
--hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 --hash=sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176
# via pytest # via pytest
pyjwt==2.13.0 \
--hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \
--hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728
# via
# geointel-backend (pyproject.toml)
# pyjwt
pyogrio==0.13.0 \ pyogrio==0.13.0 \
--hash=sha256:1b91f6d6e6757a6ea84b9459d24f479dcb52bbf4ebcdb16baf39e49d2836a1cf \ --hash=sha256:1b91f6d6e6757a6ea84b9459d24f479dcb52bbf4ebcdb16baf39e49d2836a1cf \
--hash=sha256:220a988ce2a26591d6db5c775b07289d4f54cabdf274cc048f0e17a0b9d5be14 \ --hash=sha256:220a988ce2a26591d6db5c775b07289d4f54cabdf274cc048f0e17a0b9d5be14 \
+165 -1
View File
@@ -4,7 +4,7 @@
# #
# pip-compile --extra=gis --generate-hashes --output-file=requirements-runtime.lock --strip-extras pyproject.toml # pip-compile --extra=gis --generate-hashes --output-file=requirements-runtime.lock --strip-extras pyproject.toml
# #
# geointel-input-sha256: 0d0d2cdceb01da58354610f03b5ce244523130cf9fd29e3f8988dbe684838e8b # geointel-input-sha256: 33fe220cd418bc4cdb2ecbb3abf9b9ff50bb9c90d9fed88846fae6d640caa2f0
affine==2.4.0 \ affine==2.4.0 \
--hash=sha256:8a3df80e2b2378aef598a83c1392efd47967afec4242021a0b06b4c7cbc61a92 \ --hash=sha256:8a3df80e2b2378aef598a83c1392efd47967afec4242021a0b06b4c7cbc61a92 \
--hash=sha256:a24d818d6a836c131976d22f8c27b8d3ca32d0af64c1d8d29deb7bafa4da1eea --hash=sha256:a24d818d6a836c131976d22f8c27b8d3ca32d0af64c1d8d29deb7bafa4da1eea
@@ -38,6 +38,108 @@ certifi==2026.6.17 \
# pyogrio # pyogrio
# pyproj # pyproj
# rasterio # rasterio
cffi==2.1.1 \
--hash=sha256:046bfc24911b37851ee1b51aab8bffe713d89c68c6a057b09484ce9fd5f69b4e \
--hash=sha256:06c72bb76605a4b0cd0aad6930b69d4baf7dd5d806cfc409b824191099700e66 \
--hash=sha256:0beceaabe56af686895136a2de78db54ecd8e4046b236b8fd6d6cb61389e9bf2 \
--hash=sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0 \
--hash=sha256:194cffa889098ced9976c3fc6340305e43f6303657d298da55366907c05c22d6 \
--hash=sha256:19ee6127ee34de7d83ce3d371ebc5ed91addbdcc39f9ab15ce4eb35a4e534971 \
--hash=sha256:1a18a57b58cfb21fc28d72e876acf10eaed67a1ed96226f92af4df681d571c4c \
--hash=sha256:1aa5645c30469b09530c4ebca77ebf8f17618293c58f8549cb1a543a50236e7d \
--hash=sha256:1dea0e4d7d4f11f619fe8c1d76caf49e24405b4b5743c0e3be16a500ecd930c9 \
--hash=sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517 \
--hash=sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735 \
--hash=sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80 \
--hash=sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f \
--hash=sha256:27350daa11d4f10c540e6e89dada4c54feb7256ad03e9a4dc075ebad7ba360d1 \
--hash=sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29 \
--hash=sha256:2ae64be792b8966f2c69538199728b290e34726562896df1e5dc8ffd8d8188e8 \
--hash=sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c \
--hash=sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e \
--hash=sha256:3222ba5d678f80a030e6afbcc33dc1ae5cb45facabb61cee2c7016b8432fde48 \
--hash=sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813 \
--hash=sha256:334644fbac4eff73d985a17a91226df55d0f394160c4cfb880e084c8f7161cac \
--hash=sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632 \
--hash=sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6 \
--hash=sha256:398aff33cee2767e3e781d2554c54bd0dff386bb437581e0d8011fde1a942ec1 \
--hash=sha256:3d22a20b1fb1632cc72c22f95f7b0d2961c3e1c235f245ba4c606c4771035659 \
--hash=sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688 \
--hash=sha256:42e2f76b9455f5a9a844f770bf3e200ed3da0e15f5df3db9c31fe80b04b3d004 \
--hash=sha256:42f6930c31dc7f50732c9ae793c2786c7b6b044195967bbdde40bb9be81c4cc0 \
--hash=sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062 \
--hash=sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779 \
--hash=sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94 \
--hash=sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50 \
--hash=sha256:4be96343e422f2dfcd12ab5c9f5aebe03f82f737c6bffeca6830b3875cb44aab \
--hash=sha256:4f42141fc14250de6dde5ee7ea4432be017252d91f19c5ad043c084cea629cac \
--hash=sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6 \
--hash=sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676 \
--hash=sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1 \
--hash=sha256:5a59cc1c4442bc3d5c703bf720b51138d0bfc173618807c9ee2490a7541dd3d9 \
--hash=sha256:5bb4e7ea95dcd6a014a6fef62e62467d67d8e582326443f3d68e71d6320a9fcf \
--hash=sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13 \
--hash=sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e \
--hash=sha256:616f097f2fe415bc92a247f02e11f634e1f9e9a83d327e3c915c15089c87869e \
--hash=sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973 \
--hash=sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527 \
--hash=sha256:661c298b4821edebead0c91edd2b00374d67ad7c5a1f7a91d4442633b79d6a72 \
--hash=sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890 \
--hash=sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c \
--hash=sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990 \
--hash=sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd \
--hash=sha256:75f80557d1389eddbd0de2681f6a390a0c5338c31ddaa821381c203fc3fd50d9 \
--hash=sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94 \
--hash=sha256:7750c6449dff7864bb9bb27ddfb0267756189201a3afc911d82b3caacd70dfc3 \
--hash=sha256:7bde5e4cc5c10140859842b9d383af292b22639a4dffb725314baf45968cef80 \
--hash=sha256:7ce713ace7c0e4520535b42b77eaa742c16dab813978064913e5a3cf82973b41 \
--hash=sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5 \
--hash=sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c \
--hash=sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a \
--hash=sha256:8ef53b2de9bcb9197d31854256575d59dbac0cba72ac627bb291ef5eceb74be4 \
--hash=sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e \
--hash=sha256:9d2055050ea716bd38b7f7f1579c275386646b4894c155a3e2f3cd62ed41b7c6 \
--hash=sha256:9f8d177621de5cb38ee3e731eda45d421db093ec0739f46a5594babda7987a98 \
--hash=sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b \
--hash=sha256:a48d62ab9d6f4f98c983223a547af44be6ca3691074c31cecced6facd3ba2dc1 \
--hash=sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03 \
--hash=sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af \
--hash=sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231 \
--hash=sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2 \
--hash=sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3 \
--hash=sha256:ab36d55f9ed2d067327667c2fea18dda018eb628dd6347aa01dda6cf1f5d3836 \
--hash=sha256:ad2c86c495b899d862ea0f4b42891b8713a3bd45dd4105c7fd51c2a72f39f3a5 \
--hash=sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399 \
--hash=sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96 \
--hash=sha256:b5bdfd1c873d4e093aabc0ca84c4ca6dbc4f752afb5c86f146d9742580c9da2e \
--hash=sha256:baed1e86cc735622097354b9d1281406caf42ff42a886d29faa8e8d1630333be \
--hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf \
--hash=sha256:c26608d2222fb1e94487e4a387d85f13eb55d5ed725cb25a0c589ac4ee60e7bc \
--hash=sha256:c7659f22557c5a0bc4855cd635f55edec690cc008a40768527762cb9fb263455 \
--hash=sha256:c8c69575568085ba0b1b10c0249d779a214aea6f6522e949a0fc9fb0fcb449d0 \
--hash=sha256:c8d2c9fd1f2d16f780d15127abb050d13d1a76c03a4bd87d7e4980e45e511e12 \
--hash=sha256:ca82be1a1d406ecfe1d25dc16cb33488e5a16bf4438c9fb590484ea29d92478b \
--hash=sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7 \
--hash=sha256:d18e5ac0f2f03f4f518d3e23db0f0cad7faa1da8620e9c09461d443bbf6e6692 \
--hash=sha256:d28630f5854ab07ab1fd4aba756de52326c82e6be15d414b12793f1975048b54 \
--hash=sha256:d9c275eaacd24aa73f94ffd6de08fc3f932424d8b6c376f4bed7cde376fe7bc3 \
--hash=sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b \
--hash=sha256:dd31f52ea1086513bb9df30f8fcee9b8918323ae067a3d5b78bc826a000712be \
--hash=sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d \
--hash=sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358 \
--hash=sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a \
--hash=sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7 \
--hash=sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc \
--hash=sha256:e6e8cff14d6fb0be70a09c0bdc58096f501952d04624ebf867e0e56da2df8960 \
--hash=sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125 \
--hash=sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb \
--hash=sha256:f53e442b08449d42821fa4a4fba000095af9f62742a500f978a9f557ec44339a \
--hash=sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa \
--hash=sha256:f81b3b8f3d4e343550fa4baa0e479bba9f2d29ce9c2e9b51d1ce1718d7442fcf \
--hash=sha256:f8ec5e643a9a937f64e1999eb9f75d072263751912dc5cd06d3c85f8f44be7c3 \
--hash=sha256:fb92203a88b3d3053034db775110081c49d28be6551923805e039924093761e4 \
--hash=sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264
# via cryptography
click==8.4.2 \ click==8.4.2 \
--hash=sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6 \ --hash=sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6 \
--hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76 --hash=sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76
@@ -54,6 +156,54 @@ cligj==0.7.2 \
--hash=sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27 \ --hash=sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27 \
--hash=sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df --hash=sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df
# via rasterio # via rasterio
cryptography==50.0.1 \
--hash=sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71 \
--hash=sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23 \
--hash=sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6 \
--hash=sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e \
--hash=sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361 \
--hash=sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054 \
--hash=sha256:30a125032e5642a21ff816e021152bd4e7e94f03eff3f4b7fca41cd22bc3110f \
--hash=sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6 \
--hash=sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49 \
--hash=sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5 \
--hash=sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149 \
--hash=sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88 \
--hash=sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad \
--hash=sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a \
--hash=sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f \
--hash=sha256:55d16b1ef3ee0958d893a977b19777887e546c9954ea81b200c3301a864013f2 \
--hash=sha256:5dd9bda1c12b4162f6ff568eeb5e0ff956c28d14406e875cfe8a63a2d414ff20 \
--hash=sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45 \
--hash=sha256:5fe939deeb161024a6be98229c953b6591fef1f41214497a78fe793a244c017f \
--hash=sha256:693c99b49bd37d0d096e4334c10232c77248c415b98d35236094cdf96d57258b \
--hash=sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527 \
--hash=sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3 \
--hash=sha256:804728ce710890870f3aaa344b2e161172d258d768ac139d02cfd9092d0d94e6 \
--hash=sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367 \
--hash=sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0 \
--hash=sha256:9cb3cb952cf5a8abd50c782a98a89d71699715e802fe349704b47f2425b42a94 \
--hash=sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239 \
--hash=sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b \
--hash=sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a \
--hash=sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9 \
--hash=sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5 \
--hash=sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc \
--hash=sha256:aed8db4f6d71c51efb89530e12d9464e7bf2923d46c3205dc794a2a93f8c0648 \
--hash=sha256:b8f852c65863251b9e3a1b8c150ce21e59b522dbb6a7d4bc80e680d38388e986 \
--hash=sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959 \
--hash=sha256:ca83d00d9e69cd5eb63f2e69c3a5a59e0cecae5ae14c6ae0b35830fe3b37bad0 \
--hash=sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17 \
--hash=sha256:d63ae8f6481fec907ac0f588eee8a90aefde112c633131fe540e5711ddbb5a4e \
--hash=sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733 \
--hash=sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f \
--hash=sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8 \
--hash=sha256:f74455bb086a85d5e81246412602aaa97ed095e504cd40dd261ef50be42205bf \
--hash=sha256:fb4b9672d389c738b175c4166e78310f8a70358886aacd9173ee03a85ffdc671 \
--hash=sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80 \
--hash=sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558 \
--hash=sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef
# via pyjwt
fastapi==0.139.2 \ fastapi==0.139.2 \
--hash=sha256:333145a6891e9b5b3cfceb69baf817e8240cde4d4588ae5a10bf56ffacb6255e \ --hash=sha256:333145a6891e9b5b3cfceb69baf817e8240cde4d4588ae5a10bf56ffacb6255e \
--hash=sha256:b9ad015a835173d59865e2f5d8296fbc2b317bf56a2ba1a5bfbdd03de2fd4b1c --hash=sha256:b9ad015a835173d59865e2f5d8296fbc2b317bf56a2ba1a5bfbdd03de2fd4b1c
@@ -207,6 +357,10 @@ idna==3.18 \
--hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \ --hash=sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2 \
--hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848 --hash=sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848
# via anyio # via anyio
itsdangerous==2.2.0 \
--hash=sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef \
--hash=sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173
# via geointel-backend (pyproject.toml)
mako==1.3.12 \ mako==1.3.12 \
--hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \ --hash=sha256:8f61569480282dbf557145ce441e4ba888be453c30989f879f0d652e39f53ea9 \
--hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a --hash=sha256:9f778e93289bd410bb35daadeb4fc66d95a746f0b75777b942088b7fd7af550a
@@ -589,6 +743,10 @@ psycopg-binary==3.3.4 \
--hash=sha256:fa1cbc10768a796c96d3243656016bf4e337c81c71097270bb7b0ad6210d9765 \ --hash=sha256:fa1cbc10768a796c96d3243656016bf4e337c81c71097270bb7b0ad6210d9765 \
--hash=sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7 --hash=sha256:fbd1d4ed566895ad2d3bf4ddfd8bae90026930ddf29df3b9d91d32c8c47866a7
# via psycopg # via psycopg
pycparser==3.0 \
--hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \
--hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992
# via cffi
pydantic==2.13.4 \ pydantic==2.13.4 \
--hash=sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba \ --hash=sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba \
--hash=sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6 --hash=sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6
@@ -722,6 +880,12 @@ pydantic-settings==2.14.2 \
--hash=sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440 \ --hash=sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440 \
--hash=sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f --hash=sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f
# via geointel-backend (pyproject.toml) # via geointel-backend (pyproject.toml)
pyjwt==2.13.0 \
--hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \
--hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728
# via
# geointel-backend (pyproject.toml)
# pyjwt
pyogrio==0.13.0 \ pyogrio==0.13.0 \
--hash=sha256:1b91f6d6e6757a6ea84b9459d24f479dcb52bbf4ebcdb16baf39e49d2836a1cf \ --hash=sha256:1b91f6d6e6757a6ea84b9459d24f479dcb52bbf4ebcdb16baf39e49d2836a1cf \
--hash=sha256:220a988ce2a26591d6db5c775b07289d4f54cabdf274cc048f0e17a0b9d5be14 \ --hash=sha256:220a988ce2a26591d6db5c775b07289d4f54cabdf274cc048f0e17a0b9d5be14 \
+3
View File
@@ -80,6 +80,7 @@ def test_auth_session_and_health_are_public_but_api_is_protected(monkeypatch) ->
"expires_at": None, "expires_at": None,
"role": None, "role": None,
"guest_access_enabled": False, "guest_access_enabled": False,
"authentik_enabled": False,
"guest_project_id": None, "guest_project_id": None,
} }
assert protected.status_code == 401 assert protected.status_code == 401
@@ -113,6 +114,7 @@ def test_login_uses_http_only_session_cookie_and_logout_revokes_browser_access(m
"expires_at": login.json()["data"]["expires_at"], "expires_at": login.json()["data"]["expires_at"],
"role": "operator", "role": "operator",
"guest_access_enabled": True, "guest_access_enabled": True,
"authentik_enabled": False,
"guest_project_id": None, "guest_project_id": None,
} }
cookie = login.headers["set-cookie"].lower() cookie = login.headers["set-cookie"].lower()
@@ -223,6 +225,7 @@ def test_unraid_runtime_carries_only_hashed_operator_credentials() -> None:
browser_smoke = (root / "scripts/verify_browser_runtime.sh").read_text(encoding="utf-8") browser_smoke = (root / "scripts/verify_browser_runtime.sh").read_text(encoding="utf-8")
assert '-e GEOINTEL_AUTH_PASSWORD_HASH="$GEOINTEL_AUTH_PASSWORD_HASH"' in runner assert '-e GEOINTEL_AUTH_PASSWORD_HASH="$GEOINTEL_AUTH_PASSWORD_HASH"' in runner
assert '-e GEOINTEL_AUTHENTIK_CLIENT_SECRET="$GEOINTEL_AUTHENTIK_CLIENT_SECRET"' in runner
assert "GEOINTEL_AUTH_PASSWORD_HASH=" in example assert "GEOINTEL_AUTH_PASSWORD_HASH=" in example
assert "GEOINTEL_AUTH_PASSWORD=" not in runner assert "GEOINTEL_AUTH_PASSWORD=" not in runner
assert "GEOINTEL_GUEST_ACCESS_ENABLED=true" in example assert "GEOINTEL_GUEST_ACCESS_ENABLED=true" in example
+206 -199
View File
@@ -1,201 +1,208 @@
# GeoIntel Unraid all-in-one environment template. # GeoIntel Unraid all-in-one environment template.
# Copy this file to /mnt/user/appdata/geointel/.env and edit values there. # Copy this file to /mnt/user/appdata/geointel/.env and edit values there.
# GeoIntel runs as ONE container. This makes a bare `docker compose ...` in this # GeoIntel runs as ONE container. This makes a bare `docker compose ...` in this
# directory use the single-container file instead of the multi-container # directory use the single-container file instead of the multi-container
# development stack in docker-compose.yml. # development stack in docker-compose.yml.
COMPOSE_FILE=docker-compose.unraid.yml COMPOSE_FILE=docker-compose.unraid.yml
# Browser URL: http://<unraid-ip>:<GEOINTEL_FRONTEND_PORT> # Browser URL: http://<unraid-ip>:<GEOINTEL_FRONTEND_PORT>
GEOINTEL_FRONTEND_PORT=1202 GEOINTEL_FRONTEND_PORT=1202
# Persisted application artifacts: uploads, tiles, masks, reports and exports. # Persisted application artifacts: uploads, tiles, masks, reports and exports.
GEOINTEL_STORAGE_PATH=/mnt/user/appdata/geointel/storage GEOINTEL_STORAGE_PATH=/mnt/user/appdata/geointel/storage
# Local AI model files mounted into the container as /app/models. # Local AI model files mounted into the container as /app/models.
GEOINTEL_MODELS_PATH=/mnt/user/appdata/geointel/models GEOINTEL_MODELS_PATH=/mnt/user/appdata/geointel/models
# Checksum-verified release backups mounted read-only for cleanup guards. # Checksum-verified release backups mounted read-only for cleanup guards.
GEOINTEL_BACKUPS_PATH=/mnt/user/appdata/geointel/backups GEOINTEL_BACKUPS_PATH=/mnt/user/appdata/geointel/backups
# Embedded PostGIS data directory for the all-in-one container. # Embedded PostGIS data directory for the all-in-one container.
GEOINTEL_POSTGIS_DATA_PATH=/mnt/user/appdata/geointel/postgres-data GEOINTEL_POSTGIS_DATA_PATH=/mnt/user/appdata/geointel/postgres-data
# Internal embedded PostGIS settings. The database is not published to the LAN. # Internal embedded PostGIS settings. The database is not published to the LAN.
# Replace the placeholder with a unique secret before the first start. Production # Replace the placeholder with a unique secret before the first start. Production
# startup rejects empty and known-default passwords. # startup rejects empty and known-default passwords.
GEOINTEL_POSTGRES_DB=geointel GEOINTEL_POSTGRES_DB=geointel
GEOINTEL_POSTGRES_USER=geointel GEOINTEL_POSTGRES_USER=geointel
GEOINTEL_POSTGRES_PASSWORD=change-me-before-shared-use GEOINTEL_POSTGRES_PASSWORD=change-me-before-shared-use
# Browser origins allowed when directly calling the backend API. # Browser origins allowed when directly calling the backend API.
# Neem hier ook de publieke hostname op zodra Nginx Proxy Manager ervoor staat. # Neem hier ook de publieke hostname op zodra Nginx Proxy Manager ervoor staat.
GEOINTEL_CORS_ORIGINS=https://geointel.itworx.tech,http://geointel.itworx.tech,http://localhost:1202,http://127.0.0.1:1202,http://192.168.10.150:1202 GEOINTEL_CORS_ORIGINS=https://geointel.itworx.tech,http://geointel.itworx.tech,http://localhost:1202,http://127.0.0.1:1202,http://192.168.10.150:1202
# Upload guard in MiB. The same 1-2048 limit is applied by nginx and FastAPI. # Upload guard in MiB. The same 1-2048 limit is applied by nginx and FastAPI.
GEOINTEL_MAX_UPLOAD_MB=500 GEOINTEL_MAX_UPLOAD_MB=500
GEOINTEL_AOI_WORKER_ENABLED=true GEOINTEL_AOI_WORKER_ENABLED=true
GEOINTEL_AOI_WORKER_POLL_SECONDS=2 GEOINTEL_AOI_WORKER_POLL_SECONDS=2
# Optional single-operator access gate. Never store a plaintext password here. # Optional single-operator access gate. Never store a plaintext password here.
# Generate the password hash with AuthService.hash_password and use a unique, # Generate the password hash with AuthService.hash_password and use a unique,
# random session secret of at least 32 characters. # random session secret of at least 32 characters.
GEOINTEL_AUTH_ENABLED=false GEOINTEL_AUTH_ENABLED=false
GEOINTEL_AUTH_USERNAME= GEOINTEL_AUTH_USERNAME=
GEOINTEL_AUTH_PASSWORD_HASH= GEOINTEL_AUTH_PASSWORD_HASH=
GEOINTEL_AUTH_SESSION_SECRET= GEOINTEL_AUTH_SESSION_SECRET=
GEOINTEL_AUTH_SESSION_TTL_SECONDS=43200 GEOINTEL_AUTH_SESSION_TTL_SECONDS=43200
# Guest access is enabled by default whenever operator authentication is active. # Optionele Authentik OIDC-login naast de lokale operatorlogin.
# It opens the seeded GeoIntel demo in a temporary, API-enforced restricted GEOINTEL_PUBLIC_BASE_URL=https://geointel.itworx.tech
# session. Set this to false on installations containing private project data. GEOINTEL_AUTHENTIK_ISSUER=https://auth.example.com/application/o/geointel/
# LET OP: scripts/configure_operator_login.sh zet dit op false tenzij je GEOINTEL_AUTHENTIK_CLIENT_ID=
# expliciet --guest-access true meegeeft. GEOINTEL_AUTHENTIK_CLIENT_SECRET=
GEOINTEL_GUEST_ACCESS_ENABLED=true GEOINTEL_AUTHENTIK_ALLOWED_EMAIL=
GEOINTEL_GUEST_DISPLAY_NAME=Gast
GEOINTEL_GUEST_SESSION_TTL_SECONDS=7200 # Guest access is enabled by default whenever operator authentication is active.
# It opens the seeded GeoIntel demo in a temporary, API-enforced restricted
# Explicit, bounded acquisition from the official Digitaal Vlaanderen WMS. # session. Set this to false on installations containing private project data.
ORTHOPHOTO_ENABLED=true # LET OP: scripts/configure_operator_login.sh zet dit op false tenzij je
ORTHOPHOTO_WMS_URL=https://geo.api.vlaanderen.be/OMWRGBMRVL/wms # expliciet --guest-access true meegeeft.
SPW_ORTHOPHOTO_WMS_URL=https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer GEOINTEL_GUEST_ACCESS_ENABLED=true
BRUSSELS_ORTHOPHOTO_WMS_URL=https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows GEOINTEL_GUEST_DISPLAY_NAME=Gast
ORTHOPHOTO_WMS_LAYER=Ortho GEOINTEL_GUEST_SESSION_TTL_SECONDS=7200
ORTHOPHOTO_RESOLUTION_M=1.0
ORTHOPHOTO_MIN_SIDE_M=128 # Explicit, bounded acquisition from the official Digitaal Vlaanderen WMS.
ORTHOPHOTO_MAX_SIDE_M=1024 ORTHOPHOTO_ENABLED=true
ORTHOPHOTO_CACHE_TTL_HOURS=24 ORTHOPHOTO_WMS_URL=https://geo.api.vlaanderen.be/OMWRGBMRVL/wms
SPW_ORTHOPHOTO_WMS_URL=https://geoservices.wallonie.be/arcgis/services/IMAGERIE/ORTHO_LAST/MapServer/WMSServer
# Explicit read-only edition checks for GRB, orthophoto, Statbel and ALZ publications. BRUSSELS_ORTHOPHOTO_WMS_URL=https://geoservices-grid.irisnet.be/geoserver/urbisgrid/ows
# No feature, raster, Statbel distribution or ALZ archive is downloaded by these probes. ORTHOPHOTO_WMS_LAYER=Ortho
SOURCE_CATALOG_PROBE_ENABLED=true ORTHOPHOTO_RESOLUTION_M=1.0
SOURCE_CATALOG_GRB_WFS_URL=https://geo.api.vlaanderen.be/GRB/wfs ORTHOPHOTO_MIN_SIDE_M=128
SOURCE_CATALOG_STATBEL_DCAT_URL=https://doc.statbel.be/publications/DCAT/DCAT_opendata_datasets.ttl ORTHOPHOTO_MAX_SIDE_M=1024
SOURCE_CATALOG_STATBEL_MAX_RESPONSE_MB=5 ORTHOPHOTO_CACHE_TTL_HOURS=24
SOURCE_CATALOG_ALZ_RELEASE_URL=https://landbouwcijfers.vlaanderen.be/open-geodata-landbouwgebruikspercelen
SOURCE_CATALOG_PROBE_TIMEOUT_SECONDS=10 # Explicit read-only edition checks for GRB, orthophoto, Statbel and ALZ publications.
SOURCE_CATALOG_PROBE_MAX_RESPONSE_MB=2 # No feature, raster, Statbel distribution or ALZ archive is downloaded by these probes.
SOURCE_CATALOG_PROBE_CACHE_TTL_SECONDS=900 SOURCE_CATALOG_PROBE_ENABLED=true
SOURCE_CATALOG_GRB_WFS_URL=https://geo.api.vlaanderen.be/GRB/wfs
# Bounded official Flemish, Walloon and Brussels vectors, loaded only after a map selection. SOURCE_CATALOG_STATBEL_DCAT_URL=https://doc.statbel.be/publications/DCAT/DCAT_opendata_datasets.ttl
OFFICIAL_VECTOR_ENABLED=true SOURCE_CATALOG_STATBEL_MAX_RESPONSE_MB=5
BWK_WFS_URL=https://geo.api.vlaanderen.be/BWK/wfs SOURCE_CATALOG_ALZ_RELEASE_URL=https://landbouwcijfers.vlaanderen.be/open-geodata-landbouwgebruikspercelen
DOV_SOIL_WFS_URL=https://www.dov.vlaanderen.be/geoserver/wfs SOURCE_CATALOG_PROBE_TIMEOUT_SECONDS=10
SPW_PICC_ENABLED=true SOURCE_CATALOG_PROBE_MAX_RESPONSE_MB=2
SPW_PICC_MAPSERVER_URL=https://geoservices.wallonie.be/arcgis/rest/services/TOPOGRAPHIE/PICC_VDIFF/MapServer SOURCE_CATALOG_PROBE_CACHE_TTL_SECONDS=900
SPW_FLOOD_HAZARD_ENABLED=true
SPW_FLOOD_HAZARD_MAPSERVER_URL=https://geoservices.wallonie.be/arcgis/rest/services/EAU/ALEA_INOND/MapServer # Bounded official Flemish, Walloon and Brussels vectors, loaded only after a map selection.
URBIS_ENABLED=true OFFICIAL_VECTOR_ENABLED=true
URBIS_WFS_URL=https://geoservices-vector.irisnet.be/geoserver/urbisvector/ows BWK_WFS_URL=https://geo.api.vlaanderen.be/BWK/wfs
OFFICIAL_VECTOR_MIN_SIDE_M=10 DOV_SOIL_WFS_URL=https://www.dov.vlaanderen.be/geoserver/wfs
OFFICIAL_VECTOR_MAX_SIDE_M=20000 SPW_PICC_ENABLED=true
OFFICIAL_VECTOR_PAGE_SIZE=1000 SPW_PICC_MAPSERVER_URL=https://geoservices.wallonie.be/arcgis/rest/services/TOPOGRAPHIE/PICC_VDIFF/MapServer
OFFICIAL_VECTOR_MAX_PAGES=200 SPW_FLOOD_HAZARD_ENABLED=true
OFFICIAL_VECTOR_MAX_FEATURES=100000 SPW_FLOOD_HAZARD_MAPSERVER_URL=https://geoservices.wallonie.be/arcgis/rest/services/EAU/ALEA_INOND/MapServer
OFFICIAL_VECTOR_TIMEOUT_SECONDS=180 URBIS_ENABLED=true
OFFICIAL_VECTOR_MAX_RESPONSE_MB=20 URBIS_WFS_URL=https://geoservices-vector.irisnet.be/geoserver/urbisvector/ows
OFFICIAL_VECTOR_MAX_TOTAL_RESPONSE_MB=256 OFFICIAL_VECTOR_MIN_SIDE_M=10
OFFICIAL_VECTOR_CACHE_TTL_HOURS=24 OFFICIAL_VECTOR_MAX_SIDE_M=20000
DHMV_ENABLED=true OFFICIAL_VECTOR_PAGE_SIZE=1000
DHMV_WCS_URL=https://geo.api.vlaanderen.be/DHMV/wcs OFFICIAL_VECTOR_MAX_PAGES=200
DHMV_RESOLUTION_M=5.0 OFFICIAL_VECTOR_MAX_FEATURES=100000
DHMV_MIN_SIDE_M=10 OFFICIAL_VECTOR_TIMEOUT_SECONDS=180
DHMV_MAX_SIDE_M=20000 OFFICIAL_VECTOR_MAX_RESPONSE_MB=20
DHMV_MAX_PIXELS=12000000 OFFICIAL_VECTOR_MAX_TOTAL_RESPONSE_MB=256
DHMV_TIMEOUT_SECONDS=300 OFFICIAL_VECTOR_CACHE_TTL_HOURS=24
DHMV_MAX_RESPONSE_MB=160 DHMV_ENABLED=true
FLOOD_HAZARD_ENABLED=true DHMV_WCS_URL=https://geo.api.vlaanderen.be/DHMV/wcs
FLOOD_HAZARD_WCS_URL=https://geoservice.waterinfo.be/OGRK/wcs DHMV_RESOLUTION_M=5.0
FLOOD_HAZARD_RESOLUTION_M=5.0 DHMV_MIN_SIDE_M=10
FLOOD_HAZARD_MIN_SIDE_M=10 DHMV_MAX_SIDE_M=20000
FLOOD_HAZARD_MAX_SIDE_M=20000 DHMV_MAX_PIXELS=12000000
FLOOD_HAZARD_MAX_PIXELS=12000000 DHMV_TIMEOUT_SECONDS=300
FLOOD_HAZARD_TIMEOUT_SECONDS=300 DHMV_MAX_RESPONSE_MB=160
FLOOD_HAZARD_MAX_RESPONSE_MB=160 FLOOD_HAZARD_ENABLED=true
BATHYMETRY_PROFILES_ENABLED=true FLOOD_HAZARD_WCS_URL=https://geoservice.waterinfo.be/OGRK/wcs
BATHYMETRY_PROFILES_LAYER_URL=https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/0 FLOOD_HAZARD_RESOLUTION_M=5.0
BATHYMETRY_WATERCOURSE_LAYER_URL=https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/1 FLOOD_HAZARD_MIN_SIDE_M=10
BATHYMETRY_PROFILES_PAGE_SIZE=1000 FLOOD_HAZARD_MAX_SIDE_M=20000
BATHYMETRY_PROFILES_MAX_FEATURES=50000 FLOOD_HAZARD_MAX_PIXELS=12000000
BATHYMETRY_PROFILES_TIMEOUT_SECONDS=120 FLOOD_HAZARD_TIMEOUT_SECONDS=300
BATHYMETRY_PROFILES_MAX_RESPONSE_MB=32 FLOOD_HAZARD_MAX_RESPONSE_MB=160
MDK_BATHYMETRY_PROBE_ENABLED=true BATHYMETRY_PROFILES_ENABLED=true
MDK_BATHYMETRY_WCS_URL=https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs BATHYMETRY_PROFILES_LAYER_URL=https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/0
MDK_BATHYMETRY_PROBE_TIMEOUT_SECONDS=20 BATHYMETRY_WATERCOURSE_LAYER_URL=https://vha.waterinfo.be/arcgis/rest/services/digitale_atlas/MapServer/1
MDK_BATHYMETRY_PROBE_MAX_RESPONSE_MB=4 BATHYMETRY_PROFILES_PAGE_SIZE=1000
BATHYMETRY_PROFILES_MAX_FEATURES=50000
# Bounded MDK acquisition stays fail-closed until the readiness probe reports BATHYMETRY_PROFILES_TIMEOUT_SECONDS=120
# "reachable" and an advertised coverage id is configured explicitly. BATHYMETRY_PROFILES_MAX_RESPONSE_MB=32
MDK_BATHYMETRY_ACQUISITION_ENABLED=false MDK_BATHYMETRY_PROBE_ENABLED=true
MDK_BATHYMETRY_COVERAGE_ID= MDK_BATHYMETRY_WCS_URL=https://bathy.agentschapmdk.be/spatialfusionserver/services/ows/wcs/EL_wcs
MDK_BATHYMETRY_REQUEST_CRS=EPSG:4326 MDK_BATHYMETRY_PROBE_TIMEOUT_SECONDS=20
MDK_BATHYMETRY_MAX_BBOX_DEG2=0.25 MDK_BATHYMETRY_PROBE_MAX_RESPONSE_MB=4
MDK_BATHYMETRY_ACQUISITION_TIMEOUT_SECONDS=120
MDK_BATHYMETRY_ACQUISITION_MAX_RESPONSE_MB=160 # Bounded MDK acquisition stays fail-closed until the readiness probe reports
# "reachable" and an advertised coverage id is configured explicitly.
# Allowlisted Departement Omgeving policy rasters. Regional requests are MDK_BATHYMETRY_ACQUISITION_ENABLED=false
# transferred as fixed 10 km WCS tiles before exact Area clipping. MDK_BATHYMETRY_COVERAGE_ID=
THEMATIC_RASTER_ENABLED=true MDK_BATHYMETRY_REQUEST_CRS=EPSG:4326
THEMATIC_RASTER_WCS_URL=https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs MDK_BATHYMETRY_MAX_BBOX_DEG2=0.25
THEMATIC_RASTER_MIN_SIDE_M=100 MDK_BATHYMETRY_ACQUISITION_TIMEOUT_SECONDS=120
THEMATIC_RASTER_MAX_SIDE_M=60000 MDK_BATHYMETRY_ACQUISITION_MAX_RESPONSE_MB=160
THEMATIC_RASTER_MAX_PIXELS=30000000
THEMATIC_RASTER_TIMEOUT_SECONDS=300 # Allowlisted Departement Omgeving policy rasters. Regional requests are
THEMATIC_RASTER_MAX_RESPONSE_MB=160 # transferred as fixed 10 km WCS tiles before exact Area clipping.
WALOUS_ENABLED=true THEMATIC_RASTER_ENABLED=true
WALOUS_SOURCE_DIR=/app/storage/source-cache/walous THEMATIC_RASTER_WCS_URL=https://www.mercator.vlaanderen.be/raadpleegdienstenmercatorpubliek/wcs
WALOUS_ANALYSIS_RESOLUTION_M=10 THEMATIC_RASTER_MIN_SIDE_M=100
WALOUS_MAX_SIDE_M=60000 THEMATIC_RASTER_MAX_SIDE_M=60000
WALOUS_MAX_PIXELS=36000000 THEMATIC_RASTER_MAX_PIXELS=30000000
SPW_TERRAIN_ENABLED=true THEMATIC_RASTER_TIMEOUT_SECONDS=300
SPW_TERRAIN_SOURCE_DIR=/app/storage/source-cache/spw-terrain THEMATIC_RASTER_MAX_RESPONSE_MB=160
SPW_TERRAIN_ANALYSIS_RESOLUTION_M=5 WALOUS_ENABLED=true
SPW_TERRAIN_MAX_SIDE_M=20000 WALOUS_SOURCE_DIR=/app/storage/source-cache/walous
SPW_TERRAIN_MAX_PIXELS=12000000 WALOUS_ANALYSIS_RESOLUTION_M=10
WALOUS_MAX_SIDE_M=60000
# Optional configured-YOLO runtime. Keep disabled unless a local model is mounted. WALOUS_MAX_PIXELS=36000000
GEOINTEL_INSTALL_AI=false SPW_TERRAIN_ENABLED=true
YOLO_ENABLED=false SPW_TERRAIN_SOURCE_DIR=/app/storage/source-cache/spw-terrain
YOLO_MODELS_DIR=/app/models SPW_TERRAIN_ANALYSIS_RESOLUTION_M=5
YOLO_MODEL_PATH= SPW_TERRAIN_MAX_SIDE_M=20000
YOLO_MODEL_ID=yolo-configured SPW_TERRAIN_MAX_PIXELS=12000000
YOLO_MODEL_DISPLAY_NAME=Configured YOLO detector
YOLO_MODEL_VERSION= # Optional configured-YOLO runtime. Keep disabled unless a local model is mounted.
YOLO_CONFIG_DIR=/app/storage/ultralytics GEOINTEL_INSTALL_AI=false
YOLO_DEVICE=cuda:0 YOLO_ENABLED=false
YOLO_REQUIRE_CUDA=true YOLO_MODELS_DIR=/app/models
YOLO_MODEL_CLASSES=building YOLO_MODEL_PATH=
YOLO_ENFORCE_VALIDATION_SCOPE=true YOLO_MODEL_ID=yolo-configured
YOLO_VALIDATION_SCOPE_MANIFEST_PATH=/app/storage/operator-data/model-validation-scopes/active-building-model.json YOLO_MODEL_DISPLAY_NAME=Configured YOLO detector
YOLO_VALIDATION_SCOPE_MANIFEST_SHA256= YOLO_MODEL_VERSION=
# Deprecated display metadata; never used as an inference authorization gate. YOLO_CONFIG_DIR=/app/storage/ultralytics
YOLO_VALIDATED_AREA_NAMES=Mol,Kempen YOLO_DEVICE=cuda:0
YOLO_IMAGE_SIZE=640 YOLO_REQUIRE_CUDA=true
YOLO_MAX_TILES=100 YOLO_MODEL_CLASSES=building
YOLO_MAX_DETECTIONS=1000 YOLO_ENFORCE_VALIDATION_SCOPE=true
YOLO_DUPLICATE_IOU_THRESHOLD=0.5 YOLO_VALIDATION_SCOPE_MANIFEST_PATH=/app/storage/operator-data/model-validation-scopes/active-building-model.json
YOLO_BATCH_SIZE=1 YOLO_VALIDATION_SCOPE_MANIFEST_SHA256=
# Deprecated display metadata; never used as an inference authorization gate.
# Local segmentation models. GeoIntel never downloads model weights YOLO_VALIDATED_AREA_NAMES=Mol,Kempen
# automatically; point these to existing local files to enable inference. YOLO_IMAGE_SIZE=640
YOLO_SEG_ENABLED=false YOLO_MAX_TILES=100
YOLO_SEG_MODEL_PATH= YOLO_MAX_DETECTIONS=1000
YOLO_SEG_MODEL_ID=yolo-seg-configured YOLO_DUPLICATE_IOU_THRESHOLD=0.5
YOLO_SEG_MODEL_DISPLAY_NAME=Configured YOLO segmentation YOLO_BATCH_SIZE=1
YOLO_SEG_MODEL_VERSION=
SAM_ENABLED=false # Local segmentation models. GeoIntel never downloads model weights
SAM_MODEL_PATH= # automatically; point these to existing local files to enable inference.
SAM_MODEL_ID=sam-configured YOLO_SEG_ENABLED=false
SAM_MODEL_DISPLAY_NAME=Configured SAM segmentation YOLO_SEG_MODEL_PATH=
SAM_MODEL_VERSION= YOLO_SEG_MODEL_ID=yolo-seg-configured
SEGMENTATION_MAX_MASKS_PER_TILE=300 YOLO_SEG_MODEL_DISPLAY_NAME=Configured YOLO segmentation
SEGMENTATION_DUPLICATE_IOU_THRESHOLD=0.5 YOLO_SEG_MODEL_VERSION=
SAM_ENABLED=false
# Local Ollama assistant. The all-in-one container reaches the Unraid host SAM_MODEL_PATH=
# through Docker's host-gateway mapping; no Ollama port is exposed by GeoIntel. SAM_MODEL_ID=sam-configured
OLLAMA_ENABLED=true SAM_MODEL_DISPLAY_NAME=Configured SAM segmentation
OLLAMA_BASE_URL=http://host.docker.internal:11434 SAM_MODEL_VERSION=
OLLAMA_DEFAULT_MODEL=qwen3.5:9b SEGMENTATION_MAX_MASKS_PER_TILE=300
OLLAMA_TIMEOUT_SECONDS=120 SEGMENTATION_DUPLICATE_IOU_THRESHOLD=0.5
OLLAMA_MAX_OUTPUT_TOKENS=1200
OLLAMA_CONTEXT_TOKENS=16384 # Local Ollama assistant. The all-in-one container reaches the Unraid host
# through Docker's host-gateway mapping; no Ollama port is exposed by GeoIntel.
OLLAMA_ENABLED=true
OLLAMA_BASE_URL=http://host.docker.internal:11434
OLLAMA_DEFAULT_MODEL=qwen3.5:9b
OLLAMA_TIMEOUT_SECONDS=120
OLLAMA_MAX_OUTPUT_TOKENS=1200
OLLAMA_CONTEXT_TOKENS=16384
+23
View File
@@ -40,6 +40,11 @@ GEOINTEL_AUTH_USERNAME="${GEOINTEL_AUTH_USERNAME:-}"
GEOINTEL_AUTH_PASSWORD_HASH="${GEOINTEL_AUTH_PASSWORD_HASH:-}" GEOINTEL_AUTH_PASSWORD_HASH="${GEOINTEL_AUTH_PASSWORD_HASH:-}"
GEOINTEL_AUTH_SESSION_SECRET="${GEOINTEL_AUTH_SESSION_SECRET:-}" GEOINTEL_AUTH_SESSION_SECRET="${GEOINTEL_AUTH_SESSION_SECRET:-}"
GEOINTEL_AUTH_SESSION_TTL_SECONDS="${GEOINTEL_AUTH_SESSION_TTL_SECONDS:-43200}" GEOINTEL_AUTH_SESSION_TTL_SECONDS="${GEOINTEL_AUTH_SESSION_TTL_SECONDS:-43200}"
GEOINTEL_PUBLIC_BASE_URL="${GEOINTEL_PUBLIC_BASE_URL:-}"
GEOINTEL_AUTHENTIK_ISSUER="${GEOINTEL_AUTHENTIK_ISSUER:-}"
GEOINTEL_AUTHENTIK_CLIENT_ID="${GEOINTEL_AUTHENTIK_CLIENT_ID:-}"
GEOINTEL_AUTHENTIK_CLIENT_SECRET="${GEOINTEL_AUTHENTIK_CLIENT_SECRET:-}"
GEOINTEL_AUTHENTIK_ALLOWED_EMAIL="${GEOINTEL_AUTHENTIK_ALLOWED_EMAIL:-}"
GEOINTEL_GUEST_ACCESS_ENABLED="${GEOINTEL_GUEST_ACCESS_ENABLED:-true}" GEOINTEL_GUEST_ACCESS_ENABLED="${GEOINTEL_GUEST_ACCESS_ENABLED:-true}"
GEOINTEL_GUEST_DISPLAY_NAME="${GEOINTEL_GUEST_DISPLAY_NAME:-Gast}" GEOINTEL_GUEST_DISPLAY_NAME="${GEOINTEL_GUEST_DISPLAY_NAME:-Gast}"
GEOINTEL_GUEST_SESSION_TTL_SECONDS="${GEOINTEL_GUEST_SESSION_TTL_SECONDS:-7200}" GEOINTEL_GUEST_SESSION_TTL_SECONDS="${GEOINTEL_GUEST_SESSION_TTL_SECONDS:-7200}"
@@ -212,6 +217,19 @@ validate_runtime_config() {
esac esac
fi fi
local authentik_count=0
for value in "$GEOINTEL_AUTHENTIK_ISSUER" "$GEOINTEL_AUTHENTIK_CLIENT_ID" "$GEOINTEL_AUTHENTIK_CLIENT_SECRET" "$GEOINTEL_AUTHENTIK_ALLOWED_EMAIL"; do
if [ -n "$value" ]; then authentik_count=$((authentik_count + 1)); fi
done
if [ "$authentik_count" -ne 0 ] && [ "$authentik_count" -ne 4 ]; then
echo "All GEOINTEL_AUTHENTIK_* values must be configured together." >&2
return 2
fi
if [ "$authentik_count" -eq 4 ]; then
case "$GEOINTEL_AUTHENTIK_ISSUER" in https://*) ;; *) echo "GEOINTEL_AUTHENTIK_ISSUER must use HTTPS." >&2; return 2 ;; esac
case "$GEOINTEL_PUBLIC_BASE_URL" in https://*) ;; *) echo "GEOINTEL_PUBLIC_BASE_URL must use HTTPS for Authentik." >&2; return 2 ;; esac
fi
case "$GEOINTEL_GUEST_ACCESS_ENABLED" in case "$GEOINTEL_GUEST_ACCESS_ENABLED" in
true|false) ;; true|false) ;;
*) *)
@@ -319,6 +337,11 @@ docker run -d \
-e GEOINTEL_AUTH_PASSWORD_HASH="$GEOINTEL_AUTH_PASSWORD_HASH" \ -e GEOINTEL_AUTH_PASSWORD_HASH="$GEOINTEL_AUTH_PASSWORD_HASH" \
-e GEOINTEL_AUTH_SESSION_SECRET="$GEOINTEL_AUTH_SESSION_SECRET" \ -e GEOINTEL_AUTH_SESSION_SECRET="$GEOINTEL_AUTH_SESSION_SECRET" \
-e GEOINTEL_AUTH_SESSION_TTL_SECONDS="$GEOINTEL_AUTH_SESSION_TTL_SECONDS" \ -e GEOINTEL_AUTH_SESSION_TTL_SECONDS="$GEOINTEL_AUTH_SESSION_TTL_SECONDS" \
-e GEOINTEL_PUBLIC_BASE_URL="$GEOINTEL_PUBLIC_BASE_URL" \
-e GEOINTEL_AUTHENTIK_ISSUER="$GEOINTEL_AUTHENTIK_ISSUER" \
-e GEOINTEL_AUTHENTIK_CLIENT_ID="$GEOINTEL_AUTHENTIK_CLIENT_ID" \
-e GEOINTEL_AUTHENTIK_CLIENT_SECRET="$GEOINTEL_AUTHENTIK_CLIENT_SECRET" \
-e GEOINTEL_AUTHENTIK_ALLOWED_EMAIL="$GEOINTEL_AUTHENTIK_ALLOWED_EMAIL" \
-e GEOINTEL_GUEST_ACCESS_ENABLED="$GEOINTEL_GUEST_ACCESS_ENABLED" \ -e GEOINTEL_GUEST_ACCESS_ENABLED="$GEOINTEL_GUEST_ACCESS_ENABLED" \
-e GEOINTEL_GUEST_DISPLAY_NAME="$GEOINTEL_GUEST_DISPLAY_NAME" \ -e GEOINTEL_GUEST_DISPLAY_NAME="$GEOINTEL_GUEST_DISPLAY_NAME" \
-e GEOINTEL_GUEST_SESSION_TTL_SECONDS="$GEOINTEL_GUEST_SESSION_TTL_SECONDS" \ -e GEOINTEL_GUEST_SESSION_TTL_SECONDS="$GEOINTEL_GUEST_SESSION_TTL_SECONDS" \
+12
View File
@@ -99,6 +99,10 @@ Authenticated operator sessions return `role: "operator"`. Guest sessions
return `role: "guest"` and the UUID of their bound demo project in return `role: "guest"` and the UUID of their bound demo project in
`guest_project_id`. `guest_project_id`.
`authentik_enabled` in the session payload indicates whether the landing page
may offer the additive Authentik operator login. Existing local and guest
access remain unchanged.
### POST `/api/v1/auth/login` ### POST `/api/v1/auth/login`
```json ```json
@@ -136,6 +140,14 @@ the request body against the guest-session scope.
Clears the browser cookie and returns an unauthenticated session. Logout is Clears the browser cookie and returns an unauthenticated session. Logout is
idempotent and remains callable when the current cookie is missing or expired. idempotent and remains callable when the current cookie is missing or expired.
### GET `/api/v1/auth/authentik/start`
Starts authorization-code OIDC with PKCE, state and nonce for the configured
single operator. The exact callback is `/api/v1/auth/authentik/callback`.
Only the configured, verified operator e-mail is accepted; success creates the
same operator session as local login. These redirect endpoints are the only
additional non-envelope authentication responses.
## Health ## Health
### GET `/health/live` ### GET `/health/live`
+5
View File
@@ -12947,3 +12947,8 @@ Open:
- Kept the historical evidence boundary explicit: this runtime receipt does not - Kept the historical evidence boundary explicit: this runtime receipt does not
invent human review, a historical training commit/container, protected-test invent human review, a historical training commit/container, protected-test
independence, national validity or a new promotion decision. independence, national validity or a new promotion decision.
# 2026-08-26 — Additive Authentik operator login
- Added authorization-code OIDC with PKCE, signed state, nonce, strict issuer/audience validation and a verified e-mail allowlist.
- Kept the existing local single-operator login and API-enforced project-scoped guest demo unchanged.
- Added the Authentik action to the existing landing access card and documented the callback/environment contract.
+1
View File
@@ -1244,3 +1244,4 @@ This file now starts with the current implementation status. Older preparation/b
through a bottom rail and remove clipped result metrics. through a bottom rail and remove clipped result metrics.
- [ ] Repeat the responsive audit with a physical touch device and screen - [ ] Repeat the responsive audit with a physical touch device and screen
reader before claiming full mobile accessibility certification. reader before claiming full mobile accessibility certification.
- [x] Offer Authentik as an additive single-operator login while retaining local recovery and the scoped guest demo.
+1
View File
@@ -26,6 +26,7 @@ function App(): JSX.Element {
<LandingPage <LandingPage
serviceError={sessionError} serviceError={sessionError}
guestAccessEnabled={session.guest_access_enabled} guestAccessEnabled={session.guest_access_enabled}
authentikEnabled={session.authentik_enabled}
onAuthenticated={handleAuthenticated} onAuthenticated={handleAuthenticated}
/> />
) )
@@ -28,6 +28,7 @@ interface LandingPageProps {
onAuthenticated: (session: AuthSession) => void onAuthenticated: (session: AuthSession) => void
serviceError?: string | null serviceError?: string | null
guestAccessEnabled?: boolean guestAccessEnabled?: boolean
authentikEnabled?: boolean
} }
const capabilityItems = [ const capabilityItems = [
@@ -62,6 +63,7 @@ export function LandingPage({
onAuthenticated, onAuthenticated,
serviceError = null, serviceError = null,
guestAccessEnabled = false, guestAccessEnabled = false,
authentikEnabled = false,
}: LandingPageProps): JSX.Element { }: LandingPageProps): JSX.Element {
const [username, setUsername] = useState('') const [username, setUsername] = useState('')
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
@@ -247,6 +249,13 @@ export function LandingPage({
</div> </div>
<form onSubmit={submitLogin} aria-busy={pendingAction === 'operator'}> <form onSubmit={submitLogin} aria-busy={pendingAction === 'operator'}>
{authentikEnabled ? (
<a className="landing-authentik-submit" href="/api/v1/auth/authentik/start">
<ShieldCheck aria-hidden="true" />
Aanmelden met Authentik
</a>
) : null}
{authentikEnabled ? <div className="landing-access-divider"><span>of met lokale operatorgegevens</span></div> : null}
<label htmlFor="login-username">Gebruikersnaam</label> <label htmlFor="login-username">Gebruikersnaam</label>
<input <input
ref={usernameRef} ref={usernameRef}
+1
View File
@@ -7,6 +7,7 @@ export interface AuthSession {
expires_at: string | null expires_at: string | null
role: 'operator' | 'guest' | null role: 'operator' | 'guest' | null
guest_access_enabled: boolean guest_access_enabled: boolean
authentik_enabled?: boolean
guest_project_id: string | null guest_project_id: string | null
} }
+18
View File
@@ -793,6 +793,24 @@ body.landing-body {
box-shadow: var(--gi-shadow-md); box-shadow: var(--gi-shadow-md);
transition: background 160ms ease, transform 160ms ease, box-shadow 160ms ease; transition: background 160ms ease, transform 160ms ease, box-shadow 160ms ease;
} }
.landing-authentik-submit {
display: inline-flex;
min-height: 3rem;
gap: 0.55rem;
align-items: center;
justify-content: center;
border: 1px solid var(--landing-primary);
border-radius: var(--gi-radius-md);
padding: 0.72rem 1rem;
background: var(--landing-primary);
color: #fff;
font-size: 0.77rem;
font-weight: 600;
text-decoration: none;
box-shadow: var(--gi-shadow-md);
}
.landing-authentik-submit:hover { background: var(--landing-primary-strong); color: #fff; }
.landing-authentik-submit svg { width: 1rem; height: 1rem; }
.landing-operator-submit:hover:not(:disabled) { .landing-operator-submit:hover:not(:disabled) {
background: var(--landing-primary-strong); background: var(--landing-primary-strong);