token expiry"

"
This commit is contained in:
ntr 2019-06-16 15:06:43 +10:00
parent 2b06c83ea0
commit 9fcdbeb370
5 changed files with 57 additions and 50 deletions

View File

@ -10,8 +10,8 @@ const List = require('./list');
const addState = connect(
state => {
const { game, instance, account, nav, team } = state;
return { game, instance, account, nav, team };
const { game, instance, account, nav, team, constructs } = state;
return { game, instance, account, nav, team, constructs };
}
);
@ -22,6 +22,7 @@ function Main(props) {
account,
nav,
team,
constructs,
} = props;
if (!account) {
@ -36,8 +37,8 @@ function Main(props) {
return <Instance />;
}
if (nav === 'team' || !team.some(t => t) || constructs.length < 3) return <Team />;
if (nav === 'list') return <List />;
if (nav === 'team' || !team.some(t => t)) return <Team />;
return (
<main></main>

View File

@ -4,7 +4,8 @@ exports.up = async knex => {
table.timestamps(true, true);
table.string('name', 42).notNullable().unique();
table.string('password').notNullable();
table.string('token', 64).notNullable();
table.string('token', 64);
table.timestamp('token_expiry');
table.index('name');
table.index('id');

View File

@ -29,13 +29,12 @@ struct AccountEntry {
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;
WHERE token = $1
AND token_expiry > now();
";
let result = tx
@ -71,78 +70,93 @@ pub fn account_create(name: &String, password: &String, code: &String, tx: &mut
let rounds = 8;
let password = hash(&password, rounds)?;
let mut rng = thread_rng();
let token: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.take(64)
.collect();
let query = "
INSERT INTO accounts (id, name, password, token)
VALUES ($1, $2, $3, $4)
RETURNING id, name, token;
INSERT INTO accounts (id, name, password)
VALUES ($1, $2, $3)
RETURNING id, name;
";
let result = tx
.query(query, &[&id, &name, &password, &token])?;
.query(query, &[&id, &name, &password])?;
if result.is_empty() {
return Err(err_msg("no row returned"));
}
let returned = match result.iter().next() {
Some(row) => row,
None => return Err(err_msg("account not created")),
};
info!("registration account={:?}", name);
return Ok(token);
let account = Account {
id: returned.get(0),
name: returned.get(1),
};
account_set_token(tx, &account)
}
pub fn account_login(name: &String, password: &String, tx: &mut Transaction) -> Result<String, Error> {
let query = "
SELECT id, password
SELECT id, password, name
FROM accounts
WHERE name = $1;
WHERE name = $1
RETURNING id, name;
";
let result = tx
.query(query, &[&name])?;
let mut rng = thread_rng();
let token: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.take(64)
.collect();
let returned = match result.iter().next() {
Some(row) => row,
None => {
let mut rng = thread_rng();
let garbage: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.take(64)
.collect();
// verify garbage to prevent timing attacks
verify(token.clone(), &token).ok();
verify(garbage.clone(), &garbage).ok();
return Err(err_msg("account not found"));
},
};
let id: Uuid = returned.get(0);
let account = Account {
id: returned.get(0),
name: returned.get(2),
};
let hash: String = returned.get(1);
if !verify(password, &hash)? {
return Err(err_msg("password does not match"));
}
account_set_token(tx, &account)
}
fn account_set_token(tx: &mut Transaction, account: &Account) -> Result<String, Error> {
let mut rng = thread_rng();
let token: String = iter::repeat(())
.map(|()| rng.sample(Alphanumeric))
.take(64)
.collect();
// update token
let query = "
UPDATE accounts
SET token = $1, updated_at = now()
SET token = $1, updated_at = now(), token_expiry = now() + interval '1 week'
WHERE id = $2
RETURNING id;
";
let result = tx
.query(query, &[&token, &id])?;
.query(query, &[&token, &account.id])?;
result.iter().next().ok_or(format_err!("user {:?} could not be updated", id))?;
result.iter().next().ok_or(format_err!("user {:?} could not be updated", account.id))?;
info!("login account={:?}", name);
info!("login account={:?}", account.name);
return Ok(token);
Ok(token)
}
pub fn account_constructs(tx: &mut Transaction, account: &Account) -> Result<Vec<Construct>, Error> {

View File

@ -26,11 +26,7 @@ const DB_POOL_SIZE: u32 = 20;
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
/// websocket connection is long running connection, it easier
/// to handle with an actor
pub struct MnmlSocket {
/// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
/// otherwise we drop connection.
hb: Instant,
pool: PgPool,
account: Option<Account>,
@ -170,11 +166,10 @@ fn connect(r: HttpRequest, state: web::Data<State>, stream: web::Payload) -> Res
fn token_res(token: String, secure: bool) -> HttpResponse {
HttpResponse::Ok()
.cookie(Cookie::build("x-auth-token", token)
// .path("/")
.secure(secure) // needs to be enabled for prod
.secure(secure)
.http_only(true)
.same_site(SameSite::Strict)
.max_age(60 * 60 * 24 * 7) // 1 week
.max_age(60 * 60 * 24 * 7) // 1 week aligns with db set
.finish())
.finish()
}

View File

@ -29,7 +29,7 @@ pub fn receive(data: Vec<u8>, db: &Db, _client: &mut MnmlWs, begin: Instant, acc
let mut tx = db.transaction()?;
let account_name = match &account {
let account_name = match account {
Some(a) => a.name.clone(),
None => "none".to_string(),
};
@ -37,9 +37,7 @@ pub fn receive(data: Vec<u8>, db: &Db, _client: &mut MnmlWs, begin: Instant, acc
// check the method
// if no auth required
match v.method.as_ref() {
"account_create" => (),
// "account_login" => (),
"item_info" => (),
"item_info" => return Ok(RpcResult::ItemInfo(item_info())),
_ => match account {
Some(_) => (),
None => return Err(err_msg("auth required")),
@ -51,9 +49,7 @@ pub fn receive(data: Vec<u8>, db: &Db, _client: &mut MnmlWs, begin: Instant, acc
// now we have the method name
// match on that to determine what fn to call
let response = match v.method.as_ref() {
"item_info" => Ok(RpcResult::ItemInfo(item_info())),
"account_state" => Ok(RpcResult::AccountState(account.clone())),
"account_state" => return Ok(RpcResult::AccountState(account.clone())),
"account_constructs" => handle_account_constructs(data, &mut tx, account),
"account_instances" => handle_account_instances(data, &mut tx, account),