use axum::http::{HeaderMap, Method, header};
pub const CONTENT_SECURITY_POLICY: &str = "default-src 'self'; script-src 'self'; \
style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; \
font-src 'self'; object-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'";
#[must_use]
pub fn token() -> String {
let mut bytes = [0u8; 32];
if getrandom::fill(&mut bytes).is_err() {
return String::new();
}
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
fn constant_time_eq(a: &[u8], b: &[u8]) -> bool {
a.len() == b.len() && a.iter().zip(b).fold(0u8, |acc, (x, y)| acc | (x ^ y)) == 0
}
#[must_use]
pub fn authorised(token: &str, headers: &HeaderMap) -> bool {
!token.is_empty()
&& headers
.get(header::AUTHORIZATION)
.and_then(|v| v.to_str().ok())
.and_then(|v| v.strip_prefix("Bearer "))
.is_some_and(|t| constant_time_eq(t.as_bytes(), token.as_bytes()))
}
#[must_use]
pub fn hosts(port: u16) -> [String; 3] {
[
format!("127.0.0.1:{port}"),
format!("localhost:{port}"),
format!("[::1]:{port}"),
]
}
#[must_use]
pub fn host_ok(port: u16, headers: &HeaderMap) -> bool {
headers
.get(header::HOST)
.and_then(|v| v.to_str().ok())
.is_some_and(|h| hosts(port).iter().any(|ok| ok == h))
}
#[must_use]
pub fn origin_ok(port: u16, method: &Method, headers: &HeaderMap) -> bool {
if method == Method::GET || method == Method::HEAD {
return true;
}
match headers.get(header::ORIGIN).and_then(|v| v.to_str().ok()) {
None => true,
Some(o) => hosts(port).iter().any(|h| o == format!("http://{h}")),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn headers(pairs: &[(header::HeaderName, &str)]) -> HeaderMap {
let mut h = HeaderMap::new();
for (k, v) in pairs {
if let Ok(v) = v.parse() {
h.insert(k.clone(), v);
}
}
h
}
#[test]
fn the_token_host_and_origin_are_checked() {
let t = token();
assert_eq!(t.len(), 64);
assert!(authorised(
&t,
&headers(&[(header::AUTHORIZATION, &format!("Bearer {t}"))])
));
assert!(!authorised(
&t,
&headers(&[(header::AUTHORIZATION, "Bearer nope")])
));
assert!(!authorised(&t, &HeaderMap::new()));
assert!(!authorised(
"",
&headers(&[(header::AUTHORIZATION, "Bearer ")])
));
assert!(host_ok(4321, &headers(&[(header::HOST, "127.0.0.1:4321")])));
assert!(host_ok(4321, &headers(&[(header::HOST, "localhost:4321")])));
assert!(!host_ok(
4321,
&headers(&[(header::HOST, "evil.example:4321")])
));
assert!(!host_ok(4321, &headers(&[(header::HOST, "127.0.0.1:9")])));
assert!(origin_ok(
4321,
&Method::POST,
&headers(&[(header::ORIGIN, "http://127.0.0.1:4321")])
));
assert!(!origin_ok(
4321,
&Method::POST,
&headers(&[(header::ORIGIN, "https://evil.example")])
));
assert!(origin_ok(
4321,
&Method::GET,
&headers(&[(header::ORIGIN, "https://evil.example")])
));
}
}