diff --git a/server/src/auth.rs b/server/src/auth.rs index 1588744..9e5676f 100644 --- a/server/src/auth.rs +++ b/server/src/auth.rs @@ -1,322 +1,10 @@ -// Handlers for the base auth routes -use crate::{ - DBConn, schema, - utils:: { - encode_param, new_key - }, - models::{ - Invite, - User - } -}; +use hyper::{Response, Body}; +use std::collections::HashMap; -use rocket::http::Status; -use rocket::response::{self, Responder, Response}; -use rocket::request::{Form, Request}; -use rocket_contrib::json::{Json, JsonValue}; -use diesel::{self, prelude::*}; - -use chrono::{Duration, Utc}; -use std::{error, fmt}; - -#[allow(dead_code)] // added because these fields are read through rocket, not directly; and rls keeps complainin -#[derive(FromForm)] -pub struct JoinParams { - code: u64, - name: String, +pub async fn wall_entry(params: &HashMap<&str, Option<&str>>) -> bool { + unimplemented!() } -#[derive(FromForm, Deserialize)] -pub struct AuthKey { - id: u64, - secret: String, -} - -pub type AuthResult = std::result::Result; - -#[derive(Debug, Clone)] -pub struct AuthErr { - msg: &'static str, - status: u16, -} - -impl fmt::Display for AuthErr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Authentication error") - } -} - -impl error::Error for AuthErr { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { - None - } -} - -impl<'r> Responder<'r> for AuthErr { - fn respond_to(self, _:&Request) -> response::Result<'r> { - Response::build() - .status(Status::InternalServerError) - .raw_header("db-error", self.msg) - .ok() - } -} - - -pub fn join(conn: DBConn, hashcode: u64, name: String) -> AuthResult, AuthErr>{ - /* - * Requires -> body - * Requires -> body - * Struct JoinParams enforces this for us so if something is missing then rocket should 404 - */ - use schema::invites::{self, dsl::*}; - - let diesel_result = invites - .filter(invites::dsl::id.eq(hashcode)) - .first::(&conn.0); - - if let Ok(data) = diesel_result { - match data.uses { - 1 ..= std::i32::MAX => { - let new_user = crate::users::create_new_user(name); - // At this point we don't really care about the return - let _ignore = diesel::update(invites.filter(invites::dsl::id.eq(hashcode))) - .set(uses.eq(data.uses - 1)) - .execute(&conn.0); - - Ok(Json(new_user)) - } - // The invite has been used up and thus should be removed - std::i32::MIN ..= 0 => { - let _ = diesel::delete(invites.filter(invites::dsl::id.eq(data.id))) - .execute(&conn.0) - .expect("Could not delete invite"); - - Err(AuthErr{msg: "Invite expired", status: 404}) - } - } - } - else { - Err(AuthErr{msg: "Malformed request", status: 500}) - } -} - -fn confirm_user_api_access(conn: &MysqlConnection, user_id: u64, user_secret: &str) -> bool { - use schema::users::dsl::*; - let result = users - .filter(id.eq(user_id)) - .filter(secret.eq(user_secret)) - .first::(conn); - - match result { - Ok(_data) => true, - Err(_e) => false - } -} - -fn blind_remove_session(conn: &MysqlConnection, sesh_secret: &str) { - use crate::schema::sessions::dsl::*; - - let _ignore_result = diesel::delete(sessions - .filter(secret.eq(sesh_secret))) - .execute(conn); -} - -fn create_new_session_key(conn: &MysqlConnection) -> Option { - use crate::models::InsertableSession; - - let new_session = InsertableSession { - secret: encode_param(&new_key()), - expires: (Utc::now() + Duration::hours(1)).timestamp() as u64 - }; - - // insert the new key into our db - let db_result = diesel::insert_into(schema::sessions::table) - .values(&new_session) - .execute(conn); - - // finally return the key assuming everything went well - match db_result { - Ok(_val) => Some(new_session.secret), - Err(_e) => None - } -} - -#[post("/login", data = "")] -pub fn login(conn: DBConn, api_key: Form) -> AuthResult{ - /* - * Session Tokens are used to key into a subset of online users - * This is what should make queries faster per instance as we'll have less data to sift through w/ diesel - */ - if confirm_user_api_access(&conn.0, api_key.id, &api_key.secret) { - blind_remove_session(&conn.0, &api_key.secret); - let key = create_new_session_key(&conn.0); - match key { - Some(data) => Ok(json!({"key": data})), - None => Err(AuthErr { - msg: "Could not create session", - status: 500 - }) - } - } - else { - Err(AuthErr { - msg: "Nothing found", - status: 400 - }) - } -} - -#[post("/leave", data = "")] -pub fn leave(conn: DBConn, api_key: Form) -> Status { - /* - * Basic removal of the user from our users table - */ - use crate::schema::users::dsl::*; - use crate::diesel::ExpressionMethods; - let _db_result = diesel::delete(users - .filter(id.eq(api_key.id)) - .filter(secret.eq(api_key.secret.clone()))) - .execute(&conn.0).unwrap(); - - - Status::Ok -} - -#[cfg(test)] -mod auth_tests { - use crate::{ - invites::static_rocket_route_info_for_use_invite, - schema, - models::Invite, - utils::{encode_param, new_key} - }; - use super::*; - use rocket::{ - self, local::Client, http::ContentType - }; - use diesel::mysql::MysqlConnection; - use chrono::{Duration, Utc}; - use rand::random; - use std::env; - use dotenv::dotenv; - use serde_json::Value; - - fn setup_dotenv() -> Result<(), i32> { - match dotenv() { - Ok(_) => Ok(()), - Err(e) => panic!("`.env` could not be loaded: {:?}", e) - } - } - fn mysql_conn() -> MysqlConnection { - MysqlConnection::establish(&env::var("DATABASE_URL").unwrap()) - .unwrap() - } - - #[test] - fn join_and_leave() { - // Create an invite in our db manually - // Use that invite to join - // Then leave using our neato /auth/leave route - if let Err(_denv) = setup_dotenv() { - panic!("env failed fukc") - } - let app = rocket::ignite() - .mount("/invite", routes![use_invite]) - .mount("/auth", routes![leave]) - .attach(DBConn::fairing()); - - // First we create a new invite - let conn = mysql_conn(); - let dt = Utc::now() + Duration::minutes(30); - let invite = Invite { - id: random::(), - uses: 1, - expires: dt.timestamp() as u64, - }; - let _ = diesel::insert_into(schema::invites::table) - .values(&invite) - .execute(&conn); - - // use our new invite to "join" the server - let rocket_c = Client::new(app).expect("Invalid rocket instance"); - let mut response = rocket_c.get(format!("/invite/join/{}/{}", invite.id, "billybob")).dispatch(); - let body: String = response.body_string().unwrap(); - let api_key: Value = serde_json::from_str(&body).unwrap(); - - // Go about leaving the server - let secret_str = format!("{}", api_key["secret"]); - let body_params = format!("id={}&secret={}", api_key["id"], secret_str); - println!("Parameters being sent {}", body_params); - let leave_response = rocket_c.post("/auth/leave") - .body(body_params) - .header(ContentType::Form) - .dispatch(); - - assert_eq!(leave_response.status(), Status::Ok); - println!("{}", body); - } - - #[test] - fn dummy_leave() { - /* - * Naive test for the /auth/leave route - */ - setup_dotenv().unwrap(); - let app = rocket::ignite() - .mount("/auth", routes![leave]) - .attach(DBConn::fairing()); - - let rocket_client = Client::new(app).expect("asdf"); - // Some dummy parameters as the /auth/leave route only has one type of response - let id = 12345; - let secret = encode_param(&new_key()); - let params = format!("id={}&secret={}", id, secret); - println!("Parameters posted to /auth/leave: {}", params); - let response = rocket_client.post("/auth/leave") - .body(params) - .header(ContentType::Form) - .dispatch(); - - assert_eq!(response.status(), Status::Ok); - } - - fn bogus_user_insertion(test_name: String, conn: &MysqlConnection) -> User { - use crate::models::{USER_OFFLINE, InsertableUser}; - use schema::users::{self, dsl::*}; - - let insertable_user = InsertableUser { - name: test_name, - secret: encode_param(&new_key()), - date: Utc::now().timestamp() as u64, - status: USER_OFFLINE - }; - - let _insertion_result = diesel::insert_into(users::table) - .values(&insertable_user) - .execute(conn); - - users.filter(date.eq(insertable_user.date)).first::(conn).unwrap() - } - #[test] - fn login() { - setup_dotenv().unwrap(); - let app = rocket::ignite() - .mount("/auth", routes![login]) - .attach(DBConn::fairing()); - let conn = mysql_conn(); - - // We need a valid user to login so we'll make one up - let bogus_user = bogus_user_insertion("test::auth::login".to_string(), &conn); - - // Finaly we can test our route with the boilerplate ootw - let rocket_client = Client::new(app).expect("test::auth::login => client creation failed"); - let params = format!("id={}&secret={}", bogus_user.id, bogus_user.secret); - let response = rocket_client.post("/auth/login") - .body(params) - .header(ContentType::Form) - .dispatch(); - - assert_eq!(response.status(), Status::Ok); - } - -} +pub fn wall_failure(resp: &Response) { + unimplemented!() +} \ No newline at end of file