1#![cfg_attr(not(feature = "authentication"), allow(unused_imports))]
9
10#[cfg(feature = "authentication")]
12pub mod password;
13
14#[cfg(feature = "authentication")]
15pub mod providers;
16
17#[cfg(feature = "authentication")]
19pub use password::Sha256Hasher;
20
21#[cfg(feature = "authentication")]
22pub use providers::ConstCredentialProvider;
23
24pub trait AccessLevel: Copy + Clone + PartialOrd + Ord + 'static {
27 fn from_str(s: &str) -> Option<Self>
29 where
30 Self: Sized;
31
32 fn as_str(&self) -> &'static str;
34}
35
36#[derive(Debug, Clone)]
41pub struct User<L: AccessLevel> {
42 pub username: heapless::String<32>,
44
45 pub access_level: L,
47
48 #[cfg(feature = "authentication")]
50 pub password_hash: [u8; 32],
51
52 #[cfg(feature = "authentication")]
54 pub salt: [u8; 16],
55}
56
57impl<L: AccessLevel> User<L> {
58 #[cfg(not(feature = "authentication"))]
60 pub fn new(username: &str, access_level: L) -> Result<Self, crate::error::CliError> {
61 let mut user_str = heapless::String::new();
62 user_str
63 .push_str(username)
64 .map_err(|_| crate::error::CliError::BufferFull)?;
65
66 Ok(Self {
67 username: user_str,
68 access_level,
69 })
70 }
71
72 #[cfg(feature = "authentication")]
74 pub fn new(
75 username: &str,
76 access_level: L,
77 password_hash: [u8; 32],
78 salt: [u8; 16],
79 ) -> Result<Self, crate::error::CliError> {
80 let mut user_str = heapless::String::new();
81 user_str
82 .push_str(username)
83 .map_err(|_| crate::error::CliError::BufferFull)?;
84
85 Ok(Self {
86 username: user_str,
87 access_level,
88 password_hash,
89 salt,
90 })
91 }
92}
93
94#[cfg(feature = "authentication")]
97pub trait CredentialProvider<L: AccessLevel> {
98 type Error;
100
101 fn find_user(&self, username: &str) -> Result<Option<User<L>>, Self::Error>;
105
106 fn verify_password(&self, user: &User<L>, password: &str) -> bool;
110}
111
112#[cfg(feature = "authentication")]
117pub trait PasswordHasher {
118 fn hash(&self, password: &str, salt: &[u8]) -> [u8; 32];
122
123 fn verify(&self, password: &str, salt: &[u8], hash: &[u8; 32]) -> bool;
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
135 enum TestAccessLevel {
136 Guest = 0,
137 User = 1,
138 Admin = 2,
139 }
140
141 impl AccessLevel for TestAccessLevel {
142 fn from_str(s: &str) -> Option<Self> {
143 match s {
144 "Guest" => Some(Self::Guest),
145 "User" => Some(Self::User),
146 "Admin" => Some(Self::Admin),
147 _ => None,
148 }
149 }
150
151 fn as_str(&self) -> &'static str {
152 match self {
153 Self::Guest => "Guest",
154 Self::User => "User",
155 Self::Admin => "Admin",
156 }
157 }
158 }
159
160 #[test]
161 fn test_user_creation() {
162 #[cfg(not(feature = "authentication"))]
163 {
164 let user = User::new("alice", TestAccessLevel::User).unwrap();
165 assert_eq!(user.username.as_str(), "alice");
166 assert_eq!(user.access_level, TestAccessLevel::User);
167 }
168
169 #[cfg(feature = "authentication")]
170 {
171 let hash = [42u8; 32];
172 let salt = [99u8; 16];
173 let user = User::new("alice", TestAccessLevel::User, hash, salt).unwrap();
174 assert_eq!(user.username.as_str(), "alice");
175 assert_eq!(user.access_level, TestAccessLevel::User);
176 assert_eq!(user.password_hash, hash);
177 assert_eq!(user.salt, salt);
178 }
179 }
180}