authentication/user.rs
1//! User management functionality for the Authentication module.
2
3//!
4//! This module provides comprehensive user account management including:
5//! - User creation and authentication
6//! - Password management with secure hashing
7//! - User profile management (name, primary group)
8//! - File-based persistent storage of user data
9//!
10//! All user data is stored as JSON files in the `/System/Users/` directory,
11//! with each user having their own file named after their username.
12
13use alloc::{
14 borrow::ToOwned,
15 string::{String, ToString},
16 vec::Vec,
17};
18use file_system::{AccessFlags, Path, PathOwned};
19use miniserde::{Deserialize, Serialize};
20use users::{GroupIdentifier, GroupIdentifierInner, UserIdentifier, UserIdentifierInner};
21use virtual_file_system::{Directory, File};
22
23use crate::{
24 Context, Error, READ_CHUNK_SIZE, Result, USERS_FOLDER_PATH,
25 hash::{generate_salt, hash_password},
26};
27
28/// Represents a user account with all associated metadata.
29///
30/// This structure contains all the information needed to represent a user
31/// in the system, including their unique identifier, name, primary group,
32/// and hashed password with salt for security.
33#[derive(Clone, Debug, Deserialize, Serialize)]
34pub struct User {
35 /// Unique identifier for the user
36 identifier: UserIdentifierInner,
37 /// Human-readable username
38 name: String,
39 /// Identifier of the user's primary group
40 primary_group: GroupIdentifierInner,
41 /// SHA-512 hash of the user's password combined with salt
42 hash: String,
43 /// Random salt used for password hashing
44 salt: String,
45}
46
47impl User {
48 /// Creates a new user instance with the provided information.
49 ///
50 /// # Arguments
51 ///
52 /// * `Identifier` - Unique numerical identifier for the user
53 /// * `Name` - Human-readable username
54 /// * `Primary_group` - Identifier of the user's primary group
55 /// * `Hash` - Pre-computed SHA-512 hash of password+salt
56 /// * `Salt` - Random salt used for password hashing
57 ///
58 /// # Returns
59 ///
60 /// A new `User_type` instance with the provided data.
61 pub fn new(
62 identifier: UserIdentifierInner,
63 name: String,
64 primary_group: GroupIdentifierInner,
65 hash: String,
66 salt: String,
67 ) -> Self {
68 Self {
69 identifier,
70 name,
71 primary_group,
72 hash,
73 salt,
74 }
75 }
76
77 /// Returns the user's unique identifier.
78 ///
79 /// # Returns
80 ///
81 /// A `UserIdentifier` containing the user's unique ID.
82 pub fn get_identifier(&self) -> UserIdentifier {
83 UserIdentifier::new(self.identifier)
84 }
85
86 /// Returns the user's primary group identifier.
87 ///
88 /// # Returns
89 ///
90 /// A `GroupIdentifier` containing the user's primary group ID.
91 pub fn get_primary_group(&self) -> GroupIdentifier {
92 GroupIdentifier::new(self.primary_group)
93 }
94
95 /// Returns the user's name as a string slice.
96 ///
97 /// # Returns
98 ///
99 /// A string slice containing the username.
100 pub fn get_name(&self) -> &str {
101 &self.name
102 }
103
104 /// Returns the user's password hash as a string slice.
105 ///
106 /// # Returns
107 ///
108 /// A string slice containing the SHA-512 hash of password+salt.
109 pub fn get_hash(&self) -> &str {
110 &self.hash
111 }
112
113 /// Returns the user's salt as a string slice.
114 ///
115 /// # Returns
116 ///
117 /// A string slice containing the random salt used for password hashing.
118 pub fn get_salt(&self) -> &str {
119 &self.salt
120 }
121
122 /// Updates the user's password hash.
123 ///
124 /// # Arguments
125 ///
126 /// * `Hash` - New SHA-512 hash to store
127 pub fn set_hash(&mut self, hash: String) {
128 self.hash = hash;
129 }
130
131 /// Updates the user's salt.
132 ///
133 /// # Arguments
134 ///
135 /// * `Salt` - New salt to store
136 pub fn set_salt(&mut self, salt: String) {
137 self.salt = salt;
138 }
139
140 /// Updates the user's primary group.
141 ///
142 /// # Arguments
143 ///
144 /// * `Primary_group` - New primary group identifier
145 pub fn set_primary_group(&mut self, primary_group: GroupIdentifierInner) {
146 self.primary_group = primary_group;
147 }
148
149 /// Updates the user's name.
150 ///
151 /// # Arguments
152 ///
153 /// * `Name` - New username to store
154 pub fn set_name(&mut self, name: String) {
155 self.name = name;
156 }
157}
158
159/// Constructs the file system path for a user's data file.
160///
161/// # Arguments
162///
163/// * `User_name` - The username to generate a path for
164///
165/// # Returns
166///
167/// Returns `Ok(Path_owned_type)` with the complete path to the user file,
168/// or `Err(Error::Failed_to_get_user_file_path)` if path construction fails.
169pub fn get_user_file_path(user_name: &str) -> Result<PathOwned> {
170 Path::new(USERS_FOLDER_PATH)
171 .to_owned()
172 .append(user_name)
173 .ok_or(Error::FailedToGetUserFilePath)
174}
175
176/// Authenticates a user with their username and password.
177///
178/// This function reads the user's file from the filesystem, compares the
179/// provided password hash with the stored hash, and returns the user's
180/// identifier if authentication succeeds.
181///
182/// # Arguments
183///
184/// * `Virtual_file_system` - Reference to the virtual file system
185/// * `User_name` - Username to authenticate
186/// * `Password` - Plain text password to verify
187///
188/// # Returns
189///
190/// Returns `Ok(UserIdentifier)` if authentication succeeds,
191/// or an appropriate error if authentication fails or file operations fail.
192///
193/// # Errors
194///
195/// - `Failed_to_get_user_file_path` - Invalid username or path construction failure
196/// - `Failed_to_open_user_file` - User file doesn't exist or permission denied
197/// - `Failed_to_read_user_file` - I/O error reading user file
198/// - `Failed_to_parse_user_file` - Invalid JSON format in user file
199/// - `Invalid_password` - Password doesn't match stored hash
200pub async fn authenticate_user(
201 context: &Context<'_>,
202 user_name: &str,
203 password: &str,
204) -> Result<UserIdentifier> {
205 let path = get_user_file_path(user_name)?;
206
207 let virtual_file_system = context.virtual_file_system;
208 let task = context.task;
209
210 let mut user_file = File::open(virtual_file_system, task, path, AccessFlags::Read.into())
211 .await
212 .map_err(Error::FailedToOpenUserFile)?;
213
214 let mut buffer = Vec::new();
215
216 user_file
217 .read_to_end(&mut buffer, READ_CHUNK_SIZE)
218 .await
219 .map_err(Error::FailedToReadUserFile)?;
220
221 user_file
222 .close(virtual_file_system)
223 .await
224 .map_err(Error::FailedToCloseFile)?;
225
226 let user: User = miniserde::json::from_str(core::str::from_utf8(&buffer).unwrap())
227 .map_err(Error::FailedToParseUserFile)?;
228
229 let hashed_password =
230 hash_password(virtual_file_system, task, password, user.get_salt()).await?;
231
232 if hashed_password == user.get_hash() {
233 Ok(user.get_identifier())
234 } else {
235 Err(Error::InvalidPassword)
236 }
237}
238
239/// Creates a new user account with the specified parameters.
240///
241/// This function creates a new user in the system by:
242/// 1. Generating a new user identifier (if not provided)
243/// 2. Adding the user to the Users manager
244/// 3. Generating a random salt and hashing the password
245/// 4. Creating the user file with all user data
246///
247/// # Arguments
248///
249/// * `Virtual_file_system` - Reference to the virtual file system
250/// * `User_name` - Username for the new account
251/// * `Password` - Plain text password for the new account
252/// * `Primary_group` - Primary group identifier for the user
253/// * `User_identifier` - Optional specific user identifier (auto-generated if None)
254///
255/// # Returns
256///
257/// Returns `Ok(UserIdentifier)` with the new user's identifier,
258/// or an appropriate error if creation fails.
259///
260/// # Errors
261///
262/// This function can fail for various reasons including:
263/// - User identifier generation or assignment failures
264/// - File system operations (directory creation, file writing)
265/// - Users manager operations (adding user)
266/// - Random salt generation failures
267pub async fn create_user(
268 context: &Context<'_>,
269 user_name: &str,
270 password: &str,
271 primary_group: GroupIdentifier,
272 user_identifier: Option<UserIdentifier>,
273) -> Result<UserIdentifier> {
274 let virtual_file_system = context.virtual_file_system;
275 let users_manager = context.users_manager;
276
277 // - New user identifier if not provided.
278 let user_identifier = if let Some(user_identifier) = user_identifier {
279 user_identifier
280 } else {
281 users_manager
282 .get_new_user_identifier()
283 .await
284 .map_err(Error::FailedToGetNewUserIdentifier)?
285 };
286
287 // - Add it to the users manager.
288 users_manager
289 .add_user(user_identifier, user_name, primary_group)
290 .await
291 .map_err(Error::FailedToCreateUser)?;
292
293 // - Hash password.
294 let task = context.task;
295
296 let salt = generate_salt(virtual_file_system, task).await?;
297
298 let hash = hash_password(virtual_file_system, task, password, &salt).await?;
299
300 // - Write user file.
301 let user = User::new(
302 user_identifier.as_u16(),
303 user_name.to_string(),
304 primary_group.as_u16(),
305 hash,
306 salt,
307 );
308
309 let task = context.task;
310
311 match Directory::create(virtual_file_system, task, USERS_FOLDER_PATH).await {
312 Ok(_) | Err(virtual_file_system::Error::AlreadyExists) => {}
313 Err(error) => Err(Error::FailedToCreateUsersDirectory(error))?,
314 }
315
316 let user_file_path = Path::new(USERS_FOLDER_PATH)
317 .to_owned()
318 .append(user_name)
319 .ok_or(Error::FailedToGetUserFilePath)?;
320
321 let user_json = miniserde::json::to_string(&user);
322
323 File::write_to_path(
324 virtual_file_system,
325 task,
326 &user_file_path,
327 user_json.as_bytes(),
328 )
329 .await
330 .map_err(Error::FailedToWriteUserFile)?;
331
332 Ok(user_identifier)
333}
334
335/// Changes a user's password by generating a new salt and hash.
336///
337/// This function updates a user's password by:
338/// 1. Generating a new random salt
339/// 2. Hashing the new password with the salt
340/// 3. Reading the existing user file
341/// 4. Updating the hash and salt fields
342/// 5. Writing the updated data back to the file
343///
344/// # Arguments
345///
346/// * `Virtual_file_system` - Reference to the virtual file system
347/// * `User_name` - Username of the account to update
348/// * `New_password` - New plain text password
349///
350/// # Returns
351///
352/// Returns `Ok(())` if the password was changed successfully,
353/// or an appropriate error if the operation fails.
354///
355/// # Errors
356///
357/// - File system errors (opening, reading, writing user file)
358/// - Salt generation failures
359/// - JSON parsing errors
360pub async fn change_user_password(
361 context: &Context<'_>,
362 user_name: &str,
363 new_password: &str,
364) -> Result<()> {
365 let virtual_file_system = context.virtual_file_system;
366 let task = context.task;
367
368 let salt = generate_salt(virtual_file_system, task).await?;
369
370 let hash = hash_password(virtual_file_system, task, new_password, &salt).await?;
371
372 let user_file_path = Path::new(USERS_FOLDER_PATH)
373 .to_owned()
374 .append(user_name)
375 .ok_or(Error::FailedToGetUserFilePath)?;
376
377 let mut buffer = Vec::new();
378
379 File::read_from_path(virtual_file_system, task, &user_file_path, &mut buffer)
380 .await
381 .map_err(Error::FailedToReadUserFile)?;
382
383 let mut user: User =
384 miniserde::json::from_str(core::str::from_utf8(buffer.as_slice()).unwrap())
385 .map_err(Error::FailedToParseUserFile)?;
386
387 user.set_hash(hash);
388 user.set_salt(salt);
389
390 let user_json = miniserde::json::to_string(&user);
391
392 File::write_to_path(
393 virtual_file_system,
394 task,
395 &user_file_path,
396 user_json.as_bytes(),
397 )
398 .await
399 .map_err(Error::FailedToWriteUserFile)?;
400
401 Ok(())
402}
403
404/// Changes a user's username by updating their user file.
405///
406/// This function reads the user's existing data, updates the name field,
407/// and writes the modified data back to the file system.
408///
409/// # Arguments
410///
411/// * `Virtual_file_system` - Reference to the virtual file system
412/// * `Current_name` - Current username of the account
413/// * `New_name` - New username to assign
414///
415/// # Returns
416///
417/// Returns `Ok(())` if the username was changed successfully,
418/// or an appropriate error if the operation fails.
419///
420/// # Errors
421///
422/// - File system errors (opening, reading, writing user file)
423/// - JSON parsing errors
424/// - Path construction failures
425pub async fn change_user_name(
426 context: &Context<'_>,
427 current_name: &str,
428 new_name: &str,
429) -> Result<()> {
430 let virtual_file_system = context.virtual_file_system;
431 let file_path = get_user_file_path(current_name)?;
432
433 let mut buffer = Vec::new();
434
435 File::read_from_path(virtual_file_system, context.task, &file_path, &mut buffer)
436 .await
437 .map_err(Error::FailedToReadUserFile)?;
438
439 let mut user: User =
440 miniserde::json::from_str(core::str::from_utf8(buffer.as_slice()).unwrap())
441 .map_err(Error::FailedToParseUserFile)?;
442
443 user.set_name(new_name.to_string());
444
445 let user_json = miniserde::json::to_string(&user);
446
447 File::write_to_path(
448 virtual_file_system,
449 context.task,
450 get_user_file_path(new_name)?,
451 user_json.as_bytes(),
452 )
453 .await
454 .map_err(Error::FailedToWriteUserFile)?;
455
456 Ok(())
457}
458
459/// Reads and parses a user file from the filesystem.
460///
461/// This function is used internally to load user data from JSON files.
462/// It reads the file contents into the provided buffer and deserializes
463/// the JSON data into a `User_type` structure.
464///
465/// # Arguments
466///
467/// * `Virtual_file_system` - Reference to the virtual file system
468/// * `Buffer` - Mutable buffer to use for reading file contents
469/// * `File` - Name of the user file to read
470///
471/// # Returns
472///
473/// Returns `Ok(User_type)` with the parsed user data,
474/// or an appropriate error if reading or parsing fails.
475///
476/// # Errors
477///
478/// - Path construction failures
479/// - File system errors (opening, reading)
480/// - JSON parsing errors
481pub async fn read_user_file(
482 context: &Context<'_>,
483 buffer: &mut Vec<u8>,
484 file: &str,
485) -> Result<User> {
486 let user_file_path = get_user_file_path(file)?;
487
488 let virtual_file_system = context.virtual_file_system;
489 let task = context.task;
490
491 File::read_from_path(virtual_file_system, task, &user_file_path, buffer)
492 .await
493 .map_err(Error::FailedToReadUserFile)?;
494
495 miniserde::json::from_str(core::str::from_utf8(buffer.as_slice()).unwrap())
496 .map_err(Error::FailedToParseUserFile)
497}