From 789da403036dd4c77bb9148771dbd867813b42f6 Mon Sep 17 00:00:00 2001 From: Oliver Meyer Date: Tue, 14 Apr 2026 17:29:02 +0200 Subject: [PATCH] fix(api.auth): pass ctx.env_file to AuthSettings Co-Authored-By: Claude Sonnet 4.6 --- ATTRIBUTIONS.md | 4 +-- src/aignostics_foundry_core/AGENTS.md | 4 +-- src/aignostics_foundry_core/api/auth.py | 14 +++++----- .../aignostics_foundry_core/api/auth_test.py | 27 +++++++++++++++++++ 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/ATTRIBUTIONS.md b/ATTRIBUTIONS.md index d361e05..9592c5e 100644 --- a/ATTRIBUTIONS.md +++ b/ATTRIBUTIONS.md @@ -9202,12 +9202,12 @@ SOFTWARE. ``` -## pytest (9.0.2) - UNKNOWN +## pytest (9.0.3) - UNKNOWN pytest: simple powerful testing with Python * URL: https://docs.pytest.org/en/latest/ -* Author(s): Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin, Others (See AUTHORS) +* Author(s): Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Freya Bruhin, Others (See AUTHORS) ### License Text diff --git a/src/aignostics_foundry_core/AGENTS.md b/src/aignostics_foundry_core/AGENTS.md index a6a054d..3684112 100644 --- a/src/aignostics_foundry_core/AGENTS.md +++ b/src/aignostics_foundry_core/AGENTS.md @@ -11,7 +11,7 @@ This file provides an overview of all modules in `aignostics_foundry_core`, thei | **models** | Shared output format enum | `OutputFormat` StrEnum with `YAML` and `JSON` values for use in CLI and API responses | | **process** | Current process introspection | `ProcessInfo`, `ParentProcessInfo` Pydantic models and `get_process_info()` for runtime process metadata; `SUBPROCESS_CREATION_FLAGS` for subprocess creation | | **api.exceptions** | API exception hierarchy and FastAPI handlers | `ApiException` (500), `NotFoundException` (404), `AccessDeniedException` (401); `api_exception_handler`, `unhandled_exception_handler`, `validation_exception_handler` for FastAPI registration | -| **api.auth** | Auth0 authentication FastAPI dependencies | `AuthSettings` (env-prefix configurable), `UnauthenticatedError`, `ForbiddenError` (403); `get_auth_client`, `get_user`, `require_authenticated`, `require_admin`, `require_internal`, `require_internal_admin` FastAPI dependencies; Auth0 cookie security schemes | +| **api.auth** | Auth0 authentication FastAPI dependencies | `AuthSettings` (env-prefix and env files derived from `ctx.env_prefix`/`ctx.env_file`), `UnauthenticatedError`, `ForbiddenError` (403); `get_auth_client`, `get_user`, `require_authenticated`, `require_admin`, `require_internal`, `require_internal_admin` FastAPI dependencies; Auth0 cookie security schemes | | **api.core** | Versioned API router and FastAPI factory | `VersionedAPIRouter` (tracks all created instances), `API_TAG_*` constants, `create_public/authenticated/admin/internal/internal_admin_router` factories, `build_api_metadata`, `build_versioned_api_tags`, `build_root_api_tags`, `get_versioned_api_instances(versions, build_metadata=None, *, context=None)`, `init_api()` | | **api** | Consolidated API sub-package | Re-exports all public symbols from `api.exceptions`, `api.auth`, and `api.core`; import any API symbol directly from `aignostics_foundry_core.api` | | **log** | Configurable loguru logging initialisation | `logging_initialize(filter_func=None, *, context=None)`, `LogSettings` (env-prefix configurable), `InterceptHandler` for stdlib-to-loguru bridging | @@ -112,7 +112,7 @@ This file provides an overview of all modules in `aignostics_foundry_core`, thei - **Purpose**: Provides Auth0 cookie-based session authentication dependencies for FastAPI routes. All project-specific settings (org ID, role claim) are loaded from `AuthSettings` whose env prefix is configurable at instantiation. - **Key Features**: - - `AuthSettings(OpaqueSettings)` — uses the active FoundryContext.env_prefix to derive the env prefix (`{ctx.env_prefix}AUTH_`). Fields: `internal_org_id` (required `str`; identifies the internal organization), `auth0_role_claim` (required `str`; JWT claim name for role). Both fields are mandatory — no defaults are provided. + - `AuthSettings(OpaqueSettings)` — uses the active FoundryContext to derive both the env prefix (`{ctx.env_prefix}AUTH_`) and the env file list (`ctx.env_file`). Fields: `internal_org_id` (required `str`; identifies the internal organization), `auth0_role_claim` (required `str`; JWT claim name for role). Both fields are mandatory — no defaults are provided. - `UnauthenticatedError(Exception)` — raised when a user session is missing or invalid - `ForbiddenError(ApiException)` — `status_code = 403`; raised when user lacks required role or org membership - `get_auth_client(request)` — retrieves `AuthClient` from `request.app.state.auth_client`; raises `RuntimeError` if not configured diff --git a/src/aignostics_foundry_core/api/auth.py b/src/aignostics_foundry_core/api/auth.py index 6eabefc..a0d30ed 100644 --- a/src/aignostics_foundry_core/api/auth.py +++ b/src/aignostics_foundry_core/api/auth.py @@ -31,13 +31,14 @@ class AuthSettings(OpaqueSettings): - """Auth settings whose env prefix is derived from the active FoundryContext. + """Auth settings whose env prefix and env files are derived from the active FoundryContext. - The effective prefix is ``{FoundryContext.env_prefix}AUTH_``, resolved at - instantiation time via :func:`aignostics_foundry_core.foundry.get_context`. + The effective prefix is ``{FoundryContext.env_prefix}AUTH_`` and the env files are + ``FoundryContext.env_file``, both resolved at instantiation time via + :func:`aignostics_foundry_core.foundry.get_context`. Both ``internal_org_id`` and ``auth0_role_claim`` are required — they must be - provided via the corresponding environment variables (no defaults). + provided via environment variables or ``.env`` files (no defaults). """ model_config = SettingsConfigDict(extra="ignore") @@ -46,8 +47,9 @@ class AuthSettings(OpaqueSettings): auth0_role_claim: str def __init__(self, **kwargs: Any) -> None: # noqa: ANN401 - """Initialise settings, deriving env_prefix from the active FoundryContext.""" - super().__init__(_env_prefix=f"{get_context().env_prefix}AUTH_", **kwargs) # pyright: ignore[reportCallIssue] + """Initialise settings, deriving env_prefix and env files from the active FoundryContext.""" + ctx = get_context() + super().__init__(_env_prefix=f"{ctx.env_prefix}AUTH_", _env_file=ctx.env_file, **kwargs) # pyright: ignore[reportCallIssue] class UnauthenticatedError(Exception): diff --git a/tests/aignostics_foundry_core/api/auth_test.py b/tests/aignostics_foundry_core/api/auth_test.py index 65eac6e..537f273 100644 --- a/tests/aignostics_foundry_core/api/auth_test.py +++ b/tests/aignostics_foundry_core/api/auth_test.py @@ -3,6 +3,7 @@ import os import time from collections.abc import Generator +from pathlib import Path from unittest.mock import AsyncMock, MagicMock import pytest @@ -19,7 +20,9 @@ require_internal, require_internal_admin, ) +from aignostics_foundry_core.foundry import set_context from tests.aignostics_foundry_core.api import AUTH0_ROLE_CLAIM_VAR_NAME, INTERNAL_ORG_ID_VAR_NAME +from tests.conftest import make_context _INTERNAL_ORG_ID = "org_internal_123" _OTHER_ORG_ID = "org_other_456" @@ -112,6 +115,30 @@ def test_auth_settings_raises_when_required_fields_absent(self, monkeypatch: pyt AuthSettings() +@pytest.mark.integration +class TestAuthSettingsEnvFile: + """Tests for AuthSettings env file loading.""" + + def test_auth_settings_reads_fields_from_env_file_via_context( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + """AuthSettings reads required fields from a .env file listed in the active FoundryContext.""" + env_file = tmp_path / ".env" + env_file.write_text( + f"{INTERNAL_ORG_ID_VAR_NAME}=org_from_env_file\n{AUTH0_ROLE_CLAIM_VAR_NAME}=claim_from_env_file\n" + ) + + set_context(make_context(env_file=[env_file])) + + monkeypatch.delenv(INTERNAL_ORG_ID_VAR_NAME, raising=False) + monkeypatch.delenv(AUTH0_ROLE_CLAIM_VAR_NAME, raising=False) + + settings = AuthSettings() + + assert settings.internal_org_id == "org_from_env_file" + assert settings.auth0_role_claim == "claim_from_env_file" + + @pytest.mark.integration class TestGetUser: """Tests for get_user FastAPI dependency."""