diff --git a/ffplayout/src/api/auth.rs b/ffplayout/src/api/auth.rs index eed60fe7..b3bb029e 100644 --- a/ffplayout/src/api/auth.rs +++ b/ffplayout/src/api/auth.rs @@ -15,17 +15,17 @@ const JWT_EXPIRATION_DAYS: i64 = 7; #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct Claims { pub id: i32, - pub channel: i32, + pub channels: Vec, pub username: String, pub role: Role, exp: i64, } impl Claims { - pub fn new(id: i32, channel: i32, username: String, role: Role) -> Self { + pub fn new(id: i32, channels: Vec, username: String, role: Role) -> Self { Self { id, - channel, + channels, username, role, exp: (Utc::now() + TimeDelta::try_days(JWT_EXPIRATION_DAYS).unwrap()).timestamp(), diff --git a/ffplayout/src/api/routes.rs b/ffplayout/src/api/routes.rs index d2d4d394..55952154 100644 --- a/ffplayout/src/api/routes.rs +++ b/ffplayout/src/api/routes.rs @@ -186,7 +186,7 @@ pub async fn login(pool: web::Data>, credentials: web::Json) if verified_password.is_ok() { let claims = Claims::new( user.id, - user.channel_id.unwrap_or_default(), + user.channel_ids.clone(), username.clone(), role.clone(), ); @@ -417,7 +417,7 @@ async fn remove_user( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] async fn get_channel( pool: web::Data>, @@ -459,7 +459,7 @@ async fn get_all_channels(pool: web::Data>) -> Result>, @@ -537,7 +537,7 @@ async fn remove_channel( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] async fn get_advanced_config( id: web::Path, @@ -562,7 +562,7 @@ async fn get_advanced_config( "Role::GlobalAdmin", "Role::ChannelAdmin", ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] async fn update_advanced_config( pool: web::Data>, @@ -682,7 +682,7 @@ async fn update_advanced_config( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] async fn get_playout_config( id: web::Path, @@ -707,7 +707,7 @@ async fn get_playout_config( "Role::GlobalAdmin", "Role::ChannelAdmin", ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] async fn update_playout_config( pool: web::Data>, @@ -826,7 +826,7 @@ async fn update_playout_config( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] async fn get_presets( pool: web::Data>, @@ -852,7 +852,7 @@ async fn get_presets( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] async fn update_preset( pool: web::Data>, @@ -882,7 +882,7 @@ async fn update_preset( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] async fn add_preset( pool: web::Data>, @@ -911,7 +911,7 @@ async fn add_preset( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] async fn delete_preset( pool: web::Data>, @@ -948,7 +948,7 @@ async fn delete_preset( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn send_text_message( id: web::Path, @@ -979,7 +979,7 @@ pub async fn send_text_message( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn control_playout( pool: web::Data>, @@ -1025,7 +1025,7 @@ pub async fn control_playout( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn media_current( id: web::Path, @@ -1056,7 +1056,7 @@ pub async fn media_current( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn process_control( id: web::Path, @@ -1103,7 +1103,7 @@ pub async fn process_control( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn get_playlist( id: web::Path, @@ -1132,7 +1132,7 @@ pub async fn get_playlist( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn save_playlist( id: web::Path, @@ -1172,7 +1172,7 @@ pub async fn save_playlist( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn gen_playlist( id: web::Path, @@ -1224,7 +1224,7 @@ pub async fn gen_playlist( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn del_playlist( id: web::Path, @@ -1254,7 +1254,7 @@ pub async fn del_playlist( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn get_log( id: web::Path, @@ -1277,7 +1277,7 @@ pub async fn get_log( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn file_browser( id: web::Path, @@ -1306,7 +1306,7 @@ pub async fn file_browser( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn add_dir( id: web::Path, @@ -1331,7 +1331,7 @@ pub async fn add_dir( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn move_rename( id: web::Path, @@ -1359,7 +1359,7 @@ pub async fn move_rename( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn remove( id: web::Path, @@ -1388,7 +1388,7 @@ pub async fn remove( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] async fn save_file( id: web::Path, @@ -1483,7 +1483,7 @@ async fn get_public(public: web::Path) -> Result, @@ -1544,7 +1544,7 @@ async fn import_playlist( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] async fn get_program( id: web::Path, @@ -1642,7 +1642,7 @@ async fn get_program( #[protect( any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"), ty = "Role", - expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)" + expr = "user.channels.contains(&*id) || role.has_authority(&Role::GlobalAdmin)" )] pub async fn get_system_stat( id: web::Path, diff --git a/ffplayout/src/db/handles.rs b/ffplayout/src/db/handles.rs index 65a0df69..c2e80146 100644 --- a/ffplayout/src/db/handles.rs +++ b/ffplayout/src/db/handles.rs @@ -306,13 +306,13 @@ pub async fn select_role(conn: &Pool, id: &i32) -> Result, user: &str) -> Result { let query = - "SELECT id, mail, username, password, role_id, channel_id FROM user WHERE username = $1"; + "SELECT id, mail, username, password, role_id, channel_ids FROM user WHERE username = $1"; sqlx::query_as(query).bind(user).fetch_one(conn).await } pub async fn select_user(conn: &Pool, id: i32) -> Result { - let query = "SELECT id, mail, username, role_id, channel_id FROM user WHERE id = $1"; + let query = "SELECT id, mail, username, role_id, channel_ids FROM user WHERE id = $1"; sqlx::query_as(query).bind(id).fetch_one(conn).await } @@ -338,13 +338,20 @@ pub async fn insert_user( .await .unwrap(); - let query = "INSERT INTO user (mail, username, password, role_id) VALUES($1, $2, $3, $4)"; + let query = "INSERT INTO user (mail, username, password, role_id, channel_ids) VALUES($1, $2, $3, $4, $5)"; sqlx::query(query) .bind(user.mail) .bind(user.username) .bind(password_hash) .bind(user.role_id) + .bind( + user.channel_ids + .iter() + .map(|i| i.to_string()) + .collect::>() + .join(","), + ) .execute(conn) .await } diff --git a/ffplayout/src/db/models.rs b/ffplayout/src/db/models.rs index af6dd759..1556f0be 100644 --- a/ffplayout/src/db/models.rs +++ b/ffplayout/src/db/models.rs @@ -6,6 +6,7 @@ use serde::{ de::{self, Visitor}, Deserialize, Serialize, }; +use serde_with::{formats::CommaSeparator, serde_as, StringWithSeparator}; use sqlx::{sqlite::SqliteRow, FromRow, Pool, Row, Sqlite}; use crate::db::handles; @@ -52,29 +53,44 @@ pub async fn init_globales(conn: &Pool) { INSTANCE.set(config).unwrap(); } -#[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] +#[serde_as] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct User { - #[sqlx(default)] #[serde(skip_deserializing)] pub id: i32, - #[sqlx(default)] #[serde(skip_serializing_if = "Option::is_none")] pub mail: Option, pub username: String, - #[sqlx(default)] #[serde(skip_serializing, default = "empty_string")] pub password: String, - #[sqlx(default)] #[serde(skip_serializing)] pub role_id: Option, - #[sqlx(default)] #[serde(skip_serializing)] - pub channel_id: Option, - #[sqlx(default)] + #[serde_as(as = "StringWithSeparator::")] + pub channel_ids: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub token: Option, } +impl FromRow<'_, SqliteRow> for User { + fn from_row(row: &SqliteRow) -> sqlx::Result { + Ok(Self { + id: row.try_get("id").unwrap_or_default(), + mail: row.get("mail"), + username: row.try_get("username").unwrap_or_default(), + password: row.try_get("password").unwrap_or_default(), + role_id: row.get("role_id"), + channel_ids: row + .try_get::("channel_ids") + .unwrap_or_default() + .split(',') + .map(|i| i.parse::().unwrap_or_default()) + .collect(), + token: row.get("token"), + }) + } +} + fn empty_string() -> String { "".to_string() } @@ -82,12 +98,12 @@ fn empty_string() -> String { #[derive(Debug, Deserialize, Serialize, Clone)] pub struct UserMeta { pub id: i32, - pub channel: i32, + pub channels: Vec, } impl UserMeta { - pub fn new(id: i32, channel: i32) -> Self { - Self { id, channel } + pub fn new(id: i32, channels: Vec) -> Self { + Self { id, channels } } } diff --git a/ffplayout/src/main.rs b/ffplayout/src/main.rs index 436ee73e..73745ed8 100644 --- a/ffplayout/src/main.rs +++ b/ffplayout/src/main.rs @@ -59,7 +59,7 @@ async fn validator( req.attach(vec![claims.role]); req.extensions_mut() - .insert(UserMeta::new(claims.id, claims.channel)); + .insert(UserMeta::new(claims.id, claims.channels)); Ok(req) } diff --git a/ffplayout/src/utils/args_parse.rs b/ffplayout/src/utils/args_parse.rs index 437be83b..bc403cab 100644 --- a/ffplayout/src/utils/args_parse.rs +++ b/ffplayout/src/utils/args_parse.rs @@ -250,7 +250,7 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { username: username.clone(), password: args.password.unwrap(), role_id: Some(1), - channel_id: Some(1), + channel_ids: vec![1], token: None, }; diff --git a/migrations/00001_create_tables.sql b/migrations/00001_create_tables.sql index 9f76cee5..b149d8c3 100644 --- a/migrations/00001_create_tables.sql +++ b/migrations/00001_create_tables.sql @@ -57,9 +57,8 @@ CREATE TABLE username TEXT NOT NULL, password TEXT NOT NULL, role_id INTEGER NOT NULL DEFAULT 3, - channel_id INTEGER NOT NULL DEFAULT 1, + channel_ids TEXT NOT NULL DEFAULT "1", FOREIGN KEY (role_id) REFERENCES roles (id) ON UPDATE SET NULL ON DELETE SET DEFAULT, - FOREIGN KEY (channel_id) REFERENCES channels (id) ON UPDATE CASCADE ON DELETE SET DEFAULT, UNIQUE (mail, username) );