pub trait PrincipalMapper:
Send
+ Sync
+ 'static {
// Required method
fn principal<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
verified: &'life1 VerifiedRequest<'life2>,
) -> Pin<Box<dyn Future<Output = Result<String, CratestackError>> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait;
}Expand description
Names the principal a verified request is charged to: the value of the
VerifiedPrincipal extension the layer inserts, which the rate limiter’s
default key (princ: bucket, no cardinality budget) and the idempotency
layer’s default fingerprint (princ: namespace) use.
The default is ThumbprintPrincipal. Replace it to charge a signer to
something coarser (its device’s owner, its tenant), looked up
asynchronously if need be. It only names a principal for rate limiting
and idempotency; turning a signer into an identity for authorization
is an AuthProvider’s job (the adapter of cratestack#1077), never this
trait’s (decision D2).
A synchronous mapper is a closure (second-review nit):
use cratestack_axum::envelope_layer::{EnvelopeLayerBuilder, VerifiedRequest};
fn by_kid(builder: EnvelopeLayerBuilder) -> EnvelopeLayerBuilder {
builder.principal_mapper(|v: &VerifiedRequest<'_>| {
Ok(format!("kid:{:x?}", v.signer().kid()))
})
}(That one is for illustration only: a kid is 8 bytes and two keys can
share one, which is why the default uses the full thumbprint.)
Async and fallible (API-review decision, 2026-09-26):
Err(CratestackError::Unauthorized(_))refuses the request with the layer’s coarse, unsigned401(a revoked device, say): the same answer as a failed verification, so the peer learns nothing more.- Any other
Err(a lookup that failed) is a sealed500, since the request did verify; the detail is logged, never sent.
What the layer enforces whatever this returns: it is called only
with a VerifiedRequest, after verification. An empty principal is
refused (a sealed 500), since it would put every signer in one bucket
and one idempotency namespace. A constant principal does the same thing
and cannot be detected: choose a mapper that separates callers.
Required Methods§
Sourcefn principal<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
verified: &'life1 VerifiedRequest<'life2>,
) -> Pin<Box<dyn Future<Output = Result<String, CratestackError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn principal<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
verified: &'life1 VerifiedRequest<'life2>,
) -> Pin<Box<dyn Future<Output = Result<String, CratestackError>> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
The principal to insert for this request.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".
Implementors§
impl PrincipalMapper for ThumbprintPrincipal
impl<F> PrincipalMapper for F
A synchronous mapper is a closure.