Boarders/v1/src/lib/users.rs

236 lines
5.2 KiB
Rust

use std::{
result::Result,
iter,
path::Path,
fmt::Display
};
use serde::{Serialize, Deserialize};
use bcrypt_bsd::{gen_salt, hash, to_str};
use rand::{self, Rng};
use super::Archiveable;
pub trait Account {
fn encrypt(&self, password: String) -> CryptoUser;
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct User {
pub user_id: u32,
pub nick: Option<String>,
pub username: String,
}
impl User {
pub fn new<S>(id: u32, username: S) -> User where S: AsRef<str> {
User {
user_id: id,
nick: None,
username: username.as_ref().to_string(),
}
}
}
impl Account for User {
fn encrypt(&self, mut password: String) -> CryptoUser {
let new_salt = gen_salt(12).unwrap();
#[allow(unused_assignments)]
CryptoUser {
user_id: self.user_id,
nick: self.nick.clone(),
username: self.username.clone(),
pwh: {
let s = to_str(&hash(&password, &new_salt).unwrap()).unwrap().to_string();
password = "\0".repeat(password.len());
s
},
salt: to_str(&new_salt).unwrap().to_string(),
secret: CryptoUser::gen_secret()
}
}
}
impl From<CryptoUser> for User {
fn from(item: CryptoUser) -> Self {
User {
user_id: item.user_id,
nick: item.nick.clone(),
username: item.username
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CryptoUser {
pub user_id: u32,
nick: Option<String>,
pub username: String,
pwh: String,
salt: String,
pub secret: String
}
impl CryptoUser {
pub fn gen_secret() -> String {
let mut rng = rand::thread_rng();
let s: String = iter::repeat(())
.map(|()| rng.sample(rand::distributions::Alphanumeric))
.map(char::from)
.take(32)
.collect();
s
}
#[allow(unused_assignments)]
pub fn verify(&self, mut password: String) -> Result<bool, bcrypt_bsd::CryptError> {
let h = hash(
&password,
std::ffi::CString::new(self.salt.clone()).unwrap().as_bytes_with_nul()
)?;
let new_hash = to_str(&h).unwrap();
password = "\0".repeat(password.len());
Ok(new_hash == self.pwh)
}
}
impl Account for CryptoUser {
fn encrypt(&self, mut password: String) -> CryptoUser {
let new_salt = gen_salt(16).unwrap();
#[allow(unused_assignments)]
CryptoUser {
user_id: self.user_id,
nick: self.nick.clone(),
username: self.username.clone(),
pwh: {
let s = to_str(&hash(&password, &new_salt).unwrap()).unwrap().to_string();
password = "\0".repeat(password.len());
s
},
salt: to_str(&new_salt).unwrap().to_string(),
secret: self.secret.clone()
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserManager {
pub next_id: u32,
pub(in super) data_dir: String,
pub users: Vec<CryptoUser>
}
impl UserManager {
pub fn new<P>(path: P) -> UserManager where P: AsRef<Path> {
UserManager {
next_id: 0,
data_dir: path.as_ref().to_str().unwrap().to_string(),
users: Vec::new()
}
}
pub fn assign_id(&mut self) -> u32 {
let id = self.next_id;
self.next_id += 1;
id
}
pub fn add_user(&mut self, user: CryptoUser) {
self.users.push(user);
}
pub fn get_user_by_id(&self, uid: u32) -> Option<CryptoUser> {
self.users.clone().into_iter().find(|a| a.user_id == uid)
}
pub fn get_user_by_nick(&self, nick: String) -> Option<CryptoUser> {
self.users.clone().into_iter().find(|a| {
if let Some(an) = a.nick.clone() {
an == nick
} else {
false
}
})
}
pub fn get_user_by_uname(&self, username: String) -> Option<CryptoUser> {
self.users.clone().into_iter().find(|a| a.username == username)
}
}
impl Archiveable<Self> for UserManager {
fn get_root(&self) -> &Path {
Path::new(&self.data_dir)
}
}
#[derive(Debug)]
pub struct UserCookie {
pub id: u32,
pub secret: String
}
#[derive(Debug)]
pub struct UserCookieParseError {
msg: String
}
impl UserCookieParseError {
pub fn new<S>(s: S) -> UserCookieParseError where S: AsRef<str> {
UserCookieParseError {
msg: s.as_ref().to_string()
}
}
}
impl From<&'static str> for UserCookieParseError {
fn from(s: &'static str) -> UserCookieParseError {
UserCookieParseError::new(s)
}
}
impl From<std::num::ParseIntError> for UserCookieParseError {
fn from(err: std::num::ParseIntError) -> UserCookieParseError {
UserCookieParseError::new(format!("{}", err))
}
}
impl From<UserCookieParseError> for String {
fn from(s: UserCookieParseError) -> String {
s.into()
}
}
impl Display for UserCookieParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.msg)
}
}
impl UserCookie {
/*
* Gross amounts of error handling because
* we don't want a poisoned mutex if someone
* decided to mess with cookie data
*/
pub fn parse<S>(s: S) -> Result<UserCookie, UserCookieParseError>
where
S: AsRef<str>
{
let s = s.as_ref().to_string();
let mut ss = s.splitn(2, '&');
Ok(UserCookie::new(
ss.next().ok_or_else(|| UserCookieParseError::new("Not enough fields"))?.parse::<u32>()?,
ss.next().ok_or_else(|| UserCookieParseError::new("Not enough fields"))?.to_string(),
))
}
pub fn new<S>(i: u32, s: S) -> UserCookie where S: AsRef<str> {
UserCookie {
id: i,
secret: s.as_ref().to_string()
}
}
}
impl Display for UserCookie {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}&{}", self.id, self.secret)
}
}