2022-02-11 17:44:08 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate rocket;
|
|
|
|
|
|
|
|
mod consts;
|
2022-03-05 19:43:02 +00:00
|
|
|
#[macro_use]
|
|
|
|
mod macros;
|
2023-09-24 12:11:53 +00:00
|
|
|
mod catchers;
|
|
|
|
mod guards;
|
2023-10-29 18:00:45 +00:00
|
|
|
mod metrics;
|
2022-02-11 17:44:08 +00:00
|
|
|
mod routes;
|
|
|
|
|
2023-09-24 12:11:53 +00:00
|
|
|
use std::{env, path::Path};
|
2022-02-11 17:44:08 +00:00
|
|
|
|
2022-02-19 22:11:21 +00:00
|
|
|
use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
|
2023-10-05 17:54:53 +00:00
|
|
|
use rocket::{
|
|
|
|
fs::FileServer,
|
|
|
|
http::CookieJar,
|
|
|
|
serde::json::{json, Value as JsonValue},
|
|
|
|
tokio::sync::broadcast::Sender,
|
|
|
|
};
|
2022-02-11 17:44:08 +00:00
|
|
|
use rocket_dyn_templates::Template;
|
2022-03-05 19:43:02 +00:00
|
|
|
use serenity::{
|
|
|
|
client::Context,
|
|
|
|
http::CacheHttp,
|
|
|
|
model::id::{GuildId, UserId},
|
|
|
|
};
|
2022-02-11 17:44:08 +00:00
|
|
|
use sqlx::{MySql, Pool};
|
2022-02-19 22:11:21 +00:00
|
|
|
|
2023-10-29 18:00:45 +00:00
|
|
|
use crate::{
|
|
|
|
consts::{CNC_GUILD, DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN, SUBSCRIPTION_ROLES},
|
|
|
|
metrics::{init_metrics, MetricProducer},
|
|
|
|
};
|
2022-02-11 17:44:08 +00:00
|
|
|
|
|
|
|
type Database = MySql;
|
|
|
|
|
2022-03-05 19:43:02 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum Error {
|
2024-01-06 19:48:17 +00:00
|
|
|
SQLx,
|
|
|
|
Serenity,
|
2022-03-05 19:43:02 +00:00
|
|
|
}
|
|
|
|
|
2022-02-11 17:44:08 +00:00
|
|
|
pub async fn initialize(
|
2022-03-21 23:11:52 +00:00
|
|
|
kill_channel: Sender<()>,
|
2022-02-11 17:44:08 +00:00
|
|
|
serenity_context: Context,
|
|
|
|
db_pool: Pool<Database>,
|
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
2022-04-09 11:21:28 +00:00
|
|
|
info!("Checking environment variables...");
|
2023-08-19 13:20:48 +00:00
|
|
|
|
|
|
|
if env::var("OFFLINE").map_or(true, |v| v != "1") {
|
|
|
|
env::var("OAUTH2_CLIENT_ID").expect("`OAUTH2_CLIENT_ID' not supplied");
|
|
|
|
env::var("OAUTH2_CLIENT_SECRET").expect("`OAUTH2_CLIENT_SECRET' not supplied");
|
|
|
|
env::var("OAUTH2_DISCORD_CALLBACK").expect("`OAUTH2_DISCORD_CALLBACK' not supplied");
|
|
|
|
env::var("PATREON_GUILD_ID").expect("`PATREON_GUILD_ID' not supplied");
|
|
|
|
}
|
|
|
|
|
2022-04-09 11:21:28 +00:00
|
|
|
info!("Done!");
|
|
|
|
|
2022-02-11 17:44:08 +00:00
|
|
|
let oauth2_client = BasicClient::new(
|
|
|
|
ClientId::new(env::var("OAUTH2_CLIENT_ID")?),
|
|
|
|
Some(ClientSecret::new(env::var("OAUTH2_CLIENT_SECRET")?)),
|
|
|
|
AuthUrl::new(DISCORD_OAUTH_AUTHORIZE.to_string())?,
|
|
|
|
Some(TokenUrl::new(DISCORD_OAUTH_TOKEN.to_string())?),
|
|
|
|
)
|
|
|
|
.set_redirect_uri(RedirectUrl::new(env::var("OAUTH2_DISCORD_CALLBACK")?)?);
|
|
|
|
|
|
|
|
let reqwest_client = reqwest::Client::new();
|
|
|
|
|
2023-06-14 12:29:48 +00:00
|
|
|
let static_path =
|
|
|
|
if Path::new("web/static").exists() { "web/static" } else { "/lib/reminder-rs/static" };
|
|
|
|
|
2023-10-29 18:00:45 +00:00
|
|
|
init_metrics();
|
|
|
|
|
2022-02-11 17:44:08 +00:00
|
|
|
rocket::build()
|
2023-10-29 18:00:45 +00:00
|
|
|
.attach(MetricProducer)
|
2022-02-11 17:44:08 +00:00
|
|
|
.attach(Template::fairing())
|
2022-04-07 16:13:02 +00:00
|
|
|
.register(
|
|
|
|
"/",
|
|
|
|
catchers![
|
2023-09-24 12:11:53 +00:00
|
|
|
catchers::not_authorized,
|
|
|
|
catchers::forbidden,
|
|
|
|
catchers::not_found,
|
|
|
|
catchers::internal_server_error,
|
|
|
|
catchers::unprocessable_entity,
|
|
|
|
catchers::payload_too_large,
|
2022-04-07 16:13:02 +00:00
|
|
|
],
|
|
|
|
)
|
2022-02-11 17:44:08 +00:00
|
|
|
.manage(oauth2_client)
|
|
|
|
.manage(reqwest_client)
|
|
|
|
.manage(serenity_context)
|
|
|
|
.manage(db_pool)
|
2023-06-14 12:29:48 +00:00
|
|
|
.mount("/static", FileServer::from(static_path))
|
2022-02-11 17:44:08 +00:00
|
|
|
.mount(
|
|
|
|
"/",
|
|
|
|
routes![
|
|
|
|
routes::cookies,
|
2023-10-29 18:00:45 +00:00
|
|
|
routes::index,
|
|
|
|
routes::metrics::metrics,
|
2022-02-11 17:44:08 +00:00
|
|
|
routes::privacy,
|
2023-06-20 12:13:26 +00:00
|
|
|
routes::report::report_error,
|
2023-10-29 18:00:45 +00:00
|
|
|
routes::return_to_same_site,
|
|
|
|
routes::terms,
|
2022-03-20 21:04:24 +00:00
|
|
|
],
|
|
|
|
)
|
|
|
|
.mount(
|
|
|
|
"/help",
|
|
|
|
routes![
|
2022-02-11 17:44:08 +00:00
|
|
|
routes::help,
|
2022-03-19 23:47:40 +00:00
|
|
|
routes::help_timezone,
|
2022-03-20 21:04:24 +00:00
|
|
|
routes::help_create_reminder,
|
2022-03-20 21:41:38 +00:00
|
|
|
routes::help_delete_reminder,
|
|
|
|
routes::help_timers,
|
|
|
|
routes::help_todo_lists,
|
|
|
|
routes::help_macros,
|
2022-05-14 11:02:46 +00:00
|
|
|
routes::help_intervals,
|
|
|
|
routes::help_dashboard,
|
|
|
|
routes::help_iemanager,
|
2022-02-11 17:44:08 +00:00
|
|
|
],
|
|
|
|
)
|
2023-06-20 07:50:12 +00:00
|
|
|
.mount(
|
|
|
|
"/login",
|
|
|
|
routes![
|
|
|
|
routes::login::discord_login,
|
|
|
|
routes::login::discord_logout,
|
|
|
|
routes::login::discord_callback
|
|
|
|
],
|
|
|
|
)
|
2022-02-11 17:44:08 +00:00
|
|
|
.mount(
|
|
|
|
"/dashboard",
|
|
|
|
routes![
|
2022-04-07 20:41:24 +00:00
|
|
|
routes::dashboard::dashboard,
|
2022-02-11 17:44:08 +00:00
|
|
|
routes::dashboard::dashboard_home,
|
2023-10-08 17:24:04 +00:00
|
|
|
routes::dashboard::api::user::get_user_info,
|
|
|
|
routes::dashboard::api::user::update_user_info,
|
|
|
|
routes::dashboard::api::user::get_user_guilds,
|
|
|
|
routes::dashboard::api::guild::get_guild_info,
|
|
|
|
routes::dashboard::api::guild::get_guild_channels,
|
|
|
|
routes::dashboard::api::guild::get_guild_roles,
|
|
|
|
routes::dashboard::api::guild::get_reminder_templates,
|
|
|
|
routes::dashboard::api::guild::create_reminder_template,
|
|
|
|
routes::dashboard::api::guild::delete_reminder_template,
|
|
|
|
routes::dashboard::api::guild::create_guild_reminder,
|
|
|
|
routes::dashboard::api::guild::get_reminders,
|
|
|
|
routes::dashboard::api::guild::edit_reminder,
|
|
|
|
routes::dashboard::api::guild::delete_reminder,
|
2022-07-22 22:30:45 +00:00
|
|
|
routes::dashboard::export::export_reminders,
|
|
|
|
routes::dashboard::export::export_reminder_templates,
|
|
|
|
routes::dashboard::export::export_todos,
|
|
|
|
routes::dashboard::export::import_reminders,
|
|
|
|
routes::dashboard::export::import_todos,
|
2022-02-11 17:44:08 +00:00
|
|
|
],
|
|
|
|
)
|
2023-06-21 12:26:28 +00:00
|
|
|
.mount("/admin", routes![routes::admin::admin_dashboard_home, routes::admin::bot_data])
|
2022-02-11 17:44:08 +00:00
|
|
|
.launch()
|
|
|
|
.await?;
|
|
|
|
|
2022-03-21 23:11:52 +00:00
|
|
|
warn!("Exiting rocket runtime");
|
|
|
|
// distribute kill signal
|
2022-03-22 22:21:47 +00:00
|
|
|
match kill_channel.send(()) {
|
|
|
|
Ok(_) => {}
|
|
|
|
Err(e) => {
|
|
|
|
error!("Failed to issue kill signal: {:?}", e);
|
|
|
|
}
|
|
|
|
}
|
2022-03-21 23:11:52 +00:00
|
|
|
|
2022-02-11 17:44:08 +00:00
|
|
|
Ok(())
|
2022-02-06 15:47:59 +00:00
|
|
|
}
|
2022-03-05 19:43:02 +00:00
|
|
|
|
|
|
|
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
|
2023-08-19 13:20:48 +00:00
|
|
|
offline!(true);
|
|
|
|
|
2022-03-05 19:43:02 +00:00
|
|
|
if let Some(subscription_guild) = *CNC_GUILD {
|
2024-01-06 19:48:17 +00:00
|
|
|
let guild_member = GuildId::new(subscription_guild).member(cache_http, user_id).await;
|
2022-03-05 19:43:02 +00:00
|
|
|
|
|
|
|
if let Ok(member) = guild_member {
|
|
|
|
for role in member.roles {
|
2024-01-06 19:48:17 +00:00
|
|
|
if SUBSCRIPTION_ROLES.contains(&role.get()) {
|
2022-03-05 19:43:02 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
} else {
|
|
|
|
true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn check_guild_subscription(
|
|
|
|
cache_http: impl CacheHttp,
|
|
|
|
guild_id: impl Into<GuildId>,
|
|
|
|
) -> bool {
|
2023-08-19 13:20:48 +00:00
|
|
|
offline!(true);
|
|
|
|
|
2024-01-06 19:48:17 +00:00
|
|
|
if let Some(owner) = cache_http.cache().unwrap().guild(guild_id).map(|guild| guild.owner_id) {
|
2022-03-05 19:43:02 +00:00
|
|
|
check_subscription(&cache_http, owner).await
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
2023-10-05 17:54:53 +00:00
|
|
|
|
|
|
|
pub async fn check_authorization(
|
|
|
|
cookies: &CookieJar<'_>,
|
|
|
|
ctx: &Context,
|
2024-01-06 19:48:17 +00:00
|
|
|
guild_id: u64,
|
2023-10-05 17:54:53 +00:00
|
|
|
) -> Result<(), JsonValue> {
|
|
|
|
let user_id = cookies.get_private("userid").map(|c| c.value().parse::<u64>().ok()).flatten();
|
|
|
|
|
|
|
|
if std::env::var("OFFLINE").map_or(true, |v| v != "1") {
|
|
|
|
match user_id {
|
|
|
|
Some(user_id) => {
|
|
|
|
let admin_id = std::env::var("ADMIN_ID")
|
|
|
|
.map_or(false, |u| u.parse::<u64>().map_or(false, |u| u == user_id));
|
|
|
|
|
|
|
|
if admin_id {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
2024-01-06 19:48:17 +00:00
|
|
|
let guild_id = GuildId::new(guild_id);
|
|
|
|
|
|
|
|
match guild_id.member(ctx, UserId::new(user_id)).await {
|
|
|
|
Ok(member) => {
|
|
|
|
let permissions_res = member.permissions(ctx);
|
2023-10-05 17:54:53 +00:00
|
|
|
|
2024-01-06 19:48:17 +00:00
|
|
|
match permissions_res {
|
2023-10-05 17:54:53 +00:00
|
|
|
Err(_) => {
|
2024-01-06 19:48:17 +00:00
|
|
|
return Err(json!({"error": "Couldn't fetch permissions"}));
|
2023-10-05 17:54:53 +00:00
|
|
|
}
|
|
|
|
|
2024-01-06 19:48:17 +00:00
|
|
|
Ok(permissions) => {
|
|
|
|
if !(permissions.manage_messages()
|
|
|
|
|| permissions.manage_guild()
|
|
|
|
|| permissions.administrator())
|
|
|
|
{
|
|
|
|
return Err(json!({"error": "Incorrect permissions"}));
|
2023-10-05 17:54:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-06 19:48:17 +00:00
|
|
|
Err(_) => {
|
|
|
|
return Err(json!({"error": "User not in guild"}));
|
2023-10-05 17:54:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None => {
|
|
|
|
return Err(json!({"error": "User not authorized"}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|