Skip to main content

neuron_auth_file/
lib.rs

1#![deny(missing_docs)]
2//! File-based auth provider that reads a bearer token from disk.
3//!
4//! Reads the entire file contents as the token bytes. Useful for Kubernetes
5//! projected service account tokens or other file-mounted credentials.
6
7use async_trait::async_trait;
8use neuron_auth::{AuthError, AuthProvider, AuthRequest, AuthToken};
9use std::path::PathBuf;
10
11/// Reads a bearer token from a file (e.g., K8s projected service account token).
12pub struct FileTokenProvider {
13    path: PathBuf,
14}
15
16impl FileTokenProvider {
17    /// Create with the path to the token file.
18    pub fn new(path: impl Into<PathBuf>) -> Self {
19        Self { path: path.into() }
20    }
21}
22
23#[async_trait]
24impl AuthProvider for FileTokenProvider {
25    async fn provide(&self, _request: &AuthRequest) -> Result<AuthToken, AuthError> {
26        let bytes = tokio::fs::read(&self.path)
27            .await
28            .map_err(|e| AuthError::BackendError(format!("failed to read token file: {e}")))?;
29        Ok(AuthToken::permanent(bytes))
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36    use std::sync::Arc;
37
38    fn _assert_send_sync<T: Send + Sync>() {}
39
40    #[test]
41    fn object_safety() {
42        _assert_send_sync::<Box<dyn AuthProvider>>();
43        _assert_send_sync::<Arc<dyn AuthProvider>>();
44        let _: Arc<dyn AuthProvider> = Arc::new(FileTokenProvider::new("/tmp/token"));
45    }
46
47    #[tokio::test]
48    async fn reads_token_from_file() {
49        let dir = tempfile::tempdir().unwrap();
50        let path = dir.path().join("token");
51        std::fs::write(&path, b"file-based-token").unwrap();
52        let provider = FileTokenProvider::new(&path);
53        let token = provider.provide(&AuthRequest::new()).await.unwrap();
54        token.with_bytes(|b| assert_eq!(b, b"file-based-token"));
55    }
56
57    #[tokio::test]
58    async fn returns_error_for_missing_file() {
59        let provider = FileTokenProvider::new("/tmp/nonexistent-neuron-test-token-file");
60        let err = provider.provide(&AuthRequest::new()).await.unwrap_err();
61        assert!(matches!(err, AuthError::BackendError(_)));
62        assert!(err.to_string().contains("failed to read token file"));
63    }
64}