use bcrypt::{self, BcryptResult}; use mysql_async::{Pool}; use mysql_async::error::Error as SqlError; use crate::routes; use db::{member::Member, common::FromDB}; use db::Response; // used when we create a new users for the first time #[derive(Debug)] pub enum AuthReason { Good, //passed regular check OpenAuth, // route does not require auth NoKey, // key missing BadKey, // key is bad } fn valid_secret(given_pass: &str, hash: &str) -> bool { let result = bcrypt::verify(given_pass, hash); return match result { Ok(result) => result, Err(e) => { eprintln!("{}", e); return false; } } } fn valid_perms(member: Member, path: &str) -> bool { use crate::perms; // if there are perms on the current path make sure the user has them if let Some(p) = perms::get_perm_mask(path) { return (p & member.permissions) == p; } // if no perms then we don't care return true; } pub fn generate_secret() -> String { /* * Generates a url-safe-plaintext secret for our db * */ use getrandom::getrandom; use base64::{encode_config, URL_SAFE}; let mut buf: Vec = vec![0;64]; getrandom(&mut buf).unwrap(); encode_config(buf,URL_SAFE) } pub fn encrypt_secret(raw: &str) -> BcryptResult { const BCRYPT_COST: u32 = 14; return bcrypt::hash(raw, BCRYPT_COST); } pub async fn wall_entry(path: &str, pool: &Pool, params: &serde_json::Value) -> Result { use std::borrow::Cow; // Dont need to auth if it's not required if routes::is_open(path) { Ok(AuthReason::OpenAuth) } else { // make sure we have some legit parameter to use match (params.get("id"), params.get("secret")) { /* * If we apparantly have user data then check for validity in credentials */ (Some(id_v), Some(secret_v)) => { /* unwrapping because i couldn't care less about poorly formatted request data */ let id = id_v.as_u64().unwrap_or(0); // basically nobody is allowed to have 0 as its supposed to be reserved let secret = secret_v.as_str().unwrap_or(""); return match Member::get(pool, id).await { Response::Row(user) => { if valid_secret(secret, &user.secret) && valid_perms(user, path){ Ok(AuthReason::Good) } else { Ok(AuthReason::BadKey) } }, Response::Empty => Ok(AuthReason::BadKey), Response::Other(err) => Err(SqlError::Other(Cow::from(err))), _ => Err(SqlError::Other(Cow::from("Undefined result"))) } }, _ => { Ok(AuthReason::NoKey) } } } } #[cfg(test)] mod auth_tests { use crate::testing::get_pool; use serde_json::Value; use mysql_async::prelude::Queryable; #[tokio::test] async fn missing_key() { let pool = get_pool(); let conn = pool.get_conn().await.unwrap(); let conn = conn.drop_exec( r#"INSERT INTO members (id, secret, name, joindate, status,permissions) VALUES(1, "abc", "bsname", 1,0,0) "#, ()).await.unwrap(); let params: Value = serde_json::from_str(r#" { "id": 1 } "#).unwrap(); let result = super::wall_entry("/channels/list", &pool, ¶ms).await; let _ = conn.drop_exec(r#"DELETE FROM members WHERE secret = "abc""#,()).await; assert_eq!(true, result.is_ok()); } #[test] fn validity_check() { use bcrypt::{hash, DEFAULT_COST}; let plain = super::generate_secret(); match hash(&plain, DEFAULT_COST) { Ok(hash) => assert_eq!(super::valid_secret(&plain, &hash), true), Err(err) => panic!("{}", err) } } }