Releases: fireflyframework/fireflyframework-pyfly
Release list
v26.06.114
v26.06.114 (2026-06-26)
A comprehensive security release: PyFly now aligns with RFC 9700 (OAuth 2.0
Security Best Current Practice) and OAuth 2.1, reaches broad Spring
Security parity, and ships a complete OAuth 2.1 / OpenID Connect stack — followed
by a full security documentation refresh.
Added
- OAuth 2.1 authorization server.
AuthorizationServerimplements the
authorization_codegrant (single-use codes, exact redirect-URI matching,
mandatory PKCES256, code-reuse → token revocation) alongside
client_credentialsandrefresh_token. It issues OIDCid_tokens for the
openidscope, supports symmetric (HS*) and asymmetric (RS*/ES*/PS*)
signing with a published JWK set (jwks()), and is fronted by
AuthorizationServerEndpoints:GET /oauth2/authorize,POST /oauth2/par,
POST /oauth2/token,POST /oauth2/introspect,POST /oauth2/revoke,
POST /oauth2/register,GET /oauth2/jwks, and the
/.well-known/oauth-authorization-server+/.well-known/openid-configuration
discovery documents. Includes Dynamic Client Registration (RFC 7591), Pushed
Authorization Requests (RFC 9126), JWT-Secured Authorization Requests (RFC 9101),
token introspection (RFC 7662), revocation (RFC 7009), and AS metadata (RFC 8414). - Sender-constrained tokens. DPoP (RFC 9449) and mTLS (RFC 8705): the
authorization server binds tokens via acnfclaim and the resource server
enforces proof-of-possession (DPoPProofValidator,confirm_dpop_binding,
confirm_mtls_binding) when
pyfly.security.oauth2.resource-server.enforce-sender-constraints=true. - Opaque-token introspection.
OpaqueTokenIntrospectorvalidates non-JWT
access tokens via a remote RFC 7662 endpoint, mapping claims identically to the
JWKS validator. - Authentication mechanisms (Spring parity). A
UserDetailsServiceSPI
(InMemoryUserDetailsService, SQLAlchemy-backedSqlUserDetailsService), an
AuthenticationManager(ProviderManager+DaoAuthenticationProvider, with
username-enumeration-resistant timing and credential erasure), and config-driven
form login (FormLoginFilter), HTTP Basic (HttpBasicAuthenticationFilter),
X.509 client-certificate auth (X509AuthenticationFilter), generic logout
(LogoutFilter), and run-as impersonation (SwitchUserFilter). - Password encoders.
Pbkdf2PasswordEncoder,ScryptPasswordEncoder,
Argon2PasswordEncoder(Argon2id;pip install pyfly[argon2]), and a
DelegatingPasswordEncoder({id}-prefixed, withupgrade_encodingfor
transparent on-login migration) pluscreate_delegating_password_encoder(). - Authorization breadth. HTTP-method-scoped URL rules
(request_matchers(..., methods=...)), method-security collection filtering
(@pre_filter/@post_filterbindingfilterObject), and aPermissionEvaluator
SPI backing ACL-stylehasPermission(target, perm). - RFC 9207 issuer (
iss) validation in the OAuth2 client callback (mix-up
attack defense;ClientRegistration.require_iss). - Configuration. New keys under
pyfly.security.{http-basic,form-login,logout}.*,
pyfly.security.password.delegating.enabled,pyfly.security.csrf.cookie-gated,
pyfly.security.oauth2.authorization-server.audience,
pyfly.security.oauth2.resource-server.{enforce-sender-constraints,mtls-cert-header},
per-registrationuse-pkce/require-iss, andpyfly.idp.allow-password-grant.
New optional extra:pyfly[argon2].
Changed
- PKCE is on by default for the OAuth2
authorization_codelogin flow and is
always enforced for public clients (ClientRegistration.use_pkcenow defaults
True). - CSRF is enabled by default in cookie-gated mode (stateless /
Bearerclients
are unaffected); opt out withpyfly.security.csrf.enabled=false, or set
pyfly.security.csrf.cookie-gated=falsefor strict enforcement. client_credentialsscope validation: a request for a scope the client is
not registered for is now rejected withinvalid_scopeinstead of being echoed.
Security
- Signing-secret fail-fast. The composition root refuses to start when a
token-signing secret is left at the built-in placeholderchange-me-in-production,
and requires ≥ 32-byte HMAC keys forHS*algorithms (RFC 7518 §3.2). - ROPC disabled by default. The IdP Resource Owner Password Credentials grant
(grant_type=password) on the Keycloak / Cognito / Entra adapters is refused
unlesspyfly.idp.allow-password-grant=true(OAuth 2.1 / RFC 9700 §2.4). - Refresh-token reuse detection. Replaying an already-rotated refresh token
revokes the entire token family. - Owner-scoped introspection & revocation. A client may only introspect /
revoke its own tokens (RFC 7009 §2.1); empty client credentials are rejected.
Documentation
- New OAuth 2.1 & OpenID Connect guide (
docs/modules/oauth2.md). The Security
guide gained Authentication Mechanisms, Security Headers, and Secure-by-Default
& Hardening sections; the Spring comparison, IDP, and web-filters docs were
updated; and PyFly by Example chapter 14 (English and Spanish) gained
sections on form login, modern password hashing, the authorization server, and
sender-constrained tokens.
Full Changelog: v26.06.113...v26.06.114
v26.06.113
v26.06.113 (2026-06-17)
Added
- Server-layer observability. Observability is no longer only application-layer
(thehttp_server_requests_secondsfilter, tracing/correlation, process metrics):
pyfly now emits metrics about the ASGI server itself across Uvicorn, Granian,
and Hypercorn. A pure-ASGIServerMetricsASGIMiddleware(the uniform primary
source, running in every worker) emitsserver_active_connections,
server_in_flight_requests, andserver_requests_total; aServerMetricsBinder
bound from the in-worker ASGI lifespan emitsserver_workers,
server_uptime_seconds, andserver_started_total/server_stopped_total; and
a best-effortServerStatsPortsurfaces Uvicorn's true socket count
(server_native_connections) on the in-processserve_asyncpath. Every meter is
labeledserverandworker_pid. - Correct multi-worker aggregation. With
workers > 1,pyfly runenables
prometheus_clientmultiprocess mode (setsPROMETHEUS_MULTIPROC_DIRbefore
forking), so a single/actuator/prometheusscrape aggregates across all workers
viaMultiProcessCollector— this also fixes the previous per-worker gap for
http_server_requests_*. - Live admin Observability dashboard. A new real-time Observability view
(under Monitoring) shows server workers, uptime, active connections, in-flight
requests, requests/sec, a per-worker breakdown, and worker lifecycle, with links
to the Metrics and Traces views. Backed byGET /admin/api/observabilityand the
observabilitySSE stream. - Configuration. New
pyfly.server.observability.*keys —enabled
(defaulttrue, activated by the web/core starters),sample-interval-seconds
(5.0), andaccess-log(false, opt-in). Requires the observability extra
(prometheus_client); degrades to a no-op without it. - Local observability stack.
docker-compose.ymlgained loopback-bound
Prometheus + Grafana services (config inops/prometheus/prometheus.yml) that
scrape/actuator/prometheus.
Scope: gunicorn is intentionally not added (the stack stays async-only ASGI:
Granian > Uvicorn > Hypercorn), but theServerStatsPort+ multiprocess design is
gunicorn-ready for a future adapter.
Full Changelog: v26.06.112...v26.06.113
v26.06.112
v26.06.112 (2026-06-16)
Changed
- "PyFly by Example" — all diagrams redesigned. Every content figure in both
the English and Spanish editions was rebuilt in a single, polished design
language (consistent cards, gradient headers, numbered step flows, monospace
code tokens, brand palette, vector iconography). This fixes real defects in the
previous artwork — broken/tofu glyphs (font-dependent arrows, circled numbers
and check marks) and clipped/overflowing content — by drawing all arrows,
checkmarks and numbered badges as vector shapes and keeping figure text to
ASCII/Latin-1 only. - Two new figures where they help most: Page / Pageable / Sort (Chapter 5)
and Value Object vs Entity (Chapter 6). Both editions rebuilt and re-attached
to the release.
Full Changelog: v26.06.111...v26.06.112
v26.06.111
v26.06.111 (2026-06-16)
Added
- "PyFly by Example" — Spanish edition. The book is now published in Spanish
(book/dist/pyfly-by-example-es.{epub,pdf}) alongside the English edition,
built from a parallelbook/manuscript-es/manuscript viabook.es.yaml. The
book build (book/build/build.py) is now language-parameterized (--config,
per-manifestmanuscript_dir/output_basename/ localized labels). Both
editions are attached to the GitHub release. - Quick Start tutorial + step-by-step depth. A new "Build Lumen Step by Step"
walkthrough takes the reader from an empty folder to a running, tested wallet
feature; every chapter was deepened into a more granular, beginner-friendly
tutorial. A dedication was added (EN + ES).
Fixed
pyfly newno longer scaffolds the removedpyfly.web.portkey. The project
template (pyfly.yaml.j2) now emits the port underserver:as
pyfly.server.port(Springserver.portparity); the legacypyfly.web.port
was removed in v26.06.102 and had been left as a dead key in freshly scaffolded
applications.
Full Changelog: v26.06.110...v26.06.111
v26.06.110
v26.06.110 (2026-06-16)
Fixed
- The separate management port (actuator + admin) is now OPEN by default. When
pyfly.management.server.portruns actuator/admin on a dedicated port, the
app's user security filters (e.g. anHttpSecuritygate whosedeny-all
catch-all is scoped to the main app's URL space) were applied there too —
rejecting/admin,/actuator/info,/actuator/metricswith 401/403
while only/actuator/health(explicitly permitted) worked. The management
port is a separate, typically-internal listener (Springmanagement.server.port
parity) protected by network isolation, so it no longer applies the app's
security filters by default. Opt back in with
pyfly.management.security.enabled: true.
Full Changelog: v26.06.109...v26.06.110
v26.06.109
v26.06.109 (2026-06-16)
Fixed
- CORS preflight is no longer rejected by the security gate. The
CORSMiddlewareis now the outermost middleware (ahead of the
WebFilterChainthat holds theHttpSecuritygate) on both the Starlette
and FastAPI adapters. Previously the filter chain wrapped CORS, so a browser
OPTIONSpreflight (which carries no credentials) to a gated route was
answered with401and withoutAccess-Control-*headers — the browser
then blocked the real request ("Load failed"/"Failed to fetch"). The preflight
is now answered by CORS before the gate runs, andAccess-Control-*headers
are added to every response.
Full Changelog: v26.06.108...v26.06.109
v26.06.108
v26.06.108 (2026-06-16)
Added
- The live actuator
HealthAggregatoris exposed on
app.state.pyfly_health_aggregator. Consumers can now register extra
health indicators aftercreate_app(e.g. a readiness-only probe for an
external dependency) without introspecting route closures. It is the same
aggregator the live health routes use — whether actuator runs on the main app
(shared management mode) or on the separate management port — so indicators
added through it are reflected on/actuator/healthin either mode.
Full Changelog: v26.06.107...v26.06.108
v26.06.107
v26.06.107 (2026-06-16)
Added
- OAuth2 resource server: config-driven, multi-IdP, Spring-parity. The
bearer-token resource server now works out of the box with Keycloak,
Microsoft Entra ID (v1.0 + v2.0) and AWS Cognito via configuration
alone (no subclassing), and reaches Spring-Security parity:issuer-uriOIDC discovery — derive the JWKS endpoint + issuer from
<issuer-uri>/.well-known/openid-configuration(alternative tojwks-uri).- Config-driven claim mapping (
pyfly.security.oauth2.resource-server.*):
principal-claim-names,authorities-claim-names,scope-claim-names,
attribute-claims,authority-prefix. Claim names accept dotted paths
with a*wildcard and are colon-safe, so authorities resolve from
realm_access.roles,resource_access.*.roles(Keycloak),roles+groups
(Entra), andcognito:groups(Cognito) with zero code. audiences(a list;audmust match any) andvalidate-audience
(disable for Cognito access tokens, which carry noaud).- Configurable
algorithms,clock-skew-seconds,jwks-timeout-seconds,
jwks-cache-seconds. - New typed
ResourceServerProperties(@config_properties) and
ClaimMappings.
Fixed
- OAuth2 resource server — clock-skew leeway. JWT validation now allows 60s
of clock skew by default (configurable). Previously a token whoseiat/nbf
was a few seconds ahead of the server clock — routine with real IdPs — was
rejected as "not yet valid", causing intermittent 401s. - OAuth2 resource server — event-loop stall. The bearer filter now runs JWKS
validation (which does blocking network I/O on a cache miss) in a worker thread
(anyio.to_thread) instead of inline on the event loop. - OAuth2 resource server — multi-IdP claim coverage. Token-to-
SecurityContext
mapping previously read onlyrealm_access.rolesandscope/permissions,
silently dropping Keycloakresource_accessclient roles, Entragroups/
scp, Cognitocognito:groups, and any tokenattributes. All are now mapped
(configurably). - OAuth2 resource server — case-insensitive
Bearerscheme (RFC 7235): a
bearer …/BEARER …Authorization header is now accepted. - OAuth2 resource server — opt-in strict rejection. New
authenticate-error-mode: "401"rejects a present-but-invalid token at the
filter with401+WWW-Authenticate: Bearer error="invalid_token"(RFC
6750). Default remains"anonymous"(the gate decides) — no behavioural change
unless opted in.
Full Changelog: v26.06.106...v26.06.107
v26.06.106
v26.06.106 (2026-06-16)
Fixed
- Admin dashboard: the Overview "Thread Count" gauge no longer renders the
thread count as a percentage. The gauge widget (createGaugeChart) was a
fixed 0–100 percentage meter — it clamped its value to 100 and appended a
hard-coded%to the centre readout — but the Overview page feeds it the
absolute active-thread count. A process with 8 threads therefore showed
8%, and any count above 100 clamped to100%. The gauge now accepts a
max(the value that fills the arc) and aunit(the readout suffix); the
thread gauge passesunit: ''andmax: 100, so it shows the raw count
(8,150, …) while still acting as a thread-leak indicator (amber past 60,
red past 80). The defaults (max: 100,unit: '%') keep every percentage
gauge rendering exactly as before.
Full Changelog: v26.06.105...v26.06.106
v26.06.105
v26.06.105 (2026-06-15)
Fixed
- Management-port observability now covers BOTH the application and the
management ports. When actuator + admin run on a separate
pyfly.management.server.port, the dashboard/actuator there is the single
observability pane — but it previously reflected only the application port:- Access log: the management app was built without
RequestLoggingFilter,
so health probes, Prometheus scrapes and admin calls to the management port
produced nohttp_requestlog line. It is now wired into the management
chain (honoringpyfly.web.request-logging.enabled), so management-port
traffic is logged through pyfly's structured logger like the main app. - Metrics / HTTP exchanges / traces: the data-capture filters
(MetricsFilter,HttpExchangeRecorderFilter, the adminTraceCollector)
ran only on the main app, sohttp.server.requests,/actuator/httpexchanges
and the admin Traces view excluded management-port traffic. The same shared
capture instances now also run on the management app, so the dashboard
reflects both ports. A request traverses exactly one app's chain, so there is
no double counting; the recorders keep their own path exclusions (Prometheus
scrape, admin SSE self-polling).
- Access log: the management app was built without
Full Changelog: v26.06.104...v26.06.105