This commit is contained in:
Connor Johnstone
2026-03-19 14:06:11 -04:00
parent 421ec3199b
commit f6b363c40f
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.
pub fn require_auth(session: &Session) -> Result<(i32, String, String), ApiError> {
get_session_user(session)
.ok_or_else(|| ApiError::Unauthorized("not logged in".into()))
get_session_user(session).ok_or_else(|| ApiError::Unauthorized("not logged in".into()))
}
/// Require admin role. Returns (user_id, username, role) or 403.

View File

@@ -1,7 +1,7 @@
//! Web interface backend for Shanty.
//!
//! 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.
pub mod auth;

View File

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

View File

@@ -1,5 +1,5 @@
use actix_session::Session;
use actix_web::{web, HttpResponse};
use actix_web::{HttpResponse, web};
use serde::Deserialize;
use shanty_db::entities::user::UserRole;
@@ -76,7 +76,11 @@ async fn setup(
// Adopt any orphaned wanted items from before auth was added
let adopted = queries::users::adopt_orphaned_wanted_items(state.db.conn(), user.id).await?;
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");

View File

@@ -57,7 +57,10 @@ async fn enqueue_download(
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)?;
let stats = shanty_dl::sync_wanted_to_queue(state.db.conn(), false).await?;
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)?;
let task_id = state.tasks.register("download");
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)?;
let summary = shanty_watch::library_summary(state.db.conn()).await?;
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)?;
let task_id = state.tasks.register("index");
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 })))
}
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)?;
let task_id = state.tasks.register("tag");
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 })))
}
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)?;
let task_id = state.tasks.register("organize");
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 })))
}
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)?;
let sync_id = state.tasks.register_pending("sync");
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 items = shanty_watch::list_items(state.db.conn(), None, None, Some(user_id)).await?;
Ok(HttpResponse::Ok().json(items))
@@ -343,7 +361,10 @@ async fn remove_watchlist(
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)?;
let config = state.config.read().await;
Ok(HttpResponse::Ok().json(&*config))