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")
}
}