metrale_server/rate_limiter/identity.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-26: The rate-limiter key of a request: who is asking. How fast
4//! they may go is the parent module's job.
5//!
6//! Owner: server rate limiter.
7//! Invariants: every key starts with `bearer:`, `xff:` or `peer:`, and a
8//! bearer token appears in a key only as its FNV-1a hash.
9
10/// 2026-09-26: The rate-limit key, first match wins: `bearer:<hash>` for a
11/// non-empty `Authorization: Bearer` token, `xff:<ip>` for the first
12/// non-empty `X-Forwarded-For` entry (taken as sent, not validated), then
13/// `peer:<ip>`, or `peer:unknown` without a peer address. `rate_limit_middleware`
14/// (`main_modules/middleware.rs`) keys the limiter with it.
15pub fn extract_identity(
16 headers: &axum::http::HeaderMap,
17 peer: Option<std::net::SocketAddr>,
18) -> String {
19 use axum::http::header;
20 if let Some(v) = headers
21 .get(header::AUTHORIZATION)
22 .and_then(|v| v.to_str().ok())
23 && let Some(tok) = v.strip_prefix("Bearer ")
24 {
25 let tok = tok.trim();
26 if !tok.is_empty() {
27 // 2026-09-26: Hashed so the limiter map never holds the raw token.
28 return format!("bearer:{}", hash_token(tok));
29 }
30 }
31 if let Some(xff) = headers.get("x-forwarded-for").and_then(|v| v.to_str().ok())
32 && let Some(first) = xff.split(',').next()
33 {
34 let first = first.trim();
35 if !first.is_empty() {
36 return format!("xff:{first}");
37 }
38 }
39 match peer {
40 Some(addr) => format!("peer:{}", addr.ip()),
41 None => "peer:unknown".to_string(),
42 }
43}
44
45/// 2026-09-26: FNV-1a 64-bit hash as 16 hex digits: a stable opaque key, not
46/// a cryptographic hash.
47fn hash_token(tok: &str) -> String {
48 let mut h: u64 = 0xcbf29ce484222325;
49 for b in tok.as_bytes() {
50 h ^= *b as u64;
51 h = h.wrapping_mul(0x100000001b3);
52 }
53 format!("{h:016x}")
54}