pub struct RequestContext<'a> {
pub method: &'a str,
pub uri: &'a str,
pub body: Option<&'a [u8]>,
pub headers: &'a [(&'a str, &'a str)],
pub peer_addr: Option<SocketAddr>,
}Expand description
The request fields the Digest response hash covers (RFC 7616 §3.4.1 / RFC
2326 §14): the method, the request URI, and — for qop=auth-int — the
body. Also carries the request’s headers and transport peer address, so a
server-side crate::Verifier scheme can see beyond the Authorization
header — e.g. a reverse-proxy forwarded-auth scheme reading
X-Forwarded-User/X-Forwarded-For (issue #663 extensibility wave part
1). Client-side use (crate::respond/crate::Authenticator) needs
neither field; Self::new defaults both to empty/None.
The uri is scheme-specific: an HTTP absolute/relative URL for HTTP
clients, or the RTSP request URI (e.g. rtsp://host/stream) for RTSP —
never translate one into the other (RFC 2326 §14).
Fields§
§method: &'a strThe request method ("GET", "DESCRIBE", …).
uri: &'a strThe request URI, in the caller’s protocol’s own form.
body: Option<&'a [u8]>The request body, needed only for Digest qop=auth-int. Some(&[])
for a bodyless request still lets auth-int be computed.
headers: &'a [(&'a str, &'a str)]Every request header, as (name, value) pairs — Self::header
looks one up case-insensitively (header names are case-insensitive,
RFC 7230 §3.2). Empty for client-side use (Self::new’s default): a
client answering a challenge computes Authorization from
method/uri/body alone. Server-side (crate::Verifier::verify)
this is how every scheme — including a future one — reads whatever
header it needs, not just Authorization.
peer_addr: Option<SocketAddr>The transport-layer peer address (e.g. the accepted TCP connection’s
remote address), if the caller has one to attach. This is the actual
connection peer — which, behind a reverse proxy, is the proxy itself,
not the original client (see X-Forwarded-For in Self::headers
for that). None for client-side use and whenever the caller has no
transport peer to attach.
Implementations§
Source§impl<'a> RequestContext<'a>
impl<'a> RequestContext<'a>
Sourcepub fn new(method: &'a str, uri: &'a str) -> Self
pub fn new(method: &'a str, uri: &'a str) -> Self
Builds a context for a bodyless request with no headers/peer attached
(the common client-side case) — use Self::with_headers/
Self::with_peer_addr to attach either.
Examples found in repository?
30fn digest() {
31 let verifier = Verifier::new(
32 Credentials::Digest {
33 username: "admin".into(),
34 password: "hunter2".into(),
35 },
36 REALM,
37 );
38 let challenge = verifier.challenge();
39 println!("[digest] challenge: {challenge}");
40
41 let ctx = RequestContext::new("DESCRIBE", "rtsp://cam/live");
42
43 // Correct credential: respond() answers the challenge, verify() accepts.
44 let correct = respond(&challenge, &ctx, Credentials::new("admin", "hunter2"))
45 .expect("respond computes an Authorization value");
46 let outcome = verify(&verifier, &correct, &ctx);
47 println!("[digest] correct password -> {outcome:?}");
48 assert_eq!(outcome, AuthResult::Ok);
49
50 // Wrong credential: same challenge, wrong password -> rejected.
51 let wrong = respond(&challenge, &ctx, Credentials::new("admin", "WRONG"))
52 .expect("respond computes an Authorization value even for a wrong password");
53 let outcome = verify(&verifier, &wrong, &ctx);
54 println!("[digest] wrong password -> {outcome:?}");
55 assert_eq!(outcome, AuthResult::Unauthorized);
56}
57
58/// Basic (RFC 7617): same accept/reject shape, briefly.
59fn basic() {
60 let verifier = Verifier::new(
61 Credentials::Basic {
62 username: "admin".into(),
63 password: "hunter2".into(),
64 },
65 REALM,
66 );
67 let challenge = verifier.challenge();
68 println!("[basic] challenge: {challenge}");
69
70 let ctx = RequestContext::new("GET", "/stream/media.m3u8");
71 let correct =
72 respond(&challenge, &ctx, Credentials::new("admin", "hunter2")).expect("responds");
73 assert_eq!(verify(&verifier, &correct, &ctx), AuthResult::Ok);
74 println!("[basic] correct password -> Ok");
75
76 let wrong = respond(&challenge, &ctx, Credentials::new("admin", "WRONG")).expect("responds");
77 assert_eq!(verify(&verifier, &wrong, &ctx), AuthResult::Unauthorized);
78 println!("[basic] wrong password -> Unauthorized");
79}
80
81/// Bearer (RFC 6750): no challenge round-trip needed, but still an
82/// accept/reject pair — a wrong token must not verify.
83fn bearer() {
84 let verifier = Verifier::new(Credentials::bearer("right-token"), REALM);
85 let challenge = verifier.challenge();
86 println!("[bearer] challenge: {challenge}");
87
88 let ctx = RequestContext::new("GET", "/stream/media.m3u8");
89 let correct = respond(&challenge, &ctx, Credentials::bearer("right-token")).expect("responds");
90 assert_eq!(verify(&verifier, &correct, &ctx), AuthResult::Ok);
91 println!("[bearer] correct token -> Ok");
92
93 let wrong = respond(&challenge, &ctx, Credentials::bearer("wrong-token")).expect("responds");
94 assert_eq!(verify(&verifier, &wrong, &ctx), AuthResult::Unauthorized);
95 println!("[bearer] wrong token -> Unauthorized");
96}
97
98/// Reverse-proxy forwarded-auth (`Verifier::forwarded`): no credential at
99/// all — authenticated iff the proxy-set user header is present and
100/// non-empty. See `Verifier::forwarded`'s doc for the trust assumption this
101/// scheme relies on (only safe behind a proxy that strips client-supplied
102/// copies of the header).
103fn forwarded() {
104 let verifier = Verifier::forwarded("X-Forwarded-User", Some("X-Forwarded-For".to_string()));
105 println!("[forwarded] challenge: {}", verifier.challenge());
106
107 let headers: &[(&str, &str)] = &[("X-Forwarded-User", "alice")];
108 let ctx = RequestContext::new("GET", "/stream/media.m3u8").with_headers(headers);
109 assert_eq!(verifier.verify(&ctx), AuthResult::Ok);
110 println!("[forwarded] header present -> Ok");
111
112 let ctx_no_header = RequestContext::new("GET", "/stream/media.m3u8");
113 assert_eq!(verifier.verify(&ctx_no_header), AuthResult::Unauthorized);
114 println!("[forwarded] header absent -> Unauthorized");
115}
116
117/// Builds a request context carrying `authorization` as the `Authorization`
118/// header, then verifies it against `verifier`.
119fn verify(verifier: &Verifier, authorization: &str, ctx: &RequestContext<'_>) -> AuthResult {
120 let headers: &[(&str, &str)] = &[("authorization", authorization)];
121 let ctx_with_auth = RequestContext::new(ctx.method, ctx.uri).with_headers(headers);
122 verifier.verify(&ctx_with_auth)
123}More examples
18fn main() {
19 // --- Basic (RFC 7617): a one-shot respond(), no session state needed.
20 let value = respond(
21 "Basic realm=\"cameras\"",
22 &RequestContext::new("GET", "/stream/media.m3u8"),
23 Credentials::new("admin", "hunter2"),
24 )
25 .expect("Basic responds to any challenge shape");
26 println!("[basic] Authorization: {value}");
27 assert!(value.starts_with("Basic "));
28
29 // --- Digest (RFC 7616): parses the server's nonce/realm/qop out of the
30 // challenge, then computes HA1/HA2/response. Demonstrated with an
31 // Authenticator (not the one-shot respond()) since a real session reuses
32 // it across requests so the nonce count (`nc`) advances correctly.
33 let digest_challenge = "Digest realm=\"cameras\", \
34 nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", qop=\"auth\", algorithm=MD5";
35 let mut auth =
36 Authenticator::from_challenge(digest_challenge, Credentials::new("admin", "hunter2"))
37 .expect("challenge parses");
38 let value = auth
39 .authorization(&RequestContext::new(
40 "DESCRIBE",
41 "rtsp://camera.example.com/live",
42 ))
43 .expect("computes a Digest Authorization value");
44 println!("[digest] Authorization: {value}");
45 assert!(value.starts_with("Digest "));
46
47 // A second request on the same Authenticator advances `nc` — the
48 // computed value differs even though nothing else about the request
49 // changed (RFC 7616 §3.3 requires a fresh `nc` per request).
50 let second = auth
51 .authorization(&RequestContext::new(
52 "DESCRIBE",
53 "rtsp://camera.example.com/live",
54 ))
55 .expect("computes a second Digest Authorization value");
56 assert_ne!(value, second, "nc must advance across requests");
57 println!("[digest] Authorization (2nd request, nc advanced): {second}");
58
59 // --- Bearer (RFC 6750): no challenge round-trip needed at all — the
60 // challenge value is ignored, the token is sent verbatim.
61 let value = respond(
62 "ignored — Bearer needs no challenge round-trip",
63 &RequestContext::new("GET", "/stream/media.m3u8"),
64 Credentials::bearer("mytoken123"),
65 )
66 .expect("Bearer always responds");
67 println!("[bearer] Authorization: {value}");
68 assert_eq!(value, "Bearer mytoken123");
69}Sourcepub fn with_headers(self, headers: &'a [(&'a str, &'a str)]) -> Self
pub fn with_headers(self, headers: &'a [(&'a str, &'a str)]) -> Self
Attaches the request’s headers (server-side use — see
Self::headers).
Examples found in repository?
103fn forwarded() {
104 let verifier = Verifier::forwarded("X-Forwarded-User", Some("X-Forwarded-For".to_string()));
105 println!("[forwarded] challenge: {}", verifier.challenge());
106
107 let headers: &[(&str, &str)] = &[("X-Forwarded-User", "alice")];
108 let ctx = RequestContext::new("GET", "/stream/media.m3u8").with_headers(headers);
109 assert_eq!(verifier.verify(&ctx), AuthResult::Ok);
110 println!("[forwarded] header present -> Ok");
111
112 let ctx_no_header = RequestContext::new("GET", "/stream/media.m3u8");
113 assert_eq!(verifier.verify(&ctx_no_header), AuthResult::Unauthorized);
114 println!("[forwarded] header absent -> Unauthorized");
115}
116
117/// Builds a request context carrying `authorization` as the `Authorization`
118/// header, then verifies it against `verifier`.
119fn verify(verifier: &Verifier, authorization: &str, ctx: &RequestContext<'_>) -> AuthResult {
120 let headers: &[(&str, &str)] = &[("authorization", authorization)];
121 let ctx_with_auth = RequestContext::new(ctx.method, ctx.uri).with_headers(headers);
122 verifier.verify(&ctx_with_auth)
123}Sourcepub fn with_peer_addr(self, peer_addr: SocketAddr) -> Self
pub fn with_peer_addr(self, peer_addr: SocketAddr) -> Self
Attaches the transport peer address (server-side use — see
Self::peer_addr).
Trait Implementations§
Source§impl<'a> Clone for RequestContext<'a>
impl<'a> Clone for RequestContext<'a>
Source§fn clone(&self) -> RequestContext<'a>
fn clone(&self) -> RequestContext<'a>
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreimpl<'a> Copy for RequestContext<'a>
Source§impl Debug for RequestContext<'_>
Manual Debug (rather than #[derive(Debug)]): Self::headers carries
whatever the caller attached, which — server-side — includes the real
Authorization/Proxy-Authorization header the request was authenticated
with. Basic’s value is a reversible base64 user:pass (RFC 7617 §2); a
bare tracing::debug!(?ctx, ...) call must not dump it to logs. Every
other header (name and value) is rendered normally — only the value of an
auth header is redacted, and only that header’s name is enough to tell
which one.
impl Debug for RequestContext<'_>
Manual Debug (rather than #[derive(Debug)]): Self::headers carries
whatever the caller attached, which — server-side — includes the real
Authorization/Proxy-Authorization header the request was authenticated
with. Basic’s value is a reversible base64 user:pass (RFC 7617 §2); a
bare tracing::debug!(?ctx, ...) call must not dump it to logs. Every
other header (name and value) is rendered normally — only the value of an
auth header is redacted, and only that header’s name is enough to tell
which one.