Skip to main content

wiremux_auth/providers/
static_token.rs

1//! Static API key that never expires.
2
3use crate::TokenProvider;
4use crate::error::AuthError;
5
6/// A static API key. Used when no OAuth refresh is needed.
7#[derive(Clone)]
8pub struct StaticToken {
9    key: String,
10}
11
12impl std::fmt::Debug for StaticToken {
13    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
14        f.debug_struct("StaticToken")
15            .field("key", &"[REDACTED]")
16            .finish()
17    }
18}
19
20impl StaticToken {
21    /// Wrap a raw key or access token.
22    #[must_use]
23    pub fn new(key: impl Into<String>) -> Self {
24        Self { key: key.into() }
25    }
26
27    /// Missing or whitespace-only env is an error, not an empty key.
28    pub fn from_env(var: &str) -> Result<Self, AuthError> {
29        let missing = || AuthError::MissingField(format!("env `{var}`"));
30        let value = std::env::var(var).map_err(|_| missing())?;
31        if value.trim().is_empty() {
32            return Err(missing());
33        }
34        Ok(Self::new(value))
35    }
36}
37
38impl TokenProvider for StaticToken {
39    async fn get_token(&self) -> Result<String, AuthError> {
40        Ok(self.key.clone())
41    }
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use crate::isolated_home::IsolatedHome;
48
49    #[tokio::test]
50    async fn static_token_returns_key() {
51        let provider = StaticToken::new("sk-test-key");
52        assert_eq!(provider.get_token().await.unwrap(), "sk-test-key");
53    }
54
55    #[tokio::test]
56    async fn static_wake_is_noop() {
57        let provider = StaticToken::new("sk-test-key");
58        provider.wake();
59        assert_eq!(provider.get_token().await.unwrap(), "sk-test-key");
60    }
61
62    #[test]
63    fn debug_redacts_key() {
64        let provider = StaticToken::new("sk-secret-123");
65        let debug = format!("{provider:?}");
66        assert!(!debug.contains("sk-secret"));
67        assert!(debug.contains("[REDACTED]"));
68    }
69
70    #[tokio::test]
71    async fn from_env_reads_set_var() {
72        let home = IsolatedHome::with_extra_envs(&["WIREMUX_TEST_STATIC_KEY"]);
73        home.set_env("WIREMUX_TEST_STATIC_KEY", "sk-static-issue59");
74        let token = StaticToken::from_env("WIREMUX_TEST_STATIC_KEY").expect("set key");
75        assert_eq!(
76            token.get_token().await.expect("static key"),
77            "sk-static-issue59"
78        );
79        let debug = format!("{token:?}");
80        assert!(debug.contains("[REDACTED]"), "{debug}");
81        assert!(!debug.contains("sk-static-issue59"), "{debug}");
82        let _ = home;
83    }
84
85    #[test]
86    fn from_env_missing_var_is_error() {
87        let home = IsolatedHome::with_extra_envs(&["WIREMUX_TEST_STATIC_KEY"]);
88        let err = StaticToken::from_env("WIREMUX_TEST_STATIC_KEY").expect_err("missing");
89        match err {
90            AuthError::MissingField(name) => {
91                assert!(
92                    name.contains("WIREMUX_TEST_STATIC_KEY") && name.contains("env"),
93                    "MissingField must name the env var, got {name}"
94                );
95            }
96            other => panic!("expected MissingField, got {other}"),
97        }
98        let _ = home;
99    }
100
101    #[test]
102    fn from_env_whitespace_only_is_error() {
103        let home = IsolatedHome::with_extra_envs(&["WIREMUX_TEST_STATIC_KEY"]);
104        home.set_env("WIREMUX_TEST_STATIC_KEY", "   \t");
105        let err = StaticToken::from_env("WIREMUX_TEST_STATIC_KEY").expect_err("whitespace");
106        match err {
107            AuthError::MissingField(name) => {
108                assert!(
109                    name.contains("WIREMUX_TEST_STATIC_KEY") && name.contains("env"),
110                    "MissingField must name the env var, got {name}"
111                );
112            }
113            other => panic!("expected MissingField, got {other}"),
114        }
115        let _ = home;
116    }
117}