token expiry"
"
This commit is contained in:
parent
2b06c83ea0
commit
9fcdbeb370
@ -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>
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user