use serde_json::Value; use serde::Serialize; use mysql_async; use mysql_async::{Conn, Pool}; use mysql_async::error::Error; use mysql_async::prelude::{params, Queryable}; use hyper::{Response, Body, StatusCode}; use chrono::Utc; use db::{UBigInt, BigInt}; use db::common::FromDB; use db::member::Member; #[derive(Serialize)] struct Invite { id: BigInt, // doubles as the timestamp for when it dies uses: Option, // optional because some links are permanent expires: bool, } /* * Error handling: * All errors raisable from this module come from mysql_async and thus * are of the enum mysql_async::error::Error */ async fn valid_invite(pool: &Pool, id: BigInt) -> bool { /* * Fetches an invite from the database to check for validity */ let query: Option = match db::invites::Invite::get(pool, id as u64).await { db::Response::Row(invite) => { Some(invite) }, _ => { None } }; if let Some(invite) = query { // if expires at all if invite.expires { let now = Utc::now().timestamp(); // old? let mut valid_status = now > invite.id; // used? if invite.uses.is_some() && valid_status == false { valid_status = invite.uses.unwrap() <= 0; // safe unwrap since we know its Some(_) } return valid_status } // no expiry date? no problem return true } // prolly not a real id return false } async fn use_invite(pool: &Pool, code: Option) -> Option{ /* * Attempts to change the state of the current invite being provided */ use crate::auth; use crate::perms::GENERAL_NEW; let id = match code { Some(id) => id, None => 0 }; // some random comment if valid_invite(pool, id).await { let raw_secret = auth::generate_secret(); if let Ok(secret) = auth::encrypt_secret(&raw_secret) { return match db::member::Member::add(pool, "Anonymous".into(), &secret, GENERAL_NEW).await { Ok(response) => { match response { db::Response::Row(member) => Some(member), _ => None, } }, // TODO: logggin or something idk Err(_) => return None } } // Returning None because we couldn't actually create a proper secret to store else { return None; } } // The invite itself was not valid else { return None; } } pub async fn join(pool: &Pool, response: &mut Response, params: Value) { /* * Main dispatcher for dealing with an attempted join via a given invide code */ let code = match params.get("invite-id") { Some(val) => val.as_i64(), None => None }; match use_invite(&pool, code).await { Some(new_account) => *response.body_mut() = Body::from(serde_json::to_string(&new_account).unwrap()), None => { } } } async fn insert_new_invite(pool: &Pool, invite: &Invite) -> Result<(), Error>{ let conn = pool.get_conn().await?; conn.prep_exec( "INSERT INTO invites (id, uses, expires) VALUES (:id, :uses, :expires)", params!{ "id" => invite.id, "uses" => invite.uses, "expires" => invite.expires }).await?; Ok(()) } async fn process_expires_parameter(p: &Pool, exp: &Value, id: UBigInt) -> bool { // TODO: fix this somewhat unsafe code // NOTE: its unsafe because of these lazy as heck unwraps everywhere use crate::perms::{CREATE_PERM_INVITES, CREATE_TMP_INVITES}; let conn = p.get_conn().await.unwrap(); let db_tup: (Conn, Option) = conn.first_exec( "SELECT permissions FROM members WHERE id = :id", params!{"id" => id}) .await.unwrap(); // depending on what type of invite we requested we should make sure we have the // right permissions to do so let real_perms = db_tup.1.unwrap(); // safe via auth module if let Some(exp) = exp.as_bool() { // perma? if exp { return (real_perms & CREATE_PERM_INVITES) == CREATE_PERM_INVITES; } else { return (real_perms & CREATE_TMP_INVITES) == CREATE_TMP_INVITES; } } else { return false; } } pub async fn create(pool: &Pool, response: &mut Response, params: Value) { /* * Creates a new invite */ // no user can actually have an id of 0 this won't find anyone on the backend let id = match params.get("id") { Some(val) => val.as_u64().unwrap_or(0), None => 0 }; let use_count = match params.get("uses") { Some(val) => val.as_i64(), None => None }; let expires = match params.get("expires") { Some(exp_val) => process_expires_parameter(pool, exp_val, id).await, None => true }; let invite = Invite { id: (Utc::now() + chrono::Duration::minutes(30)).timestamp(), uses: use_count, expires: expires }; match insert_new_invite(&pool, &invite).await { Ok(_) => {}, Err(mysqle) => { println!("\tINVITES::CREATE::ERROR: {}", mysqle); *response.status_mut() = StatusCode::BAD_REQUEST; } } } #[cfg(test)] mod invites_test { /* * INVITE CREATION * Good - Bad - Malicious */ use crate::testing::{get_pool, hyper_resp}; use hyper::StatusCode; use serde_json::Value; #[tokio::test] async fn create_invite_good() { // Generation of data let p = get_pool(); let mut resp = hyper_resp(); // expected params let params: Value = serde_json::from_str(r#" { "uses": 3, "expire": null } "#).unwrap(); // Collection super::join(&p, &mut resp, params).await; let _ = p.disconnect().await; assert_eq!(StatusCode::OK, resp.status()); } }