diff options
author | Kjetil Orbekk <kjetil.orbekk@gmail.com> | 2017-07-13 07:28:42 -0400 |
---|---|---|
committer | Kjetil Orbekk <kjetil.orbekk@gmail.com> | 2017-07-13 07:28:42 -0400 |
commit | fe6d45474a2024fb362ee59f7a38f827283ac0c4 (patch) | |
tree | cc09092fac8bca86372939cabc61220b1e0e303c /src | |
parent | ba1ab61901e657903a34b0b0acecb81c12b5e0d0 (diff) |
add: Persistent user id.
This will be used to limit users to one vote per quote.
Diffstat (limited to 'src')
-rw-r--r-- | src/data.rs | 31 | ||||
-rw-r--r-- | src/data/templates/base.hbs | 1 | ||||
-rw-r--r-- | src/error.rs | 10 | ||||
-rw-r--r-- | src/lib.rs | 2 | ||||
-rw-r--r-- | src/main.rs | 23 | ||||
-rw-r--r-- | src/server.rs | 58 |
6 files changed, 106 insertions, 19 deletions
diff --git a/src/data.rs b/src/data.rs index 5674f92..a5d7afb 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,5 +1,6 @@ use rusqlite::Connection; use error::{Result, LinoError}; +use rand::{OsRng, Rng}; #[derive(Serialize, Debug, Clone)] pub struct Quote { @@ -31,8 +32,27 @@ pub fn init(c: &Connection) -> Result<()> { score INTEGER NOT NULL, FOREIGN KEY(quote_id) REFERENCES quotes(id) ); + + CREATE TABLE IF NOT EXISTS keys ( + id TEXT PRIMARY KEY, + value BLOB NOT NULL + ); "#, )?; + + let key = { + let mut v = vec![0u8; 32]; + (OsRng::new()?).fill_bytes(v.as_mut()); + v + }; + c.execute( + r#" + INSERT OR IGNORE INTO keys (id, value) VALUES + ('session', ?1); + "#, + &[&key], + )?; + Ok(()) } @@ -56,6 +76,17 @@ pub fn populate_test_db(c: &Connection) -> Result<()> { Ok(()) } +pub fn get_key(c: &Connection, id: &str) -> Result<Vec<u8>> { + let key = c.query_row( + r#" + SELECT value FROM keys WHERE id = ?1 + "#, + &[&id], + |row| row.get(0), + )?; + Ok(key) +} + pub fn new_quote(c: &Connection, date: &str, author: &str, content: &str) -> Result<()> { c.execute( r#" diff --git a/src/data/templates/base.hbs b/src/data/templates/base.hbs index d548965..db7bb9e 100644 --- a/src/data/templates/base.hbs +++ b/src/data/templates/base.hbs @@ -67,6 +67,7 @@ <p>linoquotes v.3 © 2004-2017 Kjetil Ørbekk, Erlend Hamberg, Vidar Holen, John H. Anthony. <p>Source code at <a href="https://git.orbekk.com/linoquotes-gamma.git">https://git.orbekk.com/linoquotes-gamma.git</a>. <p>The quotes on this page are copyright their respective owners and submitters.</p> +<p>Your user id: {{user_id}}. </center> </body> diff --git a/src/error.rs b/src/error.rs index 41b4e64..1005564 100644 --- a/src/error.rs +++ b/src/error.rs @@ -9,6 +9,7 @@ use iron::modifiers::Header; #[derive(Debug)] pub enum LinoError { DbError(rusqlite::Error), + IoError(std::io::Error), NotFound(String), BadRequest(String), } @@ -19,6 +20,7 @@ impl fmt::Display for LinoError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { LinoError::DbError(ref err) => err.fmt(f), + LinoError::IoError(ref err) => err.fmt(f), LinoError::NotFound(ref x) => write!(f, "Could not find {}", x), LinoError::BadRequest(ref x) => write!(f, "Bad request: {}", x), } @@ -29,6 +31,7 @@ impl std::error::Error for LinoError { fn description(&self) -> &str { match *self { LinoError::DbError(ref err) => err.description(), + LinoError::IoError(ref err) => err.description(), LinoError::NotFound(_) => "not found", LinoError::BadRequest(_) => "bad request", } @@ -37,6 +40,7 @@ impl std::error::Error for LinoError { fn cause(&self) -> Option<&std::error::Error> { match *self { LinoError::DbError(ref err) => Some(err), + LinoError::IoError(ref err) => Some(err), LinoError::NotFound(_) => None, LinoError::BadRequest(_) => None, } @@ -49,6 +53,12 @@ impl From<rusqlite::Error> for LinoError { } } +impl From<std::io::Error> for LinoError { + fn from(err: std::io::Error) -> LinoError { + LinoError::IoError(err) + } +} + impl From<LinoError> for IronError { fn from(err: LinoError) -> IronError { let code = match err { @@ -13,6 +13,8 @@ extern crate persistent; extern crate params; extern crate chrono; extern crate logger; +extern crate rand; +extern crate iron_sessionstorage; pub mod server; pub mod data; diff --git a/src/main.rs b/src/main.rs index 026f610..e687255 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,17 +62,18 @@ fn main() { linoquotes_gamma::data::populate_test_db(&state.connection).unwrap(); } - for i in 1..1000 { - linoquotes_gamma::data::new_quote( - &state.connection, - "2017-07-10", - "orbekk", - &format!("another test {}", i), - ).unwrap(); - let qid = state.connection.last_insert_rowid(); - info!("Last inserted quote: {}", qid); - linoquotes_gamma::data::approve_quote(&state.connection, qid).unwrap(); - } + // for i in 1..1000 { + // linoquotes_gamma::data::new_quote( + // &state.connection, + // "2017-07-10", + // "orbekk", + // &format!("another test {}", i), + // ).unwrap(); + // let qid = state.connection.last_insert_rowid(); + // info!("Last inserted quote: {}", qid); + // linoquotes_gamma::data::approve_quote(&state.connection, qid).unwrap(); + // } + info!("Starting..."); linoquotes_gamma::server::serve(state, port); } diff --git a/src/server.rs b/src/server.rs index 4816c33..54beb34 100644 --- a/src/server.rs +++ b/src/server.rs @@ -8,11 +8,14 @@ use rusqlite::Connection; use std::collections::BTreeMap; use persistent::Write; use handlebars_iron::handlebars::to_json; -use serde_json::Map; +use serde_json; use params; use error::LinoError; use chrono; use logger::Logger; +use iron_sessionstorage::{self, SessionStorage, SessionRequestExt}; +use iron_sessionstorage::backends::SignedCookieBackend; +use rand::{OsRng, Rng}; #[derive(Debug)] pub struct State { @@ -22,6 +25,41 @@ impl iron::typemap::Key for State { type Value = State; } +#[derive(Debug, Clone, Serialize)] +struct UserId(u64); +impl iron_sessionstorage::Value for UserId { + fn get_key() -> &'static str { + "user_id" + } + fn into_raw(self) -> String { + format!("{}", self.0) + } + fn from_raw(value: String) -> Option<Self> { + value.parse().ok().map(|i| UserId(i)) + } +} + +fn user_id(r: &mut Request) -> IronResult<UserId> { + if let Some(user_id) = r.session().get::<UserId>()? { + return Ok(user_id); + } + + let mut rng = OsRng::new().map_err(|e| { + // This is ugly, but I don't know how to annotate the expression. + let e: LinoError = From::from(e); + e + })?; + let user_id = UserId(rng.next_u64()); + r.session().set(user_id.clone())?; + Ok(user_id) +} + +fn make_result(r: &mut Request) -> IronResult<serde_json::Map<String, serde_json::value::Value>> { + let mut result = serde_json::Map::new(); + result.insert("user_id".to_string(), to_json(&user_id(r)?)); + Ok(result) +} + fn get_param(r: &mut Request, param: &str) -> IronResult<String> { let map = itry!(r.get_ref::<params::Params>()); match map.get(param) { @@ -67,7 +105,7 @@ fn make_renderer() -> HandlebarsEngine { } fn quotes(r: &mut Request) -> IronResult<Response> { - let mut result = Map::new(); + let mut result = make_result(r)?; let quote_id = get_param(r, "id").ok().and_then( |id| id.parse::<i64>().ok(), ); @@ -90,9 +128,9 @@ fn quotes(r: &mut Request) -> IronResult<Response> { ))) } -fn add_get(_r: &mut Request) -> IronResult<Response> { +fn add_get(r: &mut Request) -> IronResult<Response> { let date = chrono::offset::Local::now().format("%Y-%m-%d").to_string(); - let mut result = Map::new(); + let mut result = make_result(r)?; result.insert("date".to_string(), to_json(&date)); Ok(Response::with(( status::Ok, @@ -102,6 +140,7 @@ fn add_get(_r: &mut Request) -> IronResult<Response> { } fn add_post(r: &mut Request) -> IronResult<Response> { + let result = make_result(r)?; let nick = get_param(r, "nick")?; let date = get_param(r, "date")?; let quote = get_param(r, "quote")?; @@ -127,12 +166,12 @@ fn add_post(r: &mut Request) -> IronResult<Response> { Ok(Response::with(( status::Ok, Header(ContentType::html()), - Template::new("add_post", Map::new()), + Template::new("add_post", result) ))) } fn approve(r: &mut Request) -> IronResult<Response> { - let mut result = Map::new(); + let mut result = make_result(r)?; let quote_id = get_param(r, "id").ok().and_then( |id| id.parse::<i64>().ok(), ); @@ -185,7 +224,7 @@ pub fn vote(r: &mut Request) -> IronResult<Response> { data::get_quote(&state.connection, quote_id)? }; - let mut result = Map::new(); + let mut result = make_result(r)?; result.insert("quote".to_string(), to_json("e)); Ok(Response::with(( @@ -206,13 +245,16 @@ pub fn serve(state: State, port: u16) { approve: get "/approve.jsp" => approve, vote: get "/vote" => vote, ); - let (logger_before, logger_after) = Logger::new(None); let mut chain = Chain::new(router); + let (logger_before, logger_after) = Logger::new(None); chain.link_before(logger_before); + let key = data::get_key(&state.connection, "session").expect("session key"); + chain.link_around(SessionStorage::new(SignedCookieBackend::new(key))); chain.link_after(make_renderer()); chain.link(Write::<State>::both(state)); chain.link_after(logger_after); + let bind_address = format!("{}:{}", "::", port); let _server = Iron::new(chain).http(bind_address.as_str()); info!("Serving on {}", bind_address); |