Merge pull request #725 from jb-alvarado/master

get paths from global settings, remove trailing id
This commit is contained in:
jb-alvarado 2024-08-23 14:52:34 +02:00 committed by GitHub
commit ae06fcbd20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 260 additions and 49 deletions

106
Cargo.lock generated
View File

@ -81,6 +81,31 @@ dependencies = [
"zstd",
]
[[package]]
name = "actix-http-test"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061d27c2a6fea968fdaca0961ff429d23a4ec878c4f68f5d08626663ade69c80"
dependencies = [
"actix-codec",
"actix-rt",
"actix-server",
"actix-service",
"actix-tls",
"actix-utils",
"awc",
"bytes",
"futures-core",
"http 0.2.12",
"log",
"serde",
"serde_json",
"serde_urlencoded",
"slab",
"socket2",
"tokio",
]
[[package]]
name = "actix-macros"
version = "0.2.4"
@ -150,6 +175,7 @@ version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24eda4e2a6e042aa4e55ac438a2ae052d3b5da0ecf83d7411e1a368946925208"
dependencies = [
"actix-macros",
"futures-core",
"tokio",
]
@ -182,6 +208,48 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "actix-test"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439022b5a7b5dac10798465029a9566e8e0cca7a6014541ed277b695691fac5f"
dependencies = [
"actix-codec",
"actix-http",
"actix-http-test",
"actix-rt",
"actix-service",
"actix-utils",
"actix-web",
"awc",
"futures-core",
"futures-util",
"log",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
]
[[package]]
name = "actix-tls"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac453898d866cdbecdbc2334fe1738c747b4eba14a677261f2b768ba05329389"
dependencies = [
"actix-rt",
"actix-service",
"actix-utils",
"futures-core",
"http 0.2.12",
"http 1.1.0",
"impl-more",
"pin-project-lite",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "actix-utils"
version = "3.0.1"
@ -524,6 +592,39 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "awc"
version = "3.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79049b2461279b886e46f1107efc347ebecc7b88d74d023dda010551a124967b"
dependencies = [
"actix-codec",
"actix-http",
"actix-rt",
"actix-service",
"actix-tls",
"actix-utils",
"base64 0.22.1",
"bytes",
"cfg-if",
"cookie",
"derive_more 0.99.18",
"futures-core",
"futures-util",
"h2",
"http 0.2.12",
"itoa",
"log",
"mime",
"percent-encoding",
"pin-project-lite",
"rand",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
]
[[package]]
name = "backtrace"
version = "0.3.73"
@ -3441,6 +3542,11 @@ dependencies = [
name = "tests"
version = "0.24.0-beta2"
dependencies = [
"actix-rt",
"actix-test",
"actix-web",
"actix-web-grants",
"actix-web-httpauth",
"chrono",
"crossbeam-channel",
"ffplayout",

View File

@ -160,26 +160,27 @@ struct ProgramItem {
/// }
/// ```
#[post("/auth/login/")]
pub async fn login(pool: web::Data<Pool<Sqlite>>, credentials: web::Json<User>) -> impl Responder {
pub async fn login(
pool: web::Data<Pool<Sqlite>>,
credentials: web::Json<User>,
) -> Result<impl Responder, ServiceError> {
let username = credentials.username.clone();
let password = credentials.password.clone();
match handles::select_login(&pool, &username).await {
Ok(mut user) => {
let role = handles::select_role(&pool, &user.role_id.unwrap_or_default())
.await
.unwrap_or(Role::Guest);
let role = handles::select_role(&pool, &user.role_id.unwrap_or_default()).await?;
let pass = user.password.clone();
let password_clone = password.clone();
let pass_hash = user.password.clone();
let cred_password = password.clone();
user.password = "".into();
let verified_password = web::block(move || {
let hash = PasswordHash::new(&pass).unwrap();
Argon2::default().verify_password(password_clone.as_bytes(), &hash)
let hash = PasswordHash::new(&pass_hash)?;
Argon2::default().verify_password(cred_password.as_bytes(), &hash)
})
.await;
.await?;
if verified_password.is_ok() {
let claims = Claims::new(
@ -195,31 +196,31 @@ pub async fn login(pool: web::Data<Pool<Sqlite>>, credentials: web::Json<User>)
info!("user {} login, with role: {role}", username);
web::Json(UserObj {
Ok(web::Json(UserObj {
message: "login correct!".into(),
user: Some(user),
})
.customize()
.with_status(StatusCode::OK)
.with_status(StatusCode::OK))
} else {
error!("Wrong password for {username}!");
web::Json(UserObj {
Ok(web::Json(UserObj {
message: "Wrong password!".into(),
user: None,
})
.customize()
.with_status(StatusCode::FORBIDDEN)
.with_status(StatusCode::FORBIDDEN))
}
}
Err(e) => {
error!("Login {username} failed! {e}");
web::Json(UserObj {
Ok(web::Json(UserObj {
message: format!("Login {username} failed!"),
user: None,
})
.customize()
.with_status(StatusCode::BAD_REQUEST)
.with_status(StatusCode::BAD_REQUEST))
}
}
}

View File

@ -54,7 +54,7 @@ pub async fn init_globales(conn: &Pool<Sqlite>) {
}
// #[serde_as]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct User {
#[serde(skip_deserializing)]
pub id: i32,

View File

@ -1,5 +1,8 @@
use std::sync::{Arc, Mutex};
use actix_web::{dev::ServiceRequest, Error, HttpMessage};
use actix_web_grants::authorities::AttachAuthorities;
use actix_web_httpauth::extractors::bearer::BearerAuth;
use clap::Parser;
use lazy_static::lazy_static;
use sysinfo::{Disks, Networks, System};
@ -11,6 +14,8 @@ pub mod player;
pub mod sse;
pub mod utils;
use api::auth;
use db::models::UserMeta;
use utils::advanced_config::AdvancedConfig;
use utils::args_parse::Args;
@ -22,3 +27,21 @@ lazy_static! {
Arc::new(Mutex::new(Networks::new_with_refreshed_list()));
pub static ref SYS: Arc<Mutex<System>> = Arc::new(Mutex::new(System::new_all()));
}
pub async fn validator(
req: ServiceRequest,
credentials: BearerAuth,
) -> Result<ServiceRequest, (Error, ServiceRequest)> {
// We just get permissions from JWT
match auth::decode_jwt(credentials.token()).await {
Ok(claims) => {
req.attach(vec![claims.role]);
req.extensions_mut()
.insert(UserMeta::new(claims.id, claims.channels));
Ok(req)
}
Err(e) => Err((e, req)),
}
}

View File

@ -9,11 +9,9 @@ use std::{
};
use actix_files::Files;
use actix_web::{
dev::ServiceRequest, middleware::Logger, web, App, Error, HttpMessage, HttpServer,
};
use actix_web_grants::authorities::AttachAuthorities;
use actix_web_httpauth::{extractors::bearer::BearerAuth, middleware::HttpAuthentication};
use actix_web::{middleware::Logger, web, App, HttpServer};
use actix_web_httpauth::middleware::HttpAuthentication;
#[cfg(all(not(debug_assertions), feature = "embed_frontend"))]
use actix_web_static_files::ResourceFiles;
@ -22,11 +20,8 @@ use log::*;
use path_clean::PathClean;
use ffplayout::{
api::{auth, routes::*},
db::{
db_drop, db_pool, handles,
models::{init_globales, UserMeta},
},
api::routes::*,
db::{db_drop, db_pool, handles, models::init_globales},
player::{
controller::{ChannelController, ChannelManager},
utils::{get_date, is_remote, json_validate::validate_playlist, JsonPlaylist},
@ -38,7 +33,7 @@ use ffplayout::{
logging::{init_logging, MailQueue},
playlist::generate_playlist,
},
ARGS,
validator, ARGS,
};
#[cfg(any(debug_assertions, not(feature = "embed_frontend")))]
@ -55,24 +50,6 @@ fn thread_counter() -> usize {
(available_threads / 2).max(2)
}
async fn validator(
req: ServiceRequest,
credentials: BearerAuth,
) -> Result<ServiceRequest, (Error, ServiceRequest)> {
// We just get permissions from JWT
match auth::decode_jwt(credentials.token()).await {
Ok(claims) => {
req.attach(vec![claims.role]);
req.extensions_mut()
.insert(UserMeta::new(claims.id, claims.channels));
Ok(req)
}
Err(e) => Err((e, req)),
}
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let mail_queues = Arc::new(Mutex::new(vec![]));

View File

@ -67,15 +67,15 @@ pub async fn create_channel(
channel.preview_url = preview_url(&channel.preview_url, channel.id);
if global.shared_storage {
channel.hls_path = Path::new(&channel.hls_path)
channel.hls_path = Path::new(&global.public_root)
.join(channel.id.to_string())
.to_string_lossy()
.to_string();
channel.playlist_path = Path::new(&channel.playlist_path)
channel.playlist_path = Path::new(&global.playlist_root)
.join(channel.id.to_string())
.to_string_lossy()
.to_string();
channel.storage_path = Path::new(&channel.storage_path)
channel.storage_path = Path::new(&global.storage_root)
.join(channel.id.to_string())
.to_string_lossy()
.to_string();

@ -1 +1 @@
Subproject commit fd85411c773f86cd3cef02fdd8c3fd041d9af1a8
Subproject commit 0b1e083ce5b1818589b899d2fe0cc04f4100df32

View File

@ -10,6 +10,11 @@ publish = false
[dev-dependencies]
ffplayout= { path = "../ffplayout" }
actix-web = "4"
actix-web-grants = "4"
actix-web-httpauth = "0.8"
actix-rt = "2.10"
actix-test = "0.1"
chrono = "0.4"
crossbeam-channel = "0.5"
ffprobe = "0.4"
@ -29,6 +34,10 @@ tokio = { version = "1.29", features = ["full"] }
toml_edit = {version ="0.22", features = ["serde"]}
walkdir = "2"
[[test]]
name = "api_routes"
path = "src/api_routes.rs"
[[test]]
name = "lib_utils"
path = "src/lib_utils.rs"

95
tests/src/api_routes.rs Normal file
View File

@ -0,0 +1,95 @@
use actix_web::{get, web, App, Error, HttpResponse, Responder};
// use actix_web_httpauth::extractors::bearer::BearerAuth;
use serde_json::json;
use sqlx::{sqlite::SqlitePoolOptions, Pool, Sqlite};
use ffplayout::api::routes::login;
use ffplayout::db::{
handles,
models::{init_globales, User},
};
use ffplayout::player::controller::ChannelManager;
use ffplayout::utils::config::PlayoutConfig;
// use ffplayout::validator;
async fn prepare_config() -> (PlayoutConfig, ChannelManager, Pool<Sqlite>) {
let pool = SqlitePoolOptions::new()
.connect("sqlite::memory:")
.await
.unwrap();
handles::db_migrate(&pool).await.unwrap();
sqlx::query(
r#"
UPDATE global SET public_root = "assets/hls", logging_path = "assets/log", playlist_root = "assets/playlists", storage_root = "assets/storage";
UPDATE channels SET hls_path = "assets/hls", playlist_path = "assets/playlists", storage_path = "assets/storage";
"#,
)
.execute(&pool)
.await
.unwrap();
let user = User {
id: 0,
mail: Some("admin@mail.com".to_string()),
username: "admin".to_string(),
password: "admin".to_string(),
role_id: Some(1),
channel_ids: Some(vec![1]),
token: None,
};
handles::insert_user(&pool, user.clone()).await.unwrap();
let config = PlayoutConfig::new(&pool, 1).await;
let channel = handles::select_channel(&pool, &1).await.unwrap();
let manager = ChannelManager::new(Some(pool.clone()), channel, config.clone());
(config, manager, pool)
}
#[get("/")]
async fn get_handler() -> Result<impl Responder, Error> {
Ok(HttpResponse::Ok())
}
#[actix_rt::test]
async fn test_get() {
let srv = actix_test::start(|| App::new().service(get_handler));
let req = srv.get("/");
let res = req.send().await.unwrap();
assert!(res.status().is_success());
}
#[actix_rt::test]
async fn test_login() {
let (_, _, pool) = prepare_config().await;
init_globales(&pool).await;
let srv = actix_test::start(move || {
let db_pool = web::Data::new(pool.clone());
App::new().app_data(db_pool).service(login)
});
let payload = json!({"username": "admin", "password": "admin"});
let res = srv.post("/auth/login/").send_json(&payload).await.unwrap();
assert!(res.status().is_success());
let payload = json!({"username": "admin", "password": "1234"});
let res = srv.post("/auth/login/").send_json(&payload).await.unwrap();
assert_eq!(res.status().as_u16(), 403);
let payload = json!({"username": "aaa", "password": "1234"});
let res = srv.post("/auth/login/").send_json(&payload).await.unwrap();
assert_eq!(res.status().as_u16(), 400);
}