pub trait RecoveryPolicy: Send + Sync {
// Required methods
fn reset_token_ttl(&self) -> ChronoDuration;
fn request_rate_limit(&self) -> (u32, StdDuration);
fn consume_rate_limit(&self) -> (u32, StdDuration);
fn strict_mailer_required(&self) -> bool;
// Provided method
fn public_site_url(&self, req: &Request) -> Option<String> { ... }
}Expand description
Tunables for the R1 recovery flow: token TTL, rate-limit shape, strict-mailer boot guard, and public-site-URL derivation.
Admin::new() seeds DefaultRecoveryPolicy; projects override
via crate::admin::Admin::recovery_policy. The trait is Send + Sync so the Arc<dyn RecoveryPolicy> lives on Admin and is
cheap to clone into async futures.
The trait method public_site_url has a provided default that
derives the URL from request headers via [derive_public_site_url]
per DESIGN_RECOVERY.md §12.3. Projects whose deployment can’t
rely on the standard Forwarded / X-Forwarded-* / Host headers
override this method and return their own absolute URL (e.g.
stamped at deployment time from a config secret).
§Trust boundary for forwarded headers
The default public_site_url honours these client-supplied
inputs in priority order:
- RFC 7239
Forwardedheader (for / proto / hostof the first hop) X-Forwarded-Proto+X-Forwarded-Host(first CSV entry of each)Hostheader (assumeshttp://)
The operator’s reverse proxy MUST strip incoming versions of
these headers before adding its own. The framework cannot know
the deployment topology; if a hostile client can reach the
process directly with a chosen Forwarded: … header set, the
reset link in the dispatched email will point wherever they ask.
proto is whitelisted to {http, https} (case-insensitive) and
host is rejected when it contains whitespace, control bytes, or
CRLF — so direct injection of \r\n-style header smuggling
fails — but a malicious yet shape-conformant value still needs
to be filtered upstream.
Projects that need a stricter trust posture: override
public_site_url to return a fixed string (e.g. read from
project config at startup) and the framework will use that
regardless of headers.
Required Methods§
Sourcefn reset_token_ttl(&self) -> ChronoDuration
fn reset_token_ttl(&self) -> ChronoDuration
How long a freshly-issued reset token stays valid. Default
1 hour. Locked-decision per DESIGN_RECOVERY.md §17.
Sourcefn request_rate_limit(&self) -> (u32, StdDuration)
fn request_rate_limit(&self) -> (u32, StdDuration)
Per-IP rate-limit on POST /admin/forgot-password. Returned
as (capacity, window): at most capacity requests within
window. Default (5, 15min).
Sourcefn consume_rate_limit(&self) -> (u32, StdDuration)
fn consume_rate_limit(&self) -> (u32, StdDuration)
Per-IP rate-limit on POST /admin/reset-password/<token>.
Tighter than the request limit since the consume path is the
brute-force surface. Default (10, 5min).
Sourcefn strict_mailer_required(&self) -> bool
fn strict_mailer_required(&self) -> bool
When true, the framework refuses to start at boot if the
registered mailer is still the default crate::email::LogMailer
(production deployments must opt in to a real mailer).
Default false. Enforcement lands when the recovery handlers
ship (R1 commit #7+); this commit ships the declaration only.
Provided Methods§
Sourcefn public_site_url(&self, req: &Request) -> Option<String>
fn public_site_url(&self, req: &Request) -> Option<String>
Derive the absolute base URL the reset email’s link should
point at. Default: see [derive_public_site_url] +
trust-boundary docs on this trait. Projects override this
method to return a fixed string (e.g. read from config) when
header derivation isn’t appropriate for their topology.
Returns None when nothing resolves; the caller (R1 issue
handler, commit #7) treats None as a hard failure and
records metadata.email_send_status = "failed" with a clear
log line.