Skip to content

Tags: fireflyframework/fireflyframework-rust

Tags

v26.6.36

Toggle v26.6.36's commit message
v26.6.36 — Spring Security Tier 5b: SAML2 single sign-on (SP side, op…

…t-in `saml2` feature)

v26.6.35

Toggle v26.6.35's commit message
v26.6.35 — Spring Security Tier 5c: ACL / domain-object security (spr…

…ing-security-acl parity, pure Rust)

v26.6.34

Toggle v26.6.34's commit message
v26.6.34 — Spring Security Tier 5a: LDAP / Active Directory authentic…

…ation (opt-in `ldap` feature)

v26.6.33

Toggle v26.6.33's commit message
v26.6.33 — Spring Security parity Tier 4 (OAuth2 ecosystem)

v26.6.32

Toggle v26.6.32's commit message
v26.6.32 — Spring Security parity Tier 3 (method-security depth)

v26.6.31

Toggle v26.6.31's commit message
v26.6.31 — Spring Security parity Tier 2 (web mechanisms)

v26.6.30

Toggle v26.6.30's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(security): Spring Security 6 parity — Tier 1 authentication spin…

…e (26.6.30) (#38)

* feat(security): Tier 1 — authentication manager spine (T1.1)

AuthenticationManager / ProviderManager / AuthenticationProvider — the Rust
analog of Spring's authentication architecture. AuthenticationRequest is a
#[non_exhaustive] enum (UsernamePassword / BearerToken) standing in for Spring's
pre-auth token; ProviderManager tries each supporting provider in order, first
success wins, provider-not-found otherwise. BearerTokenAuthenticationProvider
adapts the existing Verifier so JWT/JWKS verifiers slot into the spine.

+5 tests; security lib 99 green; clippy + fmt clean.

* feat(security): Tier 1 — DelegatingPasswordEncoder + NoOp (T1.3)

Spring's recommended {id}-prefixed password storage: DelegatingPasswordEncoder
hashes with a default encoder and prefixes {id} ({bcrypt}$2b$…), verify()
reads the {id} and delegates, and upgrade_encoding() flags a stored hash for
re-encoding on next login when its {id} differs from the default or it is a
legacy unprefixed hash. with_defaults() = {bcrypt} default + {argon2} + {noop},
with bare/legacy hashes verified as bcrypt for seamless migration.
NoOpPasswordEncoder ({noop}) for tests/dev.

+5 tests; security lib 104 green; clippy + fmt clean.

* feat(security): Tier 1 — UserDetails + DaoAuthenticationProvider (T1.2)

UserDetails (stored credential + the four Spring account-status flags),
UserDetailsService (load_user_by_username -> Option), UserDetailsChecker +
AccountStatusUserDetailsChecker (locked/disabled/expired), InMemoryUserDetails-
Service, and DaoAuthenticationProvider — an AuthenticationProvider for the
UsernamePassword request that loads the user, runs the status checks, verifies
via a PasswordEncoder, and checks credentials expiry. Enumeration-safe: an
unknown user and a wrong password both fail as 'Bad credentials' with comparable
encoder work. Plugs into the ProviderManager spine alongside the bearer provider.

+4 tests; security lib 108 green; clippy + fmt clean.

* feat(security): Tier 1 — SecurityContextRepository (T1.4)

SecurityContextRepository (load/save/contains) — Spring's pluggable
between-request context store — with HttpSessionSecurityContextRepository
(default; configurable session key, wire-compatible with the OAuth2/OTT/WebAuthn
handlers) and NullSecurityContextRepository (stateless). SessionAuthenticationLayer
now loads the context through a (swappable) repository instead of a hardcoded
session key; added Authentication::is_authenticated(). Restore-semantics tests
moved to the repository module.

+4 repo tests; full security suite (107 lib + integration) green; clippy + fmt clean.

* feat(security): Tier 1 — auth events + pluggable exception handlers (T1.5)

AuthenticationEventPublisher (AuthenticationEvent::Success/Failure) wired into
ProviderManager — publishes every authentication outcome (with the attempted
username on failure); LoggingAuthenticationEventPublisher default. Completes the
authentication-manager spine.

ExceptionTranslationFilter seam: AuthenticationEntryPoint (401) + AccessDenied-
Handler (403) traits with the canonical problem+json defaults; FilterChain gains
with_authentication_entry_point / with_access_denied_handler so a deployment can
customise the rejection (login redirect, WWW-Authenticate, etc.).

+2 tests; full security suite (109 lib + integration) green; clippy + fmt clean.

* docs+release: Tier 1 authentication spine — CHANGELOG, book appendix, version 26.6.30

CHANGELOG 26.6.30 (Tier 1: AuthenticationManager spine, UserDetails+DAO,
DelegatingPasswordEncoder, SecurityContextRepository, auth events, pluggable
entry-point/access-denied). Spring Security Parity appendix (EN+ES) marks the
authentication-spine row + roadmap tier as done; designed book rebuilt (EN+ES).
Workspace bumped to 26.6.30.

* fix(security): address Tier 1 adversarial review findings

- [MED] DaoAuthenticationProvider: the not-found timing-mitigation dummy hash
  now falls back to a valid bcrypt hash if the configured encoder's hash()
  fails, so the user-enumeration timing equalization can't silently collapse to
  an empty (instant-fail) hash under a misconfigured encoder.
- Test gaps closed: SecurityContextRepository seam on SessionAuthenticationLayer
  (NullSecurityContextRepository swap), Authentication::is_authenticated, DAO
  backend-error propagation, HttpSession typed-object load fallback, and the
  bearer (username:None) failure event.
- Documented the remaining by-design/refinement findings (ProviderManager
  account-status short-circuit, upgrade_encoding {id}-only, {noop}/unprefixed
  defaults) as Known limitations in the 26.6.30 CHANGELOG.

+4 tests; full security suite (113 lib + integration) green; clippy + fmt clean.

---------

Co-authored-by: Andres Contreras <andres.contreras@soon.es>

v26.6.29

Toggle v26.6.29's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat(security): Spring Security 6 parity — Tier 0 hardening + passwor…

…dless (26.6.29) (#37)

* fix(security): Tier 0 auth-core hardening (H1/H2/H14)

H1: SessionAuthenticationLayer now scopes the task-local CURRENT_AUTH around
the downstream call (not just the request extension), so #[pre_authorize] /
check_access / current_authentication() work for session- and OAuth2-login-
authenticated callers — previously method security silently failed unless the
caller was behind BearerLayer.
H2: Authentication::has_role accepts Spring's ROLE_ prefix (hasRole('ADMIN')
matches authority ROLE_ADMIN) while keeping bare roles backward-compatible.
H14: guards::permit_all() admits anonymous/absent principals like Spring
permitAll().

Tests: +4 security unit tests (red→green); full firefly-security suite and the
reactive-banking e2e suite stay green.

* fix(security): Tier 0 filter-chain hardening (H3/H10)

H3: path-prefix rules are now path-segment aware (Spring AntPathRequestMatcher
semantics) — permit("/api") matches /api and /api/... but no longer leaks to
/api-internal or /apixyz, which a raw starts_with allowed.
H10: glob compilation no longer panics on an invalid pattern — FilterChain
gains try_layer() returning a recoverable SecurityError (layer() still panics
for ergonomic use). Builders defer validation to layer/try_layer.

Tests: +2 filter-chain unit tests (red→green); full suite + reactive-banking
e2e stay green.

* feat(security): Tier 0 JWT/JWKS/bearer hardening (H5/H6/H7/H8)

H5: JwksVerifier now accepts EC (ES256/ES384) and OKP/EdDSA keys in addition
to RSA (RS*/PS*); a resource server fronting an EC/EdDSA-signing IdP can now
verify tokens. Default allowed-alg set is the asymmetric family (never HS*).
H6: OIDC id_token is never silently trusted — handle_callback rejects an
id_token it cannot validate (no JWKS) instead of falling through to userinfo;
exp/nbf now use a configurable clock-skew leeway (default 60s, Spring's
JwtTimestampValidator default) on both JwksVerifier and JwtService.
H7: nbf is now validated (future-dated tokens rejected).
H8: bearer rejections carry an RFC 6750 WWW-Authenticate: Bearer challenge
(bare when no token; error="invalid_token" when present-but-invalid).

Tests: +9 (jwks EC/EdDSA/nbf/leeway, jwt leeway/nbf, bearer challenge x2,
oauth2 id_token no-jwks); full suite + reactive-banking e2e green; clippy clean.

* fix(web,security): Tier 0 web-layer hardening (H4/H9/H11)

H4: CSRF cookie Secure attribute now follows the request scheme by default
(CookieSecure::Auto) instead of being unconditional, so the double-submit pair
works over plain-HTTP dev; Always/Never override. Applied to both the
firefly-web and firefly-security CSRF layers.
H9: HSTS is emitted only on secure requests by default (Spring's
HstsHeaderWriter); hsts_include_insecure forces it on.
H11: CorsLayer rejects the illegal wildcard-origin + credentials combination
(Spring's validateAllowCredentials) via try_new()->Result; new() panics.

BREAKING (Spring-faithful, flagged): default HSTS no longer sent over HTTP;
CSRF cookie no longer Secure over HTTP; wildcard-origin+credentials CORS config
now rejected. Three pyfly-parity tests updated to the new semantics; +5 tests.
Full security+web suites + reactive-banking e2e green; clippy clean.

* fix(session-postgres,idp): Tier 0 store hardening (H12/H13)

H12: PostgresSessionRegistry gains an expires_at column (idempotent ALTER for
existing tables) and TTL-driven pruning, so an orphaned session row ages out
instead of inflating the per-principal concurrency count forever. Default TTL
30m; with_ttl() overrides (ZERO disables); prune_expired() exposed for a
scheduled sweep. Verified end-to-end against real Postgres.
H13: idp-internal-db login runs a comparable bcrypt op on the unknown-user path
so an unknown username can't be distinguished from a wrong password by latency
(user-enumeration oracle) — Spring's userNotFoundEncodedPassword guard.

Tests: +1 idp timing test (red→green), +H12 real-Postgres pruning test, +SQL
const guards; existing PG integration tests pin TTL off (synthetic timestamps).
clippy clean.

* feat(security): one-time-token (magic-link) login — Spring 6.4 oneTimeTokenLogin()

New ott module: OneTimeTokenService (generate/consume, single-use, expiry) with
an in-memory impl; OneTimeTokenGenerationSuccessHandler for out-of-band delivery
(default logs issuance only, never the token value); and ott_login_routes
exposing POST /ott/generate + GET /login/ott (magic link) that redeems a token,
rotates the session id (anti-fixation), and stores the SECURITY_CONTEXT for
SessionAuthenticationLayer to restore. Adds tracing dep.

Tests: +6 (generate/consume, single-use, unknown/expired rejection, generate
endpoint doesn't leak the token, login endpoint authenticates + sets session
context + rejects replay). Full security suite green; clippy clean.

* docs(book): add the 'Spring Security Parity' appendix (EN + ES)

A published reference companion to the design spec: the Spring Security 6
coverage matrix, the Spring-faithful behaviours of the Tier 0 hardening, the
passwordless (one-time-token + WebAuthn) story, and the tiered roadmap. Wired
into book.yaml + book-es.yaml (Appendix B) and SUMMARY.md; the designed book
builds (EN verified, ES wired symmetrically).

* feat(security): WebAuthn / passkey login — Spring 6.4 webAuthn() (feature-gated)

New, opt-in `webauthn` module (off by default; pulls in webauthn-rs 0.5.5):
- WebAuthnRelyingParty over webauthn_rs::Webauthn (start/finish passkey
  registration + authentication).
- Pluggable ports: PasskeyCredentialRepository, PublicKeyCredentialUserEntity-
  Repository, CeremonyStateStore (+ in-memory impls).
- webauthn_routes: POST /webauthn/register/options|register,
  /webauthn/authenticate/options, POST /login/webauthn — the login route rotates
  the session id and stores the security context (reused by
  SessionAuthenticationLayer), mirroring the OTT flow.
- WebAuthnProperties (rp-id / rp-name / allowed-origins), WebAuthnError.

Tests: +5 incl. a real end-to-end ceremony driven by a software authenticator
(register → authenticate → session context set) and an RP-level round-trip.
Verified: cargo test --features webauthn (96 lib + all suites green), default
build (feature off) compiles, clippy clean.

* feat(security): wire configurable JWT clock-skew through SecurityProperties (Batch 6)

JwtProperties gains clock_skew_seconds; verifier_from_config applies it to both
the JWKS and HMAC verifiers (0 keeps the Spring-faithful 60s default). The other
Tier 0 knobs are already serde-bound on their config structs
(SecurityHeadersConfig.hsts_include_insecure, WebAuthnProperties) or set at the
layer builder (CookieSecure on CsrfLayer, OTT ttl). +1 config test.

* fix(security,web,session-postgres): address adversarial review findings

Following an adversarial review of the Tier 0 diff (5 dimensions, each finding
re-verified):
- [HIGH] In-process TLS termination now marks requests secure (SecureRequest
  extension, set by serve() when TLS is configured), so HSTS and the CSRF
  Secure-cookie flag are no longer silently dropped on direct-HTTPS deployments
  (request_is_secure previously only honoured X-Forwarded-Proto / URI scheme).
- [HIGH/MED] FilterChain role rules are now ROLE_-prefix aware (and match a
  ROLE_-prefixed authority), consistent with Authentication::has_role — a
  ROLE_ADMIN principal satisfies require(..., ["ADMIN"]) just as it does
  #[pre_authorize]. Closes the cross-surface H2 asymmetry.
- [MED] Postgres SessionRegistry pruning is now opt-in (default OFF): a fixed
  created_at+ttl expiry would wrongly evict still-active sliding sessions and
  under-count maximumSessions. with_ttl enables it for absolute-lifetime caps.
- Tests: ROLE_ cross-surface test, in-app-TLS HSTS test, default-no-prune test
  (real PG), OTT anti-fixation rotation assertion, de-flaked OTT expiry test,
  permit_all combinator test. Lower-severity findings documented as known
  limitations.

web 48+60+11+19 + security 94 + session-postgres 18 (real PG) green; clippy clean.

* chore(release): 26.6.29 — Spring Security 6 parity (Tier 0)

Bump the workspace to 26.6.29 and record the CHANGELOG for the Spring Security
parity increment: the Tier 0 hardening (H1–H14), one-time-token login, WebAuthn,
configurable clock-skew, the parity book appendix, the post-review fixes, and
the documented known limitations.

* docs(security): document the parity work across the book, README & MODULES

- MODULES.md + crates/security/README.md: reflect ROLE_-aware/segment-safe
  FilterChain, RSA/EC/EdDSA JWKS, one-time-token + WebAuthn passwordless login,
  Argon2id, and the Spring Security 6-faithful hardening.
- Security chapter (EN + ES): cross-link the new Spring Security Parity appendix.
- Rebuild the designed book dist (EN + ES PDF/EPUB) so the release ships the
  appendix and cross-links.

---------

Co-authored-by: Andrés Contreras Guillén <ancongui@Andress-MacBook-Pro-2.local>
Co-authored-by: Andres Contreras <andres.contreras@soon.es>

v26.6.28

Toggle v26.6.28's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat: declarative #[http_client] HTTP-interface client (26.6.28) (#30)

A Spring Boot parity increment — the highest single value lever from the
16-area parity-gap analysis (it lifts the REST/HTTP-clients area off the
floor). Designed via a scored 3-proposal judge panel and adversarially
reviewed before merge.

#[http_client] — the analog of Spring 6's @HttpExchange (modern OpenFeign
replacement). Annotate a trait of methods with the SAME verb attributes a
#[rest_controller] uses; the macro generates a <Trait>Impl that issues the
requests over a WebClient — the mirror image of a controller.
  - Verbs: #[get("/path")]/#[post]/#[put]/#[delete]/#[patch] + generic
    #[request(method="...")]. Path vars use the framework's :id syntax (same
    as the server macro); {id} is a compile error pointing at :id.
  - Argument binding needs no attributes in the common case: a name-matched
    :var arg is the path variable, the lone non-scalar arg on a body verb is
    the JSON body, the rest are query params (Option omits when None, Vec/&[_]
    repeat). Override with #[path]/#[query("k")]/#[header("X")]/#[body]. Every
    :var must bind exactly once or it's a compile error; an Option/Vec/slice
    path variable is rejected (caught by review — it produced .../Some(x) URLs).
  - Return shapes: async fn -> Result<T, ClientError> (ergonomic default),
    Result<T, E: From<ClientError>>, non-async Mono<T>/Flux<T> (returned
    directly; Flux defaults Accept: application/x-ndjson), WebClientResponse
    (.exchange() escape hatch).
  - Construction: <Trait>Impl::new(base_url) or ::with_client(WebClient); Clone.
    With #[http_client(... bean)] it's registered as a @service and bound to
    dyn Trait, so #[autowired] Arc<dyn Trait> resolves (sharing a WebClient bean,
    named via client = "...").
  - Error fidelity (documented): awaited Result<T, ClientError> surfaces every
    failure as ClientError::Problem (FireflyError with original status/code, so
    is_not_found()/is_server_error()/is_retryable() still classify); structured
    Transport/Decode/Encode/InvalidUrl variants survive only on Mono/Flux.

Reuses the server #[rest_controller] verb grammar (MappingAttr/VERBS/join_path
made pub(crate); no code moved) so client and server can't drift. Added
firefly_client::encode_path_segment (RFC 3986 path-segment encoding). The
firefly::prelude now also re-exports WebClient/ClientError/new_web_client.

Tests: in-process axum round-trip (path-var encode, Option-query omit, header,
JSON body, Vec decode, 204->(), 404->Problem.is_not_found(), NDJSON Flux,
Option empty-body fold, custom-error map_err, DI dyn-Trait resolve) + 12
trybuild compile-fail cases. make ci green: 317 suites, 4503 tests, 0 failed.
Book: docs/book/src/13-http-clients.md "The declarative client" section.

Co-authored-by: Andrés Contreras Guillén <ancongui@Andress-MacBook-Pro-2.local>

v26.6.27

Toggle v26.6.27's commit message

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat: declarative rollback rules on #[transactional] (26.6.27) (#29)

A Spring Boot parity increment, chosen from a 16-area parity-gap analysis
(~84% overall) as the best value-to-effort gap — the transaction runtime
already supported per-error rollback decisions; only the macro surface was
missing.

#[transactional(no_rollback_for = "<pat>", rollback_only_for = "<pat>")]
  Spring names exception *types*; because Rust's Result already separates
  failure from success, the Firefly analog names an error *pattern*. By default
  every Err rolls back; then:
  - no_rollback_for = "P" — Spring's @transactional(noRollbackFor = …): an Err
    matching pattern P commits instead of rolling back;
  - rollback_only_for = "P": roll back only for errors matching P, committing
    the rest;
  - with both, no_rollback_for wins on overlap.

  The macro lowers to the already-present transactional_with /
  transactional_with_on runtime entry points (which take a
  should_rollback(&E) -> bool predicate), composes with manager = "…", and the
  generated predicate is matches!-based, so a pattern that does not fit the
  error type is a compile error. Patterns allow `A | B` alternatives (no `if`
  guard).

  Deliberately NOT named rollback_for: Spring's rollbackFor is *additive* (it
  widens the always-rollback set), but Rust has no checked/unchecked split —
  every Err already rolls back — so the faithful rule is *restrictive*. Writing
  rollback_for is a friendly compile error pointing at the two rules above, so a
  Spring port can't be silently inverted.

Adversarially reviewed (caught and fixed the rollback_for naming footgun, the
no-guard constraint, and an untested process-global path before merge). Tests:
a spy TransactionManager records the per-call commit-vs-rollback decision across
all four cases on both the explicit-manager and process-global paths. Docs:
07-persistence.md + CHANGELOG. make ci green: 316 suites, 4489 tests, 0 failed.

Co-authored-by: Andrés Contreras Guillén <ancongui@Andress-MacBook-Pro-2.local>