latenite: QOL login stuff/permissions

pull/5/head v0.1.3-alpha
Patrick Cleavelin 2023-03-04 01:53:43 -06:00
parent 1ed1e55db4
commit f5e976103c
4 changed files with 70 additions and 25 deletions

View File

@ -34,6 +34,7 @@
pkg-config
gcc
openssl
pkg-config
python3
ffmpeg
cmake
@ -47,7 +48,8 @@
name = "memejoin-rs";
version = "0.1.2-alpha";
src = self;
nativeBuildInputs = [ local-rust cmake gcc libopus ];
buildInputs = [ openssl.dev ];
nativeBuildInputs = [ local-rust pkg-config openssl openssl.dev cmake gcc libopus ];
cargoLock = {
lockFile = ./Cargo.lock;
@ -59,7 +61,7 @@
tag = "0.1.2-alpha";
copyToRoot = buildEnv {
name = "image-root";
paths = [ default ffmpeg libopus youtube-dl ];
paths = [ default cacert openssl openssl.dev ffmpeg libopus youtube-dl yt-dlp ];
};
runAsRoot = ''
#!${runtimeShell}

View File

@ -27,8 +27,8 @@ pub(crate) struct User {
pub(crate) name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Permissions(u8);
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub(crate) struct Permissions(pub(crate) u8);
impl Default for Permissions {
fn default() -> Permissions {
Permissions(0)

View File

@ -129,7 +129,7 @@ fn spawn_api(settings: Arc<Mutex<Settings>>) {
let api = Router::new()
.route("/health", get(routes::health))
.route("/me", get(routes::me))
.route("/intros/:guild/add/:url", get(routes::add_guild_intro))
.route("/intros/:guild/add", get(routes::add_guild_intro))
.route("/intros/:guild", get(routes::intros))
.route(
"/intros/:guild/:channel/:intro",
@ -142,12 +142,13 @@ fn spawn_api(settings: Arc<Mutex<Settings>>) {
.route("/auth", get(routes::auth))
.layer(
CorsLayer::new()
.allow_origin(Any)
// TODO: move this to env variable
.allow_origin(["https://spacegirl.nl".parse().unwrap()])
.allow_headers(Any)
.allow_methods([Method::GET, Method::POST]),
)
.with_state(Arc::new(state));
let addr = SocketAddr::from(([0, 0, 0, 0], 7756));
let addr = SocketAddr::from(([0, 0, 0, 0], 8080));
info!("socket listening on {addr}");
axum::Server::bind(&addr)
.serve(api.into_make_service())

View File

@ -13,7 +13,7 @@ use serde_json::{json, Value};
use tracing::{error, info};
use uuid::Uuid;
use crate::settings::{ApiState, Intro, IntroIndex};
use crate::settings::{ApiState, Intro, IntroIndex, UserSettings};
use crate::{auth, settings::FileIntro};
#[derive(Serialize)]
@ -38,6 +38,7 @@ pub(crate) struct Me<'a> {
pub(crate) struct MeGuild<'a> {
pub(crate) name: String,
pub(crate) channels: Vec<MeChannel<'a>>,
pub(crate) permissions: auth::Permissions,
}
#[derive(Serialize)]
@ -99,6 +100,13 @@ struct DiscordUser {
pub username: String,
}
#[derive(Deserialize)]
struct DiscordUserGuild {
pub id: String,
pub name: String,
pub owner: bool,
}
pub(crate) async fn auth(
State(state): State<Arc<ApiState>>,
Query(params): Query<HashMap<String, String>>,
@ -114,7 +122,7 @@ pub(crate) async fn auth(
data.insert("client_secret", state.secrets.client_secret.as_str());
data.insert("grant_type", "authorization_code");
data.insert("code", code);
data.insert("redirect_uri", "http://localhost:5173/auth");
data.insert("redirect_uri", "https://spacegirl.nl/memes/auth");
let client = reqwest::Client::new();
@ -219,34 +227,66 @@ pub(crate) async fn intros(
Json(json!(IntroResponse::Intros(&guild.intros)))
}
pub(crate) async fn me(State(state): State<Arc<ApiState>>, headers: HeaderMap) -> Json<Value> {
let settings = state.settings.lock().await;
let Some(token) = headers.get("token").and_then(|v| v.to_str().ok()) else { return Json(json!(MeResponse::NoUserFound)); };
pub(crate) async fn me(
State(state): State<Arc<ApiState>>,
headers: HeaderMap,
) -> Result<Json<Value>, Error> {
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 user = match settings.auth_users.get(token) {
Some(user) => user.name.clone(),
None => return Json(json!(MeResponse::NoUserFound)),
let (username, permissions, access_token) = match settings.auth_users.get(token) {
Some(user) => (
user.name.clone(),
user.permissions,
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: user.clone(),
username: username.clone(),
guilds: Vec::new(),
};
for g in &settings.guilds {
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 mut guild = MeGuild {
name: g.0.to_string(),
channels: Vec::new(),
// TODO: change `auth::User` to have guild specific permissions instead of global
permissions,
};
for channel in &g.1.channels {
let user_settings = channel.1.users.iter().find(|u| *u.0 == user);
for channel in g.1.channels.iter_mut() {
let user_settings = channel
.1
.users
.entry(username.clone())
.or_insert(UserSettings { intros: Vec::new() });
let Some(user) = user_settings else { continue; };
if discord_guild.owner {
guild.permissions.0 |= auth::Permission::DownloadSounds as u8;
}
guild.channels.push(MeChannel {
name: channel.0.to_owned(),
intros: &user.1.intros,
intros: &user_settings.intros,
});
}
@ -254,22 +294,24 @@ pub(crate) async fn me(State(state): State<Arc<ApiState>>, headers: HeaderMap) -
}
if me.guilds.is_empty() {
Json(json!(MeResponse::NoUserFound))
Ok(Json(json!(MeResponse::NoUserFound)))
} else {
Json(json!(MeResponse::Me(me)))
Ok(Json(json!(MeResponse::Me(me))))
}
}
pub(crate) async fn add_guild_intro(
State(state): State<Arc<ApiState>>,
Path((guild, url)): Path<(u64, String)>,
Path(guild): Path<u64>,
Query(mut params): Query<HashMap<String, String>>,
headers: HeaderMap,
) -> Result<(), Error> {
let mut settings = state.settings.lock().await;
// TODO: make this an impl on HeaderMap
let Some(token) = headers.get("token").and_then(|v| v.to_str().ok()) else { return Err(Error::NoUserFound); };
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),
@ -284,7 +326,7 @@ pub(crate) async fn add_guild_intro(
let uuid = Uuid::new_v4().to_string();
let child = tokio::process::Command::new("yt-dlp")
.arg(&url)
.args(["-o", &format!("./sounds/{uuid}")])
.args(["-o", &format!("sounds/{uuid}")])
.args(["-x", "--audio-format", "mp3"])
.spawn()
.map_err(Error::Ytdl)?