backend: gate existing endpoints behind auth and app permissions
Move /api/pair, /api/apps, and /api/stream/start under the session auth middleware so they require a valid session token. Add app-level permission filtering: non-admin users only see and can stream apps they have been explicitly granted access to. Admins bypass all permission checks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, error};
|
||||
|
||||
use crate::{
|
||||
auth,
|
||||
common,
|
||||
common::{AppError, AppResult},
|
||||
responses,
|
||||
@@ -45,7 +46,8 @@ struct GetAppsResponse {
|
||||
#[craft]
|
||||
impl crate::backend::Backend {
|
||||
#[craft(endpoint(status_codes(StatusCode::OK, StatusCode::INTERNAL_SERVER_ERROR)))]
|
||||
pub async fn get_apps(self: ::std::sync::Arc<Self>) -> AppResult<Json<GetAppsResponse>> {
|
||||
pub async fn get_apps(self: ::std::sync::Arc<Self>, depot: &mut Depot) -> AppResult<Json<GetAppsResponse>> {
|
||||
let user = auth::get_user_from_depot(depot).cloned();
|
||||
let standard_error = Err(AppError {
|
||||
status_code: StatusCode::INTERNAL_SERVER_ERROR,
|
||||
description: "failed to get available apps".to_string(),
|
||||
@@ -143,6 +145,21 @@ impl crate::backend::Backend {
|
||||
get_apps_resp.apps.insert(server.name, resp_vec);
|
||||
}
|
||||
|
||||
// Filter apps by user permissions (admins see everything)
|
||||
if let Some(ref user) = user {
|
||||
if !user.is_admin {
|
||||
let permissions = self.db.get_permissions(&user.id).unwrap_or_default();
|
||||
for (server_name, apps) in get_apps_resp.apps.iter_mut() {
|
||||
apps.retain(|app| {
|
||||
permissions.iter().any(|p| {
|
||||
p.server == *server_name && p.app_id == app.id as i64
|
||||
})
|
||||
});
|
||||
}
|
||||
get_apps_resp.apps.retain(|_, apps| !apps.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Json(get_apps_resp))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,16 +76,15 @@ async fn run_backend(port: u16) -> Result<()> {
|
||||
let router = Router::new()
|
||||
// Public auth routes
|
||||
.push(Router::with_path("api/auth/login").post(backend_arc.login()))
|
||||
// Existing routes (not yet gated - will be gated in a subsequent commit)
|
||||
.push(Router::with_path("api/pair").post(backend_arc.post_pair()))
|
||||
.push(Router::with_path("api/apps").get(backend_arc.get_apps()))
|
||||
.push(Router::with_path("api/stream/start").post(backend_arc.post_stream_start()))
|
||||
// Authenticated routes
|
||||
.push(
|
||||
Router::with_path("api")
|
||||
.hoop(auth_middleware)
|
||||
.push(Router::with_path("auth/logout").post(backend_arc.logout()))
|
||||
.push(Router::with_path("auth/me").get(backend_arc.me()))
|
||||
.push(Router::with_path("pair").post(backend_arc.post_pair()))
|
||||
.push(Router::with_path("apps").get(backend_arc.get_apps()))
|
||||
.push(Router::with_path("stream/start").post(backend_arc.post_stream_start()))
|
||||
// Admin-only routes
|
||||
.push(
|
||||
Router::with_path("admin")
|
||||
|
||||
@@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
use crate::{
|
||||
auth,
|
||||
common::{AppError, AppResult, get_url},
|
||||
proxy, responses,
|
||||
state::{GamestreamServer, StateReadAccess, StateReader},
|
||||
@@ -81,12 +82,32 @@ impl crate::backend::Backend {
|
||||
self: ::std::sync::Arc<Self>,
|
||||
body: salvo::oapi::extract::JsonBody<PostStreamStartParams>,
|
||||
req: &mut Request,
|
||||
depot: &mut Depot,
|
||||
) -> AppResult<Json<PostStreamStartResponse>> {
|
||||
let standard_error = Err(crate::common::AppError {
|
||||
status_code: StatusCode::INTERNAL_SERVER_ERROR,
|
||||
description: "Could not start stream".to_string(),
|
||||
});
|
||||
|
||||
// Check app permission
|
||||
if let Some(user) = auth::get_user_from_depot(depot) {
|
||||
if !user.is_admin {
|
||||
match self.db.check_app_permission(&user.id, &body.server, body.id as i64) {
|
||||
Ok(true) => {}
|
||||
Ok(false) => {
|
||||
return Err(AppError {
|
||||
status_code: StatusCode::FORBIDDEN,
|
||||
description: "You do not have permission to access this application".to_string(),
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Permission check error: {e}");
|
||||
return standard_error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let reader = self.state.read().await;
|
||||
|
||||
let server = match get_server(&reader, &body.server) {
|
||||
|
||||
Reference in New Issue
Block a user