o_sfu/runtime/http_server/
access.rs1use std::net::SocketAddr;
4
5use axum::http::{HeaderMap, StatusCode, header};
6use secrecy::{ExposeSecret, SecretString};
7use sha2::{Digest, Sha256};
8use subtle::ConstantTimeEq;
9
10#[derive(Clone)]
12pub(super) struct OperatorAccessPolicy {
13 auth_digest: Option<[u8; 32]>,
14 listener_is_loopback: bool,
15}
16
17impl OperatorAccessPolicy {
18 pub(super) fn new(auth_token: Option<&SecretString>, listener_address: SocketAddr) -> Self {
19 Self {
20 auth_digest: auth_token
21 .map(|token| Sha256::digest(token.expose_secret().trim()).into()),
22 listener_is_loopback: listener_address.ip().is_loopback(),
23 }
24 }
25
26 pub(super) fn authorize(&self, headers: &HeaderMap) -> Result<(), StatusCode> {
37 if let Some(expected_digest) = self.auth_digest.as_ref() {
38 return match bearer_authorization_token(headers) {
39 Some(actual_token) if token_matches(actual_token, expected_digest) => Ok(()),
40 _ => Err(StatusCode::UNAUTHORIZED),
41 };
42 }
43 if self.listener_is_loopback {
44 Ok(())
45 } else {
46 Err(StatusCode::FORBIDDEN)
47 }
48 }
49}
50
51pub(super) fn room_authorization_token(headers: &HeaderMap) -> Option<&str> {
52 authorization_token(headers, &["Bearer", "jwt"])
53}
54
55fn bearer_authorization_token(headers: &HeaderMap) -> Option<&str> {
56 authorization_token(headers, &["Bearer"])
57}
58
59fn authorization_token<'headers>(
60 headers: &'headers HeaderMap,
61 accepted_schemes: &[&str],
62) -> Option<&'headers str> {
63 let value = headers
64 .get(header::AUTHORIZATION)
65 .and_then(|value| value.to_str().ok())?;
66 let (scheme, token) = value.split_once(' ')?;
67 if !accepted_schemes
68 .iter()
69 .any(|accepted_scheme| scheme.eq_ignore_ascii_case(accepted_scheme))
70 {
71 return None;
72 }
73 let token = token.trim_start();
74 if token.is_empty() {
75 return None;
76 }
77 Some(token)
78}
79
80fn token_matches(actual: &str, expected: &[u8; 32]) -> bool {
85 let actual: [u8; 32] = Sha256::digest(actual).into();
86 bool::from(actual.ct_eq(expected))
87}