pinakes-server: TLS support; session persistence and security polish

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: If2c9c3e3af62bbf9f33a97be89ac40bc6a6a6964
This commit is contained in:
raf 2026-01-31 15:20:27 +03:00
commit 87a4482576
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
19 changed files with 1835 additions and 111 deletions

View file

@ -44,6 +44,24 @@ pub fn create_router_with_tls(
.unwrap(),
);
// Rate limit for search: 10 requests/min per IP
let search_governor = Arc::new(
GovernorConfigBuilder::default()
.per_second(6) // replenish one every 6 seconds (10/min)
.burst_size(10)
.finish()
.unwrap(),
);
// Rate limit for streaming: 5 requests per IP (very restrictive for concurrent streams)
let stream_governor = Arc::new(
GovernorConfigBuilder::default()
.per_second(60) // replenish slowly (one per minute)
.burst_size(5) // max 5 concurrent connections
.finish()
.unwrap(),
);
// Login route with strict rate limiting
let login_route = Router::new()
.route("/auth/login", post(routes::auth::login))
@ -58,6 +76,21 @@ pub fn create_router_with_tls(
.route("/health/live", get(routes::health::liveness))
.route("/health/ready", get(routes::health::readiness));
// Search routes with enhanced rate limiting (10 req/min)
let search_routes = Router::new()
.route("/search", get(routes::search::search))
.route("/search", post(routes::search::search_post))
.layer(GovernorLayer {
config: search_governor,
});
// Streaming routes with enhanced rate limiting (5 concurrent)
let streaming_routes = Router::new()
.route("/media/{id}/stream", get(routes::media::stream_media))
.layer(GovernorLayer {
config: stream_governor,
});
// Read-only routes: any authenticated user (Viewer+)
let viewer_routes = Router::new()
.route("/health", get(routes::health::health))
@ -65,11 +98,8 @@ pub fn create_router_with_tls(
.route("/media/count", get(routes::media::get_media_count))
.route("/media", get(routes::media::list_media))
.route("/media/{id}", get(routes::media::get_media))
.route("/media/{id}/stream", get(routes::media::stream_media))
.route("/media/{id}/thumbnail", get(routes::media::get_thumbnail))
.route("/media/{media_id}/tags", get(routes::tags::get_media_tags))
.route("/search", get(routes::search::search))
.route("/search", post(routes::search::search_post))
.route("/tags", get(routes::tags::list_tags))
.route("/tags/{id}", get(routes::tags::get_tag))
.route("/collections", get(routes::collections::list_collections))
@ -107,6 +137,7 @@ pub fn create_router_with_tls(
// Auth endpoints (self-service) — login handled separately with stricter rate limit
.route("/auth/logout", post(routes::auth::logout))
.route("/auth/me", get(routes::auth::me))
.route("/auth/revoke-all", post(routes::auth::revoke_all_sessions))
// Social: ratings & comments (read)
.route(
"/media/{id}/ratings",
@ -374,6 +405,8 @@ pub fn create_router_with_tls(
"/users/{id}/libraries",
delete(routes::users::revoke_library_access),
)
// Session management (admin)
.route("/auth/sessions", get(routes::auth::list_active_sessions))
.layer(middleware::from_fn(auth::require_admin));
// CORS: allow same-origin by default, plus the desktop UI origin
@ -396,6 +429,8 @@ pub fn create_router_with_tls(
// Create protected routes with auth middleware
let protected_api = Router::new()
.merge(viewer_routes)
.merge(search_routes)
.merge(streaming_routes)
.merge(editor_routes)
.merge(admin_routes)
.layer(middleware::from_fn_with_state(