Compare commits

...

1 Commits

Author SHA1 Message Date
Connor Johnstone
f6b363c40f Format 2026-03-19 14:06:11 -04:00
6 changed files with 49 additions and 18 deletions

View File

@@ -48,8 +48,7 @@ pub fn get_session_user(session: &Session) -> Option<(i32, String, String)> {
/// Require authentication. Returns (user_id, username, role) or 401. /// Require authentication. Returns (user_id, username, role) or 401.
pub fn require_auth(session: &Session) -> Result<(i32, String, String), ApiError> { pub fn require_auth(session: &Session) -> Result<(i32, String, String), ApiError> {
get_session_user(session) get_session_user(session).ok_or_else(|| ApiError::Unauthorized("not logged in".into()))
.ok_or_else(|| ApiError::Unauthorized("not logged in".into()))
} }
/// Require admin role. Returns (user_id, username, role) or 403. /// Require admin role. Returns (user_id, username, role) or 403.

View File

@@ -1,7 +1,7 @@
//! Web interface backend for Shanty. //! Web interface backend for Shanty.
//! //!
//! An Actix-web server that ties all Shanty components together, exposing a REST //! An Actix-web server that ties all Shanty components together, exposing a REST
//! API consumed by the Elm frontend. Handles background tasks, configuration, //! API consumed by the Yew (WASM) frontend. Handles background tasks, configuration,
//! and orchestration of indexing, tagging, downloading, and more. //! and orchestration of indexing, tagging, downloading, and more.
pub mod auth; pub mod auth;

View File

@@ -98,10 +98,11 @@ async fn main() -> anyhow::Result<()> {
App::new() App::new()
.wrap(cors) .wrap(cors)
.wrap(SessionMiddleware::builder( .wrap(
CookieSessionStore::default(), SessionMiddleware::builder(CookieSessionStore::default(), session_key.clone())
session_key.clone(), .cookie_secure(false)
).cookie_secure(false).build()) .build(),
)
.wrap(TracingLogger::default()) .wrap(TracingLogger::default())
.app_data(state.clone()) .app_data(state.clone())
.configure(routes::configure) .configure(routes::configure)

View File

@@ -1,5 +1,5 @@
use actix_session::Session; use actix_session::Session;
use actix_web::{web, HttpResponse}; use actix_web::{HttpResponse, web};
use serde::Deserialize; use serde::Deserialize;
use shanty_db::entities::user::UserRole; use shanty_db::entities::user::UserRole;
@@ -76,7 +76,11 @@ async fn setup(
// Adopt any orphaned wanted items from before auth was added // Adopt any orphaned wanted items from before auth was added
let adopted = queries::users::adopt_orphaned_wanted_items(state.db.conn(), user.id).await?; let adopted = queries::users::adopt_orphaned_wanted_items(state.db.conn(), user.id).await?;
if adopted > 0 { if adopted > 0 {
tracing::info!(count = adopted, user_id = user.id, "adopted orphaned wanted items"); tracing::info!(
count = adopted,
user_id = user.id,
"adopted orphaned wanted items"
);
} }
auth::set_session(&session, user.id, &user.username, "admin"); auth::set_session(&session, user.id, &user.username, "admin");

View File

@@ -57,7 +57,10 @@ async fn enqueue_download(
Ok(HttpResponse::Ok().json(item)) Ok(HttpResponse::Ok().json(item))
} }
async fn sync_downloads(state: web::Data<AppState>, session: Session) -> Result<HttpResponse, ApiError> { async fn sync_downloads(
state: web::Data<AppState>,
session: Session,
) -> Result<HttpResponse, ApiError> {
auth::require_auth(&session)?; auth::require_auth(&session)?;
let stats = shanty_dl::sync_wanted_to_queue(state.db.conn(), false).await?; let stats = shanty_dl::sync_wanted_to_queue(state.db.conn(), false).await?;
Ok(HttpResponse::Ok().json(serde_json::json!({ Ok(HttpResponse::Ok().json(serde_json::json!({
@@ -67,7 +70,10 @@ async fn sync_downloads(state: web::Data<AppState>, session: Session) -> Result<
}))) })))
} }
async fn trigger_process(state: web::Data<AppState>, session: Session) -> Result<HttpResponse, ApiError> { async fn trigger_process(
state: web::Data<AppState>,
session: Session,
) -> Result<HttpResponse, ApiError> {
auth::require_auth(&session)?; auth::require_auth(&session)?;
let task_id = state.tasks.register("download"); let task_id = state.tasks.register("download");
let state = state.clone(); let state = state.clone();

View File

@@ -27,7 +27,10 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
); );
} }
async fn get_status(state: web::Data<AppState>, session: Session) -> Result<HttpResponse, ApiError> { async fn get_status(
state: web::Data<AppState>,
session: Session,
) -> Result<HttpResponse, ApiError> {
auth::require_auth(&session)?; auth::require_auth(&session)?;
let summary = shanty_watch::library_summary(state.db.conn()).await?; let summary = shanty_watch::library_summary(state.db.conn()).await?;
let pending_items = let pending_items =
@@ -61,7 +64,10 @@ async fn get_status(state: web::Data<AppState>, session: Session) -> Result<Http
}))) })))
} }
async fn trigger_index(state: web::Data<AppState>, session: Session) -> Result<HttpResponse, ApiError> { async fn trigger_index(
state: web::Data<AppState>,
session: Session,
) -> Result<HttpResponse, ApiError> {
auth::require_auth(&session)?; auth::require_auth(&session)?;
let task_id = state.tasks.register("index"); let task_id = state.tasks.register("index");
let state = state.clone(); let state = state.clone();
@@ -86,7 +92,10 @@ async fn trigger_index(state: web::Data<AppState>, session: Session) -> Result<H
Ok(HttpResponse::Accepted().json(serde_json::json!({ "task_id": task_id }))) Ok(HttpResponse::Accepted().json(serde_json::json!({ "task_id": task_id })))
} }
async fn trigger_tag(state: web::Data<AppState>, session: Session) -> Result<HttpResponse, ApiError> { async fn trigger_tag(
state: web::Data<AppState>,
session: Session,
) -> Result<HttpResponse, ApiError> {
auth::require_auth(&session)?; auth::require_auth(&session)?;
let task_id = state.tasks.register("tag"); let task_id = state.tasks.register("tag");
let state = state.clone(); let state = state.clone();
@@ -119,7 +128,10 @@ async fn trigger_tag(state: web::Data<AppState>, session: Session) -> Result<Htt
Ok(HttpResponse::Accepted().json(serde_json::json!({ "task_id": task_id }))) Ok(HttpResponse::Accepted().json(serde_json::json!({ "task_id": task_id })))
} }
async fn trigger_organize(state: web::Data<AppState>, session: Session) -> Result<HttpResponse, ApiError> { async fn trigger_organize(
state: web::Data<AppState>,
session: Session,
) -> Result<HttpResponse, ApiError> {
auth::require_auth(&session)?; auth::require_auth(&session)?;
let task_id = state.tasks.register("organize"); let task_id = state.tasks.register("organize");
let state = state.clone(); let state = state.clone();
@@ -156,7 +168,10 @@ async fn trigger_organize(state: web::Data<AppState>, session: Session) -> Resul
Ok(HttpResponse::Accepted().json(serde_json::json!({ "task_id": task_id }))) Ok(HttpResponse::Accepted().json(serde_json::json!({ "task_id": task_id })))
} }
async fn trigger_pipeline(state: web::Data<AppState>, session: Session) -> Result<HttpResponse, ApiError> { async fn trigger_pipeline(
state: web::Data<AppState>,
session: Session,
) -> Result<HttpResponse, ApiError> {
auth::require_auth(&session)?; auth::require_auth(&session)?;
let sync_id = state.tasks.register_pending("sync"); let sync_id = state.tasks.register_pending("sync");
let download_id = state.tasks.register_pending("download"); let download_id = state.tasks.register_pending("download");
@@ -326,7 +341,10 @@ async fn get_task(
} }
} }
async fn list_watchlist(state: web::Data<AppState>, session: Session) -> Result<HttpResponse, ApiError> { async fn list_watchlist(
state: web::Data<AppState>,
session: Session,
) -> Result<HttpResponse, ApiError> {
let (user_id, _, _) = auth::require_auth(&session)?; let (user_id, _, _) = auth::require_auth(&session)?;
let items = shanty_watch::list_items(state.db.conn(), None, None, Some(user_id)).await?; let items = shanty_watch::list_items(state.db.conn(), None, None, Some(user_id)).await?;
Ok(HttpResponse::Ok().json(items)) Ok(HttpResponse::Ok().json(items))
@@ -343,7 +361,10 @@ async fn remove_watchlist(
Ok(HttpResponse::NoContent().finish()) Ok(HttpResponse::NoContent().finish())
} }
async fn get_config(state: web::Data<AppState>, session: Session) -> Result<HttpResponse, ApiError> { async fn get_config(
state: web::Data<AppState>,
session: Session,
) -> Result<HttpResponse, ApiError> {
auth::require_auth(&session)?; auth::require_auth(&session)?;
let config = state.config.read().await; let config = state.config.read().await;
Ok(HttpResponse::Ok().json(&*config)) Ok(HttpResponse::Ok().json(&*config))