1#![deny(missing_docs)]
2use async_trait::async_trait;
8use neuron_auth::{AuthError, AuthProvider, AuthRequest, AuthToken};
9use std::path::PathBuf;
10
11pub struct FileTokenProvider {
13 path: PathBuf,
14}
15
16impl FileTokenProvider {
17 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}