Web Ui Autrhentication

This commit is contained in:
euzu
2024-05-03 01:47:41 +02:00
parent 68d87ff0da
commit ae9be38fbd
28 changed files with 525 additions and 136 deletions
+4 -4
View File
@@ -5,7 +5,7 @@ use actix_web::{HttpRequest, HttpResponse, web};
use log::{debug, error};
use url::Url;
use crate::api::api_model::{AppState, UserApiRequest};
use crate::model::api_proxy::{ApiProxyServerInfo, UserCredentials};
use crate::model::api_proxy::{ApiProxyServerInfo, ProxyUserCredentials};
use crate::model::config::{Config, ConfigTarget, ConfigInput};
use crate::utils::request_utils;
@@ -23,7 +23,7 @@ pub(crate) async fn serve_file(file_path: &Path, req: &HttpRequest, mime_type: m
}
pub(crate) fn get_user_target_by_credentials<'a>(username: &str, password: &str, api_req: &'a UserApiRequest,
app_state: &'a web::Data<AppState>) -> Option<(UserCredentials, &'a ConfigTarget)> {
app_state: &'a web::Data<AppState>) -> Option<(ProxyUserCredentials, &'a ConfigTarget)> {
if !username.is_empty() && !password.is_empty() {
app_state.config.get_target_for_user(username, password)
} else {
@@ -36,13 +36,13 @@ pub(crate) fn get_user_target_by_credentials<'a>(username: &str, password: &str,
}
}
pub(crate) fn get_user_target<'a>(api_req: &'a UserApiRequest, app_state: &'a web::Data<AppState>) -> Option<(UserCredentials, &'a ConfigTarget)> {
pub(crate) fn get_user_target<'a>(api_req: &'a UserApiRequest, app_state: &'a web::Data<AppState>) -> Option<(ProxyUserCredentials, &'a ConfigTarget)> {
let username = api_req.username.as_str().trim();
let password = api_req.password.as_str().trim();
get_user_target_by_credentials(username, password, api_req, app_state)
}
pub(crate) fn get_user_server_info(cfg: &Config, user: &UserCredentials) -> ApiProxyServerInfo {
pub(crate) fn get_user_server_info(cfg: &Config, user: &ProxyUserCredentials) -> ApiProxyServerInfo {
let server_info_list = cfg._api_proxy.read().unwrap().as_ref().unwrap().server.clone();
let server_info_name = match &user.server {
Some(server_name) => server_name.as_str(),
+7 -10
View File
@@ -5,8 +5,7 @@ use std::sync::{Arc, Mutex, RwLock};
use actix_cors::Cors;
use actix_web::{App, HttpServer, web};
use actix_web::http::StatusCode;
use actix_web::middleware::{ErrorHandlers, Logger};
use actix_web::middleware::{Logger};
use log::info;
use crate::api::api_model::{AppState, DownloadQueue, SharedLocks};
@@ -16,7 +15,6 @@ use crate::api::v1_api::v1_api_register;
use crate::api::web_index::index_register;
use crate::api::xmltv_api::xmltv_api_register;
use crate::api::xtream_api::xtream_api_register;
use crate::auth::authenticator::handle_unauthorized;
use crate::model::config::{Config, ProcessTargets};
use crate::processing::playlist_processor;
@@ -74,7 +72,6 @@ pub(crate) async fn start_server(cfg: Arc<Config>, targets: Arc<ProcessTargets>)
if web_ui_enabled {
info!("Web root: {:?}", &web_dir_path);
}
let web_auth_enabled = cfg.web_auth_enabled;
// Web Server
HttpServer::new(move || {
@@ -87,19 +84,19 @@ pub(crate) async fn start_server(cfg: Arc<Config>, targets: Arc<ProcessTargets>)
.allow_any_header()
.max_age(3600))
.app_data(shared_data.clone())
.wrap(ErrorHandlers::new().handler(StatusCode::UNAUTHORIZED, handle_unauthorized))
.configure(|cfg| {
// .wrap(Condition::new(web_auth_enabled, ErrorHandlers::new().handler(StatusCode::UNAUTHORIZED, handle_unauthorized)))
.configure(|srvcfg| {
if web_ui_enabled {
cfg.service(actix_files::Files::new("/static", web_dir_path.join("static")));
cfg.configure(v1_api_register(web_auth_enabled));
srvcfg.service(actix_files::Files::new("/static", web_dir_path.join("static")));
srvcfg.configure(v1_api_register(cfg.web_auth.as_ref().unwrap().clone()));
}
})
.configure(xtream_api_register)
.configure(m3u_api_register)
.configure(xmltv_api_register)
.configure(|cfg| {
.configure(|srvcfg| {
if web_ui_enabled {
cfg.configure(index_register(&web_dir_path, web_auth_enabled));
srvcfg.configure(index_register(&web_dir_path, cfg.web_auth.as_ref().unwrap().clone()));
}
})
}).bind(format!("{}:{}", host, port))?.run().await
+4 -3
View File
@@ -11,7 +11,7 @@ use crate::api::download_api;
use crate::auth::authenticator::validator;
use crate::m3u_filter_error::M3uFilterError;
use crate::model::api_proxy::{ApiProxyConfig, ApiProxyServerInfo, TargetUser};
use crate::model::config::{Config, ConfigDto, ConfigInput, ConfigInputOptions, ConfigSource, ConfigTarget, InputType, validate_targets};
use crate::model::config::{Config, ConfigDto, ConfigInput, ConfigInputOptions, ConfigSource, ConfigTarget, InputType, validate_targets, WebAuthConfig};
use crate::processing::playlist_processor;
use crate::utils::{config_reader, download};
@@ -212,7 +212,7 @@ pub(crate) async fn config(
app_state.config._config_file_path.as_str(),
app_state.config._sources_file_path.as_str()) {
Ok(mut cfg) => {
let _ = cfg.prepare();
let _ = cfg.prepare(true);
map_config(&cfg)
}
Err(_) => map_config(&app_state.config)
@@ -226,7 +226,8 @@ pub(crate) async fn config(
HttpResponse::Ok().json(result)
}
pub(crate) fn v1_api_register(web_auth_enabled: bool) -> impl Fn(&mut web::ServiceConfig) -> () {
pub(crate) fn v1_api_register(web_auth_config: WebAuthConfig) -> impl Fn(&mut web::ServiceConfig) -> () {
let web_auth_enabled = web_auth_config.enabled;
return move |cfg: &mut web::ServiceConfig| {
cfg.service(web::scope("/api/v1")
.wrap(Condition::new(web_auth_enabled, HttpAuthentication::with_fn(validator)))
+55 -11
View File
@@ -1,27 +1,71 @@
use std::collections::HashMap;
use std::path::PathBuf;
use actix_files::NamedFile;
use actix_web::{HttpRequest, web};
use actix_web::middleware::Condition;
use actix_web_httpauth::middleware::HttpAuthentication;
use actix_web::{HttpRequest, HttpResponse, web};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use crate::api::api_model::AppState;
use crate::auth::authenticator::validator;
use crate::auth::authenticator::{create_jwt, verify_token};
use crate::auth::password::verify_password;
use crate::auth::user::UserCredential;
use crate::model::config::WebAuthConfig;
async fn index(
async fn token(
mut req: web::Json<UserCredential>,
app_state: web::Data<AppState>,
) -> HttpResponse {
let username = req.username.as_str();
let password = req.password.as_str();
if username.len() > 0 && password.len() > 0 {
let web_auth = app_state.config.web_auth.as_ref().unwrap();
if let Some(hash) = web_auth.get_user_password(username) {
if verify_password(hash, &password.as_bytes()) {
req.zeroize();
if let Ok(token) = create_jwt(web_auth) {
return HttpResponse::Ok().json(HashMap::from([("token", token)]));
}
};
}
}
req.zeroize();
HttpResponse::BadRequest().finish()
}
async fn token_refresh(
_req: HttpRequest,
_app_state: web::Data<AppState>,
) -> std::io::Result<NamedFile> {
let path: PathBuf = [&_app_state.config.api.web_root, "index.html"].iter().collect();
NamedFile::open(path)
credentials: Option<BearerAuth>,
app_state: web::Data<AppState>,
) -> HttpResponse {
let secret_key = app_state.config.web_auth.as_ref().unwrap().secret.as_ref();
if verify_token(credentials, secret_key) {
if let Ok(token) = create_jwt(app_state.config.web_auth.as_ref().unwrap()) {
return HttpResponse::Ok().json(HashMap::from([("token", token)]));
}
}
HttpResponse::BadRequest().finish()
}
pub(crate) fn index_register(web_dir_path: &PathBuf, web_auth_enabled: bool) -> impl Fn(&mut web::ServiceConfig) -> () {
async fn index(
_req: HttpRequest,
app_state: web::Data<AppState>,
) -> std::io::Result<NamedFile> {
let path: PathBuf = [&app_state.config.api.web_root, "index.html"].iter().collect();
NamedFile::open(path)
}
pub(crate) fn index_register(web_dir_path: &PathBuf, web_auth_config: WebAuthConfig) -> impl Fn(&mut web::ServiceConfig) -> () {
let wdp = web_dir_path.clone();
let web_auth_enabled = web_auth_config.enabled;
return move |cfg: &mut web::ServiceConfig| {
if web_auth_enabled {
cfg.service(web::scope("/auth")
.route("/token", web::post().to(token))
.route("/refresh", web::post().to(token_refresh)));
}
cfg.service(web::scope("/")
.wrap(Condition::new(web_auth_enabled, HttpAuthentication::with_fn(validator)))
.route("", web::get().to(index))
.service(actix_files::Files::new("/", &wdp)));
};
+4 -4
View File
@@ -12,7 +12,7 @@ use url::Url;
use crate::api::api_model::{AppState, UserApiRequest, XtreamAuthorizationResponse, XtreamServerInfo, XtreamUserInfo};
use crate::api::api_utils::{get_user_server_info, get_user_target, get_user_target_by_credentials, serve_file, stream_response};
use crate::model::api_proxy::{ProxyType, UserCredentials};
use crate::model::api_proxy::{ProxyType, ProxyUserCredentials};
use crate::model::config::{Config, ConfigInput, InputType};
use crate::model::config::TargetType;
use crate::model::playlist::XtreamCluster;
@@ -112,7 +112,7 @@ fn get_xtream_player_api_stream_url(input: &ConfigInput, context: &str, action_p
}
fn get_user_info(user: &UserCredentials, cfg: &Config) -> XtreamAuthorizationResponse {
fn get_user_info(user: &ProxyUserCredentials, cfg: &Config) -> XtreamAuthorizationResponse {
let server_info = get_user_server_info(cfg, user);
let now = Local::now();
@@ -262,7 +262,7 @@ async fn xtream_get_stream_info(app_state: &AppState, target_name: &str, stream_
Err(Error::new(std::io::ErrorKind::Other, format!("Cant find stream with id: {}/{}/{}", target_name, &cluster, stream_id)))
}
async fn xtream_get_stream_info_response(app_state: &AppState, user: &UserCredentials,
async fn xtream_get_stream_info_response(app_state: &AppState, user: &ProxyUserCredentials,
target_name: &str, stream_id: &str,
cluster: &XtreamCluster) -> HttpResponse {
match FromStr::from_str(stream_id) {
@@ -284,7 +284,7 @@ async fn xtream_get_stream_info_response(app_state: &AppState, user: &UserCreden
}
}
async fn xtream_get_short_epg(app_state: &AppState, user: &UserCredentials, target_name: &str, stream_id: &str, limit: &str) -> HttpResponse {
async fn xtream_get_short_epg(app_state: &AppState, user: &ProxyUserCredentials, target_name: &str, stream_id: &str, limit: &str) -> HttpResponse {
if !stream_id.is_empty() {
if let Some(target_input) = app_state.config.get_input_for_target(target_name, &InputType::Xtream) {
if let Some(action_url) = get_xtream_player_api_action_url(target_input, "get_short_epg") {