fastapi-m8 2.0.0__tar.gz → 3.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastapi_m8-3.0.0/.github/FUNDING.yml +13 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/CHANGELOG.md +119 -1
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/PKG-INFO +51 -17
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/README.md +49 -15
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/_app.py +58 -11
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/_compat.py +18 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/_revocation.py +92 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/_version.py +1 -1
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/config.py +31 -13
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/pyproject.toml +2 -2
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_app.py +88 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_app_extra.py +76 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_config.py +2 -2
- fastapi_m8-3.0.0/tests/test_config_file_secrets.py +135 -0
- fastapi_m8-3.0.0/tests/test_host_header_routing.py +227 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_meta.py +27 -4
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_revocation.py +148 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/.codacy.yml +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/.env.example +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/.gitattributes +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/.github/dependabot.yml +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/.github/workflows/CI.yaml +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/.github/workflows/PiPy.yml +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/.gitignore +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/.pydocstyle +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/LICENSE +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/__init__.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/_async_stub.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/_deps.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/_engine.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/_events.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/_health.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/scripts/__init__.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/scripts/docker_start.sh +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/fastapi_m8/scripts/pre_start.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/__init__.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/conftest.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_async_stub.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_compat.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_deps.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_engine.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_events.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_health.py +0 -0
- {fastapi_m8-2.0.0 → fastapi_m8-3.0.0}/tests/test_pre_start.py +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# These are supported funding model platforms
|
|
2
|
+
|
|
3
|
+
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
|
4
|
+
patreon: # Replace with a single Patreon username
|
|
5
|
+
open_collective: # Replace with a single Open Collective username
|
|
6
|
+
ko_fi: eliserra
|
|
7
|
+
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
|
8
|
+
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
|
9
|
+
liberapay: # Replace with a single Liberapay username
|
|
10
|
+
issuehunt: # Replace with a single IssueHunt username
|
|
11
|
+
otechie: # Replace with a single Otechie username
|
|
12
|
+
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
|
13
|
+
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
|
@@ -5,7 +5,125 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) · Versioning:
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [3.0.0] — 2026-06-23 · auth-sdk-m8 2.0.0 alignment — single-mount `/ping` + SDK major floor
|
|
9
|
+
|
|
10
|
+
> **MAJOR.** Two independent breaking changes, either of which alone forces this bump:
|
|
11
|
+
> (1) `mount_service_meta` single-mounts `/ping` at the effective prefix — callers that
|
|
12
|
+
> relied on a bare root `/ping` when a prefix is configured must switch container/sidecar
|
|
13
|
+
> probes to `{API_PREFIX}/ping`; (2) the **required** `auth-sdk-m8` floor crosses a major
|
|
14
|
+
> (`<2.0.0` → `>=2.0.1,<3.0.0`), which removed deprecated SDK APIs — `pip install -U
|
|
15
|
+
> fastapi-m8` now force-upgrades the SDK across that major. (Supersedes the never-released
|
|
16
|
+
> 2.2.0 label: the same work, correctly versioned as a major.)
|
|
17
|
+
|
|
18
|
+
### ⚠️ Breaking change — `/ping` is now single-mount
|
|
19
|
+
|
|
20
|
+
`auth-sdk-m8 2.0.0` removed the dual-mount behaviour introduced in 1.5.0. When `API_PREFIX`
|
|
21
|
+
is set (the normal consumer case), `/ping` is now mounted **only** at `{API_PREFIX}/ping`
|
|
22
|
+
(e.g. `/api/ping`). The root `/ping` no longer exists when a prefix is configured.
|
|
23
|
+
|
|
24
|
+
- **What was true in 2.1.x:** root `GET /ping` always returned 200 regardless of
|
|
25
|
+
`API_PREFIX`; additionally `GET {API_PREFIX}/ping` was mounted (schema-hidden copy).
|
|
26
|
+
- **What is true in 3.0.x:** only `GET {API_PREFIX}/ping` exists when a prefix is set;
|
|
27
|
+
only `GET /ping` (root) when no prefix is set. The single mount is **always in the
|
|
28
|
+
OpenAPI schema** — it is no longer hidden.
|
|
29
|
+
- **Action required:** update container `livenessProbe` / sidecar healthcheck URLs from
|
|
30
|
+
`/ping` → `{API_PREFIX}/ping` (e.g. `/api/ping`). No Python code change is needed.
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
- **Requires `auth-sdk-m8 >= 2.0.1, < 3.0.0`** (was `>= 1.5.0, < 2.0.0`). The
|
|
35
|
+
dependency floor, `COMPAT_MATRIX` `3.0` entry, and `pyproject.toml` pin are updated.
|
|
36
|
+
auth-sdk-m8 2.0.0 also ships `ConsumerScope` / `ConsumerCredential` /
|
|
37
|
+
`ConsumerCredentialRegistry` / `make_consumer_authorizer` (Phase 9.1) and the
|
|
38
|
+
`SECURITY.md` mTLS guidance (Phase 9.2) — available to consumers via the SDK without
|
|
39
|
+
any fastapi-m8 code change.
|
|
40
|
+
|
|
41
|
+
### Why the floor is a **major** — auth-sdk-m8 2.0.0 dropped deprecated APIs
|
|
42
|
+
|
|
43
|
+
auth-sdk-m8 2.0.0 is a major because it **removes** every previously-deprecated
|
|
44
|
+
surface. fastapi-m8 was never coupled to any of them, so no consumer-facing code,
|
|
45
|
+
import, or setting changes here — the full suite is green at 100 % against the SDK
|
|
46
|
+
2.0.0 final. The removals, and why fastapi-m8 is already clear of each:
|
|
47
|
+
|
|
48
|
+
- **Redis Pub/Sub event bus** (`auth_sdk_m8.redis_events`: `EventBus` /
|
|
49
|
+
`EventPublisher` / `EventSubscriber`). fastapi-m8 consumes auth events over the
|
|
50
|
+
**fa-auth SSE bridge** (`auth_sdk_m8.events.AuthEventStreamClient`, re-exported as
|
|
51
|
+
`build_event_stream_client` / `AuthEventStreamClient` since 1.4.0), never the
|
|
52
|
+
Redis bus. The retained signing helpers moved to `auth_sdk_m8.events._signing`
|
|
53
|
+
(wire format unchanged); fastapi-m8 does not import them directly.
|
|
54
|
+
- **`ComSecurityHelper.decode_access_token`** + `LEGACY_ACCESS_TOKEN_VALIDATION_CONFIG`.
|
|
55
|
+
fastapi-m8 validates tokens through `build_auth_deps()` → `build_access_validator`
|
|
56
|
+
(`TokenValidator`), the non-deprecated path.
|
|
57
|
+
- **`TOKEN_ALGORITHM`** knob. `ConsumerServiceSettings` exposes `ACCESS_TOKEN_ALGORITHM`
|
|
58
|
+
directly (RS256 default); the deprecated seeding knob was never surfaced.
|
|
59
|
+
- **Module-level `settings_customise_sources()`**. The `_FILE`/Vault source ordering
|
|
60
|
+
comes from the retained `CommonSettings.settings_customise_sources` **classmethod**
|
|
61
|
+
that `ConsumerServiceSettings` inherits — unchanged and still regression-tested.
|
|
62
|
+
|
|
63
|
+
### Security — floor is `>= 2.0.1` to carry the `pydantic-settings` fix
|
|
64
|
+
|
|
65
|
+
The required `auth-sdk-m8` floor is **`>= 2.0.1`** (not `2.0.0`). auth-sdk-m8 2.0.1
|
|
66
|
+
raised its `[config]` `pydantic-settings` floor `>= 2.14.1` → `>= 2.14.2`, the patch
|
|
67
|
+
that hardens pydantic-settings' nested-secrets source against symlink escape/loop
|
|
68
|
+
traversal. fastapi-m8 does **not** import `pydantic_settings` directly — it inherits
|
|
69
|
+
`CommonSettings` (a `BaseSettings`) from `auth-sdk-m8[config]`, so the fix arrives
|
|
70
|
+
transitively through this floor bump. No separate `pydantic-settings` pin is added
|
|
71
|
+
here: the SDK's `[config]` extra owns that dependency, and duplicating the pin would
|
|
72
|
+
fork a single source of truth.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## [2.1.0] — 2026-06-19 · Security-remediation hardening + proxy-routable `{API_PREFIX}/ping`
|
|
77
|
+
|
|
78
|
+
> **Requires `auth-sdk-m8 >= 1.5.0`** — `mount_service_meta` dual-mounts `/ping`.
|
|
79
|
+
|
|
80
|
+
### Added
|
|
81
|
+
|
|
82
|
+
- **Proxy-routable `/ping`** picked up from `auth-sdk-m8 1.5.0`. `mount_service_meta`
|
|
83
|
+
now dual-mounts the liveness probe: the unchanged root `GET /ping` **plus** a
|
|
84
|
+
`GET {API_PREFIX}/ping` copy. `create_app` already passes `prefix=API_PREFIX`, so
|
|
85
|
+
the prefixed probe appears automatically with **no call-site change** — liveness
|
|
86
|
+
now resolves behind a prefix-routing reverse proxy (Traefik forwards only
|
|
87
|
+
`PathPrefix({API_PREFIX})`, so the root-only `/ping` previously 404'd at the
|
|
88
|
+
gateway while `{API_PREFIX}/meta` resolved). The prefixed copy is
|
|
89
|
+
`include_in_schema=False`, so OpenAPI still carries a single `ping` operation.
|
|
90
|
+
- **`_FILE` secret mounts for consumers** (security remediation 6.1). Documented and
|
|
91
|
+
regression-tested that `ConsumerServiceSettings` inherits the Docker/K8s
|
|
92
|
+
`<FIELD>_FILE` convention from `auth-sdk-m8`'s `CommonSettings` — no consumer code
|
|
93
|
+
change. Any secret can be mounted from a file via `<FIELD>_FILE` (e.g.
|
|
94
|
+
`DB_PASSWORD_FILE`, `PRIVATE_API_SECRET_FILE`, `METRICS_SCRAPE_CREDENTIAL_FILE`)
|
|
95
|
+
pointing under `/run/secrets/*`, so the production overlay keeps plaintext secrets
|
|
96
|
+
out of env files. The mount outranks plaintext `.env`/env values but not explicit
|
|
97
|
+
constructor kwargs; a missing file fails closed at construction; file-sourced
|
|
98
|
+
`SecretStr` values stay masked in `repr`. Coverage spans consumer-declared
|
|
99
|
+
(`METRICS_SCRAPE_CREDENTIAL`), `ConsumerAuthMixin` (`PRIVATE_API_SECRET`), and
|
|
100
|
+
`CommonSettings` (`DB_PASSWORD`) fields.
|
|
101
|
+
- **Revocation-cache observability** (security remediation 7.x.2). The consumer-side
|
|
102
|
+
JTI revocation cache now emits best-effort Prometheus metrics on the shared
|
|
103
|
+
`auth-sdk-m8[observability]` registry: `revocation_cache_lookups_total{result="hit"|"miss"}`
|
|
104
|
+
and a `revocation_cache_ttl_seconds` gauge for the configured stale-window TTL. Emission
|
|
105
|
+
is zero-cost when observability is disabled or the extra is absent. Metrics carry **no
|
|
106
|
+
JTI, user ID, or secret** as a label or value, and cache construction logs the TTL only
|
|
107
|
+
(never the introspection URL or secret) — satisfying the "keys/secrets are never logged"
|
|
108
|
+
acceptance criterion. The SDK owns the event-stream signals (connected/gap/reconnect);
|
|
109
|
+
this is the consumer cache hit/miss + TTL side.
|
|
110
|
+
- `create_app` now **auto-runs the shared `check_config_health()`** (from
|
|
111
|
+
`auth_sdk_m8.core.config`) as an internal startup validator, **prepended** to any
|
|
112
|
+
caller-provided `startup_validators`. It runs inside the lifespan (not at import time),
|
|
113
|
+
so a fatal misconfiguration (e.g. production `localhost` CORS origins, a wildcard
|
|
114
|
+
`ALLOWED_HOSTS` under strict mode) aborts startup with `ConfigurationError` **before**
|
|
115
|
+
user validators run and before the service is marked ready. Consumers now get the same
|
|
116
|
+
production safety checks the auth service already runs, automatically.
|
|
117
|
+
|
|
118
|
+
### Changed
|
|
119
|
+
|
|
120
|
+
- **Requires `auth-sdk-m8 >= 1.5.0`** (was `>= 1.4.0`). The dependency floor and the
|
|
121
|
+
`COMPAT_MATRIX` `2.1` entry are bumped so the dual-mounted `{API_PREFIX}/ping` is
|
|
122
|
+
guaranteed present; on `auth-sdk-m8 1.4.0` only the root `/ping` exists.
|
|
123
|
+
- `ALLOWED_HOSTS` is no longer redefined on `ConsumerServiceSettings` — it is inherited
|
|
124
|
+
from `CommonSettings` (auth-sdk-m8), the single source of truth. The default is now
|
|
125
|
+
`None` (unset) rather than `[]`; both are falsy, so `TrustedHostMiddleware` is still
|
|
126
|
+
skipped when unset. Production/strict gating lives in `check_config_health`.
|
|
9
127
|
|
|
10
128
|
---
|
|
11
129
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-m8
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0
|
|
4
4
|
Summary: FastAPI application framework for m8 consumer microservices.
|
|
5
5
|
Author-email: Eli Serra <e.serra173@gmail.com>
|
|
6
6
|
License: Apache License
|
|
@@ -216,7 +216,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
216
216
|
Classifier: Topic :: Software Development :: Libraries
|
|
217
217
|
Requires-Python: >=3.11
|
|
218
218
|
Requires-Dist: anyio>=4.0
|
|
219
|
-
Requires-Dist: auth-sdk-m8[config,events,fastapi,observability,security]<
|
|
219
|
+
Requires-Dist: auth-sdk-m8[config,events,fastapi,observability,security]<3.0.0,>=2.0.1
|
|
220
220
|
Requires-Dist: fastapi>=0.136.3
|
|
221
221
|
Requires-Dist: httpx>=0.27.0
|
|
222
222
|
Requires-Dist: packaging>=24.0
|
|
@@ -308,13 +308,15 @@ health checks; the framework wires the rest.
|
|
|
308
308
|
|---|---|
|
|
309
309
|
| JWT validation | `build_auth_deps()` + `auth-sdk-m8` validator |
|
|
310
310
|
| Role-based access control | `AuthDeps.get_current_active_admin / _superuser` |
|
|
311
|
-
| Token revocation (stateful mode) | `RemoteRevocationClient` → `fa-auth-m8` private API |
|
|
311
|
+
| Token revocation (stateful mode) | `RemoteRevocationClient` → `fa-auth-m8` private API (optional short-TTL cache via `REVOCATION_CACHE_TTL_SECONDS`) |
|
|
312
|
+
| Auth event stream (optional) | `build_event_stream_client()` → fa-auth SSE bridge for best-effort cache eviction |
|
|
312
313
|
| CORS | Auto-wired from `settings.ALLOWED_ORIGINS` |
|
|
313
|
-
| Metrics middleware | Optional; toggled via `METRICS_ENABLED` |
|
|
314
|
+
| Metrics middleware + `/metrics` | Optional; toggled via `METRICS_ENABLED`, scrape-gated via `METRICS_SCRAPE_CREDENTIAL` |
|
|
315
|
+
| Response security headers | Tiered hardening from `auth-sdk-m8` (HSTS/CSP express opt-in) |
|
|
314
316
|
| Health endpoint | `GET {API_PREFIX}/health/` with optional detail gating |
|
|
315
|
-
| Service meta + liveness | Auto-mounted `GET {API_PREFIX}/meta` + `GET /ping` (fail-closed at boot) |
|
|
317
|
+
| Service meta + liveness | Auto-mounted `GET {API_PREFIX}/meta` + `GET {API_PREFIX}/ping` (fail-closed at boot; single-mount at the effective prefix) |
|
|
316
318
|
| Database lifecycle | `create_db_engine()` wrapping SQLAlchemy |
|
|
317
|
-
| Startup validation | `
|
|
319
|
+
| Startup validation | Auto-run `check_config_health()` + caller `startup_validators` before app signals ready |
|
|
318
320
|
| Lifespan management | Auth teardown + DB pool dispose on shutdown |
|
|
319
321
|
|
|
320
322
|
**What it is NOT:**
|
|
@@ -556,9 +558,17 @@ environment variable.
|
|
|
556
558
|
|
|
557
559
|
`create_app` auto-mounts the shared service triad from `auth-sdk-m8`: `GET {API_PREFIX}/meta`
|
|
558
560
|
(cacheable service/version/contract identity, read by clients pre-auth to assert compatibility)
|
|
559
|
-
and a
|
|
560
|
-
|
|
561
|
-
|
|
561
|
+
and a dependency-free `GET {API_PREFIX}/ping` liveness probe (→ `{"status": "ok"}`). `/ping` is
|
|
562
|
+
mounted **once** at the effective prefix (single-mount since auth-sdk-m8 2.0.0): when a prefix is
|
|
563
|
+
set, only `{API_PREFIX}/ping` exists so liveness stays reachable behind a prefix-routing reverse
|
|
564
|
+
proxy (Traefik forwards only `PathPrefix({API_PREFIX})`); when no prefix is set, `/ping` is at the
|
|
565
|
+
root. The single mount always appears in the OpenAPI schema. The `/meta` values are sourced from
|
|
566
|
+
these settings, so a consumer **fails closed at boot** if it doesn't declare its identity. Keep
|
|
567
|
+
both separate from a dependency-aware `/health` readiness probe.
|
|
568
|
+
|
|
569
|
+
> **⚠️ Breaking change (3.0.0 / auth-sdk-m8 2.0.0):** root `GET /ping` no longer exists when
|
|
570
|
+
> `API_PREFIX` is set. Update container `livenessProbe` / sidecar healthcheck URLs from `/ping`
|
|
571
|
+
> to `{API_PREFIX}/ping` (e.g. `/api/ping`).
|
|
562
572
|
|
|
563
573
|
| Variable | Required | Default | Description |
|
|
564
574
|
|---|---|---|---|
|
|
@@ -604,6 +614,8 @@ Required only when `TOKEN_MODE=stateful` and `AUTH_SERVICE_ROLE=consumer`.
|
|
|
604
614
|
|---|---|---|---|
|
|
605
615
|
| `INTROSPECTION_URL` | Yes | — | `POST` endpoint on auth service for JTI revocation checks, e.g. `http://auth_user_service:8000/user/private/v1/jti-status` |
|
|
606
616
|
| `PRIVATE_API_SECRET` | Yes | — | Shared secret for `X-Internal-Token` header (must match auth service) |
|
|
617
|
+
| `ACCESS_REVOCATION_FAILURE_MODE` | No | `fail_closed` | `fail_closed` (default, secure — reject tokens when the check is unverifiable) or `fail_open` (accept on network/HTTP error). |
|
|
618
|
+
| `REVOCATION_CACHE_TTL_SECONDS` | No | `0` | Short-TTL positive validation cache. `0` (default) disables it — every request calls fa-auth. Set to e.g. `30` to trust an `active=True` result for 30 s, skipping the HTTP round-trip; stream events (`session-revoked`/`user-deleted`) evict affected entries and an unresumable gap flushes all (requires the event-stream client). |
|
|
607
619
|
|
|
608
620
|
### Auth Event Stream (fa-auth SSE bridge)
|
|
609
621
|
|
|
@@ -692,8 +704,17 @@ do not connect to Redis directly.
|
|
|
692
704
|
|
|
693
705
|
| Variable | Default | Description |
|
|
694
706
|
|---|---|---|
|
|
695
|
-
| `METRICS_ENABLED` | `false` | Enable Prometheus metrics middleware |
|
|
707
|
+
| `METRICS_ENABLED` | `false` | Enable Prometheus metrics middleware and the `/metrics` route |
|
|
696
708
|
| `METRICS_GROUPS` | — | Comma-separated groups: `traffic`, `performance`, `reliability`, `health`, `auth`, or `all` |
|
|
709
|
+
| `METRICS_SCRAPE_CREDENTIAL` | — | Optional static bearer credential for the `/metrics` scrape endpoint. When set, requests must present `Authorization: Bearer <value>` (constant-time match). When unset, `/metrics` relies on network isolation only. |
|
|
710
|
+
|
|
711
|
+
> **`/metrics` route:** when `METRICS_ENABLED=true`, `create_app` also registers a
|
|
712
|
+
> `GET /metrics` endpoint (hidden from the schema) rendering the Prometheus registry.
|
|
713
|
+
> Set `METRICS_SCRAPE_CREDENTIAL` to gate scrapes with a bearer credential — configure
|
|
714
|
+
> Prometheus `scrape_configs.authorization.credentials` to match. The revocation cache
|
|
715
|
+
> (when enabled) also emits `revocation_cache_lookups_total{result="hit"|"miss"}` and a
|
|
716
|
+
> `revocation_cache_ttl_seconds` gauge on the same registry; no JTI, user ID, or secret
|
|
717
|
+
> is ever used as a label or value.
|
|
697
718
|
|
|
698
719
|
### OpenAPI / Docs
|
|
699
720
|
|
|
@@ -811,7 +832,11 @@ app = create_app(
|
|
|
811
832
|
|
|
812
833
|
**Lifespan sequence:**
|
|
813
834
|
|
|
814
|
-
1. Run `
|
|
835
|
+
1. Run the auto-prepended `check_config_health()` validator (from `auth-sdk-m8`),
|
|
836
|
+
then `lifecycle.startup_validators` — raise any exception to prevent the ready
|
|
837
|
+
signal. The config-health check runs first, so a fatal misconfiguration (e.g.
|
|
838
|
+
production `localhost` CORS origins, a wildcard `ALLOWED_HOSTS` under strict mode)
|
|
839
|
+
aborts startup with `ConfigurationError` before any caller validators run.
|
|
815
840
|
2. Enter `lifecycle.lifespan_extras` context (if provided).
|
|
816
841
|
3. Set `app.state.service_ready = True`.
|
|
817
842
|
4. *(app serves traffic)*
|
|
@@ -876,6 +901,7 @@ Returns a frozen dataclass with everything needed for route protection.
|
|
|
876
901
|
| `is_active` | `bool` | Account active flag |
|
|
877
902
|
| `is_superuser` | `bool` | Superuser flag |
|
|
878
903
|
| `email_verified` | `bool` | Email verification status |
|
|
904
|
+
| `tenant_id` | `uuid.UUID \| None` | Tenant claim (populated when the token carries `tenant_id`; `None` for untenanted/legacy tokens). Requires `auth-sdk-m8 ≥ 1.3.0`. |
|
|
879
905
|
|
|
880
906
|
---
|
|
881
907
|
|
|
@@ -1074,8 +1100,8 @@ HTTP 200
|
|
|
1074
1100
|
],
|
|
1075
1101
|
"service": "Item Service",
|
|
1076
1102
|
"version": "1.0.0",
|
|
1077
|
-
"fastapi_m8": "
|
|
1078
|
-
"auth_sdk_m8": "
|
|
1103
|
+
"fastapi_m8": "3.0.0",
|
|
1104
|
+
"auth_sdk_m8": "2.0.x"
|
|
1079
1105
|
}
|
|
1080
1106
|
```
|
|
1081
1107
|
|
|
@@ -1326,6 +1352,12 @@ async def test_health(client):
|
|
|
1326
1352
|
|
|
1327
1353
|
| `fastapi-m8` | `auth-sdk-m8` | Python |
|
|
1328
1354
|
|---|---|---|
|
|
1355
|
+
| `3.0.0` | `>=2.0.1, <3.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1356
|
+
| `2.1.0` | `>=1.5.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1357
|
+
| `2.0.0` | `>=1.4.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1358
|
+
| `1.6.0` | `>=1.3.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1359
|
+
| `1.5.0` | `>=1.2.1, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1360
|
+
| `1.4.0` | `>=1.2.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1329
1361
|
| `1.3.0` | `>=1.1.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1330
1362
|
| `1.2.0` | `>=1.0.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1331
1363
|
| `1.1.4` | `>=0.7.3, <0.8.0` | 3.11, 3.12, 3.13 |
|
|
@@ -1341,9 +1373,11 @@ Check at runtime:
|
|
|
1341
1373
|
```python
|
|
1342
1374
|
from fastapi_m8 import CAPABILITIES, __version__
|
|
1343
1375
|
|
|
1344
|
-
print(__version__) # "
|
|
1345
|
-
print(CAPABILITIES) # {"async": False, "
|
|
1376
|
+
print(__version__) # "3.0.0"
|
|
1377
|
+
print(CAPABILITIES) # {"async": False, "plugin_system": False,
|
|
1378
|
+
# "trace_context": False, "db_optional": True,
|
|
1379
|
+
# "health_detail_gating": True}
|
|
1346
1380
|
```
|
|
1347
1381
|
|
|
1348
|
-
`create_async_app()` is a
|
|
1349
|
-
`NotImplementedError
|
|
1382
|
+
`create_async_app()` is a reserved stub for a future async app surface. Calling it
|
|
1383
|
+
raises `NotImplementedError`; check `CAPABILITIES["async"]` before using it.
|
|
@@ -52,13 +52,15 @@ health checks; the framework wires the rest.
|
|
|
52
52
|
|---|---|
|
|
53
53
|
| JWT validation | `build_auth_deps()` + `auth-sdk-m8` validator |
|
|
54
54
|
| Role-based access control | `AuthDeps.get_current_active_admin / _superuser` |
|
|
55
|
-
| Token revocation (stateful mode) | `RemoteRevocationClient` → `fa-auth-m8` private API |
|
|
55
|
+
| Token revocation (stateful mode) | `RemoteRevocationClient` → `fa-auth-m8` private API (optional short-TTL cache via `REVOCATION_CACHE_TTL_SECONDS`) |
|
|
56
|
+
| Auth event stream (optional) | `build_event_stream_client()` → fa-auth SSE bridge for best-effort cache eviction |
|
|
56
57
|
| CORS | Auto-wired from `settings.ALLOWED_ORIGINS` |
|
|
57
|
-
| Metrics middleware | Optional; toggled via `METRICS_ENABLED` |
|
|
58
|
+
| Metrics middleware + `/metrics` | Optional; toggled via `METRICS_ENABLED`, scrape-gated via `METRICS_SCRAPE_CREDENTIAL` |
|
|
59
|
+
| Response security headers | Tiered hardening from `auth-sdk-m8` (HSTS/CSP express opt-in) |
|
|
58
60
|
| Health endpoint | `GET {API_PREFIX}/health/` with optional detail gating |
|
|
59
|
-
| Service meta + liveness | Auto-mounted `GET {API_PREFIX}/meta` + `GET /ping` (fail-closed at boot) |
|
|
61
|
+
| Service meta + liveness | Auto-mounted `GET {API_PREFIX}/meta` + `GET {API_PREFIX}/ping` (fail-closed at boot; single-mount at the effective prefix) |
|
|
60
62
|
| Database lifecycle | `create_db_engine()` wrapping SQLAlchemy |
|
|
61
|
-
| Startup validation | `
|
|
63
|
+
| Startup validation | Auto-run `check_config_health()` + caller `startup_validators` before app signals ready |
|
|
62
64
|
| Lifespan management | Auth teardown + DB pool dispose on shutdown |
|
|
63
65
|
|
|
64
66
|
**What it is NOT:**
|
|
@@ -300,9 +302,17 @@ environment variable.
|
|
|
300
302
|
|
|
301
303
|
`create_app` auto-mounts the shared service triad from `auth-sdk-m8`: `GET {API_PREFIX}/meta`
|
|
302
304
|
(cacheable service/version/contract identity, read by clients pre-auth to assert compatibility)
|
|
303
|
-
and a
|
|
304
|
-
|
|
305
|
-
|
|
305
|
+
and a dependency-free `GET {API_PREFIX}/ping` liveness probe (→ `{"status": "ok"}`). `/ping` is
|
|
306
|
+
mounted **once** at the effective prefix (single-mount since auth-sdk-m8 2.0.0): when a prefix is
|
|
307
|
+
set, only `{API_PREFIX}/ping` exists so liveness stays reachable behind a prefix-routing reverse
|
|
308
|
+
proxy (Traefik forwards only `PathPrefix({API_PREFIX})`); when no prefix is set, `/ping` is at the
|
|
309
|
+
root. The single mount always appears in the OpenAPI schema. The `/meta` values are sourced from
|
|
310
|
+
these settings, so a consumer **fails closed at boot** if it doesn't declare its identity. Keep
|
|
311
|
+
both separate from a dependency-aware `/health` readiness probe.
|
|
312
|
+
|
|
313
|
+
> **⚠️ Breaking change (3.0.0 / auth-sdk-m8 2.0.0):** root `GET /ping` no longer exists when
|
|
314
|
+
> `API_PREFIX` is set. Update container `livenessProbe` / sidecar healthcheck URLs from `/ping`
|
|
315
|
+
> to `{API_PREFIX}/ping` (e.g. `/api/ping`).
|
|
306
316
|
|
|
307
317
|
| Variable | Required | Default | Description |
|
|
308
318
|
|---|---|---|---|
|
|
@@ -348,6 +358,8 @@ Required only when `TOKEN_MODE=stateful` and `AUTH_SERVICE_ROLE=consumer`.
|
|
|
348
358
|
|---|---|---|---|
|
|
349
359
|
| `INTROSPECTION_URL` | Yes | — | `POST` endpoint on auth service for JTI revocation checks, e.g. `http://auth_user_service:8000/user/private/v1/jti-status` |
|
|
350
360
|
| `PRIVATE_API_SECRET` | Yes | — | Shared secret for `X-Internal-Token` header (must match auth service) |
|
|
361
|
+
| `ACCESS_REVOCATION_FAILURE_MODE` | No | `fail_closed` | `fail_closed` (default, secure — reject tokens when the check is unverifiable) or `fail_open` (accept on network/HTTP error). |
|
|
362
|
+
| `REVOCATION_CACHE_TTL_SECONDS` | No | `0` | Short-TTL positive validation cache. `0` (default) disables it — every request calls fa-auth. Set to e.g. `30` to trust an `active=True` result for 30 s, skipping the HTTP round-trip; stream events (`session-revoked`/`user-deleted`) evict affected entries and an unresumable gap flushes all (requires the event-stream client). |
|
|
351
363
|
|
|
352
364
|
### Auth Event Stream (fa-auth SSE bridge)
|
|
353
365
|
|
|
@@ -436,8 +448,17 @@ do not connect to Redis directly.
|
|
|
436
448
|
|
|
437
449
|
| Variable | Default | Description |
|
|
438
450
|
|---|---|---|
|
|
439
|
-
| `METRICS_ENABLED` | `false` | Enable Prometheus metrics middleware |
|
|
451
|
+
| `METRICS_ENABLED` | `false` | Enable Prometheus metrics middleware and the `/metrics` route |
|
|
440
452
|
| `METRICS_GROUPS` | — | Comma-separated groups: `traffic`, `performance`, `reliability`, `health`, `auth`, or `all` |
|
|
453
|
+
| `METRICS_SCRAPE_CREDENTIAL` | — | Optional static bearer credential for the `/metrics` scrape endpoint. When set, requests must present `Authorization: Bearer <value>` (constant-time match). When unset, `/metrics` relies on network isolation only. |
|
|
454
|
+
|
|
455
|
+
> **`/metrics` route:** when `METRICS_ENABLED=true`, `create_app` also registers a
|
|
456
|
+
> `GET /metrics` endpoint (hidden from the schema) rendering the Prometheus registry.
|
|
457
|
+
> Set `METRICS_SCRAPE_CREDENTIAL` to gate scrapes with a bearer credential — configure
|
|
458
|
+
> Prometheus `scrape_configs.authorization.credentials` to match. The revocation cache
|
|
459
|
+
> (when enabled) also emits `revocation_cache_lookups_total{result="hit"|"miss"}` and a
|
|
460
|
+
> `revocation_cache_ttl_seconds` gauge on the same registry; no JTI, user ID, or secret
|
|
461
|
+
> is ever used as a label or value.
|
|
441
462
|
|
|
442
463
|
### OpenAPI / Docs
|
|
443
464
|
|
|
@@ -555,7 +576,11 @@ app = create_app(
|
|
|
555
576
|
|
|
556
577
|
**Lifespan sequence:**
|
|
557
578
|
|
|
558
|
-
1. Run `
|
|
579
|
+
1. Run the auto-prepended `check_config_health()` validator (from `auth-sdk-m8`),
|
|
580
|
+
then `lifecycle.startup_validators` — raise any exception to prevent the ready
|
|
581
|
+
signal. The config-health check runs first, so a fatal misconfiguration (e.g.
|
|
582
|
+
production `localhost` CORS origins, a wildcard `ALLOWED_HOSTS` under strict mode)
|
|
583
|
+
aborts startup with `ConfigurationError` before any caller validators run.
|
|
559
584
|
2. Enter `lifecycle.lifespan_extras` context (if provided).
|
|
560
585
|
3. Set `app.state.service_ready = True`.
|
|
561
586
|
4. *(app serves traffic)*
|
|
@@ -620,6 +645,7 @@ Returns a frozen dataclass with everything needed for route protection.
|
|
|
620
645
|
| `is_active` | `bool` | Account active flag |
|
|
621
646
|
| `is_superuser` | `bool` | Superuser flag |
|
|
622
647
|
| `email_verified` | `bool` | Email verification status |
|
|
648
|
+
| `tenant_id` | `uuid.UUID \| None` | Tenant claim (populated when the token carries `tenant_id`; `None` for untenanted/legacy tokens). Requires `auth-sdk-m8 ≥ 1.3.0`. |
|
|
623
649
|
|
|
624
650
|
---
|
|
625
651
|
|
|
@@ -818,8 +844,8 @@ HTTP 200
|
|
|
818
844
|
],
|
|
819
845
|
"service": "Item Service",
|
|
820
846
|
"version": "1.0.0",
|
|
821
|
-
"fastapi_m8": "
|
|
822
|
-
"auth_sdk_m8": "
|
|
847
|
+
"fastapi_m8": "3.0.0",
|
|
848
|
+
"auth_sdk_m8": "2.0.x"
|
|
823
849
|
}
|
|
824
850
|
```
|
|
825
851
|
|
|
@@ -1070,6 +1096,12 @@ async def test_health(client):
|
|
|
1070
1096
|
|
|
1071
1097
|
| `fastapi-m8` | `auth-sdk-m8` | Python |
|
|
1072
1098
|
|---|---|---|
|
|
1099
|
+
| `3.0.0` | `>=2.0.1, <3.0.0` | 3.11, 3.12, 3.13, 3.14 |
|
|
1100
|
+
| `2.1.0` | `>=1.5.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1101
|
+
| `2.0.0` | `>=1.4.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1102
|
+
| `1.6.0` | `>=1.3.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1103
|
+
| `1.5.0` | `>=1.2.1, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1104
|
+
| `1.4.0` | `>=1.2.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1073
1105
|
| `1.3.0` | `>=1.1.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1074
1106
|
| `1.2.0` | `>=1.0.0, <2.0.0` | 3.11, 3.12, 3.13 |
|
|
1075
1107
|
| `1.1.4` | `>=0.7.3, <0.8.0` | 3.11, 3.12, 3.13 |
|
|
@@ -1085,9 +1117,11 @@ Check at runtime:
|
|
|
1085
1117
|
```python
|
|
1086
1118
|
from fastapi_m8 import CAPABILITIES, __version__
|
|
1087
1119
|
|
|
1088
|
-
print(__version__) # "
|
|
1089
|
-
print(CAPABILITIES) # {"async": False, "
|
|
1120
|
+
print(__version__) # "3.0.0"
|
|
1121
|
+
print(CAPABILITIES) # {"async": False, "plugin_system": False,
|
|
1122
|
+
# "trace_context": False, "db_optional": True,
|
|
1123
|
+
# "health_detail_gating": True}
|
|
1090
1124
|
```
|
|
1091
1125
|
|
|
1092
|
-
`create_async_app()` is a
|
|
1093
|
-
`NotImplementedError
|
|
1126
|
+
`create_async_app()` is a reserved stub for a future async app surface. Calling it
|
|
1127
|
+
raises `NotImplementedError`; check `CAPABILITIES["async"]` before using it.
|
|
@@ -9,7 +9,6 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import inspect
|
|
11
11
|
import logging
|
|
12
|
-
import secrets
|
|
13
12
|
import time
|
|
14
13
|
from collections.abc import AsyncGenerator, Awaitable, Callable
|
|
15
14
|
from contextlib import asynccontextmanager
|
|
@@ -18,10 +17,15 @@ from typing import TYPE_CHECKING, Any
|
|
|
18
17
|
|
|
19
18
|
import anyio
|
|
20
19
|
from auth_sdk_m8.controllers.meta import mount_service_meta
|
|
20
|
+
from auth_sdk_m8.core.config import check_config_health
|
|
21
|
+
from auth_sdk_m8.security.guards import (
|
|
22
|
+
make_internal_token_authorizer,
|
|
23
|
+
make_scrape_credential_guard,
|
|
24
|
+
)
|
|
21
25
|
from auth_sdk_m8.security.headers import add_security_headers_middleware
|
|
22
|
-
from fastapi import APIRouter, FastAPI, Request
|
|
26
|
+
from fastapi import APIRouter, Depends, FastAPI, Request
|
|
23
27
|
from fastapi.middleware.cors import CORSMiddleware
|
|
24
|
-
from fastapi.responses import JSONResponse
|
|
28
|
+
from fastapi.responses import JSONResponse, Response
|
|
25
29
|
from starlette.middleware.trustedhost import TrustedHostMiddleware
|
|
26
30
|
|
|
27
31
|
from fastapi_m8._compat import _COMPAT_STATE, _assert_compat
|
|
@@ -146,6 +150,23 @@ def _build_lifespan(
|
|
|
146
150
|
return lifespan
|
|
147
151
|
|
|
148
152
|
|
|
153
|
+
def _build_config_health_validator(
|
|
154
|
+
settings: ConsumerServiceSettings,
|
|
155
|
+
) -> StartupValidator:
|
|
156
|
+
"""
|
|
157
|
+
Return a startup validator running the shared ``check_config_health``.
|
|
158
|
+
|
|
159
|
+
The validator runs inside the lifespan (not at import time) and raises
|
|
160
|
+
``ConfigurationError`` on fatal misconfiguration, aborting startup before
|
|
161
|
+
any caller-provided validators run.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
async def _validate_config_health() -> None:
|
|
165
|
+
check_config_health(settings, logger)
|
|
166
|
+
|
|
167
|
+
return _validate_config_health
|
|
168
|
+
|
|
169
|
+
|
|
149
170
|
def _add_metrics_middleware(app: FastAPI, settings: ConsumerServiceSettings) -> None:
|
|
150
171
|
if not settings.METRICS_ENABLED:
|
|
151
172
|
return
|
|
@@ -172,16 +193,37 @@ def _build_default_authorizer(
|
|
|
172
193
|
) -> Callable[[Request], bool]:
|
|
173
194
|
"""Return a token authorizer closed over the private API secret."""
|
|
174
195
|
sec = settings.PRIVATE_API_SECRET
|
|
196
|
+
return make_internal_token_authorizer(sec.get_secret_value() if sec else None)
|
|
197
|
+
|
|
175
198
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
199
|
+
def _register_metrics_route(app: FastAPI, settings: ConsumerServiceSettings) -> None:
|
|
200
|
+
"""
|
|
201
|
+
Register ``/metrics`` with an optional scrape-credential guard (1.4).
|
|
202
|
+
|
|
203
|
+
The route is only wired when ``METRICS_ENABLED=True``. When
|
|
204
|
+
``METRICS_SCRAPE_CREDENTIAL`` is unset the guard is a no-op and the network
|
|
205
|
+
boundary (internal entrypoint) remains the sole control. When set, requests
|
|
206
|
+
must present ``Authorization: Bearer <credential>`` (constant-time match).
|
|
207
|
+
"""
|
|
208
|
+
if not settings.METRICS_ENABLED:
|
|
209
|
+
return
|
|
210
|
+
cred_field = settings.METRICS_SCRAPE_CREDENTIAL
|
|
211
|
+
guard = make_scrape_credential_guard(
|
|
212
|
+
cred_field.get_secret_value() if cred_field else None
|
|
213
|
+
)
|
|
214
|
+
try:
|
|
215
|
+
from auth_sdk_m8.observability import metrics as _obs # noqa: PLC0415
|
|
216
|
+
except ImportError: # pragma: no cover
|
|
217
|
+
logger.warning(
|
|
218
|
+
"METRICS_ENABLED but auth-sdk-m8[observability] missing; "
|
|
219
|
+
"skipping /metrics route"
|
|
182
220
|
)
|
|
221
|
+
return
|
|
183
222
|
|
|
184
|
-
|
|
223
|
+
@app.get("/metrics", include_in_schema=False, dependencies=[Depends(guard)])
|
|
224
|
+
def _metrics_endpoint() -> Response:
|
|
225
|
+
data, content_type = _obs.render()
|
|
226
|
+
return Response(content=data, media_type=content_type)
|
|
185
227
|
|
|
186
228
|
|
|
187
229
|
async def _gather_health_results(
|
|
@@ -360,9 +402,13 @@ def create_app(
|
|
|
360
402
|
h = health or HealthConfig()
|
|
361
403
|
lc = lifecycle or AppLifecycle()
|
|
362
404
|
checks = list(h.checks or [])
|
|
405
|
+
startup_validators = [
|
|
406
|
+
_build_config_health_validator(settings),
|
|
407
|
+
*(lc.startup_validators or []),
|
|
408
|
+
]
|
|
363
409
|
app = FastAPI(
|
|
364
410
|
lifespan=_build_lifespan(
|
|
365
|
-
lc.auth_deps, lc.db_engine,
|
|
411
|
+
lc.auth_deps, lc.db_engine, startup_validators, lc.lifespan_extras
|
|
366
412
|
),
|
|
367
413
|
**_openapi_config(settings, service_name, service_version),
|
|
368
414
|
)
|
|
@@ -371,6 +417,7 @@ def create_app(
|
|
|
371
417
|
_add_trusted_host_middleware(app, settings)
|
|
372
418
|
add_security_headers_middleware(app, settings)
|
|
373
419
|
_add_metrics_middleware(app, settings)
|
|
420
|
+
_register_metrics_route(app, settings)
|
|
374
421
|
authorize = h.detail_authorizer or _build_default_authorizer(settings)
|
|
375
422
|
_register_health_route(
|
|
376
423
|
app, settings.API_PREFIX, checks, h, authorize, service_name, service_version
|
|
@@ -42,6 +42,24 @@ COMPAT_MATRIX: dict[str, dict[str, str]] = {
|
|
|
42
42
|
# at boot). Requires auth-sdk-m8 1.4.0, which ships mount_service_meta +
|
|
43
43
|
# ServiceMeta — see CHANGELOG. BREAKING: consumers must declare their meta.
|
|
44
44
|
"2.0": {"auth-sdk-m8": ">=1.4.0,<2.0.0"},
|
|
45
|
+
# 2.1 requires auth-sdk-m8 1.5.0, where mount_service_meta dual-mounts /ping:
|
|
46
|
+
# the unchanged root /ping plus a {API_PREFIX}/ping copy so liveness stays
|
|
47
|
+
# reachable behind a prefix-routing reverse proxy (Traefik forwards only
|
|
48
|
+
# PathPrefix({API_PREFIX}), so a root-only /ping 404s at the gateway). The
|
|
49
|
+
# create_app call site is unchanged — it already passes prefix=API_PREFIX — so
|
|
50
|
+
# the prefixed probe is picked up automatically on upgrade. See CHANGELOG.
|
|
51
|
+
"2.1": {"auth-sdk-m8": ">=1.5.0,<2.0.0"},
|
|
52
|
+
# 3.0 (MAJOR) aligns with auth-sdk-m8 2.0.0 on two breaking fronts: (1) /ping
|
|
53
|
+
# collapses to a single mount — when a prefix is set it lives only at
|
|
54
|
+
# {prefix}/ping (no root copy), always in the OpenAPI schema, so consumers
|
|
55
|
+
# that relied on root /ping behind a prefix must switch to {API_PREFIX}/ping;
|
|
56
|
+
# (2) the required auth-sdk-m8 floor crosses a major (<2.0.0 → >=2.0.1), which
|
|
57
|
+
# removed deprecated SDK APIs (Redis bus, decode_access_token, TOKEN_ALGORITHM,
|
|
58
|
+
# module-level settings_customise_sources). Either alone forces this major bump.
|
|
59
|
+
# Floor is >=2.0.1 (not 2.0.0) so the transitive pydantic-settings dep is
|
|
60
|
+
# >=2.14.2, carrying the SDK 2.0.1 nested-secrets symlink-traversal fix. See
|
|
61
|
+
# CHANGELOG.
|
|
62
|
+
"3.0": {"auth-sdk-m8": ">=2.0.1,<3.0.0"},
|
|
45
63
|
}
|
|
46
64
|
|
|
47
65
|
_EXTRAS = "[config,security,fastapi,observability]"
|