This commit is contained in:
ntr 2018-09-16 23:56:37 +10:00
parent 8fb7a3ae1e
commit a78b796c55
10 changed files with 209 additions and 90 deletions

21
client/.eslintrc.js Executable file
View File

@ -0,0 +1,21 @@
module.exports = {
extends: 'airbnb-base',
rules: {
// prevents stupid complaints a la
// (req) {
// req.something = x;
// }
'no-param-reassign': [2, {
props: false,
}],
'no-multi-spaces': [0],
'max-len': ['error', 120],
'import/no-extraneous-dependencies': [0],
'prefer-arrow-callback': [0],
'arrow-body-style': [0],
'no-console': [0],
// i like loops
'no-plusplus': [0],
'no-await-in-loop': [0],
},
};

View File

@ -1,3 +1,5 @@
const { toast } = require('bulma-toast');
const cbor = require('borc'); const cbor = require('borc');
const assert = require('assert'); const assert = require('assert');
// Create WebSocket connection. // Create WebSocket connection.
@ -8,13 +10,22 @@ ws.binaryType = 'arraybuffer';
// https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html // https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html
let user = null; let user = null;
function error_toast(err) {
console.error(err);
return toast({
message: err,
type: "is-warning",
duration: 5000,
});
}
function user_login(res) { function user_login(res) {
[struct, created] = res; [struct, user] = res;
user = created; user = user;
console.log(created); console.log(user);
return send({ method: 'cryp_spawn', params: { level: 64 }}); return send({ method: 'cryp_spawn', params: { name: 'drake' }});
} }
function new_cryp(cryp) { function new_cryp(cryp) {
@ -27,8 +38,14 @@ const handlers = {
'user_create': user_login, 'user_create': user_login,
}; };
function receive(res) { function on_message(event) {
if (res.err) return console.error(res.err); // decode binary msg from server
const blob = new Uint8Array(event.data);
const res = cbor.decode(blob);
console.log(res);
// check for error and split into response type and data
if (res.err) return error_toast(res.err);
const { method, params } = res; const { method, params } = res;
return handlers[method](params); return handlers[method](params);
} }
@ -40,17 +57,12 @@ function send(msg) {
// Connection opened // Connection opened
ws.addEventListener('open', function (event) { ws.addEventListener('open', function (event) {
send({ method: 'user_login', params: { name: 'somebodyelse', password: 'grepgrepgrep' }}); // send({ method: 'user_create', params: { name: 'ntr', password: 'grepgrepgrep' }});
send({ method: 'user_login', params: { name: 'ntr', password: 'grepgrepgrep' }});
}); });
// Listen for messages // Listen for messages
ws.addEventListener('message', function (event) { ws.addEventListener('message', on_message);
console.log('Message from server ', event.data);
const blob = new Uint8Array(event.data);
const decoded = cbor.decode(blob);
console.log(decoded);
return receive(decoded);
});
ws.addEventListener('error', function (event) { ws.addEventListener('error', function (event) {
console.error('WebSocket error', event); console.error('WebSocket error', event);

View File

@ -11,6 +11,12 @@
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"borc": "^2.0.3", "borc": "^2.0.3",
"parcel": "^1.9.7" "bulma-toast": "^1.2.0",
"parcel": "^1.9.7",
"docco": "^0.7.0",
"eslint": "^3.18.0",
"eslint-config-airbnb-base": "^11.1.1",
"eslint-plugin-import": "^2.2.0",
"jest": "^18.0.0"
} }
} }

View File

@ -1,5 +1,5 @@
exports.up = async knex => { exports.up = async knex => {
return knex.schema.createTable('users', table => { return knex.schema.createTable('accounts', table => {
table.uuid('id').primary(); table.uuid('id').primary();
table.string('name', 42).notNullable().unique(); table.string('name', 42).notNullable().unique();
table.string('password').notNullable(); table.string('password').notNullable();

View File

@ -0,0 +1,14 @@
exports.up = async knex => {
return knex.schema.createTable('cryps', table => {
table.uuid('id').primary();
table.uuid('account').notNullable()
table.foreign('account')
.references('id')
.inTable('accounts')
.onDelete('CASCADE');
table.binary('data').notNullable();
table.index('id');
});
};
exports.down = async () => {};

View File

@ -1,5 +1,5 @@
* Battling * Battling
* Logins * Logins ✔️
* Cryp Ownership * Cryp Ownership
* Matchmaking * Matchmaking
* Lobbies * Lobbies
@ -19,5 +19,10 @@
teams teams
physical, magic, pure dmg?
elemental?
items give skills
gem td style attr combinations gem td style attr combinations
stoney + spikey = jagged stoney + spikey = jagged

View File

@ -1,7 +1,13 @@
use uuid::Uuid; use uuid::Uuid;
use rand::prelude::*; use rand::prelude::*;
use serde_cbor::*; use serde_cbor::*;
use serde_cbor::{to_vec};
use failure::Error;
use failure::err_msg;
use net::Db;
use user::User;
use rpc::{CrypSpawnParams}; use rpc::{CrypSpawnParams};
use skill::{Skill}; use skill::{Skill};
@ -176,12 +182,32 @@ impl Cryp {
} }
pub fn spawn(params: CrypSpawnParams) -> Cryp { pub fn spawn(params: CrypSpawnParams, db: Db, user: User) -> Result<Cryp, Error> {
Cryp::new() let cryp = Cryp::new()
.named("hatchling".to_string()) .named(params.name)
.level(params.level) .level(1)
.learn(Skill::Stoney) .create();
.create()
let cryp_bytes = to_vec(&cryp)?;
let query = "
INSERT INTO cryps (id, user, data)
VALUES ($1, $2, $3)
RETURNING id, user;
";
let tx = db.transaction()?;
let result = tx
.query(query, &[&cryp.id, &user.id, &cryp_bytes])?;
let _returned = result.iter().next().expect("no row returned");
println!("{:?} spawned cryp {:}", user.id, cryp.id);
tx.commit()?;
return Ok(cryp);
} }

View File

@ -11,7 +11,7 @@ static DB_POOL_SIZE: u32 = 20;
pub type Db = PooledConnection<PostgresConnectionManager>; pub type Db = PooledConnection<PostgresConnectionManager>;
use rpc::{Rpc, RpcResult}; use rpc::{Rpc};
struct Server { struct Server {
out: Sender, out: Sender,
@ -39,6 +39,7 @@ impl Handler for Server {
self.out.send(response) self.out.send(response)
}, },
Err(e) => { Err(e) => {
println!("{:?}", e);
let response = to_vec(&RpcErrorResponse { err: e.to_string() }) let response = to_vec(&RpcErrorResponse { err: e.to_string() })
.expect("failed to serialize error response"); .expect("failed to serialize error response");
self.out.send(response) self.out.send(response)

View File

@ -5,7 +5,7 @@ use failure::err_msg;
use net::Db; use net::Db;
use cryp::{Cryp, spawn}; use cryp::{Cryp, spawn};
use user::{User, create, login}; use user::{User, create, login, from_token};
pub struct Rpc; pub struct Rpc;
@ -18,17 +18,27 @@ impl Rpc {
match from_slice::<RpcMessage>(&data) { match from_slice::<RpcMessage>(&data) {
Ok(v) => { Ok(v) => {
println!("{:?}", v.token); let user: Option<User> = match v.token {
Some(t) => Some(from_token(t, &db)?),
None => None,
};
println!("{:?}", user);
// 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
match v.method.as_ref() { match v.method.as_ref() {
"cryp_spawn" => { "cryp_spawn" => {
match from_slice::<CrypSpawnMsg>(&data) { match from_slice::<CrypSpawnMsg>(&data) {
Ok(v) => Ok(RpcResponse { Ok(v) => {
match user {
Some(u) => Ok(RpcResponse {
method: v.method, method: v.method,
params: RpcResult::Cryp(spawn(v.params)) params: RpcResult::SpawnCryp(spawn(v.params, db, u)?)
}), }),
None => Err(err_msg("auth required")),
}
}
Err(_e) => Err(err_msg("invalid params")), Err(_e) => Err(err_msg("invalid params")),
} }
}, },
@ -67,7 +77,7 @@ pub struct RpcResponse {
#[derive(Debug,Clone,Serialize,Deserialize)] #[derive(Debug,Clone,Serialize,Deserialize)]
pub enum RpcResult { pub enum RpcResult {
Cryp(Cryp), SpawnCryp(Cryp),
User(User), User(User),
} }
@ -85,7 +95,7 @@ struct CrypSpawnMsg {
#[derive(Debug,Clone,Serialize,Deserialize)] #[derive(Debug,Clone,Serialize,Deserialize)]
pub struct CrypSpawnParams { pub struct CrypSpawnParams {
pub level: u8, pub name: String,
} }
#[derive(Debug,Clone,Serialize,Deserialize)] #[derive(Debug,Clone,Serialize,Deserialize)]

View File

@ -12,10 +12,12 @@ use rpc::{UserCreateParams, UserLoginParams, RpcResult};
use failure::Error; use failure::Error;
use failure::err_msg; use failure::err_msg;
static PASSWORD_MIN_LEN: usize = 12;
#[derive(Debug,Clone,Serialize,Deserialize)] #[derive(Debug,Clone,Serialize,Deserialize)]
pub struct User { pub struct User {
id: Uuid, pub id: Uuid,
name: String, pub name: String,
token: String, token: String,
} }
@ -27,7 +29,31 @@ struct UserEntry {
token: String, token: String,
} }
static PASSWORD_MIN_LEN: usize = 12; // MAYBE
// hash tokens with a secret
pub fn from_token(token: String, db: &Db) -> Result<User, Error> {
let query = "
SELECT id, name, token
FROM users
WHERE token = $1;
";
let result = db
.query(query, &[&token])?;
let returned = match result.iter().next() {
Some(row) => row,
None => return Err(err_msg("invalid token")),
};
let entry = User {
id: returned.get(0),
name: returned.get(1),
token: returned.get(2),
};
return Ok(entry);
}
pub fn create(params: UserCreateParams, db: Db) -> Result<RpcResult, Error> { pub fn create(params: UserCreateParams, db: Db) -> Result<RpcResult, Error> {
let id = Uuid::new_v4(); let id = Uuid::new_v4();
@ -73,7 +99,7 @@ pub fn create(params: UserCreateParams, db: Db) -> Result<RpcResult, Error> {
tx.commit()?; tx.commit()?;
Ok(RpcResult::User(entry)) return Ok(RpcResult::User(entry));
} }
pub fn login(params: UserLoginParams, db: Db) -> Result<RpcResult, Error> { pub fn login(params: UserLoginParams, db: Db) -> Result<RpcResult, Error> {
@ -83,13 +109,13 @@ pub fn login(params: UserLoginParams, db: Db) -> Result<RpcResult, Error> {
WHERE name = $1; WHERE name = $1;
"; ";
// let tx = db.transaction()?;
let result = db let result = db
.query(query, &[&params.name])?; .query(query, &[&params.name])?;
let returned = match result.iter().next() { let returned = match result.iter().next() {
Some(row) => row, Some(row) => row,
// MAYBE
// verify gibberish to delay response for timing attacks
None => return Err(err_msg("user not found")), None => return Err(err_msg("user not found")),
}; };
@ -110,13 +136,11 @@ pub fn login(params: UserLoginParams, db: Db) -> Result<RpcResult, Error> {
// update token? // update token?
// don't necessarily want to log every session out when logging in // don't necessarily want to log every session out when logging in
// tx.commit()?;
let user = User { let user = User {
id: entry.id, id: entry.id,
name: entry.name, name: entry.name,
token: entry.token, token: entry.token,
}; };
Ok(RpcResult::User(user)) return Ok(RpcResult::User(user));
} }