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, VirtualFileSystem};
22
23use crate::{
24    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    virtual_file_system: &VirtualFileSystem,
202    user_name: &str,
203    password: &str,
204) -> Result<UserIdentifier> {
205    let path = get_user_file_path(user_name)?;
206
207    let task = task::get_instance().get_current_task_identifier().await;
208
209    let mut user_file = File::open(virtual_file_system, task, path, AccessFlags::Read.into())
210        .await
211        .map_err(Error::FailedToOpenUserFile)?;
212
213    let mut buffer = Vec::new();
214
215    user_file
216        .read_to_end(&mut buffer, READ_CHUNK_SIZE)
217        .await
218        .map_err(Error::FailedToReadUserFile)?;
219
220    user_file
221        .close(virtual_file_system)
222        .await
223        .map_err(Error::FailedToCloseFile)?;
224
225    let user: User = miniserde::json::from_str(core::str::from_utf8(&buffer).unwrap())
226        .map_err(Error::FailedToParseUserFile)?;
227
228    let hashed_password =
229        hash_password(virtual_file_system, task, password, user.get_salt()).await?;
230
231    if hashed_password == user.get_hash() {
232        Ok(user.get_identifier())
233    } else {
234        Err(Error::InvalidPassword)
235    }
236}
237
238/// Creates a new user account with the specified parameters.
239///
240/// This function creates a new user in the system by:
241/// 1. Generating a new user identifier (if not provided)
242/// 2. Adding the user to the Users manager
243/// 3. Generating a random salt and hashing the password
244/// 4. Creating the user file with all user data
245///
246/// # Arguments
247///
248/// * `Virtual_file_system` - Reference to the virtual file system
249/// * `User_name` - Username for the new account
250/// * `Password` - Plain text password for the new account
251/// * `Primary_group` - Primary group identifier for the user
252/// * `User_identifier` - Optional specific user identifier (auto-generated if None)
253///
254/// # Returns
255///
256/// Returns `Ok(UserIdentifier)` with the new user's identifier,
257/// or an appropriate error if creation fails.
258///
259/// # Errors
260///
261/// This function can fail for various reasons including:
262/// - User identifier generation or assignment failures
263/// - File system operations (directory creation, file writing)
264/// - Users manager operations (adding user)
265/// - Random salt generation failures
266pub async fn create_user(
267    virtual_file_system: &VirtualFileSystem,
268    user_name: &str,
269    password: &str,
270    primary_group: GroupIdentifier,
271    user_identifier: Option<UserIdentifier>,
272) -> Result<UserIdentifier> {
273    let users_manager = users::get_instance();
274
275    // - New user identifier if not provided.
276    let user_identifier = if let Some(user_identifier) = user_identifier {
277        user_identifier
278    } else {
279        users_manager
280            .get_new_user_identifier()
281            .await
282            .map_err(Error::FailedToGetNewUserIdentifier)?
283    };
284
285    // - Add it to the users manager.
286    users_manager
287        .add_user(user_identifier, user_name, primary_group)
288        .await
289        .map_err(Error::FailedToCreateUser)?;
290
291    // - Hash password.
292    let task = task::get_instance().get_current_task_identifier().await;
293
294    let salt = generate_salt(virtual_file_system, task).await?;
295
296    let hash = hash_password(virtual_file_system, task, password, &salt).await?;
297
298    // - Write user file.
299    let user = User::new(
300        user_identifier.as_u16(),
301        user_name.to_string(),
302        primary_group.as_u16(),
303        hash,
304        salt,
305    );
306
307    let task = task::get_instance().get_current_task_identifier().await;
308
309    match Directory::create(virtual_file_system, task, USERS_FOLDER_PATH).await {
310        Ok(_) | Err(virtual_file_system::Error::AlreadyExists) => {}
311        Err(error) => Err(Error::FailedToCreateUsersDirectory(error))?,
312    }
313
314    let user_file_path = Path::new(USERS_FOLDER_PATH)
315        .to_owned()
316        .append(user_name)
317        .ok_or(Error::FailedToGetUserFilePath)?;
318
319    let user_json = miniserde::json::to_string(&user);
320
321    File::write_to_path(
322        virtual_file_system,
323        task,
324        &user_file_path,
325        user_json.as_bytes(),
326    )
327    .await
328    .map_err(Error::FailedToWriteUserFile)?;
329
330    Ok(user_identifier)
331}
332
333/// Changes a user's password by generating a new salt and hash.
334///
335/// This function updates a user's password by:
336/// 1. Generating a new random salt
337/// 2. Hashing the new password with the salt
338/// 3. Reading the existing user file
339/// 4. Updating the hash and salt fields
340/// 5. Writing the updated data back to the file
341///
342/// # Arguments
343///
344/// * `Virtual_file_system` - Reference to the virtual file system
345/// * `User_name` - Username of the account to update
346/// * `New_password` - New plain text password
347///
348/// # Returns
349///
350/// Returns `Ok(())` if the password was changed successfully,
351/// or an appropriate error if the operation fails.
352///
353/// # Errors
354///
355/// - File system errors (opening, reading, writing user file)
356/// - Salt generation failures
357/// - JSON parsing errors
358pub async fn change_user_password(
359    virtual_file_system: &VirtualFileSystem,
360    user_name: &str,
361    new_password: &str,
362) -> Result<()> {
363    let task = task::get_instance().get_current_task_identifier().await;
364
365    let salt = generate_salt(virtual_file_system, task).await?;
366
367    let hash = hash_password(virtual_file_system, task, new_password, &salt).await?;
368
369    let user_file_path = Path::new(USERS_FOLDER_PATH)
370        .to_owned()
371        .append(user_name)
372        .ok_or(Error::FailedToGetUserFilePath)?;
373
374    let mut buffer = Vec::new();
375
376    File::read_from_path(virtual_file_system, task, &user_file_path, &mut buffer)
377        .await
378        .map_err(Error::FailedToReadUserFile)?;
379
380    let mut user: User =
381        miniserde::json::from_str(core::str::from_utf8(buffer.as_slice()).unwrap())
382            .map_err(Error::FailedToParseUserFile)?;
383
384    user.set_hash(hash);
385    user.set_salt(salt);
386
387    let user_json = miniserde::json::to_string(&user);
388
389    File::write_to_path(
390        virtual_file_system,
391        task,
392        &user_file_path,
393        user_json.as_bytes(),
394    )
395    .await
396    .map_err(Error::FailedToWriteUserFile)?;
397
398    Ok(())
399}
400
401/// Changes a user's username by updating their user file.
402///
403/// This function reads the user's existing data, updates the name field,
404/// and writes the modified data back to the file system.
405///
406/// # Arguments
407///
408/// * `Virtual_file_system` - Reference to the virtual file system
409/// * `Current_name` - Current username of the account
410/// * `New_name` - New username to assign
411///
412/// # Returns
413///
414/// Returns `Ok(())` if the username was changed successfully,
415/// or an appropriate error if the operation fails.
416///
417/// # Errors
418///
419/// - File system errors (opening, reading, writing user file)
420/// - JSON parsing errors
421/// - Path construction failures
422pub async fn change_user_name(
423    virtual_file_system: &VirtualFileSystem,
424    current_name: &str,
425    new_name: &str,
426) -> Result<()> {
427    let file_path = get_user_file_path(current_name)?;
428
429    let mut buffer = Vec::new();
430
431    File::read_from_path(
432        virtual_file_system,
433        task::get_instance().get_current_task_identifier().await,
434        &file_path,
435        &mut buffer,
436    )
437    .await
438    .map_err(Error::FailedToReadUserFile)?;
439
440    let mut user: User =
441        miniserde::json::from_str(core::str::from_utf8(buffer.as_slice()).unwrap())
442            .map_err(Error::FailedToParseUserFile)?;
443
444    user.set_name(new_name.to_string());
445
446    let user_json = miniserde::json::to_string(&user);
447
448    File::write_to_path(
449        virtual_file_system,
450        task::get_instance().get_current_task_identifier().await,
451        get_user_file_path(new_name)?,
452        user_json.as_bytes(),
453    )
454    .await
455    .map_err(Error::FailedToWriteUserFile)?;
456
457    Ok(())
458}
459
460/// Reads and parses a user file from the filesystem.
461///
462/// This function is used internally to load user data from JSON files.
463/// It reads the file contents into the provided buffer and deserializes
464/// the JSON data into a `User_type` structure.
465///
466/// # Arguments
467///
468/// * `Virtual_file_system` - Reference to the virtual file system
469/// * `Buffer` - Mutable buffer to use for reading file contents
470/// * `File` - Name of the user file to read
471///
472/// # Returns
473///
474/// Returns `Ok(User_type)` with the parsed user data,
475/// or an appropriate error if reading or parsing fails.
476///
477/// # Errors
478///
479/// - Path construction failures
480/// - File system errors (opening, reading)
481/// - JSON parsing errors
482pub async fn read_user_file(
483    virtual_file_system: &VirtualFileSystem,
484    buffer: &mut Vec<u8>,
485    file: &str,
486) -> Result<User> {
487    let user_file_path = get_user_file_path(file)?;
488
489    let task = task::get_instance().get_current_task_identifier().await;
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}