rustack_auth/credentials.rs
1//! Credential provider trait and implementations.
2//!
3//! This module defines the [`CredentialProvider`] trait for resolving secret access keys
4//! from access key IDs, along with a [`StaticCredentialProvider`] for testing and
5//! development use cases.
6
7use std::collections::HashMap;
8
9use crate::error::AuthError;
10
11/// Trait for looking up secret access keys by access key ID.
12///
13/// Implementations may back this with a database, configuration file,
14/// or any other credential store.
15pub trait CredentialProvider: Send + Sync {
16 /// Retrieve the secret access key for the given access key ID.
17 ///
18 /// # Errors
19 ///
20 /// Returns [`AuthError::AccessKeyNotFound`] if the access key ID is not recognized.
21 fn get_secret_key(&self, access_key_id: &str) -> Result<String, AuthError>;
22}
23
24/// A simple in-memory credential provider backed by a `HashMap`.
25///
26/// Suitable for testing and development environments. For production use,
27/// implement [`CredentialProvider`] with a secure credential store.
28///
29/// # Examples
30///
31/// ```
32/// use rustack_auth::credentials::{CredentialProvider, StaticCredentialProvider};
33///
34/// let provider = StaticCredentialProvider::new(vec![
35/// ("AKIAIOSFODNN7EXAMPLE".to_owned(), "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_owned()),
36/// ]);
37///
38/// let secret = provider.get_secret_key("AKIAIOSFODNN7EXAMPLE").unwrap();
39/// assert_eq!(secret, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY");
40/// ```
41#[derive(Debug, Clone)]
42pub struct StaticCredentialProvider {
43 credentials: HashMap<String, String>,
44}
45
46impl StaticCredentialProvider {
47 /// Create a new `StaticCredentialProvider` from an iterable of (access_key_id, secret_key)
48 /// pairs.
49 pub fn new(credentials: impl IntoIterator<Item = (String, String)>) -> Self {
50 Self {
51 credentials: credentials.into_iter().collect(),
52 }
53 }
54}
55
56impl CredentialProvider for StaticCredentialProvider {
57 fn get_secret_key(&self, access_key_id: &str) -> Result<String, AuthError> {
58 self.credentials
59 .get(access_key_id)
60 .cloned()
61 .ok_or_else(|| AuthError::AccessKeyNotFound(access_key_id.to_owned()))
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn test_should_return_secret_key_for_known_access_key() {
71 let provider =
72 StaticCredentialProvider::new(vec![("AKID".to_owned(), "secret".to_owned())]);
73
74 let result = provider.get_secret_key("AKID");
75 assert!(result.is_ok());
76 assert_eq!(result.unwrap(), "secret");
77 }
78
79 #[test]
80 fn test_should_return_error_for_unknown_access_key() {
81 let provider = StaticCredentialProvider::new(vec![]);
82
83 let result = provider.get_secret_key("UNKNOWN");
84 assert!(matches!(result, Err(AuthError::AccessKeyNotFound(_))));
85 }
86}