Skip to main content

o_sfu/config/
diagnostics.rs

1use anyhow::{Result, ensure};
2use secrecy::{ExposeSecret, SecretString};
3
4use super::env::Env;
5
6const MIN_TOKEN_BYTES: usize = 32;
7
8#[derive(Debug, Clone, Default)]
9pub struct DiagnosticsConfig {
10    /// Bearer token required on every listener when configured.
11    ///
12    /// Surrounding whitespace is ignored. Requires at least 32 bytes after trimming.
13    /// The token must be generated independently from cryptographically random bytes.
14    pub auth_token: Option<SecretString>,
15}
16
17impl DiagnosticsConfig {
18    pub(super) fn from_env(env: &Env<'_>) -> Result<Self> {
19        let auth_token = env
20            .var("DIAGNOSTICS_AUTH_TOKEN")
21            .exclusive_file("DIAGNOSTICS_AUTH_TOKEN_FILE")
22            .check(|key, value: SecretString| {
23                let token = value.expose_secret().trim();
24                validate_token(key, token)?;
25                if token.len() == value.expose_secret().len() {
26                    Ok(value)
27                } else {
28                    Ok(SecretString::from(token))
29                }
30            })
31            .optional()?;
32        Ok(Self { auth_token })
33    }
34
35    /// Applies credential rules to configuration supplied directly by library callers.
36    ///
37    /// # Errors
38    ///
39    /// Returns [`anyhow::Error`] for an empty token, invalid HTTP header bytes
40    /// or fewer than 32 bytes after trimming whitespace. Error text excludes the token.
41    pub(crate) fn validate(&self) -> Result<()> {
42        if let Some(token) = &self.auth_token {
43            validate_token("DIAGNOSTICS_AUTH_TOKEN", token.expose_secret().trim())?;
44        }
45        Ok(())
46    }
47}
48
49fn validate_token(key: &'static str, token: &str) -> Result<()> {
50    ensure!(!token.is_empty(), "{key} must not be empty");
51    // Checking bytes avoids a plaintext HeaderValue allocation outside SecretString.
52    ensure!(
53        token
54            .bytes()
55            .all(|byte| byte == b'\t' || (b' '..=b'~').contains(&byte)),
56        "{key} contains invalid HTTP header-value characters"
57    );
58    ensure!(
59        token.len() >= MIN_TOKEN_BYTES,
60        "{key} must contain at least {MIN_TOKEN_BYTES} bytes"
61    );
62    Ok(())
63}
64
65#[cfg(test)]
66#[path = "TESTS/diagnostics.rs"]
67mod tests;