use actix_web::{get, http::StatusCode, patch, post, put, web, Responder}; use actix_web_grants::proc_macro::has_any_role; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, SaltString}, Argon2, PasswordHasher, PasswordVerifier, }; use serde::Serialize; use simplelog::*; use crate::api::{ auth::{create_jwt, Claims}, errors::ServiceError, handles::{ db_add_user, db_get_settings, db_login, db_role, db_update_settings, db_update_user, }, models::{LoginUser, Settings, User}, utils::Role, }; #[derive(Serialize)] struct ResponseObj { message: String, status: i32, data: Option, } /// curl -X GET http://127.0.0.1:8080/api/settings/1 -H "Authorization: Bearer " #[get("/settings/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn get_settings(id: web::Path) -> Result { if let Ok(settings) = db_get_settings(&id).await { return Ok(web::Json(ResponseObj { message: format!("Settings from {}", settings.channel_name), status: 200, data: Some(settings), })); } Err(ServiceError::InternalServerError) } /// curl -X PATCH http://127.0.0.1:8080/api/settings/1 -H "Content-Type: application/json" \ /// --data '{"id":1,"channel_name":"Channel 1","preview_url":"http://localhost/live/stream.m3u8", \ /// "config_path":"/etc/ffplayout/ffplayout.yml","extra_extensions":".jpg,.jpeg,.png"}' \ /// -H "Authorization: Bearer " #[patch("/settings/{id}")] #[has_any_role("Role::Admin", type = "Role")] async fn patch_settings( id: web::Path, data: web::Json, ) -> Result { if db_update_settings(*id, data.into_inner()).await.is_ok() { return Ok("Update Success"); }; Err(ServiceError::InternalServerError) } #[get("/playout/config/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn get_playout_config(id: web::Path) -> Result { if let Ok(settings) = db_get_settings(&id).await { println!("{:?}", settings.config_path); return Ok("settings"); }; Err(ServiceError::InternalServerError) } /// curl -X PUT http://localhost:8080/api/user/1 --header 'Content-Type: application/json' \ /// --data '{"email": "", "password": ""}' --header 'Authorization: ' #[put("/user/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] async fn update_user( id: web::Path, user: web::ReqData, data: web::Json, ) -> Result { if id.into_inner() == user.id { let mut fields = String::new(); if let Some(email) = data.email.clone() { fields.push_str(format!("email = '{email}'").as_str()); } if !data.password.is_empty() { if !fields.is_empty() { fields.push_str(", "); } let salt = SaltString::generate(&mut OsRng); let password_hash = Argon2::default() .hash_password(data.password.clone().as_bytes(), &salt) .unwrap(); fields.push_str(format!("password = '{}', salt = '{salt}'", password_hash).as_str()); } if db_update_user(user.id, fields).await.is_ok() { return Ok("Update Success"); }; return Err(ServiceError::InternalServerError); } Err(ServiceError::Unauthorized) } /// curl -X POST 'http://localhost:8080/api/user/' --header 'Content-Type: application/json' \ /// -d '{"email": "", "username": "", "password": "", "role_id": 1}' \ /// --header 'Authorization: Bearer ' #[post("/user/")] #[has_any_role("Role::Admin", type = "Role")] async fn add_user(data: web::Json) -> Result { match db_add_user(data.into_inner()).await { Ok(_) => Ok("Add User Success"), Err(e) => { error!("{e}"); Err(ServiceError::InternalServerError) } } } /// curl -X POST http://127.0.0.1:8080/auth/login/ -H "Content-Type: application/json" \ /// -d '{"username": "", "password": "" }' #[post("/auth/login/")] pub async fn login(credentials: web::Json) -> impl Responder { match db_login(&credentials.username).await { Ok(mut user) => { let pass = user.password.clone(); let hash = PasswordHash::new(&pass).unwrap(); user.password = "".into(); user.salt = None; if Argon2::default() .verify_password(credentials.password.as_bytes(), &hash) .is_ok() { let role = db_role(&user.role_id.unwrap_or_default()) .await .unwrap_or_else(|_| "guest".to_string()); let claims = Claims::new(user.id, user.username.clone(), role.clone()); if let Ok(token) = create_jwt(claims) { user.token = Some(token); }; info!("user {} login, with role: {role}", credentials.username); web::Json(ResponseObj { message: "login correct!".into(), status: 200, data: Some(user), }) .customize() .with_status(StatusCode::OK) } else { error!("Wrong password for {}!", credentials.username); web::Json(ResponseObj { message: "Wrong password!".into(), status: 403, data: None, }) .customize() .with_status(StatusCode::FORBIDDEN) } } Err(e) => { error!("Login {} failed! {e}", credentials.username); return web::Json(ResponseObj { message: format!("Login {} failed!", credentials.username), status: 400, data: None, }) .customize() .with_status(StatusCode::BAD_REQUEST); } } }