guild level permissions, now not just saved for a session
parent
a484de34a6
commit
665e83a6fe
18
src/auth.rs
18
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
|
||||
}
|
||||
}
|
||||
|
|
112
src/routes.rs
112
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<MeChannel<'a>>,
|
||||
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<DiscordUserGuild> = 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<DiscordUserGuild> = 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); };
|
||||
|
|
|
@ -6,6 +6,8 @@ use serenity::prelude::TypeMapKey;
|
|||
use tracing::trace;
|
||||
use uuid::Uuid;
|
||||
|
||||
type UserToken = String;
|
||||
|
||||
pub(crate) struct ApiState {
|
||||
pub settings: Arc<tokio::sync::Mutex<Settings>>,
|
||||
pub secrets: auth::DiscordSecret,
|
||||
|
@ -22,7 +24,7 @@ pub(crate) struct Settings {
|
|||
pub(crate) guilds: HashMap<u64, GuildSettings>,
|
||||
|
||||
#[serde(default)]
|
||||
pub(crate) auth_users: HashMap<String, auth::User>,
|
||||
pub(crate) auth_users: HashMap<UserToken, auth::User>,
|
||||
}
|
||||
impl TypeMapKey for Settings {
|
||||
type Value = Arc<Settings>;
|
||||
|
@ -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<String, ChannelSettings>,
|
||||
#[serde(default)]
|
||||
pub(crate) intros: HashMap<String, Intro>,
|
||||
#[serde(default)]
|
||||
pub(crate) users: HashMap<String, GuildUser>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(crate) struct GuildUser {
|
||||
pub(crate) permissions: auth::Permissions,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
|
Loading…
Reference in New Issue