From 20d398d02c050ec6e1f0ef8c9f764128b15d0bee Mon Sep 17 00:00:00 2001 From: Paul Makles Date: Sun, 7 Apr 2024 23:41:31 +0100 Subject: [PATCH] =?UTF-8?q?chore:=20remove=20quark=20dependency=20from=20d?= =?UTF-8?q?elta=20=F0=9F=8E=89=20closes=20#283?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: allow setting port and use_tls from config closes #143 --- Cargo.lock | 5 +- crates/core/config/Cargo.toml | 8 + crates/core/config/Revolt.toml | 4 + crates/core/config/src/lib.rs | 71 +++++++++ .../core/database/src/models/users/model.rs | 2 +- crates/delta/Cargo.toml | 3 - crates/delta/src/main.rs | 106 +++++++++++--- crates/delta/src/routes/admin/mod.rs | 8 - crates/delta/src/routes/admin/stats.rs | 13 -- .../delta/src/routes/channels/message_edit.rs | 3 +- crates/delta/src/routes/mod.rs | 9 +- crates/delta/src/routes/onboard/complete.rs | 2 +- crates/delta/src/routes/root.rs | 38 +++-- crates/delta/src/routes/users/avatars/1.png | Bin 0 -> 6890 bytes crates/delta/src/routes/users/avatars/2.png | Bin 0 -> 6402 bytes crates/delta/src/routes/users/avatars/3.png | Bin 0 -> 6569 bytes crates/delta/src/routes/users/avatars/4.png | Bin 0 -> 6405 bytes crates/delta/src/routes/users/avatars/5.png | Bin 0 -> 6800 bytes crates/delta/src/routes/users/avatars/6.png | Bin 0 -> 6785 bytes crates/delta/src/routes/users/avatars/7.png | Bin 0 -> 5653 bytes .../src/routes/users/get_default_avatar.rs | 10 +- crates/delta/src/templates/deletion.html | 138 ++++++++++++++++++ .../src/templates/deletion.original.html | 30 ++++ crates/delta/src/templates/deletion.txt | 7 + crates/delta/src/templates/reset.html | 138 ++++++++++++++++++ .../delta/src/templates/reset.original.html | 26 ++++ crates/delta/src/templates/reset.txt | 7 + crates/delta/src/templates/verify.html | 138 ++++++++++++++++++ .../delta/src/templates/verify.original.html | 27 ++++ crates/delta/src/templates/verify.txt | 8 + crates/delta/src/util/ratelimiter.rs | 2 +- crates/delta/src/util/test.rs | 8 +- 32 files changed, 734 insertions(+), 77 deletions(-) delete mode 100644 crates/delta/src/routes/admin/mod.rs delete mode 100644 crates/delta/src/routes/admin/stats.rs create mode 100644 crates/delta/src/routes/users/avatars/1.png create mode 100644 crates/delta/src/routes/users/avatars/2.png create mode 100644 crates/delta/src/routes/users/avatars/3.png create mode 100644 crates/delta/src/routes/users/avatars/4.png create mode 100644 crates/delta/src/routes/users/avatars/5.png create mode 100644 crates/delta/src/routes/users/avatars/6.png create mode 100644 crates/delta/src/routes/users/avatars/7.png create mode 100644 crates/delta/src/templates/deletion.html create mode 100644 crates/delta/src/templates/deletion.original.html create mode 100644 crates/delta/src/templates/deletion.txt create mode 100644 crates/delta/src/templates/reset.html create mode 100644 crates/delta/src/templates/reset.original.html create mode 100644 crates/delta/src/templates/reset.txt create mode 100644 crates/delta/src/templates/verify.html create mode 100644 crates/delta/src/templates/verify.original.html create mode 100644 crates/delta/src/templates/verify.txt diff --git a/Cargo.lock b/Cargo.lock index 93ffd280..44c91f69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3605,8 +3605,12 @@ dependencies = [ "async-std", "cached", "config", + "dotenv", "futures-locks", + "log", "once_cell", + "pretty_env_logger", + "sentry", "serde", ] @@ -3685,7 +3689,6 @@ dependencies = [ "revolt-database", "revolt-models", "revolt-permissions", - "revolt-quark", "revolt-result", "revolt_rocket_okapi", "rocket", diff --git a/crates/core/config/Cargo.toml b/crates/core/config/Cargo.toml index c8c5ed3b..82402449 100644 --- a/crates/core/config/Cargo.toml +++ b/crates/core/config/Cargo.toml @@ -14,6 +14,7 @@ default = ["test"] [dependencies] # Utility +dotenv = "0.15.0" config = "0.13.3" cached = "0.44.0" once_cell = "1.18.0" @@ -24,3 +25,10 @@ serde = { version = "1", features = ["derive"] } # Async futures-locks = "0.7.1" async-std = { version = "1.8.0", features = ["attributes"], optional = true } + +# Logging +log = "0.4.14" +pretty_env_logger = "0.4.0" + +# Sentry +sentry = "0.31.5" diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml index dd63bf0e..e7514d47 100644 --- a/crates/core/config/Revolt.toml +++ b/crates/core/config/Revolt.toml @@ -1,3 +1,5 @@ +sentry_dsn = "" + [database] mongodb = "mongodb://database" redis = "redis://redis/" @@ -33,6 +35,7 @@ api_key = "" [api.security] authifier_shield_key = "" voso_legacy_token = "" +trust_cloudflare = false [api.security.captcha] hcaptcha_key = "" @@ -42,6 +45,7 @@ hcaptcha_sitekey = "" max_concurrent_connections = 50 [features] +webhooks_enabled = false [features.limits] diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs index 61948e97..abaa5177 100644 --- a/crates/core/config/src/lib.rs +++ b/crates/core/config/src/lib.rs @@ -56,6 +56,9 @@ pub struct ApiSmtp { pub username: String, pub password: String, pub from_address: String, + pub reply_to: Option, + pub port: Option, + pub use_tls: Option, } #[derive(Deserialize, Debug, Clone)] @@ -80,6 +83,7 @@ pub struct ApiSecurity { pub authifier_shield_key: String, pub voso_legacy_token: String, pub captcha: ApiSecurityCaptcha, + pub trust_cloudflare: bool, } #[derive(Deserialize, Debug, Clone)] @@ -131,6 +135,7 @@ pub struct FeaturesLimitsCollection { #[derive(Deserialize, Debug, Clone)] pub struct Features { pub limits: FeaturesLimitsCollection, + pub webhooks_enabled: bool, } #[derive(Deserialize, Debug, Clone)] @@ -139,6 +144,31 @@ pub struct Settings { pub hosts: Hosts, pub api: Api, pub features: Features, + pub sentry_dsn: String, +} + +impl Settings { + pub fn preflight_checks(&self) { + if self.api.smtp.host.is_empty() { + #[cfg(not(debug_assertions))] + if !env::var("REVOLT_UNSAFE_NO_EMAIL").map_or(false, |v| v == *"1") { + panic!("Running in production without email is not recommended, set REVOLT_UNSAFE_NO_EMAIL=1 to override."); + } + + #[cfg(debug_assertions)] + log::warn!("No SMTP settings specified! Remember to configure email."); + } + + if self.api.security.captcha.hcaptcha_key.is_empty() { + #[cfg(not(debug_assertions))] + if !env::var("REVOLT_UNSAFE_NO_CAPTCHA").map_or(false, |v| v == *"1") { + panic!("Running in production without CAPTCHA is not recommended, set REVOLT_UNSAFE_NO_CAPTCHA=1 to override."); + } + + #[cfg(debug_assertions)] + log::warn!("No Captcha key specified! Remember to add hCaptcha key."); + } + } } pub async fn init() { @@ -157,6 +187,47 @@ pub async fn config() -> Settings { read().await.try_deserialize::().unwrap() } +/// Configure logging and common Rust variables +pub async fn setup_logging(release: &'static str) -> Option { + dotenv::dotenv().ok(); + + if std::env::var("RUST_LOG").is_err() { + std::env::set_var("RUST_LOG", "info"); + } + + if std::env::var("ROCKET_ADDRESS").is_err() { + std::env::set_var("ROCKET_ADDRESS", "0.0.0.0"); + } + + pretty_env_logger::init(); + log::info!("Starting {release}"); + + let config = config().await; + if config.sentry_dsn.is_empty() { + None + } else { + Some(sentry::init(( + config.sentry_dsn, + sentry::ClientOptions { + release: Some(release.into()), + ..Default::default() + }, + ))) + } +} + +#[macro_export] +macro_rules! configure { + () => { + let _sentry = $crate::setup_logging(concat!( + env!("CARGO_PKG_NAME"), + "@", + env!("CARGO_PKG_VERSION") + )) + .await; + }; +} + #[cfg(feature = "test")] #[cfg(test)] mod tests { diff --git a/crates/core/database/src/models/users/model.rs b/crates/core/database/src/models/users/model.rs index 5ae289a0..d60d90db 100644 --- a/crates/core/database/src/models/users/model.rs +++ b/crates/core/database/src/models/users/model.rs @@ -7,7 +7,7 @@ use rand::seq::SliceRandom; use revolt_config::config; use revolt_models::v0; use revolt_presence::filter_online; -use revolt_result::{create_error, Error, ErrorType, Result}; +use revolt_result::{create_error, Result}; use ulid::Ulid; auto_derived_partial!( diff --git a/crates/delta/Cargo.toml b/crates/delta/Cargo.toml index a02f427e..b6b45acd 100644 --- a/crates/delta/Cargo.toml +++ b/crates/delta/Cargo.toml @@ -65,9 +65,6 @@ rocket_prometheus = "0.10.0-rc.3" schemars = "0.8.8" revolt_rocket_okapi = { version = "0.9.1", features = ["swagger"] } -# quark -revolt-quark = { path = "../quark" } - # core authifier = "1.0.8" revolt-config = { path = "../core/config" } diff --git a/crates/delta/src/main.rs b/crates/delta/src/main.rs index a962ac9f..ee4957b5 100644 --- a/crates/delta/src/main.rs +++ b/crates/delta/src/main.rs @@ -8,6 +8,8 @@ extern crate serde_json; pub mod routes; pub mod util; +use revolt_config::config; +use revolt_database::events::client::EventV1; use revolt_database::{Database, MongoDb}; use rocket::{Build, Rocket}; use rocket_cors::{AllowedOrigins, CorsOptions}; @@ -16,19 +18,24 @@ use std::net::Ipv4Addr; use std::str::FromStr; use async_std::channel::unbounded; -use revolt_quark::authifier::{Authifier, AuthifierEvent}; -use revolt_quark::events::client::EventV1; -use revolt_quark::DatabaseInfo; +use authifier::config::{ + Captcha, Config as AuthifierConfig, EmailVerificationConfig, ResolveIp, SMTPSettings, Shield, + Template, Templates, +}; +use authifier::{Authifier, AuthifierEvent}; use rocket::data::ToByteUnit; pub async fn web() -> Rocket { + // Get settings + let config = config().await; + + // Ensure environment variables are present + config.preflight_checks(); + // Setup database let db = revolt_database::DatabaseInfo::Auto.connect().await.unwrap(); db.migrate_database().await.unwrap(); - // Legacy database setup from quark - let legacy_db = DatabaseInfo::Auto.connect().await.unwrap(); - // Setup Authifier event channel let (sender, receiver) = unbounded(); @@ -40,7 +47,8 @@ pub async fn web() -> Rocket { authifier::database::MongoDb(client.database("revolt")), ), }, - config: revolt_quark::util::authifier::config(), + config: Default::default(), + // config: authifier_config().await, event_channel: Some(sender), }; @@ -65,10 +73,6 @@ pub async fn web() -> Rocket { db.clone(), authifier.database.clone(), )); - async_std::task::spawn(revolt_quark::tasks::start_workers( - legacy_db.clone(), - authifier.database.clone(), - )); // Configure CORS let cors = CorsOptions { @@ -97,7 +101,7 @@ pub async fn web() -> Rocket { let rocket = rocket::build(); let prometheus = PrometheusMetrics::new(); - routes::mount(rocket) + routes::mount(config, rocket) .attach(prometheus.clone()) .mount("/metrics", prometheus) .mount("/", rocket_cors::catch_all_options_routes()) @@ -105,7 +109,6 @@ pub async fn web() -> Rocket { .mount("/swagger/", swagger) .manage(authifier) .manage(db) - .manage(legacy_db) .manage(cors.clone()) .attach(util::ratelimiter::RatelimitFairing) .attach(cors) @@ -116,13 +119,82 @@ pub async fn web() -> Rocket { }) } +pub async fn authifier_config() -> AuthifierConfig { + let config = config().await; + + let mut auth_config = AuthifierConfig { + email_verification: if !config.api.smtp.host.is_empty() { + EmailVerificationConfig::Enabled { + smtp: SMTPSettings { + from: config.api.smtp.from_address, + host: config.api.smtp.host, + username: config.api.smtp.username, + password: config.api.smtp.password, + reply_to: Some( + config + .api + .smtp + .reply_to + .unwrap_or("support@revolt.chat".into()), + ), + port: config.api.smtp.port, + use_tls: config.api.smtp.use_tls, + }, + expiry: Default::default(), + templates: Templates { + verify: Template { + title: "Verify your Revolt account.".into(), + text: include_str!("templates/verify.txt").into(), + url: format!("{}/login/verify/", config.hosts.app), + html: Some(include_str!("templates/verify.html").into()), + }, + reset: Template { + title: "Reset your Revolt password.".into(), + text: include_str!("templates/reset.txt").into(), + url: format!("{}/login/reset/", config.hosts.app), + html: Some(include_str!("templates/reset.html").into()), + }, + deletion: Template { + title: "Confirm account deletion.".into(), + text: include_str!("templates/deletion.txt").into(), + url: format!("{}/delete/", config.hosts.app), + html: Some(include_str!("templates/deletion.html").into()), + }, + welcome: None, + }, + } + } else { + EmailVerificationConfig::Disabled + }, + ..Default::default() + }; + + auth_config.invite_only = config.api.registration.invite_only; + + if !config.api.security.captcha.hcaptcha_key.is_empty() { + auth_config.captcha = Captcha::HCaptcha { + secret: config.api.security.captcha.hcaptcha_key, + }; + } + + if !config.api.security.authifier_shield_key.is_empty() { + auth_config.shield = Shield::Enabled { + api_key: config.api.security.authifier_shield_key, + strict: false, + }; + } + + if config.api.security.trust_cloudflare { + auth_config.resolve_ip = ResolveIp::Cloudflare; + } + + auth_config +} + #[launch] async fn rocket() -> _ { // Configure logging and environment - revolt_quark::configure!(); - - // Ensure environment variables are present - revolt_quark::variables::delta::preflight_checks(); + revolt_config::configure!(); // Start web server web().await diff --git a/crates/delta/src/routes/admin/mod.rs b/crates/delta/src/routes/admin/mod.rs deleted file mode 100644 index 053bf831..00000000 --- a/crates/delta/src/routes/admin/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi; -use rocket::Route; - -mod stats; - -pub fn routes() -> (Vec, OpenApi) { - openapi_get_routes_spec![stats::stats] -} diff --git a/crates/delta/src/routes/admin/stats.rs b/crates/delta/src/routes/admin/stats.rs deleted file mode 100644 index 1d043b03..00000000 --- a/crates/delta/src/routes/admin/stats.rs +++ /dev/null @@ -1,13 +0,0 @@ -use revolt_quark::models::stats::Stats; -use revolt_quark::{Db, Result}; - -use rocket::serde::json::Json; - -/// # Query Stats -/// -/// Fetch various technical statistics. -#[openapi(tag = "Admin")] -#[get("/stats")] -pub async fn stats(db: &Db) -> Result> { - Ok(Json(db.generate_stats().await?)) -} diff --git a/crates/delta/src/routes/channels/message_edit.rs b/crates/delta/src/routes/channels/message_edit.rs index 1966cef7..4c1a78b9 100644 --- a/crates/delta/src/routes/channels/message_edit.rs +++ b/crates/delta/src/routes/channels/message_edit.rs @@ -1,6 +1,7 @@ use iso8601_timestamp::Timestamp; use revolt_config::config; use revolt_database::{ + tasks, util::{permissions::DatabasePermissionQuery, reference::Reference}, Database, Message, PartialMessage, User, }; @@ -88,7 +89,7 @@ pub async fn edit( // Queue up a task for processing embeds if the we have sufficient permissions if permissions.has_channel_permission(ChannelPermission::SendEmbeds) { if let Some(content) = edit.content { - revolt_quark::tasks::process_embeds::queue( + tasks::process_embeds::queue( message.channel.to_string(), message.id.to_string(), content, diff --git a/crates/delta/src/routes/mod.rs b/crates/delta/src/routes/mod.rs index b9b0fac0..516c2166 100644 --- a/crates/delta/src/routes/mod.rs +++ b/crates/delta/src/routes/mod.rs @@ -1,10 +1,9 @@ -use revolt_quark::variables::delta::IS_STAGING; +use revolt_config::{config, Settings}; use revolt_rocket_okapi::{revolt_okapi::openapi3::OpenApi, settings::OpenApiSettings}; pub use rocket::http::Status; pub use rocket::response::Redirect; use rocket::{Build, Rocket}; -mod admin; mod bots; mod channels; mod customisation; @@ -18,15 +17,14 @@ mod sync; mod users; mod webhooks; -pub fn mount(mut rocket: Rocket) -> Rocket { +pub fn mount(config: Settings, mut rocket: Rocket) -> Rocket { let settings = OpenApiSettings::default(); - if *IS_STAGING { + if config.features.webhooks_enabled { mount_endpoints_and_merged_docs! { rocket, "/".to_owned(), settings, "/" => (vec![], custom_openapi_spec()), "" => openapi_get_routes_spec![root::root], - "/admin" => admin::routes(), "/users" => users::routes(), "/bots" => bots::routes(), "/channels" => channels::routes(), @@ -47,7 +45,6 @@ pub fn mount(mut rocket: Rocket) -> Rocket { rocket, "/".to_owned(), settings, "/" => (vec![], custom_openapi_spec()), "" => openapi_get_routes_spec![root::root], - "/admin" => admin::routes(), "/users" => users::routes(), "/bots" => bots::routes(), "/channels" => channels::routes(), diff --git a/crates/delta/src/routes/onboard/complete.rs b/crates/delta/src/routes/onboard/complete.rs index bc20aa97..93846013 100644 --- a/crates/delta/src/routes/onboard/complete.rs +++ b/crates/delta/src/routes/onboard/complete.rs @@ -1,8 +1,8 @@ +use authifier::models::Session; use once_cell::sync::Lazy; use regex::Regex; use revolt_database::{Database, User}; use revolt_models::v0; -use revolt_quark::authifier::models::Session; use revolt_result::{create_error, Result}; use rocket::{serde::json::Json, State}; diff --git a/crates/delta/src/routes/root.rs b/crates/delta/src/routes/root.rs index f506dd0d..1e237bff 100644 --- a/crates/delta/src/routes/root.rs +++ b/crates/delta/src/routes/root.rs @@ -1,9 +1,5 @@ -use revolt_quark::variables::delta::{ - APP_URL, AUTUMN_URL, EXTERNAL_WS_URL, HCAPTCHA_SITEKEY, INVITE_ONLY, JANUARY_URL, USE_AUTUMN, - USE_EMAIL, USE_HCAPTCHA, USE_JANUARY, USE_VOSO, VAPID_PUBLIC_KEY, VOSO_URL, VOSO_WS_HOST, -}; -use revolt_quark::Result; - +use revolt_config::config; +use revolt_result::Result; use rocket::serde::json::Json; use serde::Serialize; @@ -91,32 +87,34 @@ pub struct RevoltConfig { #[openapi(tag = "Core")] #[get("/")] pub async fn root() -> Result> { + let config = config().await; + Ok(Json(RevoltConfig { revolt: env!("CARGO_PKG_VERSION").to_string(), features: RevoltFeatures { captcha: CaptchaFeature { - enabled: *USE_HCAPTCHA, - key: HCAPTCHA_SITEKEY.to_string(), + enabled: !config.api.security.captcha.hcaptcha_key.is_empty(), + key: config.api.security.captcha.hcaptcha_sitekey, }, - email: *USE_EMAIL, - invite_only: *INVITE_ONLY, + email: !config.api.smtp.host.is_empty(), + invite_only: config.api.registration.invite_only, autumn: Feature { - enabled: *USE_AUTUMN, - url: AUTUMN_URL.to_string(), + enabled: !config.hosts.autumn.is_empty(), + url: config.hosts.autumn, }, january: Feature { - enabled: *USE_JANUARY, - url: JANUARY_URL.to_string(), + enabled: !config.hosts.january.is_empty(), + url: config.hosts.january, }, voso: VoiceFeature { - enabled: *USE_VOSO, - url: VOSO_URL.to_string(), - ws: VOSO_WS_HOST.to_string(), + enabled: !config.hosts.voso_legacy.is_empty(), + url: config.hosts.voso_legacy, + ws: config.hosts.voso_legacy_ws, }, }, - ws: EXTERNAL_WS_URL.to_string(), - app: APP_URL.to_string(), - vapid: VAPID_PUBLIC_KEY.to_string(), + ws: config.hosts.events, + app: config.hosts.app, + vapid: config.api.vapid.public_key, build: BuildInformation { commit_sha: option_env!("VERGEN_GIT_SHA") .unwrap_or_else(|| "") diff --git a/crates/delta/src/routes/users/avatars/1.png b/crates/delta/src/routes/users/avatars/1.png new file mode 100644 index 0000000000000000000000000000000000000000..88d2e28bf499553944843ead1682bbc5ef1c3089 GIT binary patch literal 6890 zcmeHM`9IWa-@j&zU6>Gxu@#ldmaM~+VUlH1B6nFb+LN+3gBiKYzC1@|DHN6Ls&k0R zQdv)yGa(bl7RNe_VvI7(bB*WRuh;Vj+&`Y5=DNN<*XQ~y@9VRPKkiJ}BC%Zp0I48fJ_On;*^e?w{h z{rlx5AK6a0x}n6Fvvoc7>nR1s3_h5S&goiSi{-PA{Kmp|HJFj18Tf)G7S~f zuih1cV?&8ouIieHvF$?G!^=z6-a<>Z0C%O%>!7H#C6i&|;K10YN05pw(xGV1ulL1< z2k&$ZTd)+n-BosmQFl|Tc(D4S#m+?1kinKyaFnW1;&A{xYrnZhV{&j6OIXh4))+Nsr&54)m>5F#A z$fu4S2`=|?!YZ@OkO1R;$~JQPxZ|Ni`Bs5`U|kkcG3$^{bjcy(1M~*5#1*Wm>>%4=FXR)=c>Ze)s!TQnILUOd0+dZ1h$5^1dcA z=r=Te=1~_~Y!+n^0L@q!Q5#qEifjKieqivUQ}vGYsl3$orEa2(OvFmonj}sPNc5|i z$_`%awtHQlOP0Sdx-k~r)%lRYU$oJ{F;Kwvio2Wd8tcmo88v6Dg!n;SU4x%P?EIkl ze@Sg@aA)?%J<&(f@_%)3c)9+`!9~p)@|eU0E&5zrACqC!1@942i+pV4JqHT>QFJ)wq=C}>13zHY5tayK%L%h-gZG`Hzp}>Z< z<<~Z|$;ofRCfdSch1KPdw0#0~$>Uo-uCr32tgF1E%(MxqEsH~8Wg=<90qM-a;=L_; z^-5=`t1&s&^m30gi4-c2)JsTxjuFnyQUEKZq31O0rn)JoWnLRAHBEP#6mb8_FjyUp ziK}(SqAe8=jk&IB7TXF&c-r|RM6AS}Y)gIw#J2&jfu9IpTTyGY&ZI1$bgh(Bv`i

m?GW{R&)>RK_gnr)L4@~mh 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 literal 0 HcmV?d00001 diff --git a/crates/delta/src/routes/users/avatars/2.png b/crates/delta/src/routes/users/avatars/2.png new file mode 100644 index 0000000000000000000000000000000000000000..dd0d2967cdd1456550d36f5d9cbc095b02d39513 GIT binary patch literal 6402 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 Py#UXHz6Yw^La+P}c2kv} literal 0 HcmV?d00001 diff --git a/crates/delta/src/routes/users/avatars/4.png b/crates/delta/src/routes/users/avatars/4.png new file mode 100644 index 0000000000000000000000000000000000000000..6db7af6099351f5d233830c987f68e9fb9251d81 GIT binary patch literal 6405 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 literal 0 HcmV?d00001 diff --git a/crates/delta/src/routes/users/avatars/5.png b/crates/delta/src/routes/users/avatars/5.png new file mode 100644 index 0000000000000000000000000000000000000000..9f32511c055dbbc1b877b34e48df3c9c921424a5 GIT binary patch literal 6800 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 Lni~|HL*M!zf6y<{ literal 0 HcmV?d00001 diff --git a/crates/delta/src/routes/users/avatars/6.png b/crates/delta/src/routes/users/avatars/6.png new file mode 100644 index 0000000000000000000000000000000000000000..533f920dfcd42cbfd8315ad73dcfcd96726a86f0 GIT binary patch literal 6785 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 literal 0 HcmV?d00001 diff --git a/crates/delta/src/routes/users/avatars/7.png b/crates/delta/src/routes/users/avatars/7.png new file mode 100644 index 0000000000000000000000000000000000000000..7d9ffd0ed14791394445a7768d4eeb0cfdd94394 GIT binary patch literal 5653 zcmeHLdo2%Bge+MI-t~LedjER=J!^f}dY=7!_ukKb_S&Dl_w!t{Cs>GV z*uDV(M64_i9s@ulQ#1&!M+Sb#`$c3x1s=254+X6az zq-?&_Q4wxwdbZ(O#@n7}1M_R^dGjA#Ui7?`RxSQ?AcfdeakU4({kg^w#oZvlpCmtV zynUSTZZ~vhef4e@{v=ng1!lWT9%BLWvj8YpFaVpC0J7y#0PBPRc3A?LnImRxkN~j1 z`47uKe)+$HflDv}7RJ+xplWc(gJ?5H9Z$lo`stQMc{wi2%#Fs+tjRM|J*vO2Ol6H) zeK|l<<&I9Q;58$MT&CAfx+!D8_kn{qZ6Qs|fX3#ZCB4D-OsX$LO_r}kd~#TQ?XR~7 z0<@z3;s_7~H%Ze8$({H;mS}u-Vsj#3Bub}}?6~OIyFwJ>; z-?r6QEd#w-if;2`rIYn@fkX4&T2liz!?kU6_U$qU#`9*Xv$M_I)&}N6_0hm36`Zxf__1j zQeh(V=}cKb@~r+H_MPIDW?H`#^;?1@n$?W~nL2fxN>;*_78N~f-dJ~}pK`gWb+uzZ z9rPvWz&fR&p^`?om~ZJ{KO~I!#!-?oT8xI|^hbN9T{HU1-(1v4>@ zd5!0DG|XoZNEZiHqP+X#$-So0c3ytQnB;)e7;AUZMH>yD;R)nqJ7`a_TplBLLKEhN zV64JeWc<*ofq6L=qB z$}|de{q*UmUL1RJPS8GC5*9D(UL3icm{t4QSVdE=v=Fx%@x=vwTLCy-gkGIhANLWG zX4_1!BE~fHMaz}YTdhK{7<8$)rm*o-@TsJX(hm>1sLMlyK2^w&0RA~Vvru$=e6;SK zEzb_il|@HoyugZ^dto4=yJe)grDBgYe=OD6;Cz^X>7^alss*8Js7{MKM8c~*r?KfX z>?2e>w_C|l&v@{6u8tDUoUB`PbEs1(WA6RYM;6lM4s2i@!ZoA-C3dX{{Iqv>YFp5^agL~@-nA!i@_as7OwX?`0CT4+t}-! z%33F)pWtT68#Fd338|1rK*I3svE$W5Usmaxe5Tctp&BKQ@>PjHrQ7gXYa#L5N)Wp@ zb-bB|4|6CXV<3I_MI9lE)ABSy@Vl$)CXbT8j^)gM>}T5z72M!%`7`O2uNcuHXSUUN zA+}R-!z*JIxvQWX|5CA&>|-;ycfsMWSgnN&K{xFX>3xYl3D`w5ovnu;y~gS0Y}h5= zdAs;02SJ+AmpogsuPptc*`@iN4BDQwtKb%)u_t)HK+B;lp`4xIm|PN&9aHNZj1$gL z0LLl^@Agk|T z+*O%GT)#%@9WhXEh*ct1dJ0)zHkHk+J0b`!{RHYz_X}aQz)So)zoighYGg2DX>ebD zZ{e4TJmxVWaA}TJDm2uNzSC#h=dsaR5%kMXQRmfHYI~ih+T~L$z@J21x-OAFU{rmf zi6jVwFX6q3M<}y*mB~Ym(wYSD-d+#Ai^*=wMPVsuuy8;F-BH3iGu z0+zp~ayeTb?ez5-`MLJ2e0LCtG^WQ;=H48=j&lHG{u6PC&+iVFAW|OEy`=hSWq&ednrz`a@qBvcCy>VtCS0Q_kc*Q85-*OU7 zz5(*&i+loQkgHs-$R{(-WU=pR3+x)c3oI1MegG*Gh|!c6uQ)TU0_bc*FN}SHBz_CV z>|AAlt=N;|(fD=5n8t!6?uq+mJE~`EuRz}TlSgK|nCCzd$*inX!}cG`m6jZER~`#d zp12fa1Rqshkjyqj*>{M|uf&hJ6l3##5RZx@1R&2xk6R75mEo7?%Q&TK5&L_P=ecEQ z1$$R*LdZUiUVXR8bc*q(`ka8I=P!BnRJp0WbWD>3+!=rP450tAw++G8L@-Aleku3h zVMWil+FPNsCA`)cXT}9~S;4Olc+s`aQhmz%kBGx@=c_n<|5pkGF{Jf7?i@TsWNmX^ zzsmxC9j+BIZc?VBo`{3wy=xY0=r-NAhlkH%%+?ifQ5M6FN3TO3W78YTAO88f4gufK za_KsbF*B~xlNBnKp8%YJ$k&aGj&k?&+j8OtPxEH+EGZf7Mlq-gw8iO*K8-sUk`|_$ z@Quuv77iG({~b6zvBeRosH9p`iUh{2ZG06=*blemAyfq@G<})}QrU_wptHRS)&x{% zshh423Q=4#lE1Ann5WFgGlBD=8mBph#+c1x%nC6urPx~cDcVRCLO(C=n0kU~fd%8hA88|7AALym5MW1^vUgU(%)-%m#>=W^}!#*@Z z2;FgL`ts_XGOAsg4wQ3&&R?m(VT!$k^(k$S>NJEXnXk@Zi#D8q3MB|7)yHpkjPBHO z3i`{smxiQxL^lx~WC473DPqE*R>NxkVc@^r07btb5<4OEEbf1%>%dV`Rcb&Ss=5Og_}=K_ zCFpiZRC|JnoQ+%ET#hU>A(jPP7(>CpCio#K28<`CyvMHtr$(K*7lFitK?mmtHSt~m zj&vu|5|acevV!1t8gcmn;InRjBQfrF3wDd3+6B~rQ8BJR%!vg~G{DEY=^SZvHnxC? z`lbjK3J_Z4VmGq03`u%F#s2eu!0WF+b~`EV zxD_$M!2%a9AARZHi#VmfHLq}Gfy|9yC7ccPYVUJZU5#+!{7Qg8~ijFQDV>j zdu!Gl;_k+xpGS`A3(y3&*?PpWSAkQ9v?#zCY|-!eN!fONrbsSz1mviqv$vY2GM*T; z1|ewu6FE8pCN*Sn4&Z5UAQJdC2!RzxF~*jqaOklcNTlkej8Y^6!oMaV^DY;H+o5Vn zsM2!hv*VR!kOCCsmB|i6VjI;?(_(g+K4efM@cQMPo4{!iL4kVOrUWF!2g6OeA8%Ce zq9d?EATjg32;DR@d$T_S$r4u(5MI($D^lvpu;(}0bo?<%>%WT>laG~jwi>D(`JT4D zE@f=2Fb!EIHZ)6AEry?;74?^Jr$(hJhJN|L{b0=WN|~T+T0Cnxft2nOR=^+cO#5ua z@-Wph;kT;wmxL8>0GXj#g>sWwc}E>odu6iM92zY2?09+(2iCcK9vrad-J);Pe_-cX zu(I`xXC7mI3JO z3Y-^{t_Y3acQd2<`0*g`-9$No&W_TfEsrKStxykw|4@7h+d&)#78RIL+z@>!JW?2^ zI+`6A*%m{hDp8q5MzhepH%plL>=bX+xL6+gCOhvBtnZJ*%kG4bcZmw+RkUz_)(&*v z1)Iv5T-$2n*jX3R`2*CoZf~QwXf8?bVo4S=%S#N52c9XdRl5hu`_hPU?4jWPLHg)( zMpH>kr!IETlDBG33LQhC;`XErVpxq%TXQCD+?zoZUY4AL;u{}bJCeHlz9Nw%G9O5wpJr5$qM|#@sBbtI&Y(0{=!UqjK!>r&?;RDM&d@^ zR}IBrM(8NHPX4%8N$-b`BlYdAi$sEnSQ*lx)ZMT$m7{6~Q<09lg-i+_GdGW~cD7kY z$|hA1QrvAw5taq`&FIk_+0DM?F{N!Eyi_7n?4T&zRg_6LVj>p^cN>kK3`_$_n>m`0 z8`PRBD_kW3`K>PxEE&+*y~boRKPkqm+_W@{7?F|uLAoA)gV5%ftZ?x->vM5LjG3^R zQ}5c@fU-&Ni2PgNW=OAR^Ay)3=~OtsW1w_k+0~%W%MeXz@z>b1UZah+60%uqrJPpx z5N}OY^_@$hVO}|eq zBeF(P@7X=q6zsO2DOH_{z?WWIlWubK)r~)_ zD=K6U?C@75uZFpMSF{s`ka7ORd2+X%m7fTAe&yP#XaCiBKjbPmswhC-x7D=4F6R9vR3%w#U2dm__IM(9 z{&Ah>+W{q`1i6nsnPt2{Rc2M4ZP0|qYPcRZYf@b$B)K_ORS-h%h9zWKjIg$KiA*#w z+TJu+tdXVqqQWhmPD_a{klB;T)04!;3w2ec*T|W3^64>N%axy|IB%AwvKoe&Z_-C= z4n~EYTf=1wqo9d|mx|21C!4%3U6j&Nyn9px7~B7O82jfn@c;ixx+-V3M#`8sBs(MT PykK>RaIj#%+m-(U&MoaJ literal 0 HcmV?d00001 diff --git a/crates/delta/src/routes/users/get_default_avatar.rs b/crates/delta/src/routes/users/get_default_avatar.rs index 8bd32e2a..193d291c 100644 --- a/crates/delta/src/routes/users/get_default_avatar.rs +++ b/crates/delta/src/routes/users/get_default_avatar.rs @@ -59,6 +59,14 @@ impl revolt_rocket_okapi::response::OpenApiResponderInner for CachedFile { pub async fn default_avatar(target: String) -> CachedFile { CachedFile(( ContentType::PNG, - revolt_quark::util::pfp::avatar(target.chars().last().unwrap()), + match target.chars().last().unwrap() { + '0' | '1' | '2' | '3' | 'S' | 'Z' => include_bytes!("avatars/2.png").to_vec(), + '4' | '5' | '6' | '7' | 'T' => include_bytes!("avatars/3.png").to_vec(), + '8' | '9' | 'A' | 'B' => include_bytes!("avatars/4.png").to_vec(), + 'C' | 'D' | 'E' | 'F' | 'V' => include_bytes!("avatars/5.png").to_vec(), + 'G' | 'H' | 'J' | 'K' | 'W' => include_bytes!("avatars/6.png").to_vec(), + 'M' | 'N' | 'P' | 'Q' | 'X' => include_bytes!("avatars/7.png").to_vec(), + _ => include_bytes!("avatars/1.png").to_vec(), + }, )) } diff --git a/crates/delta/src/templates/deletion.html b/crates/delta/src/templates/deletion.html new file mode 100644 index 00000000..e18b1d8a --- /dev/null +++ b/crates/delta/src/templates/deletion.html @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/crates/delta/src/templates/deletion.original.html b/crates/delta/src/templates/deletion.original.html new file mode 100644 index 00000000..1028ce49 --- /dev/null +++ b/crates/delta/src/templates/deletion.original.html @@ -0,0 +1,30 @@ + + + + + + +

+ Revolt Logo +
+

Account Deletion

+

+ You requested to have your account deleted, if you did not perform + this action please take measures to secure your account immediately. +

+ Confirm +
+
+ This email is intended for {{email}}
+ Sent from Revolt
+ Made in the EU +
+
+ + diff --git a/crates/delta/src/templates/deletion.txt b/crates/delta/src/templates/deletion.txt new file mode 100644 index 00000000..88112b8e --- /dev/null +++ b/crates/delta/src/templates/deletion.txt @@ -0,0 +1,7 @@ +You requested to have your account deleted, if you did not perform this action please take measures to secure your account immediately. + +Please navigate to: {{url}} + +This email is intended for {{email}} +Sent by Revolt +Made in the EU diff --git a/crates/delta/src/templates/reset.html b/crates/delta/src/templates/reset.html new file mode 100644 index 00000000..6c4da8f6 --- /dev/null +++ b/crates/delta/src/templates/reset.html @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/crates/delta/src/templates/reset.original.html b/crates/delta/src/templates/reset.original.html new file mode 100644 index 00000000..f40c7254 --- /dev/null +++ b/crates/delta/src/templates/reset.original.html @@ -0,0 +1,26 @@ + + + + + + +
+ +
+

Password Reset

+

You requested a password reset, click below to continue.

+ Reset +
+
+ This email is intended for {{email}}
+ Sent from Revolt
+ Made in the EU +
+
+ + diff --git a/crates/delta/src/templates/reset.txt b/crates/delta/src/templates/reset.txt new file mode 100644 index 00000000..fdc2751f --- /dev/null +++ b/crates/delta/src/templates/reset.txt @@ -0,0 +1,7 @@ +You requested a password reset, if you did not perform this action you can safely ignore this email. + +Please navigate to: {{url}} + +This email is intended for {{email}} +Sent by Revolt +Made in the EU diff --git a/crates/delta/src/templates/verify.html b/crates/delta/src/templates/verify.html new file mode 100644 index 00000000..d7016f62 --- /dev/null +++ b/crates/delta/src/templates/verify.html @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/crates/delta/src/templates/verify.original.html b/crates/delta/src/templates/verify.original.html new file mode 100644 index 00000000..e3ce0d5c --- /dev/null +++ b/crates/delta/src/templates/verify.original.html @@ -0,0 +1,27 @@ + + + + + + +
+ Revolt Logo +
+

Almost there!

+

To complete your sign up, we just need to verify your email.

+ Confirm +
+
+ This email is intended for {{email}}
+ Sent from Revolt
+ Made in the EU +
+
+ + diff --git a/crates/delta/src/templates/verify.txt b/crates/delta/src/templates/verify.txt new file mode 100644 index 00000000..1fdaaa63 --- /dev/null +++ b/crates/delta/src/templates/verify.txt @@ -0,0 +1,8 @@ +Almost there! +To complete your sign up, we just need to verify your email. + +Please navigate to: {{url}} + +This email is intended for {{email}} +Sent by Revolt +Made in the EU diff --git a/crates/delta/src/util/ratelimiter.rs b/crates/delta/src/util/ratelimiter.rs index df89e899..ce7317ff 100644 --- a/crates/delta/src/util/ratelimiter.rs +++ b/crates/delta/src/util/ratelimiter.rs @@ -3,7 +3,7 @@ use std::hash::Hasher; use std::ops::Add; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use revolt_quark::authifier::models::Session; +use authifier::models::Session; use rocket::fairing::{Fairing, Info, Kind}; use rocket::http::uri::Origin; use rocket::http::{Method, Status}; diff --git a/crates/delta/src/util/test.rs b/crates/delta/src/util/test.rs index 250b8537..428f8bbb 100644 --- a/crates/delta/src/util/test.rs +++ b/crates/delta/src/util/test.rs @@ -1,12 +1,12 @@ +use authifier::{ + models::{Account, Session}, + Authifier, +}; use futures::StreamExt; use rand::Rng; use redis_kiss::redis::aio::PubSub; use revolt_database::{events::client::EventV1, Database, User}; use revolt_models::v0; -use revolt_quark::authifier::{ - models::{Account, Session}, - Authifier, -}; use rocket::local::asynchronous::Client; pub struct TestHarness {