lrau 0.2.0

LrAU is an authentication and permission management system for rust.
Documentation
use crate::Permissions;
use argon2::{Config, Variant};
use core::fmt;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "diesel-support")]
pub mod diesel_support;

/// The User struct stores information about an
/// individual login.
///
/// It support serde and so can be serialized to, for example, json or
/// toml. For diesel support you can convert it to a
/// [DieselUser](user::diesel_support::DieselUser), which intern supports
/// `Queryable`, `Insertable`, and `Identifiable`.
#[derive(Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct User {
    /// This is then username / id of the user
    pub username: String,
    /// This is the password of the user.
    ///
    /// This is stores hashed with Argon2id.
    pub password: String,
    pub permissions: Permissions,
    /// This stores information about wether the user
    /// is logged in or not.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub logged_in: Option<std::time::Instant>,
    /// This is how long until the user is expire.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub expire: Option<std::time::Duration>,
}

impl User {
    /// Creates a new user from an existing hash.
    pub fn new_basic(
        username: String,
        password: String,
        permissions: Permissions,
    ) -> Self {
        Self {
            username,
            password,
            permissions,
            logged_in: None,
            expire: None,
        }
    }
    /// Creates a new user. The field `password` is the unhashed password.
    /// After this is suplied the program will hash it using the Argon2id
    /// agorithm.
    ///
    /// The `username` field is any username which you wish to identify
    /// the user by.
    ///
    /// Permissions is a representation of the permissions for the user.
    pub fn new(
        username: String,
        password: String,
        permissions: Permissions,
    ) -> Self {
        // Turns the password into bytes
        let password = password.as_bytes();
        // DONE: Create a function to gen a salt
        let salt = crate::salt();
        let salt = salt.as_bytes();

        // Get an Argon2 config
        let config = Config {
            variant: Variant::Argon2id,
            ..Default::default()
        };

        // Encode the password
        let password = argon2::hash_encoded(password, salt, &config).unwrap();

        Self {
            username,
            password,
            permissions,
            logged_in: None,
            expire: None,
        }
    }

    /// Validates a users password
    pub fn validate(&self, password: &str) -> bool {
        argon2::verify_encoded(&self.password, password.as_bytes()).unwrap()
    }

    /// Logs a user in
    pub fn log_in(
        &mut self,
        password: &str,
        expire: std::time::Duration,
    ) -> bool {
        // Checks if we have validated thd password
        match self.validate(password) {
            true => {
                self.logged_in = Some(std::time::Instant::now());
                self.expire = Some(expire);
                true
            }
            false => false,
        }
    }

    /// Checks if the user is logged in.
    ///
    /// This does **not** check if the user has expired.
    pub fn check_login(&self) -> bool {
        // Checks if we have a login
        self.logged_in != None
    }

    /// Checks for a valid login. This makes sure the
    /// user is logged in *and* has a valid session.
    pub fn check_valid_login(&self) -> bool {
        // Checks we have a login and that we haven't expired
        self.logged_in != None
            && match self.expire {
                // If we have no expiry (should be impossible)
                // then return false
                None => false,
                // If not check if we have a logged in, again
                // if we have got here this _should_
                // be true.
                Some(expire) => match self.logged_in {
                    None => false,
                    Some(logged_in) => logged_in.elapsed() < expire,
                },
            }
    }

    /// Checks if a user has permissions to do something.
    ///
    /// Note this does **not** check for a valid session.
    pub fn get_permission(&self, path: &str, r#mut: bool) -> bool {
        self.permissions.get_permission(path, r#mut)
    }

    /// Checks if the user has permissions to do something.
    ///
    /// This function also checks if they have a valid
    /// session and will return an `Err` if they do not.
    pub fn get_valid_permissions(
        &self,
        path: &str,
        r#mut: bool,
    ) -> Result<bool, SessionExpired> {
        // Checks for a valid login
        if !self.check_valid_login() {
            return Err(SessionExpired {});
        }
        // If not return if we have the permissions.
        Ok(self.get_permission(path, r#mut))
    }
}

/// A Session Expired, in get_valid_permissions.
#[derive(Clone, Debug, Hash, PartialEq, Eq, Ord, PartialOrd)]
pub struct SessionExpired {}

impl fmt::Display for SessionExpired {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "session_expired")
    }
}