#![cfg_attr(not(feature = "authentication"), allow(unused_imports))]
#[cfg(feature = "authentication")]
pub mod password;
#[cfg(feature = "authentication")]
pub mod providers;
#[cfg(feature = "authentication")]
pub use password::Sha256Hasher;
#[cfg(feature = "authentication")]
pub use providers::ConstCredentialProvider;
pub trait AccessLevel: Copy + Clone + PartialOrd + Ord + 'static {
fn from_str(s: &str) -> Option<Self>
where
Self: Sized;
fn as_str(&self) -> &'static str;
}
#[derive(Debug, Clone)]
pub struct User<L: AccessLevel> {
pub username: heapless::String<32>,
pub access_level: L,
#[cfg(feature = "authentication")]
pub password_hash: [u8; 32],
#[cfg(feature = "authentication")]
pub salt: [u8; 16],
}
impl<L: AccessLevel> User<L> {
#[cfg(not(feature = "authentication"))]
pub fn new(username: &str, access_level: L) -> Result<Self, crate::error::CliError> {
let mut user_str = heapless::String::new();
user_str
.push_str(username)
.map_err(|_| crate::error::CliError::BufferFull)?;
Ok(Self {
username: user_str,
access_level,
})
}
#[cfg(feature = "authentication")]
pub fn new(
username: &str,
access_level: L,
password_hash: [u8; 32],
salt: [u8; 16],
) -> Result<Self, crate::error::CliError> {
let mut user_str = heapless::String::new();
user_str
.push_str(username)
.map_err(|_| crate::error::CliError::BufferFull)?;
Ok(Self {
username: user_str,
access_level,
password_hash,
salt,
})
}
}
#[cfg(feature = "authentication")]
pub trait CredentialProvider<L: AccessLevel> {
type Error;
fn find_user(&self, username: &str) -> Result<Option<User<L>>, Self::Error>;
fn verify_password(&self, user: &User<L>, password: &str) -> bool;
}
#[cfg(feature = "authentication")]
pub trait PasswordHasher {
fn hash(&self, password: &str, salt: &[u8]) -> [u8; 32];
fn verify(&self, password: &str, salt: &[u8], hash: &[u8; 32]) -> bool;
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum TestAccessLevel {
Guest = 0,
User = 1,
Admin = 2,
}
impl AccessLevel for TestAccessLevel {
fn from_str(s: &str) -> Option<Self> {
match s {
"Guest" => Some(Self::Guest),
"User" => Some(Self::User),
"Admin" => Some(Self::Admin),
_ => None,
}
}
fn as_str(&self) -> &'static str {
match self {
Self::Guest => "Guest",
Self::User => "User",
Self::Admin => "Admin",
}
}
}
#[test]
fn test_user_creation() {
#[cfg(not(feature = "authentication"))]
{
let user = User::new("alice", TestAccessLevel::User).unwrap();
assert_eq!(user.username.as_str(), "alice");
assert_eq!(user.access_level, TestAccessLevel::User);
}
#[cfg(feature = "authentication")]
{
let hash = [42u8; 32];
let salt = [99u8; 16];
let user = User::new("alice", TestAccessLevel::User, hash, salt).unwrap();
assert_eq!(user.username.as_str(), "alice");
assert_eq!(user.access_level, TestAccessLevel::User);
assert_eq!(user.password_hash, hash);
assert_eq!(user.salt, salt);
}
}
}