use uuid::Uuid; use bcrypt::{hash, verify}; use rand::{thread_rng, Rng}; use rand::distributions::Alphanumeric; use std::iter; use serde_cbor::{from_slice}; use postgres::transaction::Transaction; use rpc::{AccountCreateParams, AccountLoginParams}; use cryp::{Cryp, cryp_recover}; use instance::{Instance, instance_delete}; use failure::Error; use failure::err_msg; static PASSWORD_MIN_LEN: usize = 12; #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Account { pub id: Uuid, pub name: String, token: String, } #[derive(Debug,Clone,Serialize,Deserialize)] struct AccountEntry { id: Uuid, name: String, password: String, token: String, } // MAYBE // hash tokens with a secret pub fn account_from_token(token: String, tx: &mut Transaction) -> Result { let query = " SELECT id, name, token FROM accounts WHERE token = $1; "; let result = tx .query(query, &[&token])?; let returned = match result.iter().next() { Some(row) => row, None => return Err(err_msg("invalid token")), }; let entry = Account { id: returned.get(0), name: returned.get(1), token: returned.get(2), }; return Ok(entry); } pub fn account_create(params: AccountCreateParams, tx: &mut Transaction) -> Result { let id = Uuid::new_v4(); if params.password.len() < PASSWORD_MIN_LEN { return Err(err_msg("password must be at least 12 characters")); } let rounds = 8; let password = hash(¶ms.password, rounds)?; let mut rng = thread_rng(); let token: String = iter::repeat(()) .map(|()| rng.sample(Alphanumeric)) .take(64) .collect(); let account = AccountEntry { name: params.name, id, password, token, }; let query = " INSERT INTO accounts (id, name, password, token) VALUES ($1, $2, $3, $4) RETURNING id, name, token; "; let result = tx .query(query, &[&account.id, &account.name, &account.password, &account.token])?; let returned = result.iter().next().expect("no row returned"); let entry = Account { id: returned.get(0), name: returned.get(1), token: returned.get(2), }; println!("{:?} registered", entry.name); return Ok(entry); } pub fn account_login(params: AccountLoginParams, tx: &mut Transaction) -> Result { let query = " SELECT id, name, token, password FROM accounts WHERE name = $1; "; let result = tx .query(query, &[¶ms.name])?; let returned = match result.iter().next() { Some(row) => row, // MAYBE // verify gibberish to delay response for timing attacks None => return Err(err_msg("account not found")), }; let entry = AccountEntry { id: returned.get(0), name: returned.get(1), token: returned.get(2), password: returned.get(3), }; if !verify(¶ms.password, &entry.password)? { return Err(err_msg("password does not match")); } println!("{:?} logged in", entry.name); // MAYBE // update token? // don't necessarily want to log every session out when logging in let account = Account { id: entry.id, name: entry.name, token: entry.token, }; return Ok(account); } pub fn account_cryps(tx: &mut Transaction, account: &Account) -> Result, Error> { let query = " SELECT data FROM cryps WHERE account = $1; "; let result = tx .query(query, &[&account.id])?; let cryps: Result, _> = result.iter() .map(|row| { let cryp_bytes: Vec = row.get(0); match from_slice::(&cryp_bytes) { Ok(c) => Ok(c), Err(_e) => cryp_recover(cryp_bytes, tx), } }) .collect(); // catch any errors if cryps.is_err() { return Err(err_msg("could not deserialize a cryp")); } let mut cryps = cryps.unwrap(); cryps.sort_by_key(|c| c.id); return Ok(cryps); } pub fn account_instances(tx: &mut Transaction, account: &Account) -> Result, Error> { let query = " SELECT data, id FROM instances WHERE open = true OR id IN ( SELECT instance FROM players WHERE account = $1 ); "; let result = tx .query(query, &[&account.id])?; let mut list = vec![]; for row in result.into_iter() { let bytes: Vec = row.get(0); let id = row.get(1); match from_slice::(&bytes) { Ok(i) => list.push(i), Err(_e) => { instance_delete(tx, id)?; } }; } list.sort_unstable_by_key(|c| c.name.clone()); return Ok(list); }