Skip to main content

PrincipalMapper

Trait PrincipalMapper 

Source
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, unsigned 401 (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 sealed 500, 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§

Source

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§

Source§

impl PrincipalMapper for ThumbprintPrincipal

Source§

impl<F> PrincipalMapper for F
where F: Fn(&VerifiedRequest<'_>) -> Result<String, CratestackError> + Send + Sync + 'static,

A synchronous mapper is a closure.