From a78b796c55c12f61fc61473b4357f26ff743ed91 Mon Sep 17 00:00:00 2001 From: ntr Date: Sun, 16 Sep 2018 23:56:37 +1000 Subject: [PATCH] before --- client/.eslintrc.js | 21 +++ client/index.js | 136 ++++++++++-------- client/package.json | 8 +- ...s.js => 20180913000513_create_accounts.js} | 2 +- ops/migrations/20180916221309_cryps_table.js | 14 ++ server/WORKLOG.md | 7 +- server/src/cryp.rs | 38 ++++- server/src/net.rs | 3 +- server/src/rpc.rs | 26 ++-- server/src/user.rs | 44 ++++-- 10 files changed, 209 insertions(+), 90 deletions(-) create mode 100755 client/.eslintrc.js rename ops/migrations/{20180913000513_create_users.js => 20180913000513_create_accounts.js} (84%) create mode 100755 ops/migrations/20180916221309_cryps_table.js diff --git a/client/.eslintrc.js b/client/.eslintrc.js new file mode 100755 index 00000000..dc863ba8 --- /dev/null +++ b/client/.eslintrc.js @@ -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], + }, +}; \ No newline at end of file diff --git a/client/index.js b/client/index.js index 06368cef..0d1fe6ae 100755 --- a/client/index.js +++ b/client/index.js @@ -1,63 +1,75 @@ -const cbor = require('borc'); -const assert = require('assert'); -// Create WebSocket connection. -const ws = new WebSocket('ws://localhost:40000'); -ws.binaryType = 'arraybuffer'; - -// handle user auth within the socket itself -// https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html -let user = null; - -function user_login(res) { - [struct, created] = res; - - user = created; - - console.log(created); - return send({ method: 'cryp_spawn', params: { level: 64 }}); -} - -function new_cryp(cryp) { - console.log('got a new cryp'); -} - -const handlers = { - 'cryp_spawn': new_cryp, - 'user_login': user_login, - 'user_create': user_login, -}; - -function receive(res) { - if (res.err) return console.error(res.err); - const { method, params } = res; - return handlers[method](params); -} - -function send(msg) { - msg.token = user && user.token; - ws.send(cbor.encode(msg)); -} - -// Connection opened -ws.addEventListener('open', function (event) { - send({ method: 'user_login', params: { name: 'somebodyelse', password: 'grepgrepgrep' }}); -}); - -// Listen for messages -ws.addEventListener('message', function (event) { - 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) { - console.error('WebSocket error', event); - user = null; -}); - -ws.addEventListener('close', function (event) { - console.error('WebSocket closed', event); - user = null; +const { toast } = require('bulma-toast'); + +const cbor = require('borc'); +const assert = require('assert'); +// Create WebSocket connection. +const ws = new WebSocket('ws://localhost:40000'); +ws.binaryType = 'arraybuffer'; + +// handle user auth within the socket itself +// https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html +let user = null; + +function error_toast(err) { + console.error(err); + return toast({ + message: err, + type: "is-warning", + duration: 5000, + }); +} + +function user_login(res) { + [struct, user] = res; + + user = user; + + console.log(user); + return send({ method: 'cryp_spawn', params: { name: 'drake' }}); +} + +function new_cryp(cryp) { + console.log('got a new cryp'); +} + +const handlers = { + 'cryp_spawn': new_cryp, + 'user_login': user_login, + 'user_create': user_login, +}; + +function on_message(event) { + // 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; + return handlers[method](params); +} + +function send(msg) { + msg.token = user && user.token; + ws.send(cbor.encode(msg)); +} + +// Connection opened +ws.addEventListener('open', function (event) { + // send({ method: 'user_create', params: { name: 'ntr', password: 'grepgrepgrep' }}); + send({ method: 'user_login', params: { name: 'ntr', password: 'grepgrepgrep' }}); +}); + +// Listen for messages +ws.addEventListener('message', on_message); + +ws.addEventListener('error', function (event) { + console.error('WebSocket error', event); + user = null; +}); + +ws.addEventListener('close', function (event) { + console.error('WebSocket closed', event); + user = null; }); \ No newline at end of file diff --git a/client/package.json b/client/package.json index 8ef2f7f4..71bd0de1 100755 --- a/client/package.json +++ b/client/package.json @@ -11,6 +11,12 @@ "license": "UNLICENSED", "dependencies": { "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" } } diff --git a/ops/migrations/20180913000513_create_users.js b/ops/migrations/20180913000513_create_accounts.js similarity index 84% rename from ops/migrations/20180913000513_create_users.js rename to ops/migrations/20180913000513_create_accounts.js index a618a7e0..9fcc7b18 100755 --- a/ops/migrations/20180913000513_create_users.js +++ b/ops/migrations/20180913000513_create_accounts.js @@ -1,5 +1,5 @@ exports.up = async knex => { - return knex.schema.createTable('users', table => { + return knex.schema.createTable('accounts', table => { table.uuid('id').primary(); table.string('name', 42).notNullable().unique(); table.string('password').notNullable(); diff --git a/ops/migrations/20180916221309_cryps_table.js b/ops/migrations/20180916221309_cryps_table.js new file mode 100755 index 00000000..9016b8b5 --- /dev/null +++ b/ops/migrations/20180916221309_cryps_table.js @@ -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 () => {}; \ No newline at end of file diff --git a/server/WORKLOG.md b/server/WORKLOG.md index cdaa1997..5cc68530 100755 --- a/server/WORKLOG.md +++ b/server/WORKLOG.md @@ -1,5 +1,5 @@ * Battling - * Logins + * Logins ✔️ * Cryp Ownership * Matchmaking * Lobbies @@ -19,5 +19,10 @@ teams +physical, magic, pure dmg? + elemental? + +items give skills + gem td style attr combinations stoney + spikey = jagged \ No newline at end of file diff --git a/server/src/cryp.rs b/server/src/cryp.rs index bbb1645d..70ac963f 100755 --- a/server/src/cryp.rs +++ b/server/src/cryp.rs @@ -1,7 +1,13 @@ use uuid::Uuid; use rand::prelude::*; 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 skill::{Skill}; @@ -176,12 +182,32 @@ impl Cryp { } -pub fn spawn(params: CrypSpawnParams) -> Cryp { - Cryp::new() - .named("hatchling".to_string()) - .level(params.level) - .learn(Skill::Stoney) - .create() +pub fn spawn(params: CrypSpawnParams, db: Db, user: User) -> Result { + let cryp = Cryp::new() + .named(params.name) + .level(1) + .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); } diff --git a/server/src/net.rs b/server/src/net.rs index 945acd83..57ebce38 100755 --- a/server/src/net.rs +++ b/server/src/net.rs @@ -11,7 +11,7 @@ static DB_POOL_SIZE: u32 = 20; pub type Db = PooledConnection; -use rpc::{Rpc, RpcResult}; +use rpc::{Rpc}; struct Server { out: Sender, @@ -39,6 +39,7 @@ impl Handler for Server { self.out.send(response) }, Err(e) => { + println!("{:?}", e); let response = to_vec(&RpcErrorResponse { err: e.to_string() }) .expect("failed to serialize error response"); self.out.send(response) diff --git a/server/src/rpc.rs b/server/src/rpc.rs index dc716d31..4eca5a9b 100755 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -5,7 +5,7 @@ use failure::err_msg; use net::Db; use cryp::{Cryp, spawn}; -use user::{User, create, login}; +use user::{User, create, login, from_token}; pub struct Rpc; @@ -18,17 +18,27 @@ impl Rpc { match from_slice::(&data) { Ok(v) => { - println!("{:?}", v.token); + let user: Option = match v.token { + Some(t) => Some(from_token(t, &db)?), + None => None, + }; + + println!("{:?}", user); // now we have the method name // match on that to determine what fn to call match v.method.as_ref() { "cryp_spawn" => { match from_slice::(&data) { - Ok(v) => Ok(RpcResponse { - method: v.method, - params: RpcResult::Cryp(spawn(v.params)) - }), + Ok(v) => { + match user { + Some(u) => Ok(RpcResponse { + method: v.method, + params: RpcResult::SpawnCryp(spawn(v.params, db, u)?) + }), + None => Err(err_msg("auth required")), + } + } Err(_e) => Err(err_msg("invalid params")), } }, @@ -67,7 +77,7 @@ pub struct RpcResponse { #[derive(Debug,Clone,Serialize,Deserialize)] pub enum RpcResult { - Cryp(Cryp), + SpawnCryp(Cryp), User(User), } @@ -85,7 +95,7 @@ struct CrypSpawnMsg { #[derive(Debug,Clone,Serialize,Deserialize)] pub struct CrypSpawnParams { - pub level: u8, + pub name: String, } #[derive(Debug,Clone,Serialize,Deserialize)] diff --git a/server/src/user.rs b/server/src/user.rs index 086675c7..3b7f6403 100755 --- a/server/src/user.rs +++ b/server/src/user.rs @@ -12,10 +12,12 @@ use rpc::{UserCreateParams, UserLoginParams, RpcResult}; use failure::Error; use failure::err_msg; +static PASSWORD_MIN_LEN: usize = 12; + #[derive(Debug,Clone,Serialize,Deserialize)] pub struct User { - id: Uuid, - name: String, + pub id: Uuid, + pub name: String, token: String, } @@ -27,7 +29,31 @@ struct UserEntry { token: String, } -static PASSWORD_MIN_LEN: usize = 12; +// MAYBE +// hash tokens with a secret +pub fn from_token(token: String, db: &Db) -> Result { + 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 { let id = Uuid::new_v4(); @@ -73,7 +99,7 @@ pub fn create(params: UserCreateParams, db: Db) -> Result { tx.commit()?; - Ok(RpcResult::User(entry)) + return Ok(RpcResult::User(entry)); } pub fn login(params: UserLoginParams, db: Db) -> Result { @@ -83,13 +109,13 @@ pub fn login(params: UserLoginParams, db: Db) -> Result { WHERE name = $1; "; - // let tx = db.transaction()?; - let result = db .query(query, &[¶ms.name])?; let returned = match result.iter().next() { Some(row) => row, + // MAYBE + // verify gibberish to delay response for timing attacks None => return Err(err_msg("user not found")), }; @@ -110,13 +136,11 @@ pub fn login(params: UserLoginParams, db: Db) -> Result { // update token? // don't necessarily want to log every session out when logging in - // tx.commit()?; - let user = User { id: entry.id, name: entry.name, token: entry.token, }; - Ok(RpcResult::User(user)) -} + return Ok(RpcResult::User(user)); +} \ No newline at end of file