diff --git a/src/database/channel.rs b/src/database/channel.rs index a49d0f0d6e940a001160b0786403213f2f74784d..ac35285b3ee33ae65e629540f207cbc5c759c8ec 100644 --- a/src/database/channel.rs +++ b/src/database/channel.rs @@ -1,18 +1,18 @@ -use serde::{ Deserialize, Serialize }; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] pub struct Channel { #[serde(rename = "_id")] pub id: String, #[serde(rename = "type")] - pub channel_type: u8, + pub channel_type: u8, - pub last_message: Option<String>, - - // for Direct Messages - pub recipients: Option<Vec<String>>, + pub last_message: Option<String>, + + // for Direct Messages + pub recipients: Option<Vec<String>>, pub active: Option<bool>, - + // for Guilds pub name: Option<String>, diff --git a/src/database/guild.rs b/src/database/guild.rs index a3ffa0e0f1c39fd5a6e7284511d7c7e21484ecdc..c9254d2393af8dba9d9b4fda192ef06daddc2076 100644 --- a/src/database/guild.rs +++ b/src/database/guild.rs @@ -1,4 +1,4 @@ -use serde::{ Deserialize, Serialize }; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] pub struct Member { @@ -16,7 +16,7 @@ pub struct Invite { pub struct Guild { #[serde(rename = "_id")] pub id: String, - // pub nonce: String, used internally + // pub nonce: String, used internally pub name: String, pub description: String, pub owner: String, diff --git a/src/database/message.rs b/src/database/message.rs index 5bb6992b8e1e74cfb16e615e7fc4629c379d7d39..0e18d23e88ad533873b24e4d8f0518cb6835010e 100644 --- a/src/database/message.rs +++ b/src/database/message.rs @@ -1,5 +1,5 @@ -use serde::{ Deserialize, Serialize }; -use bson::{ UtcDateTime }; +use bson::UtcDateTime; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] pub struct PreviousEntry { @@ -10,13 +10,13 @@ pub struct PreviousEntry { #[derive(Serialize, Deserialize, Debug)] pub struct Message { #[serde(rename = "_id")] - pub id: String, - // pub nonce: String, used internally - pub channel: String, - pub author: String, + pub id: String, + // pub nonce: String, used internally + pub channel: String, + pub author: String, - pub content: String, + pub content: String, pub edited: Option<UtcDateTime>, - - pub previous_content: Option<Vec<PreviousEntry>> + + pub previous_content: Option<Vec<PreviousEntry>>, } diff --git a/src/database/mod.rs b/src/database/mod.rs index d425bd1c8e55e6495f004773e871c8693678e6b7..6e9222f188bdfd4d8f01985a018b16a02ac7ff66 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,30 +1,30 @@ -use mongodb::{ Client, Collection, Database }; +use mongodb::{Client, Collection, Database}; use std::env; use once_cell::sync::OnceCell; static DBCONN: OnceCell<Client> = OnceCell::new(); pub fn connect() { - let client = Client::with_uri_str( - &env::var("DB_URI").expect("DB_URI not in environment variables!")) - .expect("Failed to init db connection."); + let client = + Client::with_uri_str(&env::var("DB_URI").expect("DB_URI not in environment variables!")) + .expect("Failed to init db connection."); - DBCONN.set(client).unwrap(); + DBCONN.set(client).unwrap(); } pub fn get_connection() -> &'static Client { - DBCONN.get().unwrap() + DBCONN.get().unwrap() } pub fn get_db() -> Database { - get_connection().database("revolt") + get_connection().database("revolt") } pub fn get_collection(collection: &str) -> Collection { - get_db().collection(collection) + get_db().collection(collection) } -pub mod user; pub mod channel; -pub mod message; pub mod guild; +pub mod message; +pub mod user; diff --git a/src/database/user.rs b/src/database/user.rs index 1e869e2af8082d5e5e7e764eb373142655d50de8..56eff95c8dc7265d5a1a4f5faa3ac02ac5483e63 100644 --- a/src/database/user.rs +++ b/src/database/user.rs @@ -1,19 +1,19 @@ -use serde::{ Deserialize, Serialize }; use bson::UtcDateTime; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] pub struct UserEmailVerification { - pub verified: bool, - pub target: Option<String>, - pub expiry: Option<UtcDateTime>, - pub rate_limit: Option<UtcDateTime>, - pub code: Option<String>, + pub verified: bool, + pub target: Option<String>, + pub expiry: Option<UtcDateTime>, + pub rate_limit: Option<UtcDateTime>, + pub code: Option<String>, } #[derive(Serialize, Deserialize, Debug)] pub struct UserRelationship { - pub id: String, - pub status: u8, + pub id: String, + pub status: u8, } #[derive(Serialize, Deserialize, Debug)] @@ -21,9 +21,9 @@ pub struct User { #[serde(rename = "_id")] pub id: String, pub email: String, - pub username: String, - pub password: String, - pub access_token: Option<String>, - pub email_verification: UserEmailVerification, - pub relations: Option<Vec<UserRelationship>>, + pub username: String, + pub password: String, + pub access_token: Option<String>, + pub email_verification: UserEmailVerification, + pub relations: Option<Vec<UserRelationship>>, } diff --git a/src/email.rs b/src/email.rs index 6b218588417f8f96d31aa5fc072a7349abf4c579..3b1d5eb349bda061e6add6ac609962826b23fe6e 100644 --- a/src/email.rs +++ b/src/email.rs @@ -3,40 +3,48 @@ use std::collections::HashMap; use std::env; pub fn send_email(target: String, subject: String, body: String, html: String) -> Result<(), ()> { - let mut map = HashMap::new(); - map.insert("target", target.clone()); - map.insert("subject", subject); - map.insert("body", body); - map.insert("html", html); + let mut map = HashMap::new(); + map.insert("target", target.clone()); + map.insert("subject", subject); + map.insert("body", body); + map.insert("html", html); - let client = Client::new(); - match client.post("http://192.168.0.26:3838/send") - .json(&map) - .send() { - Ok(_) => Ok(()), - Err(_) => Err(()) - } + let client = Client::new(); + match client + .post("http://192.168.0.26:3838/send") + .json(&map) + .send() + { + Ok(_) => Ok(()), + Err(_) => Err(()), + } } fn public_uri() -> String { - env::var("PUBLIC_URI").expect("PUBLIC_URI not in environment variables!") + env::var("PUBLIC_URI").expect("PUBLIC_URI not in environment variables!") } pub fn send_verification_email(email: String, code: String) -> bool { - let url = format!("{}/api/account/verify/{}", public_uri(), code); - send_email( - email, - "Verify your email!".to_string(), - format!("Verify your email here: {}", url), - format!("<a href=\"{}\">Click to verify your email!</a>", url) - ).is_ok() + let url = format!("{}/api/account/verify/{}", public_uri(), code); + send_email( + email, + "Verify your email!".to_string(), + format!("Verify your email here: {}", url), + format!("<a href=\"{}\">Click to verify your email!</a>", url), + ) + .is_ok() } pub fn send_welcome_email(email: String, username: String) -> bool { - send_email( - email, - "Welcome to REVOLT!".to_string(), - format!("Welcome, {}! You can now use REVOLT.", username.clone()), - format!("<b>Welcome, {}!</b><br/>You can now use REVOLT.<br/><a href=\"{}\">Go to REVOLT</a>", username.clone(), public_uri()) - ).is_ok() + send_email( + email, + "Welcome to REVOLT!".to_string(), + format!("Welcome, {}! You can now use REVOLT.", username.clone()), + format!( + "<b>Welcome, {}!</b><br/>You can now use REVOLT.<br/><a href=\"{}\">Go to REVOLT</a>", + username.clone(), + public_uri() + ), + ) + .is_ok() } diff --git a/src/guards/auth.rs b/src/guards/auth.rs index 7c3b2bc128c978ca55024efe32915193336c3139..c9381f18166d20b3fd40dd1d1f15f5ac5aa2cc63 100644 --- a/src/guards/auth.rs +++ b/src/guards/auth.rs @@ -1,8 +1,8 @@ +use rocket::http::{RawStr, Status}; +use rocket::request::{self, FromParam, FromRequest, Request}; use rocket::Outcome; -use rocket::http::{ Status, RawStr }; -use rocket::request::{ self, Request, FromRequest, FromParam }; -use bson::{ bson, doc, from_bson }; +use bson::{bson, doc, from_bson}; use crate::database; use database::user::User; @@ -20,18 +20,20 @@ impl<'a, 'r> FromRequest<'a, 'r> for User { fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> { let keys: Vec<_> = request.headers().get("x-auth-token").collect(); match keys.len() { - 0 => Outcome::Failure((Status::Forbidden, AuthError::Missing)), - 1 => { - let key = keys[0]; - let col = database::get_db().collection("users"); - let result = col.find_one(doc! { "access_token": key }, None).unwrap(); - - if let Some(user) = result { - Outcome::Success(from_bson(bson::Bson::Document(user)).expect("Failed to unwrap user.")) - } else { - Outcome::Failure((Status::Forbidden, AuthError::Invalid)) - } - }, + 0 => Outcome::Failure((Status::Forbidden, AuthError::Missing)), + 1 => { + let key = keys[0]; + let col = database::get_db().collection("users"); + let result = col.find_one(doc! { "access_token": key }, None).unwrap(); + + if let Some(user) = result { + Outcome::Success( + from_bson(bson::Bson::Document(user)).expect("Failed to unwrap user."), + ) + } else { + Outcome::Failure((Status::Forbidden, AuthError::Invalid)) + } + } _ => Outcome::Failure((Status::BadRequest, AuthError::BadCount)), } } @@ -41,13 +43,15 @@ impl<'r> FromParam<'r> for User { type Error = &'r RawStr; fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { - let col = database::get_db().collection("users"); - let result = col.find_one(doc! { "_id": param.to_string() }, None).unwrap(); - - if let Some(user) = result { - Ok(from_bson(bson::Bson::Document(user)).expect("Failed to unwrap user.")) - } else { - Err(param) - } + let col = database::get_db().collection("users"); + let result = col + .find_one(doc! { "_id": param.to_string() }, None) + .unwrap(); + + if let Some(user) = result { + Ok(from_bson(bson::Bson::Document(user)).expect("Failed to unwrap user.")) + } else { + Err(param) + } } } diff --git a/src/guards/channel.rs b/src/guards/channel.rs index daa277d5ced8aefd760ab31e77571414f47e1c99..ee42bf4ea667f2e59cd8566cd28a379e1533623d 100644 --- a/src/guards/channel.rs +++ b/src/guards/channel.rs @@ -1,6 +1,6 @@ -use rocket::http::{ RawStr }; -use rocket::request::{ FromParam }; -use bson::{ bson, doc, from_bson }; +use bson::{bson, doc, from_bson}; +use rocket::http::RawStr; +use rocket::request::FromParam; use crate::database; @@ -11,14 +11,16 @@ impl<'r> FromParam<'r> for Channel { type Error = &'r RawStr; fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { - let col = database::get_db().collection("channels"); - let result = col.find_one(doc! { "_id": param.to_string() }, None).unwrap(); - - if let Some(channel) = result { - Ok(from_bson(bson::Bson::Document(channel)).expect("Failed to unwrap channel.")) - } else { - Err(param) - } + let col = database::get_db().collection("channels"); + let result = col + .find_one(doc! { "_id": param.to_string() }, None) + .unwrap(); + + if let Some(channel) = result { + Ok(from_bson(bson::Bson::Document(channel)).expect("Failed to unwrap channel.")) + } else { + Err(param) + } } } @@ -26,13 +28,15 @@ impl<'r> FromParam<'r> for Message { type Error = &'r RawStr; fn from_param(param: &'r RawStr) -> Result<Self, Self::Error> { - let col = database::get_db().collection("messages"); - let result = col.find_one(doc! { "_id": param.to_string() }, None).unwrap(); - - if let Some(message) = result { - Ok(from_bson(bson::Bson::Document(message)).expect("Failed to unwrap message.")) - } else { - Err(param) - } + let col = database::get_db().collection("messages"); + let result = col + .find_one(doc! { "_id": param.to_string() }, None) + .unwrap(); + + if let Some(message) = result { + Ok(from_bson(bson::Bson::Document(message)).expect("Failed to unwrap message.")) + } else { + Err(param) + } } } diff --git a/src/main.rs b/src/main.rs index 1a27d934733632ea6ea0de442108bf25338c92de..13ade671775a1f5cd645fc2fb053c6769e6ad989 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,31 +1,33 @@ #![feature(proc_macro_hygiene, decl_macro)] -#[macro_use] extern crate rocket; -#[macro_use] extern crate rocket_contrib; +#[macro_use] +extern crate rocket; +#[macro_use] +extern crate rocket_contrib; -pub mod websocket; pub mod database; +pub mod email; pub mod guards; pub mod routes; -pub mod email; +pub mod websocket; use dotenv; -use std::thread; use rocket_cors::AllowedOrigins; +use std::thread; fn main() { - dotenv::dotenv().ok(); - database::connect(); + dotenv::dotenv().ok(); + database::connect(); - thread::spawn(|| { - websocket::launch_server(); - }); + thread::spawn(|| { + websocket::launch_server(); + }); let cors = rocket_cors::CorsOptions { allowed_origins: AllowedOrigins::All, ..Default::default() - }.to_cors().unwrap(); + } + .to_cors() + .unwrap(); - routes::mount(rocket::ignite()) - .attach(cors) - .launch(); + routes::mount(rocket::ignite()).attach(cors).launch(); } diff --git a/src/routes/account.rs b/src/routes/account.rs index 41d4a1ab9a9193ad04d8fbb1e61a59943a2c5a2f..487ce52b22820ff1d39c6eb40770ea2e6530b9d4 100644 --- a/src/routes/account.rs +++ b/src/routes/account.rs @@ -1,28 +1,28 @@ use crate::database; use crate::email; -use bson::{ bson, doc, Bson::UtcDatetime, from_bson }; -use rand::{ Rng, distributions::Alphanumeric }; -use rocket_contrib::json::{ Json, JsonValue }; -use serde::{ Serialize, Deserialize }; -use validator::validate_email; -use bcrypt::{ hash, verify }; -use database::user::User; +use bcrypt::{hash, verify}; +use bson::{bson, doc, from_bson, Bson::UtcDatetime}; use chrono::prelude::*; +use database::user::User; +use rand::{distributions::Alphanumeric, Rng}; +use rocket_contrib::json::{Json, JsonValue}; +use serde::{Deserialize, Serialize}; use ulid::Ulid; +use validator::validate_email; fn gen_token(l: usize) -> String { - rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(l) - .collect::<String>() + rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(l) + .collect::<String>() } #[derive(Serialize, Deserialize)] pub struct Create { - username: String, - password: String, - email: String, + username: String, + password: String, + email: String, } /// create a new Revolt account @@ -34,73 +34,79 @@ pub struct Create { /// (3) add user and send email verification #[post("/create", data = "<info>")] pub fn create(info: Json<Create>) -> JsonValue { - let col = database::get_collection("users"); - - if info.username.len() < 2 || info.username.len() > 32 { - return json!({ - "success": false, - "error": "Username requirements not met! Must be between 2 and 32 characters.", - }) - } - - if info.password.len() < 8 || info.password.len() > 72 { - return json!({ - "success": false, - "error": "Password requirements not met! Must be between 8 and 72 characters.", - }) - } - - if !validate_email(info.email.clone()) { - return json!({ - "success": false, - "error": "Invalid email provided!", - }) - } - - if let Some(_) = col.find_one(doc! { "email": info.email.clone() }, None).expect("Failed user lookup") { - return json!({ - "success": false, - "error": "Email already in use!", - }) - } - - if let Ok(hashed) = hash(info.password.clone(), 10) { - let access_token = gen_token(92); - let code = gen_token(48); - - match col.insert_one(doc! { - "_id": Ulid::new().to_string(), - "email": info.email.clone(), - "username": info.username.clone(), - "password": hashed, - "access_token": access_token, - "email_verification": { - "verified": false, - "target": info.email.clone(), - "expiry": UtcDatetime(Utc::now() + chrono::Duration::days(1)), - "rate_limit": UtcDatetime(Utc::now() + chrono::Duration::minutes(1)), - "code": code.clone(), - } - }, None) { - Ok(_) => { - let sent = email::send_verification_email(info.email.clone(), code); - - json!({ - "success": true, - "email_sent": sent, - }) - }, - Err(_) => json!({ - "success": false, - "error": "Failed to create account!", - }) - } - } else { - json!({ - "success": false, - "error": "Failed to hash password!", - }) - } + let col = database::get_collection("users"); + + if info.username.len() < 2 || info.username.len() > 32 { + return json!({ + "success": false, + "error": "Username requirements not met! Must be between 2 and 32 characters.", + }); + } + + if info.password.len() < 8 || info.password.len() > 72 { + return json!({ + "success": false, + "error": "Password requirements not met! Must be between 8 and 72 characters.", + }); + } + + if !validate_email(info.email.clone()) { + return json!({ + "success": false, + "error": "Invalid email provided!", + }); + } + + if let Some(_) = col + .find_one(doc! { "email": info.email.clone() }, None) + .expect("Failed user lookup") + { + return json!({ + "success": false, + "error": "Email already in use!", + }); + } + + if let Ok(hashed) = hash(info.password.clone(), 10) { + let access_token = gen_token(92); + let code = gen_token(48); + + match col.insert_one( + doc! { + "_id": Ulid::new().to_string(), + "email": info.email.clone(), + "username": info.username.clone(), + "password": hashed, + "access_token": access_token, + "email_verification": { + "verified": false, + "target": info.email.clone(), + "expiry": UtcDatetime(Utc::now() + chrono::Duration::days(1)), + "rate_limit": UtcDatetime(Utc::now() + chrono::Duration::minutes(1)), + "code": code.clone(), + } + }, + None, + ) { + Ok(_) => { + let sent = email::send_verification_email(info.email.clone(), code); + + json!({ + "success": true, + "email_sent": sent, + }) + } + Err(_) => json!({ + "success": false, + "error": "Failed to create account!", + }), + } + } else { + json!({ + "success": false, + "error": "Failed to hash password!", + }) + } } /// verify an email for a Revolt account @@ -109,57 +115,57 @@ pub fn create(info: Json<Create>) -> JsonValue { /// (3) set account as verified #[get("/verify/<code>")] pub fn verify_email(code: String) -> JsonValue { - let col = database::get_collection("users"); - - if let Some(u) = - col.find_one(doc! { "email_verification.code": code.clone() }, None).expect("Failed user lookup") { - let user: User = from_bson(bson::Bson::Document(u)).expect("Failed to unwrap user."); - let ev = user.email_verification; - - if Utc::now() > *ev.expiry.unwrap() { - json!({ - "success": false, - "error": "Token has expired!", - }) - } else { - let target = ev.target.unwrap(); - col.update_one( - doc! { "_id": user.id }, - doc! { - "$unset": { - "email_verification.code": "", - "email_verification.expiry": "", - "email_verification.target": "", - "email_verification.rate_limit": "", - }, - "$set": { - "email_verification.verified": true, - "email": target.clone(), - }, - }, - None, - ).expect("Failed to update user!"); - - email::send_welcome_email( - target.to_string(), - user.username - ); - - json!({ - "success": true - }) - } - } else { - json!({ - "success": false, - "error": "Invalid code!", - }) - } + let col = database::get_collection("users"); + + if let Some(u) = col + .find_one(doc! { "email_verification.code": code.clone() }, None) + .expect("Failed user lookup") + { + let user: User = from_bson(bson::Bson::Document(u)).expect("Failed to unwrap user."); + let ev = user.email_verification; + + if Utc::now() > *ev.expiry.unwrap() { + json!({ + "success": false, + "error": "Token has expired!", + }) + } else { + let target = ev.target.unwrap(); + col.update_one( + doc! { "_id": user.id }, + doc! { + "$unset": { + "email_verification.code": "", + "email_verification.expiry": "", + "email_verification.target": "", + "email_verification.rate_limit": "", + }, + "$set": { + "email_verification.verified": true, + "email": target.clone(), + }, + }, + None, + ) + .expect("Failed to update user!"); + + email::send_welcome_email(target.to_string(), user.username); + + json!({ + "success": true + }) + } + } else { + json!({ + "success": false, + "error": "Invalid code!", + }) + } } #[derive(Serialize, Deserialize)] pub struct Resend { - email: String, + email: String, } /// resend a verification email @@ -168,36 +174,41 @@ pub struct Resend { /// (3) resend the email #[post("/resend", data = "<info>")] pub fn resend_email(info: Json<Resend>) -> JsonValue { - let col = database::get_collection("users"); - - if let Some(u) = - col.find_one(doc! { "email_verification.target": info.email.clone() }, None).expect("Failed user lookup") { - let user: User = from_bson(bson::Bson::Document(u)).expect("Failed to unwrap user."); - let ev = user.email_verification; - - let expiry = ev.expiry.unwrap(); - let rate_limit = ev.rate_limit.unwrap(); - - if Utc::now() < *rate_limit { - json!({ - "success": false, - "error": "Hit rate limit! Please try again in a minute or so.", - }) - } else { - let mut new_expiry = UtcDatetime(Utc::now() + chrono::Duration::days(1)); - if info.email.clone() != user.email { - if Utc::now() > *expiry { - return json!({ - "success": "false", - "error": "For security reasons, please login and change your email again.", - }) - } - - new_expiry = UtcDatetime(*expiry); - } - - let code = gen_token(48); - col.update_one( + let col = database::get_collection("users"); + + if let Some(u) = col + .find_one( + doc! { "email_verification.target": info.email.clone() }, + None, + ) + .expect("Failed user lookup") + { + let user: User = from_bson(bson::Bson::Document(u)).expect("Failed to unwrap user."); + let ev = user.email_verification; + + let expiry = ev.expiry.unwrap(); + let rate_limit = ev.rate_limit.unwrap(); + + if Utc::now() < *rate_limit { + json!({ + "success": false, + "error": "Hit rate limit! Please try again in a minute or so.", + }) + } else { + let mut new_expiry = UtcDatetime(Utc::now() + chrono::Duration::days(1)); + if info.email.clone() != user.email { + if Utc::now() > *expiry { + return json!({ + "success": "false", + "error": "For security reasons, please login and change your email again.", + }); + } + + new_expiry = UtcDatetime(*expiry); + } + + let code = gen_token(48); + col.update_one( doc! { "_id": user.id }, doc! { "$set": { @@ -209,31 +220,28 @@ pub fn resend_email(info: Json<Resend>) -> JsonValue { None, ).expect("Failed to update user!"); - match email::send_verification_email( - info.email.to_string(), - code, - ) { - true => json!({ - "success": true, - }), - false => json!({ - "success": false, - "error": "Failed to send email! Likely an issue with the backend API.", - }) - } - } - } else { - json!({ - "success": false, - "error": "Email not pending verification!", - }) - } + match email::send_verification_email(info.email.to_string(), code) { + true => json!({ + "success": true, + }), + false => json!({ + "success": false, + "error": "Failed to send email! Likely an issue with the backend API.", + }), + } + } + } else { + json!({ + "success": false, + "error": "Email not pending verification!", + }) + } } #[derive(Serialize, Deserialize)] pub struct Login { - email: String, - password: String, + email: String, + password: String, } /// login to a Revolt account @@ -242,69 +250,73 @@ pub struct Login { /// (3) return access token #[post("/login", data = "<info>")] pub fn login(info: Json<Login>) -> JsonValue { - let col = database::get_collection("users"); - - if let Some(u) = - col.find_one(doc! { "email": info.email.clone() }, None).expect("Failed user lookup") { - let user: User = from_bson(bson::Bson::Document(u)).expect("Failed to unwrap user."); - - match verify(info.password.clone(), &user.password) - .expect("Failed to check hash of password.") { - true => { - let token = - match user.access_token { - Some(t) => t.to_string(), - None => { - let token = gen_token(92); - col.update_one( - doc! { "_id": &user.id }, - doc! { "$set": { "access_token": token.clone() } }, - None - ).expect("Failed to update user object"); - token - } - }; - - json!({ - "success": true, - "access_token": token, - "id": user.id - }) - }, - false => json!({ - "success": false, - "error": "Invalid password." - }) - } - } else { - json!({ - "success": false, - "error": "Email is not registered.", - }) - } + let col = database::get_collection("users"); + + if let Some(u) = col + .find_one(doc! { "email": info.email.clone() }, None) + .expect("Failed user lookup") + { + let user: User = from_bson(bson::Bson::Document(u)).expect("Failed to unwrap user."); + + match verify(info.password.clone(), &user.password) + .expect("Failed to check hash of password.") + { + true => { + let token = match user.access_token { + Some(t) => t.to_string(), + None => { + let token = gen_token(92); + col.update_one( + doc! { "_id": &user.id }, + doc! { "$set": { "access_token": token.clone() } }, + None, + ) + .expect("Failed to update user object"); + token + } + }; + + json!({ + "success": true, + "access_token": token, + "id": user.id + }) + } + false => json!({ + "success": false, + "error": "Invalid password." + }), + } + } else { + json!({ + "success": false, + "error": "Email is not registered.", + }) + } } - #[derive(Serialize, Deserialize)] pub struct Token { - token: String, + token: String, } /// login to a Revolt account via token #[post("/token", data = "<info>")] pub fn token(info: Json<Token>) -> JsonValue { - let col = database::get_collection("users"); - - if let Some(u) = - col.find_one(doc! { "access_token": info.token.clone() }, None).expect("Failed user lookup") { - json!({ - "success": true, - "id": u.get_str("_id").unwrap(), - }) - } else { - json!({ - "success": false, - "error": "Invalid token!", - }) - } + let col = database::get_collection("users"); + + if let Some(u) = col + .find_one(doc! { "access_token": info.token.clone() }, None) + .expect("Failed user lookup") + { + json!({ + "success": true, + "id": u.get_str("_id").unwrap(), + }) + } else { + json!({ + "success": false, + "error": "Invalid token!", + }) + } } diff --git a/src/routes/channel.rs b/src/routes/channel.rs index f518cc1a56b0a279d252f47f01cca5e2aa7553b4..eacd68c8720c971f29124c69e6b66f9338c0f45c 100644 --- a/src/routes/channel.rs +++ b/src/routes/channel.rs @@ -1,62 +1,59 @@ -use crate::database::{ self, user::User, channel::Channel, message::Message }; +use crate::database::{self, channel::Channel, message::Message, user::User}; use crate::websocket; -use bson::{ bson, doc, from_bson, Bson::UtcDatetime }; -use rocket_contrib::json::{ JsonValue, Json }; -use serde::{ Serialize, Deserialize }; -use num_enum::TryFromPrimitive; +use bson::{bson, doc, from_bson, Bson::UtcDatetime}; use chrono::prelude::*; +use num_enum::TryFromPrimitive; +use rocket_contrib::json::{Json, JsonValue}; +use serde::{Deserialize, Serialize}; use ulid::Ulid; #[derive(Debug, TryFromPrimitive)] #[repr(usize)] pub enum ChannelType { - DM = 0, - GROUPDM = 1, - GUILDCHANNEL = 2, + DM = 0, + GROUPDM = 1, + GUILDCHANNEL = 2, } fn has_permission(user: &User, target: &Channel) -> bool { - match target.channel_type { - 0..=1 => { - if let Some(arr) = &target.recipients { - for item in arr { - if item == &user.id { - return true; - } - } - } - - false - }, - 2 => - false, - _ => - false - } + match target.channel_type { + 0..=1 => { + if let Some(arr) = &target.recipients { + for item in arr { + if item == &user.id { + return true; + } + } + } + + false + } + 2 => false, + _ => false, + } } fn get_recipients(target: &Channel) -> Vec<String> { - match target.channel_type { - 0..=1 => target.recipients.clone().unwrap(), - _ => vec![] - } + match target.channel_type { + 0..=1 => target.recipients.clone().unwrap(), + _ => vec![], + } } /// fetch channel information #[get("/<target>")] pub fn channel(user: User, target: Channel) -> Option<JsonValue> { - if !has_permission(&user, &target) { - return None - } - - Some( - json!({ - "id": target.id, - "type": target.channel_type, - "recipients": get_recipients(&target), - } - )) + if !has_permission(&user, &target) { + return None; + } + + Some(json!({ + "id": target.id, + "type": target.channel_type, + "recipients": get_recipients(&target), + } + )) } /// delete channel @@ -64,152 +61,153 @@ pub fn channel(user: User, target: Channel) -> Option<JsonValue> { /// or close DM conversation #[delete("/<target>")] pub fn delete(user: User, target: Channel) -> Option<JsonValue> { - if !has_permission(&user, &target) { - return None - } - - let col = database::get_collection("channels"); - Some(match target.channel_type { - 0 => { - col.update_one( - doc! { "_id": target.id }, - doc! { "$set": { "active": false } }, - None - ).expect("Failed to update channel."); - - json!({ - "success": true - }) - }, - 1 => { - // ? TODO: group dm - - json!({ - "success": true - }) - }, - 2 => { - // ? TODO: guild - - json!({ - "success": true - }) - }, - _ => - json!({ - "success": false - }) - }) + if !has_permission(&user, &target) { + return None; + } + + let col = database::get_collection("channels"); + Some(match target.channel_type { + 0 => { + col.update_one( + doc! { "_id": target.id }, + doc! { "$set": { "active": false } }, + None, + ) + .expect("Failed to update channel."); + + json!({ + "success": true + }) + } + 1 => { + // ? TODO: group dm + + json!({ + "success": true + }) + } + 2 => { + // ? TODO: guild + + json!({ + "success": true + }) + } + _ => json!({ + "success": false + }), + }) } /// fetch channel messages #[get("/<target>/messages")] pub fn messages(user: User, target: Channel) -> Option<JsonValue> { - if !has_permission(&user, &target) { - return None - } - - let col = database::get_collection("messages"); - let result = col.find( - doc! { "channel": target.id }, - None - ).unwrap(); - - let mut messages = Vec::new(); - for item in result { - let message: Message = from_bson(bson::Bson::Document(item.unwrap())).expect("Failed to unwrap message."); - messages.push( - json!({ - "id": message.id, - "author": message.author, - "content": message.content, - "edited": if let Some(t) = message.edited { Some(t.timestamp()) } else { None } - }) - ); - } - - Some(json!(messages)) + if !has_permission(&user, &target) { + return None; + } + + let col = database::get_collection("messages"); + let result = col.find(doc! { "channel": target.id }, None).unwrap(); + + let mut messages = Vec::new(); + for item in result { + let message: Message = + from_bson(bson::Bson::Document(item.unwrap())).expect("Failed to unwrap message."); + messages.push(json!({ + "id": message.id, + "author": message.author, + "content": message.content, + "edited": if let Some(t) = message.edited { Some(t.timestamp()) } else { None } + })); + } + + Some(json!(messages)) } #[derive(Serialize, Deserialize)] pub struct SendMessage { - content: String, - nonce: String, + content: String, + nonce: String, } /// send a message to a channel #[post("/<target>/messages", data = "<message>")] pub fn send_message(user: User, target: Channel, message: Json<SendMessage>) -> Option<JsonValue> { - if !has_permission(&user, &target) { - return None - } - - let content: String = message.content.chars().take(2000).collect(); - let nonce: String = message.nonce.chars().take(32).collect(); - - let col = database::get_collection("messages"); - if let Some(_) = col.find_one(doc! { "nonce": nonce.clone() }, None).unwrap() { - return Some( - json!({ - "success": false, - "error": "Message already sent!" - }) - ) - } - - let id = Ulid::new().to_string(); - Some(if col.insert_one( - doc! { - "_id": id.clone(), - "nonce": nonce.clone(), - "channel": target.id.clone(), - "author": user.id.clone(), - "content": content.clone(), - }, - None - ).is_ok() { - if target.channel_type == ChannelType::DM as u8 { - let col = database::get_collection("channels"); - col.update_one( - doc! { "_id": target.id.clone() }, - doc! { "$set": { "active": true } }, - None - ).unwrap(); - } + if !has_permission(&user, &target) { + return None; + } - websocket::queue_message( - get_recipients(&target), - json!({ - "type": "message", - "data": { - "id": id.clone(), - "nonce": nonce, - "channel": target.id, - "author": user.id, - "content": content, - }, - }).to_string() - ); + let content: String = message.content.chars().take(2000).collect(); + let nonce: String = message.nonce.chars().take(32).collect(); - json!({ - "success": true, - "id": id - }) - } else { - json!({ + let col = database::get_collection("messages"); + if let Some(_) = col.find_one(doc! { "nonce": nonce.clone() }, None).unwrap() { + return Some(json!({ "success": false, - "error": "Failed database query." - }) - }) + "error": "Message already sent!" + })); + } + + let id = Ulid::new().to_string(); + Some( + if col + .insert_one( + doc! { + "_id": id.clone(), + "nonce": nonce.clone(), + "channel": target.id.clone(), + "author": user.id.clone(), + "content": content.clone(), + }, + None, + ) + .is_ok() + { + if target.channel_type == ChannelType::DM as u8 { + let col = database::get_collection("channels"); + col.update_one( + doc! { "_id": target.id.clone() }, + doc! { "$set": { "active": true } }, + None, + ) + .unwrap(); + } + + websocket::queue_message( + get_recipients(&target), + json!({ + "type": "message", + "data": { + "id": id.clone(), + "nonce": nonce, + "channel": target.id, + "author": user.id, + "content": content, + }, + }) + .to_string(), + ); + + json!({ + "success": true, + "id": id + }) + } else { + json!({ + "success": false, + "error": "Failed database query." + }) + }, + ) } /// get a message #[get("/<target>/messages/<message>")] pub fn get_message(user: User, target: Channel, message: Message) -> Option<JsonValue> { - if !has_permission(&user, &target) { - return None + if !has_permission(&user, &target) { + return None; } - + let prev = // ! CHECK IF USER HAS PERMISSION TO VIEW EDITS OF MESSAGES if let Some(previous) = message.previous_content { @@ -226,132 +224,127 @@ pub fn get_message(user: User, target: Channel, message: Message) -> Option<Json None }; - Some( - json!({ - "id": message.id, - "author": message.author, - "content": message.content, - "edited": if let Some(t) = message.edited { Some(t.timestamp()) } else { None }, - "previous_content": prev, - }) - ) + Some(json!({ + "id": message.id, + "author": message.author, + "content": message.content, + "edited": if let Some(t) = message.edited { Some(t.timestamp()) } else { None }, + "previous_content": prev, + })) } #[derive(Serialize, Deserialize)] pub struct EditMessage { - content: String, + content: String, } /// edit a message #[patch("/<target>/messages/<message>", data = "<edit>")] -pub fn edit_message(user: User, target: Channel, message: Message, edit: Json<EditMessage>) -> Option<JsonValue> { - if !has_permission(&user, &target) { - return None - } - - Some( - if message.author != user.id { - json!({ - "success": false, - "error": "You did not send this message." - }) - } else { - let col = database::get_collection("messages"); - - let time = - if let Some(edited) = message.edited { - edited.0 - } else { - Ulid::from_string(&message.id).unwrap().datetime() - }; - - let edited = Utc::now(); - match col.update_one( - doc! { "_id": message.id.clone() }, - doc! { - "$set": { - "content": edit.content.clone(), - "edited": UtcDatetime(edited.clone()) - }, - "$push": { - "previous_content": { - "content": message.content, - "time": time, - } - }, - }, - None - ) { - Ok(_) => { - websocket::queue_message( - get_recipients(&target), - json!({ - "type": "message_update", - "data": { - "id": message.id, - "channel": target.id, - "content": edit.content.clone(), - "edited": edited.timestamp() - }, - }).to_string() - ); - - json!({ - "success": true - }) - }, - Err(_) => - json!({ - "success": false, - "error": "Failed to update message." - }) - } - } - ) +pub fn edit_message( + user: User, + target: Channel, + message: Message, + edit: Json<EditMessage>, +) -> Option<JsonValue> { + if !has_permission(&user, &target) { + return None; + } + + Some(if message.author != user.id { + json!({ + "success": false, + "error": "You did not send this message." + }) + } else { + let col = database::get_collection("messages"); + + let time = if let Some(edited) = message.edited { + edited.0 + } else { + Ulid::from_string(&message.id).unwrap().datetime() + }; + + let edited = Utc::now(); + match col.update_one( + doc! { "_id": message.id.clone() }, + doc! { + "$set": { + "content": edit.content.clone(), + "edited": UtcDatetime(edited.clone()) + }, + "$push": { + "previous_content": { + "content": message.content, + "time": time, + } + }, + }, + None, + ) { + Ok(_) => { + websocket::queue_message( + get_recipients(&target), + json!({ + "type": "message_update", + "data": { + "id": message.id, + "channel": target.id, + "content": edit.content.clone(), + "edited": edited.timestamp() + }, + }) + .to_string(), + ); + + json!({ + "success": true + }) + } + Err(_) => json!({ + "success": false, + "error": "Failed to update message." + }), + } + }) } /// delete a message #[delete("/<target>/messages/<message>")] pub fn delete_message(user: User, target: Channel, message: Message) -> Option<JsonValue> { - if !has_permission(&user, &target) { - return None - } - - Some( - if message.author != user.id { - json!({ - "success": false, - "error": "You did not send this message." - }) - } else { - let col = database::get_collection("messages"); - - match col.delete_one( - doc! { "_id": message.id.clone() }, - None - ) { - Ok(_) => { - websocket::queue_message( - get_recipients(&target), - json!({ - "type": "message_delete", - "data": { - "id": message.id, - "channel": target.id - }, - }).to_string() - ); - - json!({ - "success": true - }) - }, - Err(_) => - json!({ - "success": false, - "error": "Failed to delete message." - }) - } - } - ) + if !has_permission(&user, &target) { + return None; + } + + Some(if message.author != user.id { + json!({ + "success": false, + "error": "You did not send this message." + }) + } else { + let col = database::get_collection("messages"); + + match col.delete_one(doc! { "_id": message.id.clone() }, None) { + Ok(_) => { + websocket::queue_message( + get_recipients(&target), + json!({ + "type": "message_delete", + "data": { + "id": message.id, + "channel": target.id + }, + }) + .to_string(), + ); + + json!({ + "success": true + }) + } + Err(_) => json!({ + "success": false, + "error": "Failed to delete message." + }), + } + }) } diff --git a/src/routes/guild.rs b/src/routes/guild.rs index 8bf22db84b8f188970bfae1f2595c7bc075d708c..478b76116a6ea3e3fa7d74b1b12b38adaf816657 100644 --- a/src/routes/guild.rs +++ b/src/routes/guild.rs @@ -1,8 +1,8 @@ -use crate::database::{ self, user::User }; +use crate::database::{self, user::User}; -use bson::{ bson, doc }; -use rocket_contrib::json::{ JsonValue, Json }; -use serde::{ Serialize, Deserialize }; +use bson::{bson, doc}; +use rocket_contrib::json::{Json, JsonValue}; +use serde::{Deserialize, Serialize}; use ulid::Ulid; use super::channel::ChannelType; @@ -25,16 +25,22 @@ pub fn create_guild(user: User, info: Json<CreateGuild>) -> JsonValue { } let name: String = info.name.chars().take(32).collect(); - let description: String = info.description.clone().unwrap_or("No description.".to_string()).chars().take(255).collect(); + let description: String = info + .description + .clone() + .unwrap_or("No description.".to_string()) + .chars() + .take(255) + .collect(); let nonce: String = info.nonce.chars().take(32).collect(); - - let channels = database::get_collection("channels"); - let col = database::get_collection("guilds"); - if let Some(_) = col.find_one(doc! { "nonce": nonce.clone() }, None).unwrap() { - return json!({ - "success": false, - "error": "Guild already created!" - }) + + let channels = database::get_collection("channels"); + let col = database::get_collection("guilds"); + if let Some(_) = col.find_one(doc! { "nonce": nonce.clone() }, None).unwrap() { + return json!({ + "success": false, + "error": "Guild already created!" + }); } let channel_id = Ulid::new().to_string(); @@ -44,37 +50,43 @@ pub fn create_guild(user: User, info: Json<CreateGuild>) -> JsonValue { "channel_type": ChannelType::GUILDCHANNEL as u32, "name": "general", }, - None) { + None, + ) { return json!({ "success": false, "error": "Failed to create guild channel." - }) + }); } - - let id = Ulid::new().to_string(); - if col.insert_one( - doc! { - "_id": id.clone(), - "nonce": nonce, - "name": name, - "description": description, - "owner": user.id.clone(), - "channels": [ - channel_id.clone() - ], - "members": [ - user.id - ], - "invites": [], - }, - None - ).is_ok() { + + let id = Ulid::new().to_string(); + if col + .insert_one( + doc! { + "_id": id.clone(), + "nonce": nonce, + "name": name, + "description": description, + "owner": user.id.clone(), + "channels": [ + channel_id.clone() + ], + "members": [ + user.id + ], + "invites": [], + }, + None, + ) + .is_ok() + { json!({ "success": true, "id": id, }) } else { - channels.delete_one(doc! { "_id": channel_id }, None).expect("Failed to delete the channel we just made."); + channels + .delete_one(doc! { "_id": channel_id }, None) + .expect("Failed to delete the channel we just made."); json!({ "success": false, diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 1865b089da3b2b73d9c4a502a5566b48d223d425..b6e409e87987e87ba11c9ac5e7c9b1e8141e9dbe 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,16 +1,49 @@ use rocket::Rocket; -pub mod root; pub mod account; -pub mod user; pub mod channel; pub mod guild; +pub mod root; +pub mod user; pub fn mount(rocket: Rocket) -> Rocket { - rocket - .mount("/api", routes![ root::root ]) - .mount("/api/account", routes![ account::create, account::verify_email, account::resend_email, account::login, account::token ]) - .mount("/api/users", routes![ user::me, user::user, user::lookup, user::dms, user::dm, user::get_friends, user::get_friend, user::add_friend, user::remove_friend ]) - .mount("/api/channels", routes![ channel::channel, channel::delete, channel::messages, channel::get_message, channel::send_message, channel::edit_message, channel::delete_message ]) - .mount("/api/guild", routes![ guild::create_guild ]) + rocket + .mount("/api", routes![root::root]) + .mount( + "/api/account", + routes![ + account::create, + account::verify_email, + account::resend_email, + account::login, + account::token + ], + ) + .mount( + "/api/users", + routes![ + user::me, + user::user, + user::lookup, + user::dms, + user::dm, + user::get_friends, + user::get_friend, + user::add_friend, + user::remove_friend + ], + ) + .mount( + "/api/channels", + routes![ + channel::channel, + channel::delete, + channel::messages, + channel::get_message, + channel::send_message, + channel::edit_message, + channel::delete_message + ], + ) + .mount("/api/guild", routes![guild::create_guild]) } diff --git a/src/routes/root.rs b/src/routes/root.rs index 112f05cc5d92b5cf3605feae2471da70f9545931..14aa560cce962242d18ae7109dfb33a66e3abbd7 100644 --- a/src/routes/root.rs +++ b/src/routes/root.rs @@ -1,10 +1,10 @@ -use rocket_contrib::json::{ JsonValue }; -use bson::{ doc }; +use bson::doc; +use rocket_contrib::json::JsonValue; /// root #[get("/")] pub fn root() -> JsonValue { - json!({ - "revolt": "0.0.1" - }) + json!({ + "revolt": "0.0.1" + }) } diff --git a/src/routes/user.rs b/src/routes/user.rs index 2f9d89abcb6ba32311f5ad18fa5e2b0d6a6f6e44..bf94bf30b1847516e7bfae7cbb1be41fd2151258 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -1,107 +1,109 @@ -use crate::database::{ self, user::User, channel::Channel }; +use crate::database::{self, channel::Channel, user::User}; use crate::routes::channel; -use rocket_contrib::json::{ Json, JsonValue }; -use serde::{ Serialize, Deserialize }; -use bson::{ bson, doc, from_bson }; +use bson::{bson, doc, from_bson}; use mongodb::options::FindOptions; +use rocket_contrib::json::{Json, JsonValue}; +use serde::{Deserialize, Serialize}; use ulid::Ulid; /// retrieve your user information #[get("/@me")] pub fn me(user: User) -> JsonValue { - json!({ - "id": user.id, - "username": user.username, - "email": user.email, - "verified": user.email_verification.verified, - }) + json!({ + "id": user.id, + "username": user.username, + "email": user.email, + "verified": user.email_verification.verified, + }) } /// retrieve another user's information #[get("/<target>")] pub fn user(user: User, target: User) -> JsonValue { - json!({ - "id": target.id, - "username": target.username, - "relationship": get_relationship(&user, &target) as u8 - }) + json!({ + "id": target.id, + "username": target.username, + "relationship": get_relationship(&user, &target) as u8 + }) } #[derive(Serialize, Deserialize)] pub struct Query { - username: String, + username: String, } /// lookup a user on Revolt /// currently only supports exact username searches #[post("/lookup", data = "<query>")] pub fn lookup(user: User, query: Json<Query>) -> JsonValue { - let col = database::get_collection("users"); + let col = database::get_collection("users"); - let users = col.find( - doc! { "username": query.username.clone() }, - FindOptions::builder().limit(10).build() - ).expect("Failed user lookup"); + let users = col + .find( + doc! { "username": query.username.clone() }, + FindOptions::builder().limit(10).build(), + ) + .expect("Failed user lookup"); - let mut results = Vec::new(); - for item in users { - let u: User = from_bson(bson::Bson::Document(item.unwrap())).expect("Failed to unwrap user."); - results.push( - json!({ - "id": u.id, - "username": u.username, - "relationship": get_relationship(&user, &u) as u8 - }) - ); - } + let mut results = Vec::new(); + for item in users { + let u: User = + from_bson(bson::Bson::Document(item.unwrap())).expect("Failed to unwrap user."); + results.push(json!({ + "id": u.id, + "username": u.username, + "relationship": get_relationship(&user, &u) as u8 + })); + } - json!(results) + json!(results) } /// retrieve all of your DMs #[get("/@me/dms")] pub fn dms(user: User) -> JsonValue { - let col = database::get_collection("channels"); + let col = database::get_collection("channels"); - let results = col.find( - doc! { - "$or": [ - { - "type": channel::ChannelType::DM as i32 - }, - { - "type": channel::ChannelType::GROUPDM as i32 - } - ], - "recipients": user.id - }, - None - ).expect("Failed channel lookup"); + let results = col + .find( + doc! { + "$or": [ + { + "type": channel::ChannelType::DM as i32 + }, + { + "type": channel::ChannelType::GROUPDM as i32 + } + ], + "recipients": user.id + }, + None, + ) + .expect("Failed channel lookup"); - let mut channels = Vec::new(); - for item in results { - let channel: Channel = from_bson(bson::Bson::Document(item.unwrap())).expect("Failed to unwrap channel."); - - channels.push( - json!({ - "id": channel.id, - "type": channel.channel_type, - "recipients": channel.recipients, - "active": channel.active.unwrap() - }) - ); - } + let mut channels = Vec::new(); + for item in results { + let channel: Channel = + from_bson(bson::Bson::Document(item.unwrap())).expect("Failed to unwrap channel."); + + channels.push(json!({ + "id": channel.id, + "type": channel.channel_type, + "recipients": channel.recipients, + "active": channel.active.unwrap() + })); + } - json!(channels) + json!(channels) } /// open a DM with a user #[get("/<target>/dm")] pub fn dm(user: User, target: User) -> JsonValue { - let col = database::get_collection("channels"); + let col = database::get_collection("channels"); - match col.find_one( + match col.find_one( doc! { "type": channel::ChannelType::DM as i32, "recipients": { "$all": [ user.id.clone(), target.id.clone() ] } }, None ).expect("Failed channel lookup") { @@ -132,231 +134,215 @@ pub fn dm(user: User, target: User) -> JsonValue { } enum Relationship { - FRIEND = 0, - OUTGOING = 1, - INCOMING = 2, - BLOCKED = 3, - BLOCKEDOTHER = 4, - NONE = 5, - SELF = 6, + FRIEND = 0, + OUTGOING = 1, + INCOMING = 2, + BLOCKED = 3, + BLOCKEDOTHER = 4, + NONE = 5, + SELF = 6, } fn get_relationship(a: &User, b: &User) -> Relationship { - if a.id == b.id { - return Relationship::SELF - } + if a.id == b.id { + return Relationship::SELF; + } - if let Some(arr) = &b.relations { - for entry in arr { - if entry.id == a.id { - match entry.status { - 0 => { - return Relationship::FRIEND - }, - 1 => { - return Relationship::INCOMING - }, - 2 => { - return Relationship::OUTGOING - }, - 3 => { - return Relationship::BLOCKEDOTHER - }, - 4 => { - return Relationship::BLOCKED - }, - _ => { - return Relationship::NONE - } - } - } - } - } + if let Some(arr) = &b.relations { + for entry in arr { + if entry.id == a.id { + match entry.status { + 0 => return Relationship::FRIEND, + 1 => return Relationship::INCOMING, + 2 => return Relationship::OUTGOING, + 3 => return Relationship::BLOCKEDOTHER, + 4 => return Relationship::BLOCKED, + _ => return Relationship::NONE, + } + } + } + } - Relationship::NONE + Relationship::NONE } /// retrieve all of your friends #[get("/@me/friend")] pub fn get_friends(user: User) -> JsonValue { - let mut results = Vec::new(); - if let Some(arr) = user.relations { - for item in arr { - results.push( - json!({ - "id": item.id, - "status": item.status - }) - ) - } - } - - json!(results) + let mut results = Vec::new(); + if let Some(arr) = user.relations { + for item in arr { + results.push(json!({ + "id": item.id, + "status": item.status + })) + } + } + + json!(results) } /// retrieve friend status with user #[get("/<target>/friend")] pub fn get_friend(user: User, target: User) -> JsonValue { - let relationship = get_relationship(&user, &target); + let relationship = get_relationship(&user, &target); - json!({ - "id": target.id, - "status": relationship as u8 - }) + json!({ + "id": target.id, + "status": relationship as u8 + }) } /// create or accept a friend request #[put("/<target>/friend")] pub fn add_friend(user: User, target: User) -> JsonValue { - let col = database::get_collection("users"); - let relationship = get_relationship(&user, &target); + let col = database::get_collection("users"); + let relationship = get_relationship(&user, &target); - match relationship { - Relationship::FRIEND => - json!({ - "success": false, - "error": "Already friends." - }), - Relationship::OUTGOING => - json!({ - "success": false, - "error": "Already sent a friend request." - }), - Relationship::INCOMING => { - col.update_one( - doc! { - "_id": user.id.clone(), - "relations.id": target.id.clone() - }, - doc! { - "$set": { - "relations.$.status": Relationship::FRIEND as i32 - } - }, - None - ).expect("Failed update query."); - - col.update_one( - doc! { - "_id": target.id, - "relations.id": user.id - }, - doc! { - "$set": { - "relations.$.status": Relationship::FRIEND as i32 - } - }, - None - ).expect("Failed update query."); + match relationship { + Relationship::FRIEND => json!({ + "success": false, + "error": "Already friends." + }), + Relationship::OUTGOING => json!({ + "success": false, + "error": "Already sent a friend request." + }), + Relationship::INCOMING => { + col.update_one( + doc! { + "_id": user.id.clone(), + "relations.id": target.id.clone() + }, + doc! { + "$set": { + "relations.$.status": Relationship::FRIEND as i32 + } + }, + None, + ) + .expect("Failed update query."); - json!({ - "success": true, - "status": Relationship::FRIEND as u8, - }) - }, - Relationship::BLOCKED => - json!({ - "success": false, - "error": "You have blocked this person." - }), - Relationship::BLOCKEDOTHER => - json!({ - "success": false, - "error": "You have been blocked by this person." - }), - Relationship::NONE => { - col.update_one( - doc! { - "_id": user.id.clone() - }, - doc! { - "$push": { - "relations": { - "id": target.id.clone(), - "status": Relationship::OUTGOING as i32 - } - } - }, - None - ).expect("Failed update query."); - - col.update_one( - doc! { - "_id": target.id - }, - doc! { - "$push": { - "relations": { - "id": user.id, - "status": Relationship::INCOMING as i32 - } - } - }, - None - ).expect("Failed update query."); + col.update_one( + doc! { + "_id": target.id, + "relations.id": user.id + }, + doc! { + "$set": { + "relations.$.status": Relationship::FRIEND as i32 + } + }, + None, + ) + .expect("Failed update query."); - json!({ - "success": true, - "status": Relationship::OUTGOING as u8, - }) - }, - Relationship::SELF => - json!({ - "success": false, - "error": "Cannot add yourself as a friend." - }) - } + json!({ + "success": true, + "status": Relationship::FRIEND as u8, + }) + } + Relationship::BLOCKED => json!({ + "success": false, + "error": "You have blocked this person." + }), + Relationship::BLOCKEDOTHER => json!({ + "success": false, + "error": "You have been blocked by this person." + }), + Relationship::NONE => { + col.update_one( + doc! { + "_id": user.id.clone() + }, + doc! { + "$push": { + "relations": { + "id": target.id.clone(), + "status": Relationship::OUTGOING as i32 + } + } + }, + None, + ) + .expect("Failed update query."); + + col.update_one( + doc! { + "_id": target.id + }, + doc! { + "$push": { + "relations": { + "id": user.id, + "status": Relationship::INCOMING as i32 + } + } + }, + None, + ) + .expect("Failed update query."); + + json!({ + "success": true, + "status": Relationship::OUTGOING as u8, + }) + } + Relationship::SELF => json!({ + "success": false, + "error": "Cannot add yourself as a friend." + }), + } } /// remove a friend or deny a request #[delete("/<target>/friend")] pub fn remove_friend(user: User, target: User) -> JsonValue { - let col = database::get_collection("users"); - let relationship = get_relationship(&user, &target); - - match relationship { - Relationship::FRIEND | - Relationship::OUTGOING | - Relationship::INCOMING => { - col.update_one( - doc! { - "_id": user.id.clone() - }, - doc! { - "$pull": { - "relations": { - "id": target.id.clone() - } - } - }, - None - ).expect("Failed update query."); + let col = database::get_collection("users"); + let relationship = get_relationship(&user, &target); - col.update_one( - doc! { - "_id": target.id - }, - doc! { - "$pull": { - "relations": { - "id": user.id - } - } - }, - None - ).expect("Failed update query."); + match relationship { + Relationship::FRIEND | Relationship::OUTGOING | Relationship::INCOMING => { + col.update_one( + doc! { + "_id": user.id.clone() + }, + doc! { + "$pull": { + "relations": { + "id": target.id.clone() + } + } + }, + None, + ) + .expect("Failed update query."); - json!({ - "success": true - }) - }, - Relationship::BLOCKED | - Relationship::BLOCKEDOTHER | - Relationship::NONE | - Relationship::SELF => - json!({ - "success": false, - "error": "This has no effect." - }) - } + col.update_one( + doc! { + "_id": target.id + }, + doc! { + "$pull": { + "relations": { + "id": user.id + } + } + }, + None, + ) + .expect("Failed update query."); + + json!({ + "success": true + }) + } + Relationship::BLOCKED + | Relationship::BLOCKEDOTHER + | Relationship::NONE + | Relationship::SELF => json!({ + "success": false, + "error": "This has no effect." + }), + } } diff --git a/src/websocket/mod.rs b/src/websocket/mod.rs index d0fbdf51bc04729963a15a0f8737d4a5b369a6b9..f162361f8c474a0c3eece29ced2a88ba5c10b9bf 100644 --- a/src/websocket/mod.rs +++ b/src/websocket/mod.rs @@ -2,138 +2,147 @@ extern crate ws; use crate::database; -use ulid::Ulid; -use std::sync::RwLock; use hashbrown::HashMap; +use std::sync::RwLock; +use ulid::Ulid; -use bson::{ bson, doc }; -use serde_json::{ Value, from_str, json }; +use bson::{bson, doc}; +use serde_json::{from_str, json, Value}; -use ws::{ listen, Handler, Sender, Result, Message, Handshake, CloseCode, Error }; +use ws::{listen, CloseCode, Error, Handler, Handshake, Message, Result, Sender}; struct Cell { - id: String, - out: Sender, + id: String, + out: Sender, } use once_cell::sync::OnceCell; static mut CLIENTS: OnceCell<RwLock<HashMap<String, Vec<Cell>>>> = OnceCell::new(); struct Server { - out: Sender, - id: Option<String>, - internal: String, + out: Sender, + id: Option<String>, + internal: String, } impl Handler for Server { fn on_open(&mut self, _: Handshake) -> Result<()> { - Ok(()) + Ok(()) } fn on_message(&mut self, msg: Message) -> Result<()> { - if let Message::Text(text) = msg { - let data: Value = from_str(&text).unwrap(); - - if let Value::String(packet_type) = &data["type"] { - match packet_type.as_str() { - "authenticate" => { - if let Some(_) = self.id { - self.out.send( - json!({ - "type": "authenticate", - "success": false, - "error": "Already authenticated!" - }) - .to_string() - ) - } else if let Value::String(token) = &data["token"] { - let col = database::get_collection("users"); - - match col.find_one( - doc! { "access_token": token }, - None - ).unwrap() { - Some(u) => { - let id = u.get_str("_id").expect("Missing id."); - - unsafe { - let mut map = CLIENTS.get_mut().unwrap().write().unwrap(); - let cell = Cell { id: self.internal.clone(), out: self.out.clone() }; - if map.contains_key(&id.to_string()) { - map.get_mut(&id.to_string()) - .unwrap() - .push(cell); - } else { - map.insert(id.to_string(), vec![cell]); - } - } - - println!("Websocket client connected. [ID: {} // {}]", id.to_string(), self.internal); - - self.id = Some(id.to_string()); - self.out.send( - json!({ - "type": "authenticate", - "success": true - }) - .to_string() - ) - }, - None => - self.out.send( - json!({ - "type": "authenticate", - "success": false, - "error": "Invalid authentication token." - }) - .to_string() - ) - } - } else { - self.out.send( - json!({ - "type": "authenticate", - "success": false, - "error": "Missing authentication token." - }) - .to_string() - ) - } - }, - _ => Ok(()) - } - } else { - Ok(()) - } - } else { - Ok(()) - } + if let Message::Text(text) = msg { + let data: Value = from_str(&text).unwrap(); + + if let Value::String(packet_type) = &data["type"] { + match packet_type.as_str() { + "authenticate" => { + if let Some(_) = self.id { + self.out.send( + json!({ + "type": "authenticate", + "success": false, + "error": "Already authenticated!" + }) + .to_string(), + ) + } else if let Value::String(token) = &data["token"] { + let col = database::get_collection("users"); + + match col.find_one(doc! { "access_token": token }, None).unwrap() { + Some(u) => { + let id = u.get_str("_id").expect("Missing id."); + + unsafe { + let mut map = CLIENTS.get_mut().unwrap().write().unwrap(); + let cell = Cell { + id: self.internal.clone(), + out: self.out.clone(), + }; + if map.contains_key(&id.to_string()) { + map.get_mut(&id.to_string()).unwrap().push(cell); + } else { + map.insert(id.to_string(), vec![cell]); + } + } + + println!( + "Websocket client connected. [ID: {} // {}]", + id.to_string(), + self.internal + ); + + self.id = Some(id.to_string()); + self.out.send( + json!({ + "type": "authenticate", + "success": true + }) + .to_string(), + ) + } + None => self.out.send( + json!({ + "type": "authenticate", + "success": false, + "error": "Invalid authentication token." + }) + .to_string(), + ), + } + } else { + self.out.send( + json!({ + "type": "authenticate", + "success": false, + "error": "Missing authentication token." + }) + .to_string(), + ) + } + } + _ => Ok(()), + } + } else { + Ok(()) + } + } else { + Ok(()) + } } fn on_close(&mut self, code: CloseCode, reason: &str) { match code { CloseCode::Normal => println!("The client is done with the connection."), - CloseCode::Away => println!("The client is leaving the site."), - CloseCode::Abnormal => println!( - "Closing handshake failed! Unable to obtain closing status from client."), + CloseCode::Away => println!("The client is leaving the site."), + CloseCode::Abnormal => { + println!("Closing handshake failed! Unable to obtain closing status from client.") + } _ => println!("The client encountered an error: {}", reason), } - if let Some(id) = &self.id { - println!("Websocket client disconnected. [ID: {} // {}]", id, self.internal); - unsafe { - let mut map = CLIENTS.get_mut().unwrap().write().unwrap(); - let arr = map.get_mut(&id.clone()).unwrap(); - - if arr.len() == 1 { - map.remove(&id.clone()); - } else { - let index = arr.iter().position(|x| x.id == self.internal).unwrap(); - arr.remove(index); - println!("User [{}] is still connected {} times", self.id.as_ref().unwrap(), arr.len()); - } - } - } + if let Some(id) = &self.id { + println!( + "Websocket client disconnected. [ID: {} // {}]", + id, self.internal + ); + unsafe { + let mut map = CLIENTS.get_mut().unwrap().write().unwrap(); + let arr = map.get_mut(&id.clone()).unwrap(); + + if arr.len() == 1 { + map.remove(&id.clone()); + } else { + let index = arr.iter().position(|x| x.id == self.internal).unwrap(); + arr.remove(index); + println!( + "User [{}] is still connected {} times", + self.id.as_ref().unwrap(), + arr.len() + ); + } + } + } } fn on_error(&mut self, err: Error) { @@ -142,37 +151,43 @@ impl Handler for Server { } pub fn launch_server() { - unsafe { - if let Err(_) = CLIENTS.set(RwLock::new(HashMap::new())) { - panic!("Failed to set CLIENTS map!"); - } - } + unsafe { + if let Err(_) = CLIENTS.set(RwLock::new(HashMap::new())) { + panic!("Failed to set CLIENTS map!"); + } + } - listen("192.168.0.10:9999", |out| { Server { out: out, id: None, internal: Ulid::new().to_string() } }).unwrap() + listen("192.168.0.10:9999", |out| Server { + out: out, + id: None, + internal: Ulid::new().to_string(), + }) + .unwrap() } pub fn send_message(id: String, message: String) -> std::result::Result<(), ()> { - unsafe { - let map = CLIENTS.get().unwrap().read().unwrap(); - if map.contains_key(&id) { - let arr = map.get(&id).unwrap(); - - for item in arr { - if let Err(_) = item.out.send(message.clone()) { - return Err(()); - } - } - } - - Ok(()) - } + unsafe { + let map = CLIENTS.get().unwrap().read().unwrap(); + if map.contains_key(&id) { + let arr = map.get(&id).unwrap(); + + for item in arr { + if let Err(_) = item.out.send(message.clone()) { + return Err(()); + } + } + } + + Ok(()) + } } // ! TODO: WRITE THREADED QUEUE SYSTEM // ! FETCH RECIPIENTS HERE INSTEAD OF IN METHOD pub fn queue_message(ids: Vec<String>, message: String) { - for id in ids { - send_message(id, message.clone()).expect("uhhhhhhhhhh can i get uhhhhhhhhhhhhhhhhhh mcdonald cheese burger with fries"); - } + for id in ids { + send_message(id, message.clone()) + .expect("uhhhhhhhhhh can i get uhhhhhhhhhhhhhhhhhh mcdonald cheese burger with fries"); + } }