1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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")
    }
}