mnml/server/src/account.rs
2019-05-21 11:34:00 +10:00

216 lines
5.0 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, 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<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"));
}
if params.name.len() == 0 {
return Err(err_msg("account name not supplied"));
}
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),
};
info!("registration account={:?}", 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"));
}
info!("login account={:?}", 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<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) => 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<Vec<Instance>, 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<u8> = row.get(0);
let id = row.get(1);
match from_slice::<Instance>(&bytes) {
Ok(i) => list.push(i),
Err(_e) => {
instance_delete(tx, id)?;
}
};
}
list.sort_unstable_by_key(|c| c.name.clone());
return Ok(list);
}