236 lines
5.2 KiB
Rust
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)
|
|
}
|
|
}
|