Changeset View
Changeset View
Standalone View
Standalone View
source/tools/rdb/src/account_actions.rs
//! The handler for actions relating to user acounts. | |||||
use diesel::prelude::*; | |||||
use rand::RngCore; | |||||
use subtle::ConstantTimeEq; | |||||
use tokio::prelude::*; | |||||
use crate::models::{NewUser, User, UserPrivilegeLevel, UserId}; | |||||
use crate::{ip_tracker::IPTracker, ActionRetType, WriterType, DbConn}; | |||||
use crate::connection::{ConnState, Msg, PublicUser}; | |||||
pub struct AccountActions(); | |||||
impl AccountActions { | |||||
fn failure(msg: &str, writer: &mut WriterType, for_register: bool) -> ActionRetType { | |||||
writer.start_send(Msg::Error { | |||||
msg: format!( | |||||
"Failed to {}, invalid username/password.{}", | |||||
if for_register { "register" } else { "login" }, | |||||
msg | |||||
), | |||||
})?; | |||||
Ok(()) | |||||
} | |||||
fn verify( | |||||
version: u64, | |||||
_username: &str, | |||||
password: &str, | |||||
writer: &mut WriterType, | |||||
for_register: bool, | |||||
) -> Result<(), ActionRetType> { | |||||
if version != 1 { | |||||
return Err(Self::failure( | |||||
" Client version unsupported.", | |||||
writer, | |||||
for_register, | |||||
)); | |||||
} | |||||
if password.len() < 8 { | |||||
return Err(Self::failure(" Password too short.", writer, for_register)); | |||||
} | |||||
Ok(()) | |||||
} | |||||
pub fn handle_register( | |||||
conn_state: &mut ConnState, | |||||
writer: &mut WriterType, | |||||
data: &Msg, | |||||
db_conn: &DbConn, | |||||
) -> ActionRetType { | |||||
if let Msg::Register { | |||||
ref username, | |||||
ref password, | |||||
version, | |||||
} = data { | |||||
if let Err(ret) = Self::verify(*version, username, password, writer, true) { | |||||
return ret; | |||||
} | |||||
use crate::schema::ddb_users::dsl::*; | |||||
let res_users = ddb_users | |||||
.filter(ddb_username.eq(username)) | |||||
.load::<User>(db_conn) | |||||
.unwrap(); | |||||
assert!(res_users.len() <= 1); | |||||
if res_users.len() == 1 { | |||||
return Self::failure(" Username already taken.", writer, true); | |||||
} | |||||
if !IPTracker::can_register(db_conn, &conn_state.peer_ip) { | |||||
return Self::failure(" Access denied.", writer, true); | |||||
} | |||||
let mut salt = [0u8; 32]; | |||||
rand::thread_rng().fill_bytes(&mut salt); | |||||
let mut password = password.clone().into_bytes(); | |||||
password.extend_from_slice(dotenv::var("PEPPER").unwrap().as_bytes()); | |||||
let scrypt_log_n = dotenv::var("SCRYPT_LOG_N").unwrap().parse().unwrap(); | |||||
let scrypt_r = dotenv::var("SCRYPT_R").unwrap().parse().unwrap(); | |||||
let scrypt_p = dotenv::var("SCRYPT_P").unwrap().parse().unwrap(); | |||||
let scrypt_settings = | |||||
scrypt::ScryptParams::new(scrypt_log_n, scrypt_r as _, scrypt_p as _).unwrap(); | |||||
let mut password_hashed = vec![]; | |||||
password_hashed.resize(512, 0); | |||||
scrypt::scrypt(&password, &salt, &scrypt_settings, &mut password_hashed).unwrap(); | |||||
let nuser = NewUser { | |||||
ddb_username: &username, | |||||
ddb_salt: &salt, | |||||
ddb_password_hashed: &password_hashed, | |||||
ddb_privilege_level: UserPrivilegeLevel::Standard, | |||||
ddb_scrypt_log_n: &[scrypt_log_n], | |||||
ddb_scrypt_r: &[scrypt_r], | |||||
ddb_scrypt_p: &[scrypt_p], | |||||
}; | |||||
diesel::insert_into(ddb_users) | |||||
.values(&nuser) | |||||
.execute(db_conn) | |||||
.unwrap(); | |||||
IPTracker::add_register(db_conn, &conn_state.peer_ip, &username); | |||||
let user_id = ddb_users | |||||
.filter(ddb_username.eq(username)) | |||||
.select(ddb_id) | |||||
.load::<UserId>(db_conn) | |||||
.unwrap(); | |||||
assert_eq!(res_users.len(), 1); | |||||
conn_state.username = Some(username.clone()); | |||||
conn_state.user_id = Some(user_id[0]); | |||||
writer.start_send(Msg::Success)?; | |||||
} else { | |||||
panic!() | |||||
} | |||||
Ok(()) | |||||
} | |||||
pub fn handle_login( | |||||
conn_state: &mut ConnState, | |||||
writer: &mut WriterType, | |||||
data: &Msg, | |||||
db_conn: &DbConn, | |||||
) -> ActionRetType { | |||||
if let Msg::Login { | |||||
ref username, | |||||
ref password, | |||||
version, | |||||
} = data { | |||||
if let Err(ret) = Self::verify(*version, username, password, writer, false) { | |||||
return ret; | |||||
} | |||||
use crate::schema::ddb_users::dsl::*; | |||||
let res_users = ddb_users | |||||
.filter(ddb_username.eq(username)) | |||||
.load::<User>(db_conn) | |||||
.unwrap(); | |||||
assert!(res_users.len() <= 1); | |||||
if res_users.len() == 0 { | |||||
return Self::failure(" Access denied.", writer, false); | |||||
} | |||||
let res_user = &res_users[0]; | |||||
let mut password = password.clone().into_bytes(); | |||||
password.extend_from_slice(dotenv::var("PEPPER").unwrap().as_bytes()); | |||||
let scrypt_settings = scrypt::ScryptParams::new( | |||||
res_user.ddb_scrypt_log_n[0], | |||||
res_user.ddb_scrypt_r[0] as _, | |||||
res_user.ddb_scrypt_p[0] as _, | |||||
) | |||||
.unwrap(); | |||||
let mut password_hashed = vec![]; | |||||
password_hashed.resize(512, 0); | |||||
scrypt::scrypt( | |||||
&password, | |||||
&res_user.ddb_salt, | |||||
&scrypt_settings, | |||||
&mut password_hashed, | |||||
) | |||||
.unwrap(); | |||||
if password_hashed | |||||
.ct_eq(&res_user.ddb_password_hashed) | |||||
.unwrap_u8() | |||||
!= 1 | |||||
{ | |||||
return Self::failure(" Access denied.", writer, false); | |||||
} else { | |||||
conn_state.username = Some(username.clone()); | |||||
conn_state.user_id = Some(res_user.ddb_id); | |||||
writer.start_send(Msg::Success)?; | |||||
} | |||||
} else { | |||||
panic!() | |||||
} | |||||
Ok(()) | |||||
} | |||||
pub fn handle_query_for_user_data( | |||||
_conn_state: &mut ConnState, | |||||
writer: &mut WriterType, | |||||
data: &Msg, | |||||
db_conn: &DbConn, | |||||
) -> ActionRetType { | |||||
if let Msg::QueryForUserData { | |||||
ref user_ids, | |||||
ref usernames, | |||||
} = data { | |||||
use crate::schema::ddb_users::dsl::*; | |||||
let res_users_id = ddb_users | |||||
.filter(ddb_id.eq_any(user_ids)) | |||||
.load::<User>(db_conn) | |||||
.unwrap(); | |||||
let res_users_username = ddb_users | |||||
.filter(ddb_username.eq_any(usernames)) | |||||
.load::<User>(db_conn) | |||||
.unwrap(); | |||||
let mut user_datas = res_users_id; | |||||
user_datas.extend(res_users_username); | |||||
let mut user_datas: Vec<_> = user_datas | |||||
.into_iter() | |||||
.map(|user_data| PublicUser { | |||||
id: user_data.ddb_id, | |||||
username: user_data.ddb_username, | |||||
}) | |||||
.collect(); | |||||
user_datas.sort_unstable_by(|a, b| a.id.cmp(&b.id)); | |||||
user_datas.dedup_by(|a, b| a.id == b.id); | |||||
writer.start_send(Msg::UserDatas { user_datas })?; | |||||
writer.start_send(Msg::Success)?; | |||||
} else { | |||||
panic!() | |||||
} | |||||
Ok(()) | |||||
} | |||||
} |
Wildfire Games · Phabricator