a mighty battle against an evil opponent
This commit is contained in:
+42
-50
@@ -7,8 +7,6 @@ use serde_cbor::{from_slice};
|
||||
|
||||
use postgres::transaction::Transaction;
|
||||
|
||||
use rpc::{AccountCreateParams, AccountLoginParams};
|
||||
|
||||
use construct::{Construct, construct_recover};
|
||||
use instance::{Instance, instance_delete};
|
||||
|
||||
@@ -58,23 +56,22 @@ pub fn account_from_token(token: String, tx: &mut Transaction) -> Result<Account
|
||||
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 {
|
||||
pub fn account_create(name: &String, password: &String, code: &String, tx: &mut Transaction) -> Result<String, Error> {
|
||||
if password.len() < PASSWORD_MIN_LEN {
|
||||
return Err(err_msg("password must be at least 12 characters"));
|
||||
}
|
||||
|
||||
if params.code.to_lowercase() != "grep842" {
|
||||
if code.to_lowercase() != "grep842" {
|
||||
return Err(err_msg("https://discord.gg/YJJgurM"));
|
||||
}
|
||||
|
||||
if params.name.len() == 0 {
|
||||
if name.len() == 0 {
|
||||
return Err(err_msg("account name not supplied"));
|
||||
}
|
||||
|
||||
let id = Uuid::new_v4();
|
||||
let rounds = 8;
|
||||
let password = hash(¶ms.password, rounds)?;
|
||||
let password = hash(&password, rounds)?;
|
||||
|
||||
let mut rng = thread_rng();
|
||||
let token: String = iter::repeat(())
|
||||
@@ -82,77 +79,72 @@ pub fn account_create(params: AccountCreateParams, tx: &mut Transaction) -> Resu
|
||||
.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])?;
|
||||
.query(query, &[&id, &name, &password, &token])?;
|
||||
|
||||
let returned = result.iter().next().expect("no row returned");
|
||||
if result.is_empty() {
|
||||
return Err(err_msg("no row returned"));
|
||||
}
|
||||
|
||||
let entry = Account {
|
||||
id: returned.get(0),
|
||||
name: returned.get(1),
|
||||
token: returned.get(2),
|
||||
};
|
||||
info!("registration account={:?}", name);
|
||||
|
||||
info!("registration account={:?}", entry.name);
|
||||
|
||||
return Ok(entry);
|
||||
return Ok(token);
|
||||
}
|
||||
|
||||
pub fn account_login(params: AccountLoginParams, tx: &mut Transaction) -> Result<Account, Error> {
|
||||
pub fn account_login(name: &String, password: &String, tx: &mut Transaction) -> Result<String, Error> {
|
||||
let query = "
|
||||
SELECT id, name, token, password
|
||||
SELECT id, password
|
||||
FROM accounts
|
||||
WHERE name = $1;
|
||||
";
|
||||
|
||||
let result = tx
|
||||
.query(query, &[¶ms.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() {
|
||||
Some(row) => row,
|
||||
// MAYBE
|
||||
// verify gibberish to delay response for timing attacks
|
||||
None => return Err(err_msg("account not found")),
|
||||
None => {
|
||||
// verify garbage to prevent timing attacks
|
||||
verify(token.clone(), &token).ok();
|
||||
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),
|
||||
};
|
||||
let id: Uuid = returned.get(0);
|
||||
let hash: String = returned.get(1);
|
||||
|
||||
if !verify(¶ms.password, &entry.password)? {
|
||||
if !verify(password, &hash)? {
|
||||
return Err(err_msg("password does not match"));
|
||||
}
|
||||
|
||||
info!("login account={:?}", entry.name);
|
||||
// update token
|
||||
let query = "
|
||||
UPDATE accounts
|
||||
SET token = $1, updated_at = now()
|
||||
WHERE id = $2
|
||||
RETURNING id;
|
||||
";
|
||||
|
||||
// MAYBE
|
||||
// update token?
|
||||
// don't necessarily want to log every session out when logging in
|
||||
let result = tx
|
||||
.query(query, &[&token, &id])?;
|
||||
|
||||
let account = Account {
|
||||
id: entry.id,
|
||||
name: entry.name,
|
||||
token: entry.token,
|
||||
};
|
||||
result.iter().next().ok_or(format_err!("user {:?} could not be updated", id))?;
|
||||
|
||||
return Ok(account);
|
||||
info!("login account={:?}", name);
|
||||
|
||||
return Ok(token);
|
||||
}
|
||||
|
||||
pub fn account_constructs(tx: &mut Transaction, account: &Account) -> Result<Vec<Construct>, Error> {
|
||||
|
||||
+99
-16
@@ -1,18 +1,26 @@
|
||||
use std::time::{Instant, Duration};
|
||||
use std::env;
|
||||
|
||||
use chrono::Duration as ChronoDuration;
|
||||
|
||||
use failure::err_msg;
|
||||
|
||||
use serde_cbor::{to_vec};
|
||||
|
||||
use actix::prelude::*;
|
||||
use actix_web::{middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer};
|
||||
use actix_web::middleware::cors::Cors;
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::{StatusCode, Cookie};
|
||||
use actix_web_actors::ws;
|
||||
|
||||
use r2d2::{Pool};
|
||||
use r2d2::{PooledConnection};
|
||||
use r2d2_postgres::{TlsMode, PostgresConnectionManager};
|
||||
|
||||
use rpc::{receive, RpcErrorResponse};
|
||||
use rpc::{receive, RpcErrorResponse, AccountLoginParams, AccountCreateParams};
|
||||
use warden::{warden};
|
||||
use account::{Account, account_login, account_create};
|
||||
|
||||
pub type Db = PooledConnection<PostgresConnectionManager>;
|
||||
type PgPool = Pool<PostgresConnectionManager>;
|
||||
@@ -103,18 +111,80 @@ impl MnmlSocket {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
enum MnmlError {
|
||||
#[fail(display="internal server error")]
|
||||
ServerError,
|
||||
#[fail(display="unauthorized")]
|
||||
Unauthorized,
|
||||
#[fail(display="bad request")]
|
||||
BadRequest,
|
||||
}
|
||||
|
||||
impl ResponseError for MnmlError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match *self {
|
||||
MnmlError::ServerError => HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
MnmlError::Unauthorized => HttpResponse::new(StatusCode::UNAUTHORIZED),
|
||||
MnmlError::BadRequest => HttpResponse::new(StatusCode::BAD_REQUEST),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// idk how this stuff works
|
||||
// but the args extract what you need from the incoming requests
|
||||
// this grabs
|
||||
// the req obj itself which we need for cookies
|
||||
// the application state
|
||||
// and the websocket stream
|
||||
fn ws_index(r: HttpRequest, state: web::Data<State>, stream: web::Payload) -> Result<HttpResponse, Error> {
|
||||
let res = ws::start(MnmlSocket::new(state), &r, stream);
|
||||
fn ws(r: HttpRequest, state: web::Data<State>, stream: web::Payload) -> Result<HttpResponse, Error> {
|
||||
ws::start(MnmlSocket::new(state), &r, stream)
|
||||
}
|
||||
|
||||
// response of upgrade being sent back
|
||||
// info!("res={:?}", res.as_ref().unwrap());
|
||||
res
|
||||
fn login(state: web::Data<State>, params: web::Json::<AccountLoginParams>) -> Result<HttpResponse, MnmlError> {
|
||||
let db = state.pool.get().or(Err(MnmlError::ServerError))?;
|
||||
let mut tx = db.transaction().or(Err(MnmlError::ServerError))?;
|
||||
|
||||
match account_login(¶ms.name, ¶ms.password, &mut tx) {
|
||||
Ok(token) => {
|
||||
tx.commit().or(Err(MnmlError::ServerError))?;
|
||||
Ok(HttpResponse::Ok()
|
||||
.cookie(Cookie::build("x-auth-token", token)
|
||||
// .path("/")
|
||||
.http_only(true)
|
||||
// .secure(true)
|
||||
.max_age(60 * 60 * 24 * 7) // 1 week
|
||||
.finish())
|
||||
.finish())
|
||||
},
|
||||
Err(e) => {
|
||||
info!("{:?}", e);
|
||||
Err(MnmlError::Unauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn register(state: web::Data<State>, params: web::Json::<AccountCreateParams>) -> Result<HttpResponse, MnmlError> {
|
||||
let db = state.pool.get().or(Err(MnmlError::ServerError))?;
|
||||
let mut tx = db.transaction().or(Err(MnmlError::ServerError))?;
|
||||
|
||||
match account_create(¶ms.name, ¶ms.password, ¶ms.code, &mut tx) {
|
||||
Ok(token) => {
|
||||
tx.commit().or(Err(MnmlError::ServerError))?;
|
||||
Ok(HttpResponse::Created()
|
||||
.cookie(Cookie::build("x-auth-token", token)
|
||||
.path("/")
|
||||
.http_only(true)
|
||||
.secure(true)
|
||||
.max_age(60 * 60 * 24 * 7) // 1 week
|
||||
.finish())
|
||||
.finish())
|
||||
},
|
||||
Err(e) => {
|
||||
info!("{:?}", e);
|
||||
Err(MnmlError::Unauthorized)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_pool(url: String) -> Pool<PostgresConnectionManager> {
|
||||
@@ -137,14 +207,27 @@ pub fn start() {
|
||||
|
||||
let pool = create_pool(database_url);
|
||||
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.data(State { pool: pool.clone() })
|
||||
// enable logger
|
||||
.wrap(middleware::Logger::default())
|
||||
// websocket route
|
||||
.service(web::resource("/ws/").route(web::get().to(ws_index)))
|
||||
})
|
||||
.bind("127.0.0.1:40000").expect("could not bind to port")
|
||||
.run().expect("could not start http server");
|
||||
match env::var("DEV_CORS") {
|
||||
Ok(_) => {
|
||||
warn!("enabling dev CORS middleware");
|
||||
HttpServer::new(move || App::new()
|
||||
.data(State { pool: pool.clone() })
|
||||
.wrap(middleware::Logger::default())
|
||||
.wrap(Cors::new().supports_credentials())
|
||||
.service(web::resource("/login").route(web::post().to(login)))
|
||||
.service(web::resource("/register").route(web::post().to(register)))
|
||||
.service(web::resource("/ws/").route(web::get().to(ws))))
|
||||
.bind("127.0.0.1:40000").expect("could not bind to port")
|
||||
.run().expect("could not start http server")
|
||||
},
|
||||
Err(_) =>
|
||||
HttpServer::new(move || App::new()
|
||||
.data(State { pool: pool.clone() })
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(web::resource("/login").route(web::post().to(login)))
|
||||
.service(web::resource("/register").route(web::post().to(register)))
|
||||
.service(web::resource("/ws/").route(web::get().to(ws))))
|
||||
.bind("127.0.0.1:40000").expect("could not bind to port")
|
||||
.run().expect("could not start http server"),
|
||||
}
|
||||
}
|
||||
|
||||
+13
-13
@@ -11,7 +11,7 @@ use failure::err_msg;
|
||||
use net::{Db, MnmlSocket};
|
||||
use construct::{Construct, construct_spawn, construct_delete};
|
||||
use game::{Game, game_state, game_skill, game_ready};
|
||||
use account::{Account, account_create, account_login, account_from_token, account_constructs, account_instances};
|
||||
use account::{Account, account_from_token, account_constructs, account_instances};
|
||||
use skill::{Skill};
|
||||
use instance::{Instance, instance_state, instance_list, instance_new, instance_ready, instance_join};
|
||||
use vbox::{vbox_accept, vbox_apply, vbox_discard, vbox_combine, vbox_reclaim, vbox_unequip};
|
||||
@@ -43,7 +43,7 @@ pub fn receive(data: Vec<u8>, db: &Db, _client: &mut MnmlWs, begin: Instant) ->
|
||||
// if no auth required
|
||||
match v.method.as_ref() {
|
||||
"account_create" => (),
|
||||
"account_login" => (),
|
||||
// "account_login" => (),
|
||||
"item_info" => (),
|
||||
_ => match account {
|
||||
Some(_) => (),
|
||||
@@ -55,8 +55,8 @@ pub fn receive(data: Vec<u8>, db: &Db, _client: &mut MnmlWs, begin: Instant) ->
|
||||
// match on that to determine what fn to call
|
||||
let response = match v.method.as_ref() {
|
||||
// NO AUTH
|
||||
"account_create" => handle_account_create(data, &mut tx),
|
||||
"account_login" => handle_account_login(data, &mut tx),
|
||||
// "account_create" => handle_account_create(data, &mut tx),
|
||||
// "account_login" => handle_account_login(data, &mut tx),
|
||||
"item_info" => Ok(RpcResult::ItemInfo(item_info())),
|
||||
|
||||
// AUTH METHODS
|
||||
@@ -139,16 +139,16 @@ fn handle_construct_delete(data: Vec<u8>, tx: &mut Transaction, account: Account
|
||||
}
|
||||
|
||||
|
||||
fn handle_account_create(data: Vec<u8>, tx: &mut Transaction) -> Result<RpcResult, Error> {
|
||||
let msg = from_slice::<AccountCreateMsg>(&data).or(Err(err_msg("invalid params")))?;
|
||||
let account = account_create(msg.params, tx)?;
|
||||
Ok(RpcResult::Account(account))
|
||||
}
|
||||
// fn handle_account_create(data: Vec<u8>, tx: &mut Transaction) -> Result<RpcResult, Error> {
|
||||
// let msg = from_slice::<AccountCreateMsg>(&data).or(Err(err_msg("invalid params")))?;
|
||||
// let account = account_create(msg.params, tx)?;
|
||||
// Ok(RpcResult::Account(account))
|
||||
// }
|
||||
|
||||
fn handle_account_login(data: Vec<u8>, tx: &mut Transaction) -> Result<RpcResult, Error> {
|
||||
let msg = from_slice::<AccountLoginMsg>(&data).or(Err(err_msg("invalid params")))?;
|
||||
Ok(RpcResult::Account(account_login(msg.params, tx)?))
|
||||
}
|
||||
// fn handle_account_login(data: Vec<u8>, tx: &mut Transaction) -> Result<RpcResult, Error> {
|
||||
// let msg = from_slice::<AccountLoginMsg>(&data).or(Err(err_msg("invalid params")))?;
|
||||
// Ok(RpcResult::Account(account_login(msg.params, tx)?))
|
||||
// }
|
||||
|
||||
fn handle_account_constructs(_data: Vec<u8>, tx: &mut Transaction, account: Account) -> Result<RpcResult, Error> {
|
||||
Ok(RpcResult::AccountConstructs(account_constructs(tx, &account)?))
|
||||
|
||||
Reference in New Issue
Block a user