cratestack_axum/headers/verified_signer.rs
1//! Carrying the envelope's verified signer from the request extensions into
2//! the handler's `CratestackContext` (maintainer decision D2 on
3//! cratestack#1006).
4//!
5//! The envelope layer runs before any context exists, so it can only leave
6//! the fact on the request, as a `cratestack_core::VerifiedSigner`
7//! extension. Generated handlers build their context from the
8//! `AuthProvider`'s answer and then call [`enrich_context_from_envelope`],
9//! next to [`super::enrich_context_from_headers`]. A separate function, not a
10//! new parameter on that one, so its public signature does not change.
11//!
12//! This is a recorded fact, **not an authentication**:
13//! `CratestackContext::record_verified_signer` leaves `is_authenticated()`
14//! and the principal alone. Who the caller is stays the `AuthProvider`'s
15//! call (the signer-to-principal adapter is cratestack#1077). The helper is
16//! compiled with or without the `cose` feature, so generated code does not
17//! depend on it; without the layer nothing ever inserts the extension and
18//! this is a no-op.
19
20use cratestack_core::{CratestackContext, VerifiedSigner};
21
22/// Record the `VerifiedSigner` the envelope layer left in `extensions`, if
23/// any, on `ctx`. `extensions` is the request's (generated handlers pass
24/// `ClientIpContext::extensions`, a clone of them).
25///
26/// Extensions are in-process only, so nothing on the wire can put one
27/// there: a signer is present only when an in-process layer (the envelope
28/// layer, or one the application trusts as much) inserted it.
29pub fn enrich_context_from_envelope(
30 mut ctx: CratestackContext,
31 extensions: &http::Extensions,
32) -> CratestackContext {
33 if let Some(signer) = extensions.get::<VerifiedSigner>() {
34 ctx.record_verified_signer(signer.clone());
35 }
36 ctx
37}
38
39#[cfg(test)]
40mod tests {
41 use cratestack_core::{CratestackContext, VerifiedSigner};
42
43 use super::enrich_context_from_envelope;
44
45 #[test]
46 fn a_signer_in_the_extensions_is_recorded_but_does_not_authenticate() {
47 let mut extensions = http::Extensions::new();
48 extensions.insert(VerifiedSigner::new(vec![1; 8], [7; 32], -19));
49 let ctx = enrich_context_from_envelope(CratestackContext::anonymous(), &extensions);
50 let signer = ctx.verified_signer().expect("recorded");
51 assert_eq!(signer.thumbprint(), &[7; 32]);
52 assert!(!ctx.is_authenticated());
53 }
54
55 #[test]
56 fn without_one_the_context_is_unchanged() {
57 let ctx =
58 enrich_context_from_envelope(CratestackContext::anonymous(), &http::Extensions::new());
59 assert!(ctx.verified_signer().is_none());
60 }
61}