219 lines
6.0 KiB
Rust
219 lines
6.0 KiB
Rust
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<BigInt>, // 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<db::invites::Invite> = 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<BigInt>) -> Option<Member>{
|
|
/*
|
|
* 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<Body>, 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<UBigInt>) = 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<Body>, 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());
|
|
}
|
|
}
|