diff --git a/client/src/components/login.jsx b/client/src/components/login.jsx index 2d76d3ad..34737640 100644 --- a/client/src/components/login.jsx +++ b/client/src/components/login.jsx @@ -3,38 +3,22 @@ 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 { postData } = require('../utils'); const addState = connect( - null, - (dispatch) => { + (state) => { + const { + ws + } = state; function submitLogin(name, password) { - postData(`${SERVER}/login`, { name, password }) - .then(data => console.log(JSON.stringify(data))) + postData('/login', { name, password }) + .then(data => ws.connect()) .catch(error => console.error(error)); } function submitRegister(name, password, code) { - postData(`${SERVER}/register`, { name, password, code }) - .then(data => console.log(JSON.stringify(data))) + postData('/register', { name, password, code }) + .then(data => ws.connect()) .catch(error => console.error(error)); } diff --git a/client/src/components/nav.jsx b/client/src/components/nav.jsx index 931fa328..57d1665e 100644 --- a/client/src/components/nav.jsx +++ b/client/src/components/nav.jsx @@ -1,8 +1,9 @@ const { connect } = require('preact-redux'); const preact = require('preact'); const { Fragment } = require('preact'); -const actions = require('../actions'); +const { postData } = require('../utils'); +const actions = require('../actions'); const { saw } = require('./shapes'); const testGame = process.env.NODE_ENV === 'development' && require('./../test.game'); @@ -38,6 +39,10 @@ const addState = connect( return ws.sendInstanceList(); } + function logout() { + postData('/logout').then(() => window.location.reload(true)); + } + return { account, instances, @@ -47,6 +52,7 @@ const addState = connect( sendInstanceState, sendAccountInstances, sendInstanceList, + logout, }; }, function receiveDispatch(dispatch) { @@ -97,6 +103,7 @@ function Nav(args) { sendInstanceState, sendAccountInstances, sendInstanceList, + logout, setTestGame, setTestInstance, @@ -133,6 +140,7 @@ function Nav(args) {

Hax

+ ) : null; diff --git a/client/src/utils.jsx b/client/src/utils.jsx index 3b506704..5c328a34 100644 --- a/client/src/utils.jsx +++ b/client/src/utils.jsx @@ -345,6 +345,24 @@ const TARGET_COLOURS = { BROWN: '#583108', }; +const SERVER = process.env.NODE_ENV === 'production' ? '/' : 'http://localhost:40000'; +function postData(url = '/', data = {}) { + // Default options are marked with * + return fetch(`${SERVER}${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 + }) +} + module.exports = { stringSort, convertItem, @@ -352,6 +370,8 @@ module.exports = { eventClasses, getCombatSequence, getCombatText, + postData, + SERVER, NULL_UUID, STATS, COLOURS, diff --git a/ops/migrations/20180913000513_create_accounts.js b/ops/migrations/20180913000513_create_accounts.js index 675bfc7c..d0699f89 100755 --- a/ops/migrations/20180913000513_create_accounts.js +++ b/ops/migrations/20180913000513_create_accounts.js @@ -4,7 +4,7 @@ exports.up = async knex => { table.timestamps(true, true); table.string('name', 42).notNullable().unique(); table.string('password').notNullable(); - table.string('token', 64); + table.string('token', 64).notNullable(); table.timestamp('token_expiry'); table.index('name'); diff --git a/server/src/account.rs b/server/src/account.rs index 3b85e4da..3a01285a 100644 --- a/server/src/account.rs +++ b/server/src/account.rs @@ -99,7 +99,6 @@ pub fn account_login(name: &String, password: &String, tx: &mut Transaction) -> SELECT id, password, name FROM accounts WHERE name = $1 - RETURNING id, name; "; let result = tx @@ -134,7 +133,7 @@ pub fn account_login(name: &String, password: &String, tx: &mut Transaction) -> account_set_token(tx, &account) } -fn account_set_token(tx: &mut Transaction, account: &Account) -> Result { +pub fn account_set_token(tx: &mut Transaction, account: &Account) -> Result { let mut rng = thread_rng(); let token: String = iter::repeat(()) .map(|()| rng.sample(Alphanumeric)) diff --git a/server/src/net.rs b/server/src/net.rs index 0f4973df..dd9905e0 100644 --- a/server/src/net.rs +++ b/server/src/net.rs @@ -17,7 +17,7 @@ use r2d2_postgres::{TlsMode, PostgresConnectionManager}; use rpc::{receive, RpcResult, RpcErrorResponse, AccountLoginParams, AccountCreateParams}; use warden::{warden}; -use account::{Account, account_login, account_create, account_from_token}; +use account::{Account, account_login, account_create, account_from_token, account_set_token}; pub type Db = PooledConnection; type PgPool = Pool; @@ -174,6 +174,17 @@ fn token_res(token: String, secure: bool) -> HttpResponse { .finish() } +fn token_clear() -> HttpResponse { + HttpResponse::Ok() + .cookie(Cookie::build("x-auth-token", "") + // .secure(secure) + .http_only(true) + .same_site(SameSite::Strict) + .max_age(-1) // 1 week aligns with db set + .finish()) + .finish() +} + fn login(state: web::Data, params: web::Json::) -> Result { let db = state.pool.get().or(Err(MnmlError::ServerError))?; let mut tx = db.transaction().or(Err(MnmlError::ServerError))?; @@ -190,6 +201,24 @@ fn login(state: web::Data, params: web::Json::) -> Re } } +fn logout(r: HttpRequest, state: web::Data) -> Result { + match r.cookie("x-auth-token") { + Some(t) => { + let db = state.pool.get().or(Err(MnmlError::ServerError))?; + let mut tx = db.transaction().or(Err(MnmlError::ServerError))?; + match account_from_token(t.value().to_string(), &mut tx) { + Ok(a) => { + account_set_token(&mut tx, &a).or(Err(MnmlError::Unauthorized))?; + tx.commit().or(Err(MnmlError::ServerError))?; + return Ok(token_clear()); + }, + Err(_) => Err(MnmlError::Unauthorized), + } + }, + None => Err(MnmlError::Unauthorized), + } +} + fn register(state: web::Data, params: web::Json::) -> Result { let db = state.pool.get().or(Err(MnmlError::ServerError))?; let mut tx = db.transaction().or(Err(MnmlError::ServerError))?; @@ -235,6 +264,7 @@ pub fn start() { .wrap(middleware::Logger::default()) .wrap(Cors::new().supports_credentials()) .service(web::resource("/login").route(web::post().to(login))) + .service(web::resource("/logout").route(web::post().to(logout))) .service(web::resource("/register").route(web::post().to(register))) .service(web::resource("/ws/").route(web::get().to(connect)))) .bind("127.0.0.1:40000").expect("could not bind to port") @@ -245,6 +275,7 @@ pub fn start() { .data(State { pool: pool.clone(), secure: true }) .wrap(middleware::Logger::default()) .service(web::resource("/login").route(web::post().to(login))) + .service(web::resource("/logout").route(web::post().to(logout))) .service(web::resource("/register").route(web::post().to(register))) .service(web::resource("/ws/").route(web::get().to(connect)))) .bind("127.0.0.1:40000").expect("could not bind to port")