diff --git a/src/auth.rs b/src/auth.rs index 3c240e2..497f4be 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,8 +1,4 @@ -use std::{collections::HashMap, sync::Arc}; - use serde::{Deserialize, Serialize}; -use serenity::prelude::TypeMapKey; -use tracing::trace; #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct Discord { @@ -22,8 +18,6 @@ pub(crate) struct DiscordSecret { #[derive(Debug, Clone, Serialize, Deserialize)] pub(crate) struct User { pub(crate) auth: Discord, - #[serde(default)] - pub(crate) permissions: Permissions, pub(crate) name: String, } @@ -35,6 +29,12 @@ impl Default for Permissions { } } +impl Permissions { + pub(crate) fn can(&self, perm: Permission) -> bool { + self.0 & (perm as u8) > 0 + } +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[repr(u8)] pub enum Permission { @@ -42,8 +42,8 @@ pub enum Permission { DownloadSounds, } -impl Permissions { - pub(crate) fn can(&self, perm: Permission) -> bool { - self.0 & (perm as u8) > 0 +impl Permission { + pub(crate) fn all() -> u8 { + 0xFF } } diff --git a/src/routes.rs b/src/routes.rs index 9d2613d..60cda9a 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -13,7 +13,7 @@ use serde_json::{json, Value}; use tracing::{error, info}; use uuid::Uuid; -use crate::settings::{ApiState, Intro, IntroIndex, UserSettings}; +use crate::settings::{ApiState, GuildUser, Intro, IntroIndex, UserSettings}; use crate::{auth, settings::FileIntro}; #[derive(Serialize)] @@ -36,6 +36,8 @@ pub(crate) struct Me<'a> { #[derive(Serialize)] pub(crate) struct MeGuild<'a> { + // NOTE(pcleavelin): for some reason this doesn't serialize properly if a u64 + pub(crate) id: String, pub(crate) name: String, pub(crate) channels: Vec>, pub(crate) permissions: auth::Permissions, @@ -150,16 +152,51 @@ pub(crate) async fn auth( .json() .await?; + // TODO: get bot's guilds so we only save users who are able to use the bot + let discord_guilds: Vec = client + .get("https://discord.com/api/v10/users/@me/guilds") + .bearer_auth(&auth.access_token) + .send() + .await? + .json() + .await + .map_err(|err| Error::Auth(err.to_string()))?; + let mut settings = state.settings.lock().await; + let mut in_a_guild = false; + for g in settings.guilds.iter_mut() { + let Some(discord_guild) = discord_guilds + .iter() + .find(|discord_guild| discord_guild.id == g.0.to_string()) else { continue; }; + + in_a_guild = true; + + if !g.1.users.contains_key(&user.username) { + g.1.users.insert( + user.username.clone(), + GuildUser { + permissions: if discord_guild.owner { + auth::Permissions(auth::Permission::all()) + } else { + Default::default() + }, + }, + ); + } + } + + if !in_a_guild { + return Err(Error::NoGuildFound); + } + settings.auth_users.insert( token.clone(), auth::User { auth, - // TODO: replace with roles - permissions: auth::Permissions::default(), name: user.username.clone(), }, ); + // TODO: add permissions based on roles Ok(Json(json!({"token": token, "username": user.username}))) } @@ -186,9 +223,10 @@ pub(crate) async fn add_intro_to_user( volume: 20, }); - if let Err(err) = settings.save() { - error!("Failed to save config: {err:?}"); - } + // TODO: don't save on every change + //if let Err(err) = settings.save() { + // error!("Failed to save config: {err:?}"); + //} } } @@ -216,9 +254,10 @@ pub(crate) async fn remove_intro_to_user( user.intros.remove(index); } - if let Err(err) = settings.save() { - error!("Failed to save config: {err:?}"); - } + // TODO: don't save on every change + //if let Err(err) = settings.save() { + // error!("Failed to save config: {err:?}"); + //} } pub(crate) async fn intros( @@ -238,29 +277,11 @@ pub(crate) async fn me( let mut settings = state.settings.lock().await; let Some(token) = headers.get("token").and_then(|v| v.to_str().ok()) else { return Err(Error::NoUserFound); }; - let (username, permissions, access_token) = match settings.auth_users.get(token) { - Some(user) => ( - user.name.clone(), - user.permissions, - user.auth.access_token.clone(), - ), + let (username, access_token) = match settings.auth_users.get(token) { + Some(user) => (user.name.clone(), user.auth.access_token.clone()), None => return Err(Error::NoUserFound), }; - // TODO: get bot's guilds so we only save users who are able to use the bot - let discord_guilds: Vec = reqwest::Client::new() - .get("https://discord.com/api/v10/users/@me/guilds") - .bearer_auth(access_token) - .send() - .await? - .json() - .await - .map_err(|err| { - settings.auth_users.remove(token); - - Error::Auth(err.to_string()) - })?; - let mut me = Me { username: username.clone(), guilds: Vec::new(), @@ -268,13 +289,18 @@ pub(crate) async fn me( for g in settings.guilds.iter_mut() { // TODO: don't do this n^2 lookup - let Some(discord_guild) = discord_guilds.iter().find(|discord_guild| discord_guild.id == g.0.to_string()) else { continue; }; + + let guild_user = + g.1.users + // TODO: why must clone + .entry(username.clone()) + .or_insert(Default::default()); let mut guild = MeGuild { - name: g.0.to_string(), + id: g.0.to_string(), + name: g.1.name.clone(), channels: Vec::new(), - // TODO: change `auth::User` to have guild specific permissions instead of global - permissions, + permissions: guild_user.permissions, }; for channel in g.1.channels.iter_mut() { @@ -284,10 +310,6 @@ pub(crate) async fn me( .entry(username.clone()) .or_insert(UserSettings { intros: Vec::new() }); - if discord_guild.owner { - guild.permissions.0 |= auth::Permission::DownloadSounds as u8; - } - guild.channels.push(MeChannel { name: channel.0.to_owned(), intros: &user_settings.intros, @@ -316,13 +338,17 @@ pub(crate) async fn add_guild_intro( let Some(url) = params.remove("url") else { return Err(Error::InvalidRequest); }; let Some(friendly_name) = params.remove("name") else { return Err(Error::InvalidRequest); }; - let user = match settings.auth_users.get(token) { - Some(user) => user, - None => return Err(Error::NoUserFound), - }; + { + let Some(guild) = settings.guilds.get(&guild) else { return Err(Error::NoGuildFound); }; + let auth_user = match settings.auth_users.get(token) { + Some(user) => user, + None => return Err(Error::NoUserFound), + }; + let Some(guild_user) = guild.users.get(&auth_user.name) else { return Err(Error::NoUserFound) }; - if !user.permissions.can(auth::Permission::DownloadSounds) { - return Err(Error::InvalidPermission); + if !guild_user.permissions.can(auth::Permission::DownloadSounds) { + return Err(Error::InvalidPermission); + } } let Some(guild) = settings.guilds.get_mut(&guild) else { return Err(Error::NoGuildFound); }; diff --git a/src/settings.rs b/src/settings.rs index dddd257..e9ca564 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -6,6 +6,8 @@ use serenity::prelude::TypeMapKey; use tracing::trace; use uuid::Uuid; +type UserToken = String; + pub(crate) struct ApiState { pub settings: Arc>, pub secrets: auth::DiscordSecret, @@ -22,7 +24,7 @@ pub(crate) struct Settings { pub(crate) guilds: HashMap, #[serde(default)] - pub(crate) auth_users: HashMap, + pub(crate) auth_users: HashMap, } impl TypeMapKey for Settings { type Value = Arc; @@ -52,10 +54,20 @@ impl Settings { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct GuildSettings { - #[serde(alias = "userEnteredSoundDelay")] + pub(crate) name: String, pub(crate) sound_delay: u64, + #[serde(default)] pub(crate) channels: HashMap, + #[serde(default)] pub(crate) intros: HashMap, + #[serde(default)] + pub(crate) users: HashMap, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub(crate) struct GuildUser { + pub(crate) permissions: auth::Permissions, } #[derive(Debug, Clone, Serialize, Deserialize)]