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 item::{Item, ItemAction, item_create}; use cryp::Cryp; use game::Game; 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 password = hash(¶ms.password, 4)?; 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!("{:?}", entry); // give them a revive let revive = Item::new(ItemAction::Revive, &entry); item_create(revive, tx, &entry)?; 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); from_slice::(&cryp_bytes) }).collect(); // catch any errors if cryps.is_err() { return Err(err_msg("could not deserialize a cryp")); } // now unwrap is safe return Ok(cryps.unwrap()); } pub fn account_game_history(tx: &mut Transaction, account: &Account) -> Result, Error> { let query = " SELECT games.data FROM players join games ON (players.game = games.id) WHERE account = $1; "; let result = tx .query(query, &[&account.id])?; let games: Result, _> = result.iter().map(|row| { let cryp_bytes: Vec = row.get(0); from_slice::(&cryp_bytes) }).collect(); // catch any errors if games.is_err() { return Err(err_msg("could not deserialize a game")); } // now unwrap is safe return Ok(games.unwrap()); }