a mighty battle against an evil opponent
This commit is contained in:
parent
5c6e587044
commit
66462ac670
@ -3,16 +3,45 @@ const preact = require('preact');
|
||||
const { Component } = require('preact')
|
||||
const { connect } = require('preact-redux');
|
||||
|
||||
const SERVER = process.env.NODE_ENV === 'production' ? '/' : 'http://localhost:40000';
|
||||
|
||||
function postData(url = '/', data = {}) {
|
||||
// Default options are marked with *
|
||||
return fetch(url, {
|
||||
method: "POST", // *GET, POST, PUT, DELETE, etc.
|
||||
// mode: "no-cors", // no-cors, cors, *same-origin
|
||||
cache: "no-cache", // *default, no-cache, reload, force-cache, only-if-cached
|
||||
credentials: "include", // include, same-origin, *omit
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
redirect: "error", // manual, *follow, error
|
||||
// referrer: "", // no-referrer, *client
|
||||
body: JSON.stringify(data), // body data type must match "Content-Type" header
|
||||
})
|
||||
.then(response => response.json()); // parses response to JSON
|
||||
}
|
||||
|
||||
const addState = connect(
|
||||
(state) => {
|
||||
const { ws, account } = state;
|
||||
null,
|
||||
(dispatch) => {
|
||||
function submitLogin(name, password) {
|
||||
return ws.sendAccountLogin(name, password);
|
||||
postData(`${SERVER}/login`, { name, password })
|
||||
.then(data => console.log(JSON.stringify(data)))
|
||||
.catch(error => console.error(error));
|
||||
}
|
||||
|
||||
function submitRegister(name, password, code) {
|
||||
return ws.sendAccountCreate(name, password, code);
|
||||
postData(`${SERVER}/register`, { name, password, code })
|
||||
.then(data => console.log(JSON.stringify(data)))
|
||||
.catch(error => console.error(error));
|
||||
}
|
||||
|
||||
return {
|
||||
submitLogin,
|
||||
submitRegister,
|
||||
}
|
||||
return { account, submitLogin, submitRegister };
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@ -15,21 +15,20 @@ function errorToast(err) {
|
||||
function createSocket(events) {
|
||||
let ws;
|
||||
|
||||
// handle account auth within the socket itself
|
||||
// https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html
|
||||
let account;
|
||||
try {
|
||||
account = JSON.parse(localStorage.getItem('account'));
|
||||
} catch (e) {
|
||||
localStorage.removeItem('account');
|
||||
}
|
||||
// // handle account auth within the socket itself
|
||||
// // https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html
|
||||
// let account;
|
||||
// try {
|
||||
// account = JSON.parse(localStorage.getItem('account'));
|
||||
// } catch (e) {
|
||||
// localStorage.removeItem('account');
|
||||
// }
|
||||
|
||||
// -------------
|
||||
// Outgoing
|
||||
// -------------
|
||||
function send(msg) {
|
||||
if (msg.method !== 'ping') console.log('outgoing msg', msg);
|
||||
msg.token = account && account.token && account.token;
|
||||
ws.send(cbor.encode(msg));
|
||||
}
|
||||
|
||||
@ -144,8 +143,6 @@ function createSocket(events) {
|
||||
// Incoming
|
||||
// -------------
|
||||
function onAccount(login) {
|
||||
account = login;
|
||||
localStorage.setItem('account', JSON.stringify(login));
|
||||
events.setAccount(login);
|
||||
sendAccountConstructs();
|
||||
sendAccountInstances();
|
||||
@ -231,7 +228,6 @@ function createSocket(events) {
|
||||
function errHandler(error) {
|
||||
switch (error) {
|
||||
case 'invalid token': return logout();
|
||||
case 'no active zone': return sendZoneCreate();
|
||||
case 'no constructs selected': return events.errorPrompt('select_constructs');
|
||||
case 'node requirements not met': return events.errorPrompt('complete_nodes');
|
||||
case 'construct at max skills (4)': return events.errorPrompt('max_skills');
|
||||
@ -249,7 +245,7 @@ function createSocket(events) {
|
||||
const res = cbor.decode(blob);
|
||||
const [msgType, params] = res;
|
||||
|
||||
if (msgType !== 'pong') console.log(res);
|
||||
if (msgType !== 'Pong') console.log(res);
|
||||
|
||||
// check for error and split into response type and data
|
||||
if (res.err) return errHandler(res.err);
|
||||
@ -271,13 +267,6 @@ function createSocket(events) {
|
||||
sendPing();
|
||||
sendItemInfo();
|
||||
|
||||
if (account) {
|
||||
events.setAccount(account);
|
||||
sendAccountInstances();
|
||||
sendInstanceList();
|
||||
sendAccountConstructs();
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
@ -1 +1,2 @@
|
||||
DATABASE_URL=postgres://mnml:craftbeer@localhost/mnml
|
||||
DEV_CORS=true
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)?))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user