Sync whiteboard images and add board ownership
CI / server (push) Successful in 33s
CI / frontend (push) Successful in 28s
CI / kiosk (push) Failing after 7m17s
Build and Publish Docker Images / build-and-push (apps/web-client/Dockerfile, pistation-web) (push) Successful in 2m17s
Build and Publish Docker Images / build-and-push (server/Dockerfile, pistation-server) (push) Successful in 2m32s
CI / server (push) Successful in 33s
CI / frontend (push) Successful in 28s
CI / kiosk (push) Failing after 7m17s
Build and Publish Docker Images / build-and-push (apps/web-client/Dockerfile, pistation-web) (push) Successful in 2m17s
Build and Publish Docker Images / build-and-push (server/Dockerfile, pistation-server) (push) Successful in 2m32s
This commit is contained in:
@@ -78,6 +78,11 @@ pub struct KioskIdentity {
|
||||
pub room_name: String,
|
||||
}
|
||||
|
||||
pub struct SessionIdentity {
|
||||
pub session_id: String,
|
||||
pub kiosk_id: String,
|
||||
}
|
||||
|
||||
impl FromRequestParts<AppState> for AdminIdentity {
|
||||
type Rejection = AppError;
|
||||
|
||||
@@ -126,6 +131,36 @@ impl FromRequestParts<AppState> for KioskIdentity {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRequestParts<AppState> for SessionIdentity {
|
||||
type Rejection = AppError;
|
||||
|
||||
async fn from_request_parts(
|
||||
parts: &mut Parts,
|
||||
state: &AppState,
|
||||
) -> Result<Self, Self::Rejection> {
|
||||
let session_id = bearer_token(parts)?;
|
||||
|
||||
let row: Option<(String, i64)> = sqlx::query_as(
|
||||
"SELECT kiosk_id, expires_at FROM sessions WHERE id = ? AND revoked = 0",
|
||||
)
|
||||
.bind(&session_id)
|
||||
.fetch_optional(&state.db)
|
||||
.await?;
|
||||
|
||||
let (kiosk_id, expires_at) =
|
||||
row.ok_or_else(|| AppError::Unauthorized("unknown session".into()))?;
|
||||
|
||||
if expires_at <= crate::clock::now_ms() {
|
||||
return Err(AppError::Unauthorized("session expired".into()));
|
||||
}
|
||||
|
||||
Ok(SessionIdentity {
|
||||
session_id,
|
||||
kiosk_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn bearer_token(parts: &Parts) -> AppResult<String> {
|
||||
let header = parts
|
||||
.headers
|
||||
|
||||
+12
-1
@@ -69,12 +69,23 @@ async fn main() {
|
||||
.allow_headers(Any)
|
||||
.allow_origin(Any);
|
||||
|
||||
// Uploaded images are public and are read with fetch by kiosks and browsers alike, which
|
||||
// is blocked without these headers even though the same file loads fine in an img tag.
|
||||
let media_cors = CorsLayer::new()
|
||||
.allow_methods([Method::GET, Method::HEAD])
|
||||
.allow_origin(Any);
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", axum::routing::get(routes::service_index))
|
||||
.route("/install.sh", axum::routing::get(routes::install_script))
|
||||
.nest("/api/kiosk", routes::kiosk_router().layer(kiosk_cors))
|
||||
.nest("/api", routes::api_router().layer(cors))
|
||||
.nest_service("/media", ServeDir::new(state.config.media_dir.clone()))
|
||||
.nest_service(
|
||||
"/media",
|
||||
Router::new()
|
||||
.fallback_service(ServeDir::new(state.config.media_dir.clone()))
|
||||
.layer(media_cors),
|
||||
)
|
||||
.nest_service(
|
||||
"/downloads",
|
||||
ServeDir::new(state.config.package_dir.clone()),
|
||||
|
||||
@@ -4,6 +4,7 @@ pub mod join;
|
||||
pub mod kiosk;
|
||||
pub mod media;
|
||||
pub mod organization;
|
||||
pub mod whiteboard;
|
||||
|
||||
pub use install::install_script;
|
||||
|
||||
@@ -19,6 +20,7 @@ pub fn api_router() -> Router<AppState> {
|
||||
.route("/health", get(health))
|
||||
.merge(join::router())
|
||||
.merge(organization::public_router())
|
||||
.merge(whiteboard::router())
|
||||
.nest(
|
||||
"/admin",
|
||||
admin::router()
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
use axum::body::Bytes;
|
||||
use axum::extract::{DefaultBodyLimit, State};
|
||||
use axum::http::HeaderMap;
|
||||
use axum::routing::put;
|
||||
use axum::{Json, Router};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::auth::SessionIdentity;
|
||||
use crate::error::AppResult;
|
||||
use crate::routes::media::{store_image, MAX_UPLOAD_BYTES};
|
||||
use crate::state::AppState;
|
||||
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/whiteboard/image", put(upload_image))
|
||||
.layer(DefaultBodyLimit::max(MAX_UPLOAD_BYTES))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct UploadResponse {
|
||||
image_url: String,
|
||||
}
|
||||
|
||||
async fn upload_image(
|
||||
State(state): State<AppState>,
|
||||
session: SessionIdentity,
|
||||
headers: HeaderMap,
|
||||
body: Bytes,
|
||||
) -> AppResult<Json<UploadResponse>> {
|
||||
let prefix = format!("wb-{}", session.kiosk_id);
|
||||
let image_url = store_image(&state, &headers, &body, &prefix).await?;
|
||||
Ok(Json(UploadResponse { image_url }))
|
||||
}
|
||||
Reference in New Issue
Block a user