From 72cfb26e38c134641d0d274525357cd42190246a Mon Sep 17 00:00:00 2001 From: Doloro1978 Date: Sun, 16 Aug 2026 15:25:06 +0100 Subject: [PATCH] Bump: 0.5.2 : label charset tightened; password/custom-id validation --- Cargo.lock | 2 +- crates/server/Cargo.toml | 2 +- crates/server/src/http.rs | 22 ++++++++++++++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50ce6fb..08bbdd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2906,7 +2906,7 @@ dependencies = [ [[package]] name = "server" -version = "0.5.1" +version = "0.5.2" dependencies = [ "argon2", "async-broadcast", diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml index b516ccf..c7c7e93 100644 --- a/crates/server/Cargo.toml +++ b/crates/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "server" -version = "0.5.1" +version = "0.5.2" edition = "2024" [target.x86_64-unknown-linux-gnu] diff --git a/crates/server/src/http.rs b/crates/server/src/http.rs index 29bd09a..e5ef723 100644 --- a/crates/server/src/http.rs +++ b/crates/server/src/http.rs @@ -16,7 +16,13 @@ use regex::Regex; // cannot be ported verbatim. The `(?=.*[A-Za-z])` lookahead only means // "must contain at least one letter" — we drop it from the pattern and // enforce that condition with a separate `.chars().any(..)` check below. -static KEY_RE: LazyLock = LazyLock::new(|| Regex::new(r"^[A-Za-z0-9 _-]{1,67}$").unwrap()); +static KEY_RE: LazyLock = LazyLock::new(|| Regex::new(r"^[A-Za-z0-9'-]{1,67}$").unwrap()); + +// Allowed charset for stream-key passwords and custom IDs: letters, numbers, +// dashes and apostrophes only — no spaces. Mirrors the frontend +// `/^[A-Za-z0-9'-]{0,67}$/` used by the keys-page popups; empty clears the value. +static PASSWORD_RE: LazyLock = + LazyLock::new(|| Regex::new(r"^[A-Za-z0-9'-]{0,67}$").unwrap()); use axum::{ Json, Router, @@ -166,7 +172,7 @@ async fn edit_stream_key( ) -> Result { // Trim surrounding whitespace so labels aren't stored with leading/trailing spaces. let new_label = payload.new.as_deref().map(str::trim); - // Length (1..=67) and allowed charset ([A-Za-z0-9 _-], space included). + // Length (1..=67) and allowed charset ([A-Za-z0-9'-], no spaces). if let Some(label) = new_label { if !KEY_RE.is_match(label) { return Err(HttpError::BadRequest("invalid label".into())); @@ -176,6 +182,18 @@ async fn edit_stream_key( return Err(HttpError::BadRequest("label must contain a letter".into())); } } + // Custom IDs and passwords share a charset: letters, numbers, dashes, + // apostrophes — no spaces. Empty values are allowed (they clear the field). + if let Some(custom_id) = payload.custom_id.as_deref() { + if !custom_id.is_empty() && !PASSWORD_RE.is_match(custom_id) { + return Err(HttpError::BadRequest("invalid custom id".into())); + } + } + if let Some(pwd) = payload.password.as_deref() { + if !pwd.is_empty() && !PASSWORD_RE.is_match(pwd) { + return Err(HttpError::BadRequest("invalid password".into())); + } + } let stream_key = stream_key::Entity::find_by_id(payload.id) .one(&state.db)