From eabeb7ed7b641dea0b8e71ab33ab97b4ed7a4cda Mon Sep 17 00:00:00 2001 From: Ilion Beyst Date: Mon, 13 Dec 2021 22:41:20 +0100 Subject: start implementing basic login functionality --- backend/src/routes/mod.rs | 1 + backend/src/routes/users.rs | 100 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 backend/src/routes/mod.rs create mode 100644 backend/src/routes/users.rs (limited to 'backend/src/routes') diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs new file mode 100644 index 0000000..913bd46 --- /dev/null +++ b/backend/src/routes/mod.rs @@ -0,0 +1 @@ +pub mod users; diff --git a/backend/src/routes/users.rs b/backend/src/routes/users.rs new file mode 100644 index 0000000..274b712 --- /dev/null +++ b/backend/src/routes/users.rs @@ -0,0 +1,100 @@ +use crate::db::{sessions, users}; +use crate::{ + db::users::{Credentials, User}, + DbConn, +}; +use rocket::serde::json::Json; +use serde::{Deserialize, Serialize}; + +use rocket::http::Status; +use rocket::request::{self, FromRequest, Outcome, Request}; + +#[derive(Debug)] +pub enum AuthTokenError { + BadCount, + Missing, + Invalid, +} + +// TODO: error handling and proper lifetimes +#[rocket::async_trait] +impl<'r> FromRequest<'r> for User { + type Error = AuthTokenError; + + async fn from_request(request: &'r Request<'_>) -> Outcome { + let keys: Vec<_> = request.headers().get("Authorization").collect(); + let token = match keys.len() { + 0 => return Outcome::Failure((Status::BadRequest, AuthTokenError::Missing)), + 1 => keys[0].to_string(), + _ => return Outcome::Failure((Status::BadRequest, AuthTokenError::BadCount)), + }; + let db = request.guard::().await.unwrap(); + let (_session, user) = db + .run(move |conn| sessions::find_user_by_session(&token, conn)) + .await + .unwrap(); + Outcome::Success(user) + } +} + +#[derive(Serialize, Deserialize)] +pub struct UserData { + pub user_id: i32, + pub username: String, +} + +impl From for UserData { + fn from(user: User) -> Self { + UserData { + user_id: user.user_id, + username: user.username, + } + } +} + +#[derive(Deserialize)] +pub struct RegistrationParams { + pub username: String, + pub password: String, +} + +#[post("/register", data = "")] +pub async fn register(db_conn: DbConn, params: Json) -> Json { + db_conn + .run(move |conn| { + let credentials = Credentials { + username: ¶ms.username, + password: ¶ms.password, + }; + let user = users::create_user(&credentials, conn).unwrap(); + Json(user.into()) + }) + .await +} + +#[derive(Deserialize)] +pub struct LoginParams { + pub username: String, + pub password: String, +} + +#[post("/login", data = "")] +pub async fn login(db_conn: DbConn, params: Json) -> String { + db_conn + .run(move |conn| { + let credentials = Credentials { + username: ¶ms.username, + password: ¶ms.password, + }; + // TODO: handle failures + let user = users::authenticate_user(&credentials, conn).unwrap(); + let session = sessions::create_session(&user, conn); + return session.token; + }) + .await +} + +#[get("/users/me")] +pub async fn current_user(user: User) -> Json { + Json(user.into()) +} -- cgit v1.2.3 From 6aa72b3c8717f32e62c772aeed327d3cd9a6fa65 Mon Sep 17 00:00:00 2001 From: Ilion Beyst Date: Wed, 15 Dec 2021 22:40:55 +0100 Subject: gracefully handle invalid login credentials --- backend/src/routes/users.rs | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) (limited to 'backend/src/routes') diff --git a/backend/src/routes/users.rs b/backend/src/routes/users.rs index 274b712..72a857f 100644 --- a/backend/src/routes/users.rs +++ b/backend/src/routes/users.rs @@ -7,7 +7,8 @@ use rocket::serde::json::Json; use serde::{Deserialize, Serialize}; use rocket::http::Status; -use rocket::request::{self, FromRequest, Outcome, Request}; +use rocket::request::{FromRequest, Outcome, Request}; +use rocket::response::status; #[derive(Debug)] pub enum AuthTokenError { @@ -23,17 +24,25 @@ impl<'r> FromRequest<'r> for User { async fn from_request(request: &'r Request<'_>) -> Outcome { let keys: Vec<_> = request.headers().get("Authorization").collect(); - let token = match keys.len() { + let auth_header = match keys.len() { 0 => return Outcome::Failure((Status::BadRequest, AuthTokenError::Missing)), - 1 => keys[0].to_string(), + 1 => keys[0], _ => return Outcome::Failure((Status::BadRequest, AuthTokenError::BadCount)), }; + + let token = match auth_header.strip_prefix("Bearer ") { + Some(token) => token.to_string(), + None => return Outcome::Failure((Status::BadRequest, AuthTokenError::Invalid)), + }; + let db = request.guard::().await.unwrap(); - let (_session, user) = db + let res = db .run(move |conn| sessions::find_user_by_session(&token, conn)) - .await - .unwrap(); - Outcome::Success(user) + .await; + match res { + Ok((_session, user)) => Outcome::Success(user), + Err(_) => Outcome::Failure((Status::Unauthorized, AuthTokenError::Invalid)), + } } } @@ -79,7 +88,10 @@ pub struct LoginParams { } #[post("/login", data = "")] -pub async fn login(db_conn: DbConn, params: Json) -> String { +pub async fn login( + db_conn: DbConn, + params: Json, +) -> Result> { db_conn .run(move |conn| { let credentials = Credentials { @@ -87,9 +99,15 @@ pub async fn login(db_conn: DbConn, params: Json) -> String { password: ¶ms.password, }; // TODO: handle failures - let user = users::authenticate_user(&credentials, conn).unwrap(); - let session = sessions::create_session(&user, conn); - return session.token; + let authenticated = users::authenticate_user(&credentials, conn); + + match authenticated { + None => Err(status::Forbidden(Some("invalid auth"))), + Some(user) => { + let session = sessions::create_session(&user, conn); + Ok(session.token) + } + } }) .await } -- cgit v1.2.3 From 52242b03f1af7f73e73592c2e5ee2bc54813a64d Mon Sep 17 00:00:00 2001 From: Ilion Beyst Date: Sun, 19 Dec 2021 00:16:46 +0100 Subject: simple bot uploads --- backend/src/routes/bots.rs | 96 +++++++++++++++++++++++++++++++++++++++++++++ backend/src/routes/mod.rs | 1 + backend/src/routes/users.rs | 2 +- 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 backend/src/routes/bots.rs (limited to 'backend/src/routes') diff --git a/backend/src/routes/bots.rs b/backend/src/routes/bots.rs new file mode 100644 index 0000000..413c145 --- /dev/null +++ b/backend/src/routes/bots.rs @@ -0,0 +1,96 @@ +use rand::Rng; +use rocket::data::ToByteUnit; +use rocket::fs::TempFile; +use rocket::Data; +use rocket::{response::status, serde::json::Json}; +use serde::{Deserialize, Serialize}; +use std::io::Cursor; +use std::path::Path; + +use crate::DbConn; + +use crate::db::bots::{self, CodeBundle}; +use crate::db::users::User; +use bots::Bot; + +#[derive(Serialize, Deserialize, Debug)] +pub struct BotParams { + name: String, +} + +// TODO: handle errors +#[post("/bots", data = "")] +pub async fn create_bot( + db_conn: DbConn, + user: User, + params: Json, +) -> status::Created> { + db_conn + .run(move |conn| { + let bot_params = bots::NewBot { + owner_id: user.id, + name: ¶ms.name, + }; + let bot = bots::create_bot(&bot_params, conn).unwrap(); + let bot_url = uri!(get_bot(bot.id)).to_string(); + status::Created::new(bot_url).body(Json(bot)) + }) + .await +} + +// TODO: handle errors +#[get("/bots/")] +pub async fn get_bot(db_conn: DbConn, bot_id: i32) -> Json { + db_conn + .run(move |conn| { + let bot = bots::find_bot(bot_id, conn).unwrap(); + Json(bot) + }) + .await +} + +// TODO: proper error handling +#[post("/bots//upload", data = "")] +pub async fn upload_bot_code( + db_conn: DbConn, + user: User, + bot_id: i32, + data: Data<'_>, +) -> status::Created> { + // TODO: put in config somewhere + let data_path = "./data/bots"; + + let bot = db_conn + .run(move |conn| bots::find_bot(bot_id, conn)) + .await + .expect("Bot not found"); + + assert_eq!(user.id, bot.owner_id); + + // generate a random filename + let token: [u8; 16] = rand::thread_rng().gen(); + let name = base64::encode(&token); + + let path = Path::new(data_path).join(name); + let capped_buf = data.open(10usize.megabytes()).into_bytes().await.unwrap(); + assert!(capped_buf.is_complete()); + let buf = capped_buf.into_inner(); + + zip::ZipArchive::new(Cursor::new(buf)) + .unwrap() + .extract(&path) + .unwrap(); + + let code_bundle = db_conn + .run(move |conn| { + let bundle = bots::NewCodeBundle { + bot_id: bot.id, + path: path.to_str().unwrap(), + }; + bots::create_code_bundle(&bundle, conn).expect("Failed to create code bundle") + }) + .await; + + // TODO: proper location + status::Created::new("").body(Json(code_bundle)) +} diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs index 913bd46..718d7ef 100644 --- a/backend/src/routes/mod.rs +++ b/backend/src/routes/mod.rs @@ -1 +1,2 @@ +pub mod bots; pub mod users; diff --git a/backend/src/routes/users.rs b/backend/src/routes/users.rs index 72a857f..45a94b9 100644 --- a/backend/src/routes/users.rs +++ b/backend/src/routes/users.rs @@ -55,7 +55,7 @@ pub struct UserData { impl From for UserData { fn from(user: User) -> Self { UserData { - user_id: user.user_id, + user_id: user.id, username: user.username, } } -- cgit v1.2.3 From 1fb4a5151bd8cfe6de4d8c19e2066a9281a0b61a Mon Sep 17 00:00:00 2001 From: Ilion Beyst Date: Wed, 29 Dec 2021 16:11:27 +0100 Subject: migrate to axum --- backend/src/routes/bots.rs | 87 +++++++++++++------------------- backend/src/routes/users.rs | 118 ++++++++++++++++++-------------------------- 2 files changed, 80 insertions(+), 125 deletions(-) (limited to 'backend/src/routes') diff --git a/backend/src/routes/bots.rs b/backend/src/routes/bots.rs index 413c145..da09669 100644 --- a/backend/src/routes/bots.rs +++ b/backend/src/routes/bots.rs @@ -1,16 +1,14 @@ +use axum::extract::{Path, RawBody}; +use axum::http::StatusCode; +use axum::Json; use rand::Rng; -use rocket::data::ToByteUnit; -use rocket::fs::TempFile; -use rocket::Data; -use rocket::{response::status, serde::json::Json}; use serde::{Deserialize, Serialize}; use std::io::Cursor; -use std::path::Path; - -use crate::DbConn; +use std::path; use crate::db::bots::{self, CodeBundle}; use crate::db::users::User; +use crate::DatabaseConnection; use bots::Bot; #[derive(Serialize, Deserialize, Debug)] @@ -18,52 +16,36 @@ pub struct BotParams { name: String, } -// TODO: handle errors -#[post("/bots", data = "")] pub async fn create_bot( - db_conn: DbConn, + conn: DatabaseConnection, user: User, params: Json, -) -> status::Created> { - db_conn - .run(move |conn| { - let bot_params = bots::NewBot { - owner_id: user.id, - name: ¶ms.name, - }; - let bot = bots::create_bot(&bot_params, conn).unwrap(); - let bot_url = uri!(get_bot(bot.id)).to_string(); - status::Created::new(bot_url).body(Json(bot)) - }) - .await +) -> (StatusCode, Json) { + let bot_params = bots::NewBot { + owner_id: user.id, + name: ¶ms.name, + }; + let bot = bots::create_bot(&bot_params, &conn).unwrap(); + (StatusCode::CREATED, Json(bot)) } // TODO: handle errors -#[get("/bots/")] -pub async fn get_bot(db_conn: DbConn, bot_id: i32) -> Json { - db_conn - .run(move |conn| { - let bot = bots::find_bot(bot_id, conn).unwrap(); - Json(bot) - }) - .await +pub async fn get_bot(conn: DatabaseConnection, Path(bot_id): Path) -> Json { + let bot = bots::find_bot(bot_id, &conn).unwrap(); + Json(bot) } // TODO: proper error handling -#[post("/bots//upload", data = "")] pub async fn upload_bot_code( - db_conn: DbConn, + conn: DatabaseConnection, user: User, - bot_id: i32, - data: Data<'_>, -) -> status::Created> { + Path(bot_id): Path, + RawBody(body): RawBody, +) -> (StatusCode, Json) { // TODO: put in config somewhere let data_path = "./data/bots"; - let bot = db_conn - .run(move |conn| bots::find_bot(bot_id, conn)) - .await - .expect("Bot not found"); + let bot = bots::find_bot(bot_id, &conn).expect("Bot not found"); assert_eq!(user.id, bot.owner_id); @@ -71,26 +53,23 @@ pub async fn upload_bot_code( let token: [u8; 16] = rand::thread_rng().gen(); let name = base64::encode(&token); - let path = Path::new(data_path).join(name); - let capped_buf = data.open(10usize.megabytes()).into_bytes().await.unwrap(); - assert!(capped_buf.is_complete()); - let buf = capped_buf.into_inner(); + let path = path::Path::new(data_path).join(name); + // let capped_buf = data.open(10usize.megabytes()).into_bytes().await.unwrap(); + // assert!(capped_buf.is_complete()); + // let buf = capped_buf.into_inner(); + let buf = hyper::body::to_bytes(body).await.unwrap(); zip::ZipArchive::new(Cursor::new(buf)) .unwrap() .extract(&path) .unwrap(); - let code_bundle = db_conn - .run(move |conn| { - let bundle = bots::NewCodeBundle { - bot_id: bot.id, - path: path.to_str().unwrap(), - }; - bots::create_code_bundle(&bundle, conn).expect("Failed to create code bundle") - }) - .await; + let bundle = bots::NewCodeBundle { + bot_id: bot.id, + path: path.to_str().unwrap(), + }; + let code_bundle = + bots::create_code_bundle(&bundle, &conn).expect("Failed to create code bundle"); - // TODO: proper location - status::Created::new("").body(Json(code_bundle)) + (StatusCode::CREATED, Json(code_bundle)) } diff --git a/backend/src/routes/users.rs b/backend/src/routes/users.rs index 45a94b9..fc77d7b 100644 --- a/backend/src/routes/users.rs +++ b/backend/src/routes/users.rs @@ -1,48 +1,32 @@ +use crate::db::users::{Credentials, User}; use crate::db::{sessions, users}; -use crate::{ - db::users::{Credentials, User}, - DbConn, -}; -use rocket::serde::json::Json; +use crate::DatabaseConnection; +use axum::extract::{FromRequest, RequestParts, TypedHeader}; +use axum::headers::authorization::Bearer; +use axum::headers::Authorization; +use axum::http::StatusCode; +use axum::{async_trait, Json}; use serde::{Deserialize, Serialize}; -use rocket::http::Status; -use rocket::request::{FromRequest, Outcome, Request}; -use rocket::response::status; +type AuthorizationHeader = TypedHeader>; -#[derive(Debug)] -pub enum AuthTokenError { - BadCount, - Missing, - Invalid, -} - -// TODO: error handling and proper lifetimes -#[rocket::async_trait] -impl<'r> FromRequest<'r> for User { - type Error = AuthTokenError; +#[async_trait] +impl FromRequest for User +where + B: Send, +{ + type Rejection = (StatusCode, String); - async fn from_request(request: &'r Request<'_>) -> Outcome { - let keys: Vec<_> = request.headers().get("Authorization").collect(); - let auth_header = match keys.len() { - 0 => return Outcome::Failure((Status::BadRequest, AuthTokenError::Missing)), - 1 => keys[0], - _ => return Outcome::Failure((Status::BadRequest, AuthTokenError::BadCount)), - }; + async fn from_request(req: &mut RequestParts) -> Result { + let conn = DatabaseConnection::from_request(req).await?; + let TypedHeader(Authorization(bearer)) = AuthorizationHeader::from_request(req) + .await + .map_err(|_| (StatusCode::UNAUTHORIZED, "".to_string()))?; - let token = match auth_header.strip_prefix("Bearer ") { - Some(token) => token.to_string(), - None => return Outcome::Failure((Status::BadRequest, AuthTokenError::Invalid)), - }; + let (_session, user) = sessions::find_user_by_session(bearer.token(), &conn) + .map_err(|_| (StatusCode::UNAUTHORIZED, "".to_string()))?; - let db = request.guard::().await.unwrap(); - let res = db - .run(move |conn| sessions::find_user_by_session(&token, conn)) - .await; - match res { - Ok((_session, user)) => Outcome::Success(user), - Err(_) => Outcome::Failure((Status::Unauthorized, AuthTokenError::Invalid)), - } + Ok(user) } } @@ -67,18 +51,16 @@ pub struct RegistrationParams { pub password: String, } -#[post("/register", data = "")] -pub async fn register(db_conn: DbConn, params: Json) -> Json { - db_conn - .run(move |conn| { - let credentials = Credentials { - username: ¶ms.username, - password: ¶ms.password, - }; - let user = users::create_user(&credentials, conn).unwrap(); - Json(user.into()) - }) - .await +pub async fn register( + conn: DatabaseConnection, + params: Json, +) -> Json { + let credentials = Credentials { + username: ¶ms.username, + password: ¶ms.password, + }; + let user = users::create_user(&credentials, &conn).unwrap(); + Json(user.into()) } #[derive(Deserialize)] @@ -87,32 +69,26 @@ pub struct LoginParams { pub password: String, } -#[post("/login", data = "")] pub async fn login( - db_conn: DbConn, + conn: DatabaseConnection, params: Json, -) -> Result> { - db_conn - .run(move |conn| { - let credentials = Credentials { - username: ¶ms.username, - password: ¶ms.password, - }; - // TODO: handle failures - let authenticated = users::authenticate_user(&credentials, conn); +) -> Result { + let credentials = Credentials { + username: ¶ms.username, + password: ¶ms.password, + }; + // TODO: handle failures + let authenticated = users::authenticate_user(&credentials, &conn); - match authenticated { - None => Err(status::Forbidden(Some("invalid auth"))), - Some(user) => { - let session = sessions::create_session(&user, conn); - Ok(session.token) - } - } - }) - .await + match authenticated { + None => Err(StatusCode::FORBIDDEN), + Some(user) => { + let session = sessions::create_session(&user, &conn); + Ok(session.token) + } + } } -#[get("/users/me")] pub async fn current_user(user: User) -> Json { Json(user.into()) } -- cgit v1.2.3