mnml/server/src/account.rs
2018-12-29 12:21:34 +11:00

252 lines
5.8 KiB
Rust

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, CrypRecover, cryp_write};
use game::Game;
use zone::{Zone, zone_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<Account, Error> {
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<Account, Error> {
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(&params.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<Account, Error> {
let query = "
SELECT id, name, token, password
FROM accounts
WHERE name = $1;
";
let result = tx
.query(query, &[&params.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(&params.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);
}
fn recover_cryp(cryp_bytes: Vec<u8>, tx: &mut Transaction) -> Result<Cryp, Error> {
let c = from_slice::<CrypRecover>(&cryp_bytes)?;
let mut cryp = Cryp::new()
.named(&c.name)
.level(c.lvl)
.set_account(c.account)
.create();
cryp.id = c.id;
println!("recovered cryp {:?}", c.name);
return cryp_write(cryp, tx);
}
pub fn account_cryps(tx: &mut Transaction, account: &Account) -> Result<Vec<Cryp>, Error> {
let query = "
SELECT data
FROM cryps
WHERE account = $1;
";
let result = tx
.query(query, &[&account.id])?;
let cryps: Result<Vec<Cryp>, _> = result.iter()
.map(|row| {
let cryp_bytes: Vec<u8> = row.get(0);
match from_slice::<Cryp>(&cryp_bytes) {
Ok(c) => Ok(c),
Err(_e) => recover_cryp(cryp_bytes, tx),
}
})
.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<Vec<Game>, 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<Vec<Game>, _> = result.iter().map(|row| {
let cryp_bytes: Vec<u8> = row.get(0);
from_slice::<Game>(&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());
}
pub fn account_zone(tx: &mut Transaction, account: &Account) -> Result<Zone, Error> {
let query = "
SELECT *
FROM zones
WHERE account = $1
AND active = true;
";
let result = tx
.query(query, &[&account.id])?;
let returned = match result.iter().next() {
Some(row) => row,
None => return Err(err_msg("no active zone")),
};
// tells from_slice to cast into a cryp
let bytes: Vec<u8> = returned.get("data");
let zone = match from_slice::<Zone>(&bytes) {
Ok(z) => z,
Err(_) => {
zone_delete(tx, returned.get("id"))?;
return Err(err_msg("invalid zone removed"))
},
};
return Ok(zone);
}