Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Showing
with 558 additions and 321 deletions
use crate::database::*;
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
use rauth::auth::{Auth, Session};
use regex::Regex;
use rocket::State;
use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};
use validator::Validate;
// ! FIXME: should be global somewhere; maybe use config(?)
lazy_static! {
static ref RE_USERNAME: Regex = Regex::new(r"^[a-zA-Z0-9_.]+$").unwrap();
}
#[derive(Validate, Serialize, Deserialize)]
pub struct Data {
#[validate(length(min = 2, max = 32), regex = "RE_USERNAME")]
username: Option<String>,
#[validate(length(min = 8, max = 72))]
password: String,
}
#[patch("/<_ignore_id>/username", data = "<data>")]
pub async fn req(
auth: State<'_, Auth>,
session: Session,
user: User,
data: Json<Data>,
_ignore_id: String,
) -> Result<()> {
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
auth.verify_password(&session, data.password.clone())
.await
.map_err(|_| Error::InvalidCredentials)?;
let mut set = doc! {};
if let Some(username) = &data.username {
if User::is_username_taken(&username).await? {
return Err(Error::UsernameTaken);
}
set.insert("username", username.clone());
}
get_collection("users")
.update_one(doc! { "_id": &user.id }, doc! { "$set": set }, None)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "user",
})?;
ClientboundNotification::UserUpdate {
id: user.id.clone(),
data: json!({
"username": data.username
}),
clear: None,
}
.publish_as_user(user.id.clone());
Ok(())
}
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use crate::{database::*, notifications::events::RemoveUserField};
use mongodb::bson::{doc, to_document};
use rocket_contrib::json::Json;
use serde::{Deserialize, Serialize};
use validator::Validate;
#[derive(Validate, Serialize, Deserialize, Debug)]
pub struct UserProfileData {
#[validate(length(min = 0, max = 2000))]
#[serde(skip_serializing_if = "Option::is_none")]
content: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[validate(length(min = 1, max = 128))]
background: Option<String>,
}
#[derive(Validate, Serialize, Deserialize)]
pub struct Data {
#[validate]
status: Option<UserStatus>,
#[validate]
profile: Option<UserProfileData>,
#[validate(length(min = 1, max = 128))]
avatar: Option<String>,
remove: Option<RemoveUserField>,
}
#[patch("/<_ignore_id>", data = "<data>")]
pub async fn req(user: User, data: Json<Data>, _ignore_id: String) -> Result<()> {
let mut data = data.into_inner();
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
if data.status.is_none()
&& data.profile.is_none()
&& data.avatar.is_none()
&& data.remove.is_none()
{
return Ok(());
}
let mut unset = doc! {};
let mut set = doc! {};
let mut remove_background = false;
let mut remove_avatar = false;
if let Some(remove) = &data.remove {
match remove {
RemoveUserField::ProfileContent => {
unset.insert("profile.content", 1);
}
RemoveUserField::ProfileBackground => {
unset.insert("profile.background", 1);
remove_background = true;
}
RemoveUserField::StatusText => {
unset.insert("status.text", 1);
}
RemoveUserField::Avatar => {
unset.insert("avatar", 1);
remove_avatar = true;
}
}
}
if let Some(status) = &data.status {
set.insert(
"status",
to_document(&status).map_err(|_| Error::DatabaseError {
operation: "to_document",
with: "status",
})?,
);
}
if let Some(profile) = data.profile {
if let Some(content) = profile.content {
set.insert("profile.content", content);
}
if let Some(attachment_id) = profile.background {
let attachment =
File::find_and_use(&attachment_id, "backgrounds", "user", &user.id).await?;
set.insert(
"profile.background",
to_document(&attachment).map_err(|_| Error::DatabaseError {
operation: "to_document",
with: "attachment",
})?,
);
remove_background = true;
}
}
let avatar = std::mem::replace(&mut data.avatar, None);
if let Some(attachment_id) = avatar {
let attachment = File::find_and_use(&attachment_id, "avatars", "user", &user.id).await?;
set.insert(
"avatar",
to_document(&attachment).map_err(|_| Error::DatabaseError {
operation: "to_document",
with: "attachment",
})?,
);
remove_avatar = true;
}
let mut operations = doc! {};
if set.len() > 0 {
operations.insert("$set", &set);
}
if unset.len() > 0 {
operations.insert("$unset", unset);
}
if operations.len() > 0 {
get_collection("users")
.update_one(doc! { "_id": &user.id }, operations, None)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "user",
})?;
}
ClientboundNotification::UserUpdate {
id: user.id.clone(),
data: json!(set),
clear: data.remove,
}
.publish_as_user(user.id.clone());
if remove_avatar {
if let Some(old_avatar) = user.avatar {
old_avatar.delete().await?;
}
}
if remove_background {
if let Some(profile) = user.profile {
if let Some(old_background) = profile.background {
old_background.delete().await?;
}
}
}
Ok(())
}
......@@ -12,11 +12,11 @@ pub async fn req(user: User) -> Result<JsonValue> {
doc! {
"$or": [
{
"type": "DirectMessage",
"channel_type": "DirectMessage",
"active": true
},
{
"type": "Group"
"channel_type": "Group"
}
],
"recipients": user.id
......
use crate::database::*;
use crate::util::result::{Error, Result};
use mongodb::bson::doc;
use rocket_contrib::json::JsonValue;
#[get("/<target>/profile")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let target = target.fetch_user().await?;
let perm = permissions::PermissionCalculator::new(&user)
.with_user(&target)
.for_user_given()
.await?;
if !perm.get_view_profile() {
Err(Error::MissingPermission)?
}
if target.profile.is_some() {
Ok(json!(target.profile))
} else {
Ok(json!({}))
}
}
......@@ -5,5 +5,5 @@ use rocket_contrib::json::JsonValue;
#[get("/<target>/relationship")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
Ok(json!({ "status": get_relationship(&user, &target) }))
Ok(json!({ "status": get_relationship(&user, &target.id) }))
}
......@@ -5,31 +5,16 @@ use rocket_contrib::json::JsonValue;
#[get("/<target>")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let mut target = target.fetch_user().await?;
let target = target.fetch_user().await?;
if user.id != target.id {
// Check whether we are allowed to fetch this user.
let perm = crate::database::permissions::temp_calc_perm(&user, &target).await;
if !perm.get_access() {
Err(Error::LabelMe)?
}
let perm = permissions::PermissionCalculator::new(&user)
.with_user(&target)
.for_user_given()
.await?;
// Only return user relationships if the target is the caller.
target.relations = None;
// Add relevant relationship
if let Some(relationships) = &user.relations {
target.relationship = relationships
.iter()
.find(|x| x.id == user.id)
.map(|x| x.status.clone())
.or_else(|| Some(RelationshipStatus::None));
} else {
target.relationship = Some(RelationshipStatus::None);
}
} else {
target.relationship = Some(RelationshipStatus::User);
if !perm.get_access() {
Err(Error::MissingPermission)?
}
Ok(json!(target))
Ok(json!(target.from(&user).with(perm)))
}
use crate::database::*;
use crate::util::result::{Error, Result};
use futures::StreamExt;
use mongodb::bson::{doc, Document};
use mongodb::options::FindOptions;
use rocket_contrib::json::JsonValue;
#[get("/<target>/mutual")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let users = get_collection("users")
.find(
doc! {
"$and": [
{ "relations": { "$elemMatch": { "_id": &user.id, "status": "Friend" } } },
{ "relations": { "$elemMatch": { "_id": &target.id, "status": "Friend" } } }
]
},
FindOptions::builder().projection(doc! { "_id": 1 }).build(),
)
.await
.map_err(|_| Error::DatabaseError {
operation: "find",
with: "users",
})?
.filter_map(async move |s| s.ok())
.collect::<Vec<Document>>()
.await
.into_iter()
.filter_map(|x| x.get_str("_id").ok().map(|x| x.to_string()))
.collect::<Vec<String>>();
Ok(json!({ "users": users }))
}
use rocket::response::NamedFile;
use std::path::Path;
#[get("/<_target>/avatar")]
pub async fn req(_target: String) -> Option<NamedFile> {
NamedFile::open(Path::new("avatar.png")).await.ok()
}
use rocket::{Request, Response};
use rocket::response::{self, NamedFile, Responder};
use std::path::Path;
use crate::database::Ref;
pub struct CachedFile(NamedFile);
pub static CACHE_CONTROL: &'static str = "public, max-age=31536000, immutable";
impl<'r> Responder<'r, 'static> for CachedFile {
fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
Response::build_from(self.0.respond_to(req)?)
.raw_header("Cache-control", CACHE_CONTROL)
.ok()
}
}
#[get("/<target>/default_avatar")]
pub async fn req(target: Ref) -> Option<CachedFile> {
match target.id.chars().nth(25).unwrap() {
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' => {
NamedFile::open(Path::new("assets/user_red.png")).await.ok().map(|n| CachedFile(n))
}
'8' | '9' | 'A' | 'C' | 'B' | 'D' | 'E' | 'F' => {
NamedFile::open(Path::new("assets/user_green.png"))
.await
.ok().map(|n| CachedFile(n))
}
'G' | 'H' | 'J' | 'K' | 'M' | 'N' | 'P' | 'Q' => {
NamedFile::open(Path::new("assets/user_blue.png"))
.await
.ok().map(|n| CachedFile(n))
}
'R' | 'S' | 'T' | 'V' | 'W' | 'X' | 'Y' | 'Z' => {
NamedFile::open(Path::new("assets/user_yellow.png"))
.await
.ok().map(|n| CachedFile(n))
}
_ => unreachable!(),
}
}
......@@ -2,11 +2,15 @@ use rocket::Route;
mod add_friend;
mod block_user;
mod change_username;
mod edit_user;
mod fetch_dms;
mod fetch_profile;
mod fetch_relationship;
mod fetch_relationships;
mod fetch_user;
mod get_avatar;
mod find_mutual;
mod get_default_avatar;
mod open_dm;
mod remove_friend;
mod unblock_user;
......@@ -15,11 +19,15 @@ pub fn routes() -> Vec<Route> {
routes![
// User Information
fetch_user::req,
get_avatar::req,
edit_user::req,
change_username::req,
get_default_avatar::req,
fetch_profile::req,
// Direct Messaging
fetch_dms::req,
open_dm::req,
// Relationships
find_mutual::req,
fetch_relationships::req,
fetch_relationship::req,
add_friend::req,
......
......@@ -9,12 +9,12 @@ use ulid::Ulid;
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let query = if user.id == target.id {
doc! {
"type": "SavedMessages",
"channel_type": "SavedMessages",
"user": &user.id
}
} else {
doc! {
"type": "DirectMessage",
"channel_type": "DirectMessage",
"recipients": {
"$all": [ &user.id, &target.id ]
}
......@@ -40,10 +40,11 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
id,
active: false,
recipients: vec![user.id, target.id],
last_message: None,
}
};
channel.save().await?;
channel.clone().publish().await?;
Ok(json!(channel))
}
}
use crate::database::*;
use crate::notifications::{events::ClientboundNotification, hive};
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use futures::try_join;
use hive_pubsub::PubSub;
use mongodb::bson::doc;
use rocket_contrib::json::JsonValue;
......@@ -11,7 +10,9 @@ use rocket_contrib::json::JsonValue;
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let col = get_collection("users");
match get_relationship(&user, &target) {
let target = target.fetch_user().await?;
match get_relationship(&user, &target.id) {
RelationshipStatus::Friend
| RelationshipStatus::Outgoing
| RelationshipStatus::Incoming => {
......@@ -44,25 +45,27 @@ pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
)
) {
Ok(_) => {
try_join!(
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target.id.clone(),
status: RelationshipStatus::None
}
.publish(user.id.clone()),
ClientboundNotification::UserRelationship {
id: target.id.clone(),
user: user.id.clone(),
status: RelationshipStatus::None
}
.publish(target.id.clone())
)
.ok();
let target = target
.from_override(&user, RelationshipStatus::None)
.await?;
let user = user
.from_override(&target, RelationshipStatus::None)
.await?;
let target_id = target.id.clone();
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target,
status: RelationshipStatus::None,
}
.publish(user.id.clone());
let hive = hive::get_hive();
hive.unsubscribe(&user.id, &target.id).ok();
hive.unsubscribe(&target.id, &user.id).ok();
ClientboundNotification::UserRelationship {
id: target_id.clone(),
user,
status: RelationshipStatus::None,
}
.publish(target_id);
Ok(json!({ "status": "None" }))
}
......
use crate::database::*;
use crate::notifications::{events::ClientboundNotification, hive};
use crate::notifications::events::ClientboundNotification;
use crate::util::result::{Error, Result};
use futures::try_join;
use hive_pubsub::PubSub;
use mongodb::bson::doc;
use rocket_contrib::json::JsonValue;
#[delete("/<target>/block")]
pub async fn req(user: User, target: Ref) -> Result<JsonValue> {
let col = get_collection("users");
let target = target.fetch_user().await?;
match get_relationship(&user, &target) {
RelationshipStatus::Blocked => {
match get_relationship(&target.fetch_user().await?, &user.as_ref()) {
RelationshipStatus::Blocked => {
match get_relationship(&user, &target.id) {
RelationshipStatus::Blocked => match get_relationship(&target, &user.id) {
RelationshipStatus::Blocked => {
col.update_one(
doc! {
"_id": &user.id,
"relations._id": &target.id
},
doc! {
"$set": {
"relations.$.status": "BlockedOther"
}
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "user",
})?;
let target = target
.from_override(&user, RelationshipStatus::BlockedOther)
.await?;
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target,
status: RelationshipStatus::BlockedOther,
}
.publish(user.id.clone());
Ok(json!({ "status": "BlockedOther" }))
}
RelationshipStatus::BlockedOther => {
match try_join!(
col.update_one(
doc! {
"_id": &user.id,
"relations._id": &target.id
"_id": &user.id
},
doc! {
"$set": {
"relations.$.status": "BlockedOther"
"$pull": {
"relations": {
"_id": &target.id
}
}
},
None,
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
with: "user",
})?;
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target.id.clone(),
status: RelationshipStatus::BlockedOther,
}
.publish(user.id.clone())
.await
.ok();
Ok(json!({ "status": "BlockedOther" }))
}
RelationshipStatus::BlockedOther => {
match try_join!(
col.update_one(
doc! {
"_id": &user.id
},
doc! {
"$pull": {
"relations": {
"_id": &target.id
}
}
},
None
),
col.update_one(
doc! {
"_id": &target.id
},
doc! {
"$pull": {
"relations": {
"_id": &user.id
}
}
},
None
)
) {
Ok(_) => {
try_join!(
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target.id.clone(),
status: RelationshipStatus::None
}
.publish(user.id.clone()),
ClientboundNotification::UserRelationship {
id: target.id.clone(),
user: user.id.clone(),
status: RelationshipStatus::None
None
),
col.update_one(
doc! {
"_id": &target.id
},
doc! {
"$pull": {
"relations": {
"_id": &user.id
}
.publish(target.id.clone())
)
.ok();
}
},
None
)
) {
Ok(_) => {
let target = target
.from_override(&user, RelationshipStatus::None)
.await?;
let user = user
.from_override(&target, RelationshipStatus::None)
.await?;
let target_id = target.id.clone();
let hive = hive::get_hive();
hive.unsubscribe(&user.id, &target.id).ok();
hive.unsubscribe(&target.id, &user.id).ok();
ClientboundNotification::UserRelationship {
id: user.id.clone(),
user: target,
status: RelationshipStatus::None,
}
.publish(user.id.clone());
Ok(json!({ "status": "None" }))
ClientboundNotification::UserRelationship {
id: target_id.clone(),
user: user,
status: RelationshipStatus::None,
}
Err(_) => Err(Error::DatabaseError {
operation: "update_one",
with: "user",
}),
.publish(target_id);
Ok(json!({ "status": "None" }))
}
Err(_) => Err(Error::DatabaseError {
operation: "update_one",
with: "user",
}),
}
_ => Err(Error::InternalError),
}
}
_ => Err(Error::InternalError),
},
_ => Err(Error::NoEffect),
}
}
use crate::util::variables::{HCAPTCHA_KEY, USE_HCAPTCHA};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize)]
struct CaptchaResponse {
success: bool,
}
pub async fn verify(user_token: &Option<String>) -> Result<(), String> {
if *USE_HCAPTCHA {
if let Some(token) = user_token {
let mut map = HashMap::new();
map.insert("secret", HCAPTCHA_KEY.to_string());
map.insert("response", token.to_string());
let client = Client::new();
if let Ok(response) = client
.post("https://hcaptcha.com/siteverify")
.form(&map)
.send()
.await
{
let result: CaptchaResponse = response
.json()
.await
.map_err(|_| "Failed to deserialise captcha result.".to_string())?;
if result.success {
Ok(())
} else {
Err("Unsuccessful captcha verification".to_string())
}
} else {
Err("Failed to verify with hCaptcha".to_string())
}
} else {
Err("Missing hCaptcha token!".to_string())
}
} else {
Ok(())
}
}
use lettre::message::{header, MultiPart, SinglePart};
use lettre::transport::smtp::authentication::Credentials;
use lettre::{Message, SmtpTransport, Transport};
use super::variables::{PUBLIC_URL, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD, SMTP_USERNAME};
lazy_static! {
static ref MAILER: lettre::transport::smtp::SmtpTransport =
SmtpTransport::relay(SMTP_HOST.as_ref())
.unwrap()
.credentials(Credentials::new(
SMTP_USERNAME.to_string(),
SMTP_PASSWORD.to_string()
))
.build();
}
fn send(message: Message) -> Result<(), String> {
MAILER
.send(&message)
.map_err(|err| format!("Failed to send email! {}", err.to_string()))?;
Ok(())
}
fn generate_multipart(text: &str, html: &str) -> MultiPart {
MultiPart::mixed().multipart(
MultiPart::alternative()
.singlepart(
SinglePart::quoted_printable()
.header(header::ContentType(
"text/plain; charset=utf8".parse().unwrap(),
))
.body(text),
)
.multipart(
MultiPart::related().singlepart(
SinglePart::eight_bit()
.header(header::ContentType(
"text/html; charset=utf8".parse().unwrap(),
))
.body(html),
),
),
)
}
pub fn send_verification_email(email: String, code: String) -> Result<(), String> {
let url = format!("{}/api/account/verify/{}", PUBLIC_URL.to_string(), code);
let email = Message::builder()
.from(SMTP_FROM.to_string().parse().unwrap())
.to(email.parse().unwrap())
.subject("Verify your email!")
.multipart(generate_multipart(
&format!("Verify your email here: {}", url),
&format!("<a href=\"{}\">Click to verify your email!</a>", url),
))
.unwrap();
send(email)
}
pub fn send_password_reset(email: String, code: String) -> Result<(), String> {
let url = format!("{}/api/account/reset/{}", PUBLIC_URL.to_string(), code);
let email = Message::builder()
.from(SMTP_FROM.to_string().parse().unwrap())
.to(email.parse().unwrap())
.subject("Reset your password.")
.multipart(generate_multipart(
&format!("Reset your password here: {}", url),
&format!("<a href=\"{}\">Click to reset your password!</a>", url),
))
.unwrap();
send(email)
}
pub fn send_welcome_email(email: String, username: String) -> Result<(), String> {
let email = Message::builder()
.from(SMTP_FROM.to_string().parse().unwrap())
.to(email.parse().unwrap())
.subject("Welcome to REVOLT!")
.multipart(
generate_multipart(
&format!("Welcome, {}! You can now use REVOLT.", username),
&format!(
"<b>Welcome, {}!</b><br/>You can now use REVOLT.<br/><a href=\"{}\">Go to REVOLT</a>",
username,
PUBLIC_URL.to_string()
)
)
)
.unwrap();
send(email)
}
use rand::{distributions::Alphanumeric, Rng};
use std::collections::HashSet;
use std::iter::FromIterator;
pub mod captcha;
pub mod email;
pub mod result;
pub mod variables;
pub fn vec_to_set<T: Clone + Eq + std::hash::Hash>(data: &[T]) -> HashSet<T> {
HashSet::from_iter(data.iter().cloned())
}
pub fn gen_token(l: usize) -> String {
rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(l)
.collect::<String>()
}
......@@ -3,45 +3,63 @@ use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response::{self, Responder, Response};
use serde::Serialize;
use snafu::Snafu;
use std::io::Cursor;
use validator::ValidationErrors;
#[derive(Serialize, Debug, Snafu)]
#[derive(Serialize, Debug)]
#[serde(tag = "type")]
pub enum Error {
#[snafu(display("This error has not been labelled."))]
LabelMe,
// ? Onboarding related errors.
#[snafu(display("Already finished onboarding."))]
AlreadyOnboarded,
// ? User related errors.
#[snafu(display("Username has already been taken."))]
UsernameTaken,
#[snafu(display("This user does not exist!"))]
UnknownUser,
#[snafu(display("Already friends with this user."))]
AlreadyFriends,
#[snafu(display("Already sent a request to this user."))]
AlreadySentRequest,
#[snafu(display("You have blocked this user."))]
Blocked,
#[snafu(display("You have been blocked by this user."))]
BlockedByOther,
NotFriends,
// ? Channel related errors.
UnknownChannel,
UnknownAttachment,
UnknownMessage,
CannotEditMessage,
CannotJoinCall,
TooManyAttachments,
TooManyReplies,
EmptyMessage,
CannotRemoveYourself,
GroupTooLarge {
max: usize,
},
AlreadyInGroup,
NotInGroup,
// ? Server related errors.
UnknownServer,
InvalidRole,
Banned,
// ? General errors.
#[snafu(display("Failed to validate fields."))]
FailedValidation { error: ValidationErrors },
#[snafu(display("Encountered a database error."))]
TooManyIds,
FailedValidation {
error: ValidationErrors,
},
DatabaseError {
operation: &'static str,
with: &'static str,
},
#[snafu(display("Internal server error."))]
InternalError,
#[snafu(display("This request had no effect."))]
MissingPermission,
InvalidOperation,
InvalidCredentials,
DuplicateNonce,
VosoUnavailable,
NotFound,
NoEffect,
}
......@@ -61,10 +79,35 @@ impl<'r> Responder<'r, 'static> for Error {
Error::AlreadySentRequest => Status::Conflict,
Error::Blocked => Status::Conflict,
Error::BlockedByOther => Status::Forbidden,
Error::NotFriends => Status::Forbidden,
Error::UnknownChannel => Status::NotFound,
Error::UnknownMessage => Status::NotFound,
Error::UnknownAttachment => Status::BadRequest,
Error::CannotEditMessage => Status::Forbidden,
Error::CannotJoinCall => Status::BadRequest,
Error::TooManyAttachments => Status::BadRequest,
Error::TooManyReplies => Status::BadRequest,
Error::EmptyMessage => Status::UnprocessableEntity,
Error::CannotRemoveYourself => Status::BadRequest,
Error::GroupTooLarge { .. } => Status::Forbidden,
Error::AlreadyInGroup => Status::Conflict,
Error::NotInGroup => Status::NotFound,
Error::UnknownServer => Status::NotFound,
Error::InvalidRole => Status::NotFound,
Error::Banned => Status::Forbidden,
Error::FailedValidation { .. } => Status::UnprocessableEntity,
Error::DatabaseError { .. } => Status::InternalServerError,
Error::InternalError => Status::InternalServerError,
Error::MissingPermission => Status::Forbidden,
Error::InvalidOperation => Status::BadRequest,
Error::TooManyIds => Status::BadRequest,
Error::InvalidCredentials => Status::Forbidden,
Error::DuplicateNonce => Status::Conflict,
Error::VosoUnavailable => Status::BadRequest,
Error::NotFound => Status::NotFound,
Error::NoEffect => Status::Ok,
};
......
......@@ -7,19 +7,37 @@ lazy_static! {
// Application Settings
pub static ref MONGO_URI: String =
env::var("REVOLT_MONGO_URI").expect("Missing REVOLT_MONGO_URI environment variable.");
pub static ref WS_HOST: String =
env::var("REVOLT_WS_HOST").unwrap_or_else(|_| "0.0.0.0:9000".to_string());
pub static ref PUBLIC_URL: String =
env::var("REVOLT_PUBLIC_URL").expect("Missing REVOLT_PUBLIC_URL environment variable.");
pub static ref APP_URL: String =
env::var("REVOLT_APP_URL").expect("Missing REVOLT_APP_URL environment variable.");
pub static ref EXTERNAL_WS_URL: String =
env::var("REVOLT_EXTERNAL_WS_URL").expect("Missing REVOLT_EXTERNAL_WS_URL environment variable.");
pub static ref AUTUMN_URL: String =
env::var("AUTUMN_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
pub static ref JANUARY_URL: String =
env::var("JANUARY_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
pub static ref VOSO_URL: String =
env::var("VOSO_PUBLIC_URL").unwrap_or_else(|_| "https://example.com".to_string());
pub static ref VOSO_WS_HOST: String =
env::var("VOSO_WS_HOST").unwrap_or_else(|_| "wss://example.com".to_string());
pub static ref VOSO_MANAGE_TOKEN: String =
env::var("VOSO_MANAGE_TOKEN").unwrap_or_else(|_| "0".to_string());
pub static ref HCAPTCHA_KEY: String =
env::var("REVOLT_HCAPTCHA_KEY").unwrap_or_else(|_| "0x0000000000000000000000000000000000000000".to_string());
pub static ref HCAPTCHA_SITEKEY: String =
env::var("REVOLT_HCAPTCHA_SITEKEY").unwrap_or_else(|_| "10000000-ffff-ffff-ffff-000000000001".to_string());
pub static ref WS_HOST: String =
env::var("REVOLT_WS_HOST").unwrap_or_else(|_| "0.0.0.0:9000".to_string());
pub static ref VAPID_PRIVATE_KEY: String =
env::var("REVOLT_VAPID_PRIVATE_KEY").expect("Missing REVOLT_VAPID_PRIVATE_KEY environment variable.");
pub static ref VAPID_PUBLIC_KEY: String =
env::var("REVOLT_VAPID_PUBLIC_KEY").expect("Missing REVOLT_VAPID_PUBLIC_KEY environment variable.");
// Application Flags
pub static ref DISABLE_REGISTRATION: bool = env::var("REVOLT_DISABLE_REGISTRATION").map_or(false, |v| v == "*1");
pub static ref INVITE_ONLY: bool = env::var("REVOLT_INVITE_ONLY").map_or(false, |v| v == "1");
pub static ref USE_EMAIL: bool = env::var("REVOLT_USE_EMAIL_VERIFICATION").map_or(
env::var("REVOLT_SMTP_HOST").is_ok()
&& env::var("REVOLT_SMTP_USERNAME").is_ok()
......@@ -28,18 +46,36 @@ lazy_static! {
|v| v == *"1"
);
pub static ref USE_HCAPTCHA: bool = env::var("REVOLT_HCAPTCHA_KEY").is_ok();
pub static ref USE_PROMETHEUS: bool = env::var("REVOLT_ENABLE_PROMETHEUS").map_or(false, |v| v == "1");
pub static ref USE_AUTUMN: bool = env::var("AUTUMN_PUBLIC_URL").is_ok();
pub static ref USE_JANUARY: bool = env::var("JANUARY_PUBLIC_URL").is_ok();
pub static ref USE_VOSO: bool = env::var("VOSO_PUBLIC_URL").is_ok() && env::var("VOSO_MANAGE_TOKEN").is_ok();
// SMTP Settings
pub static ref SMTP_HOST: String =
env::var("REVOLT_SMTP_HOST").unwrap_or_else(|_| "".to_string());
pub static ref SMTP_USERNAME: String =
env::var("SMTP_USERNAME").unwrap_or_else(|_| "".to_string());
env::var("REVOLT_SMTP_USERNAME").unwrap_or_else(|_| "".to_string());
pub static ref SMTP_PASSWORD: String =
env::var("SMTP_PASSWORD").unwrap_or_else(|_| "".to_string());
pub static ref SMTP_FROM: String = env::var("SMTP_FROM").unwrap_or_else(|_| "".to_string());
env::var("REVOLT_SMTP_PASSWORD").unwrap_or_else(|_| "".to_string());
pub static ref SMTP_FROM: String = env::var("REVOLT_SMTP_FROM").unwrap_or_else(|_| "".to_string());
// Application Logic Settings
pub static ref MAX_GROUP_SIZE: usize =
env::var("REVOLT_MAX_GROUP_SIZE").unwrap_or_else(|_| "50".to_string()).parse().unwrap();
pub static ref EARLY_ADOPTER_BADGE: i64 =
env::var("REVOLT_EARLY_ADOPTER_BADGE").unwrap_or_else(|_| "0".to_string()).parse().unwrap();
}
pub fn preflight_checks() {
format!("{}", *APP_URL);
format!("{}", *MONGO_URI);
format!("{}", *PUBLIC_URL);
format!("{}", *EXTERNAL_WS_URL);
format!("{}", *VAPID_PRIVATE_KEY);
format!("{}", *VAPID_PUBLIC_KEY);
if *USE_EMAIL == false {
#[cfg(not(debug_assertions))]
if !env::var("REVOLT_UNSAFE_NO_EMAIL").map_or(false, |v| v == *"1") {
......
pub const VERSION: &str = "0.5.1-alpha.21";