diff --git a/routes.json b/routes.json index a7d69df..d6b6395 100644 --- a/routes.json +++ b/routes.json @@ -1,8 +1,11 @@ { - "/": "static/html/index.html", - "/results": "static/html/results.html", - "/archives": "static/html/archives.html", - "/favicon.ico": "static/img/favicon.ico", - "/login": "static/html/login.html", - "/register": "static/html/register.html" -} + "/": {"file": "static/html/index.html", "permission": 0}, + "/results": {"file": "static/html/results.html", "permission": 1}, + "/archives": {"file": "static/html/archives.html", "permission": 2}, + "/favicon.ico": {"file": "static/img/favicon.ico", "permission": 0}, + "/login": {"file": "static/html/login.html", "permission": 0}, + "/register": {"file": "static/html/register.html", "permission": 0}, + "/logout": {"file": "static/html/logout.html", "permission": 0}, + "/unauthorised": {"file": "static/html/unauthorised.html", "permission": 0}, + "/admin" : {"file": "static/html/admin.html", "permission": 3} +} \ No newline at end of file diff --git a/settings.json b/settings.json index 1cf14dd..70b86c4 100644 --- a/settings.json +++ b/settings.json @@ -1,4 +1,4 @@ { "database_url": "sqlite:vote.db", - "bind_address": "127.0.0.1:8080" + "bind_address": "127.0.0.1:8000" } diff --git a/src/main.rs b/src/main.rs index c6e9376..58240a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use daemonize::Daemonize; use futures; use http_body_util::{BodyExt, Full}; use hyper::body::{Body, Incoming}; -use hyper::header::{LOCATION, SET_COOKIE}; +use hyper::header::{LOCATION, SET_COOKIE, COOKIE}; use hyper::server::conn::http1; use hyper::service::service_fn; use hyper::{Error, Method, Request, Response, StatusCode}; @@ -30,6 +30,7 @@ use std::net::SocketAddr; use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::SystemTime; +use hyper::http::HeaderValue; use tokio::net::TcpListener; #[derive(Serialize, Deserialize)] struct Player { @@ -79,20 +80,27 @@ async fn get(req: Request, db: Arc>) -> Result Result>, Error> { +async fn get_page(req: &Request, path: &str, db: Arc>) -> Result>, Error> { let mut routes = env::current_dir().expect("Could not get app directory (Required to get routes)"); routes.push("routes.json"); let file = File::open(routes).expect("Could not open routes file."); let map: Value = from_reader(file).expect("Could not parse routes, please verify syntax."); - match &map[path] { - Value::String(s) => get_file(&s).await, + match map.get(path) { + Some(Value::Object(s)) => { + let perm = is_authorised(req, db).await; + if s.get("permission").unwrap().as_i64().unwrap() <= perm { + get_file(s.get("file").unwrap().as_str().unwrap()).await + } else { + get_file(map.get("/unauthorised").unwrap().get("file").unwrap().as_str().unwrap()).await + } + }, _ => not_found().await } } @@ -185,10 +193,8 @@ async fn get_votes(req: &Request, db: Arc>) -> Vec, db: Arc>) -> Vec, path: &str, db: Arc>) -> Result>, Error> { + let perm = is_authorised(req, db.clone()).await; + if perm < 3 { + return not_found().await; + } + if path == "/admin" { + return get_page(req, path, db).await; + } + if path == "/admin/users" { + let pool = db.clone().lock().unwrap().clone(); + let users = sqlx::query!(r#"SELECT username, permissions FROM users"#).fetch_all(&pool).await.unwrap(); + let users: Vec<(String, i64)> = users.iter().map(|x| (x.username.clone(), x.permissions)).collect(); + let stringed = serde_json::to_string(&users).unwrap_or("".to_string()); + return Ok(Response::builder().body(Full::new(Bytes::from(stringed))).unwrap()); + } + not_found().await +} + async fn post(req: Request, db: Arc>) -> Result>, Error> { let path = req.uri().path(); match path { @@ -211,6 +235,9 @@ async fn post(req: Request, db: Arc>) -> Result { register(req, db).await } + "/logout" => { + logout().await + } _ => { not_found().await } @@ -254,8 +281,7 @@ async fn login(req: Request, db: Arc>) -> Result { let argon = Argon2::default(); @@ -268,6 +294,7 @@ async fn login(req: Request, db: Arc>) -> Result, db: Arc>) -> Result< let argon2 = Argon2::default(); let hash = argon2.hash_password(data.password.as_bytes(), &SaltString::generate(&mut OsRng)).unwrap().to_string(); let token = Alphanumeric.sample_string(&mut OsRng, 256); - println!("{}", token); let result = sqlx::query!(r#"INSERT INTO users ( username, saltyhash, permissions, token) VALUES ( ?1, ?2, ?3, ?4 )"#, data.username, hash, 0, token).execute(&mut *conn).await; match result { Ok(_) => Ok(Response::builder().body(Full::new(Bytes::from(""))).unwrap()), @@ -316,12 +342,40 @@ async fn register(req: Request, db: Arc>) -> Result< async fn logout() -> Result>, Error> { let date: DateTime = DateTime::from(SystemTime::now()); Ok(Response::builder() - .status(StatusCode::SEE_OTHER) - .header(LOCATION, "/") + //.status(StatusCode::SEE_OTHER) + //.header(LOCATION, "/") .header(SET_COOKIE, format!("token=''; Expires={}; Secure; HttpOnly; SameSite=Strict", date.to_rfc2822())) + .header(SET_COOKIE, format!("logged=false; Expires={}; Secure; HttpOnly; SameSite=Strict", date.to_rfc2822())) .body(Full::new(Bytes::from(""))).unwrap()) } +async fn is_authorised(req: &Request, db: Arc>) -> i64 { + let cookies = req.headers().get(COOKIE); + let token = match cookies { + Some(cookies) => { + cookies + .to_str() + .unwrap_or("") + .split("; ") + .find(|x| x.starts_with("token=")) + .unwrap_or("") + .strip_prefix("token=") + .unwrap_or("")}, + None => "" + }; + let pool = db.clone().lock().unwrap().clone(); + let user = sqlx::query!(r#"SELECT permissions FROM users WHERE token=?1"#, token).fetch_optional(&pool).await; + match user { + Ok(user) => { + match user { + Some(user) => user.permissions, + None => 0 + } + }, + Err(_) => 0 + } +} + fn check_username(username: &String) -> bool { if username.len() > 21 { return false;