token expiry"
"
This commit is contained in:
parent
2b06c83ea0
commit
9fcdbeb370
@ -10,8 +10,8 @@ const List = require('./list');
|
|||||||
|
|
||||||
const addState = connect(
|
const addState = connect(
|
||||||
state => {
|
state => {
|
||||||
const { game, instance, account, nav, team } = state;
|
const { game, instance, account, nav, team, constructs } = state;
|
||||||
return { game, instance, account, nav, team };
|
return { game, instance, account, nav, team, constructs };
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -22,6 +22,7 @@ function Main(props) {
|
|||||||
account,
|
account,
|
||||||
nav,
|
nav,
|
||||||
team,
|
team,
|
||||||
|
constructs,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
@ -36,8 +37,8 @@ function Main(props) {
|
|||||||
return <Instance />;
|
return <Instance />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nav === 'team' || !team.some(t => t) || constructs.length < 3) return <Team />;
|
||||||
if (nav === 'list') return <List />;
|
if (nav === 'list') return <List />;
|
||||||
if (nav === 'team' || !team.some(t => t)) return <Team />;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main></main>
|
<main></main>
|
||||||
|
|||||||
@ -4,7 +4,8 @@ exports.up = async knex => {
|
|||||||
table.timestamps(true, true);
|
table.timestamps(true, true);
|
||||||
table.string('name', 42).notNullable().unique();
|
table.string('name', 42).notNullable().unique();
|
||||||
table.string('password').notNullable();
|
table.string('password').notNullable();
|
||||||
table.string('token', 64).notNullable();
|
table.string('token', 64);
|
||||||
|
table.timestamp('token_expiry');
|
||||||
|
|
||||||
table.index('name');
|
table.index('name');
|
||||||
table.index('id');
|
table.index('id');
|
||||||
|
|||||||
@ -29,13 +29,12 @@ struct AccountEntry {
|
|||||||
token: String,
|
token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// MAYBE
|
|
||||||
// hash tokens with a secret
|
|
||||||
pub fn account_from_token(token: String, tx: &mut Transaction) -> Result<Account, Error> {
|
pub fn account_from_token(token: String, tx: &mut Transaction) -> Result<Account, Error> {
|
||||||
let query = "
|
let query = "
|
||||||
SELECT id, name, token
|
SELECT id, name, token
|
||||||
FROM accounts
|
FROM accounts
|
||||||
WHERE token = $1;
|
WHERE token = $1
|
||||||
|
AND token_expiry > now();
|
||||||
";
|
";
|
||||||
|
|
||||||
let result = tx
|
let result = tx
|
||||||
@ -71,78 +70,93 @@ pub fn account_create(name: &String, password: &String, code: &String, tx: &mut
|
|||||||
let rounds = 8;
|
let rounds = 8;
|
||||||
let password = hash(&password, rounds)?;
|
let password = hash(&password, rounds)?;
|
||||||
|
|
||||||
let mut rng = thread_rng();
|
|
||||||
let token: String = iter::repeat(())
|
|
||||||
.map(|()| rng.sample(Alphanumeric))
|
|
||||||
.take(64)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let query = "
|
let query = "
|
||||||
INSERT INTO accounts (id, name, password, token)
|
INSERT INTO accounts (id, name, password)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3)
|
||||||
RETURNING id, name, token;
|
RETURNING id, name;
|
||||||
";
|
";
|
||||||
|
|
||||||
let result = tx
|
let result = tx
|
||||||
.query(query, &[&id, &name, &password, &token])?;
|
.query(query, &[&id, &name, &password])?;
|
||||||
|
|
||||||
if result.is_empty() {
|
let returned = match result.iter().next() {
|
||||||
return Err(err_msg("no row returned"));
|
Some(row) => row,
|
||||||
}
|
None => return Err(err_msg("account not created")),
|
||||||
|
};
|
||||||
|
|
||||||
info!("registration account={:?}", name);
|
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> {
|
pub fn account_login(name: &String, password: &String, tx: &mut Transaction) -> Result<String, Error> {
|
||||||
let query = "
|
let query = "
|
||||||
SELECT id, password
|
SELECT id, password, name
|
||||||
FROM accounts
|
FROM accounts
|
||||||
WHERE name = $1;
|
WHERE name = $1
|
||||||
|
RETURNING id, name;
|
||||||
";
|
";
|
||||||
|
|
||||||
let result = tx
|
let result = tx
|
||||||
.query(query, &[&name])?;
|
.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() {
|
let returned = match result.iter().next() {
|
||||||
Some(row) => row,
|
Some(row) => row,
|
||||||
None => {
|
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 garbage to prevent timing attacks
|
||||||
verify(token.clone(), &token).ok();
|
verify(garbage.clone(), &garbage).ok();
|
||||||
return Err(err_msg("account not found"));
|
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);
|
let hash: String = returned.get(1);
|
||||||
|
|
||||||
if !verify(password, &hash)? {
|
if !verify(password, &hash)? {
|
||||||
return Err(err_msg("password does not match"));
|
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
|
// update token
|
||||||
let query = "
|
let query = "
|
||||||
UPDATE accounts
|
UPDATE accounts
|
||||||
SET token = $1, updated_at = now()
|
SET token = $1, updated_at = now(), token_expiry = now() + interval '1 week'
|
||||||
WHERE id = $2
|
WHERE id = $2
|
||||||
RETURNING id;
|
RETURNING id;
|
||||||
";
|
";
|
||||||
|
|
||||||
let result = tx
|
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> {
|
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 HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
|
||||||
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
|
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
/// websocket connection is long running connection, it easier
|
|
||||||
/// to handle with an actor
|
|
||||||
pub struct MnmlSocket {
|
pub struct MnmlSocket {
|
||||||
/// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
|
|
||||||
/// otherwise we drop connection.
|
|
||||||
hb: Instant,
|
hb: Instant,
|
||||||
pool: PgPool,
|
pool: PgPool,
|
||||||
account: Option<Account>,
|
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 {
|
fn token_res(token: String, secure: bool) -> HttpResponse {
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.cookie(Cookie::build("x-auth-token", token)
|
.cookie(Cookie::build("x-auth-token", token)
|
||||||
// .path("/")
|
.secure(secure)
|
||||||
.secure(secure) // needs to be enabled for prod
|
|
||||||
.http_only(true)
|
.http_only(true)
|
||||||
.same_site(SameSite::Strict)
|
.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())
|
||||||
.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 mut tx = db.transaction()?;
|
||||||
|
|
||||||
let account_name = match &account {
|
let account_name = match account {
|
||||||
Some(a) => a.name.clone(),
|
Some(a) => a.name.clone(),
|
||||||
None => "none".to_string(),
|
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
|
// check the method
|
||||||
// if no auth required
|
// if no auth required
|
||||||
match v.method.as_ref() {
|
match v.method.as_ref() {
|
||||||
"account_create" => (),
|
"item_info" => return Ok(RpcResult::ItemInfo(item_info())),
|
||||||
// "account_login" => (),
|
|
||||||
"item_info" => (),
|
|
||||||
_ => match account {
|
_ => match account {
|
||||||
Some(_) => (),
|
Some(_) => (),
|
||||||
None => return Err(err_msg("auth required")),
|
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
|
// now we have the method name
|
||||||
// match on that to determine what fn to call
|
// match on that to determine what fn to call
|
||||||
let response = match v.method.as_ref() {
|
let response = match v.method.as_ref() {
|
||||||
"item_info" => Ok(RpcResult::ItemInfo(item_info())),
|
"account_state" => return Ok(RpcResult::AccountState(account.clone())),
|
||||||
"account_state" => Ok(RpcResult::AccountState(account.clone())),
|
|
||||||
|
|
||||||
"account_constructs" => handle_account_constructs(data, &mut tx, account),
|
"account_constructs" => handle_account_constructs(data, &mut tx, account),
|
||||||
"account_instances" => handle_account_instances(data, &mut tx, account),
|
"account_instances" => handle_account_instances(data, &mut tx, account),
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user