fix(web): edge validation hardening + tier Stripe IDs (BUNYIP-112/113/115/122) #162
No reviewers
Labels
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
psa-systems/bunyip!162
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/web-validation-bundle"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Bundle of four medium-severity validation gaps in bunyip-web. The domain layer enforces correctness in every case; the gap was that the web edge posted unbounded / unformatted input to the API, so malformed values surfaced as raw 500s when they hit DB VARCHAR caps and numeric inputs silently coerced on parse failure.
New shared validator module
bunyip-web/src/handlers/validate.rs(8 helpers + 9 unit tests):trim_bounded,trim_bounded_opt,slug,url_opt,parse_i32,parse_i32_opt,email,email_opt. Used by every fixed form below.BUNYIP-113: application group
sort_orderwasparse::<i64>().unwrap_or(0), so non-numeric input silently became 0 and i64 values > i32::MAX truncated against the INTEGER column.group_bodynow returns Result<Value, String> andparse_i32rejects non-numeric / out-of-range with an inline error. Bothapplication_group_createandapplication_group_saveshort-circuit the API call when validation fails, rendering the form again via the existingerror_boxpattern.BUNYIP-112:
create_app_bodyhad only trim on identity fields. Junk slug like" $$$ "was accepted even though slug is load-bearing for OCI repo paths inApplication::oci_pull_image; a > 255-char name hit the DB cap as a raw 500. Now name / display_name / container_name are length-capped (200 each, mirroring the column widths), slug uses the strict^[a-z0-9]([a-z0-9-]*[a-z0-9])?$shape, distribution coordinates are bounded at 200 chars each, andforgejo_package(when sent on a generic_package source) is bounded. New unit test pins" $$$ "and a 300-char name as rejected.BUNYIP-115: public feedback form posted
name/subject/emailunbounded. Over-length input round-tripped to the API and surfaced as a raw 500 from the DB VARCHAR caps; malformed email was accepted silently.feedback_postnow bounds name (100), subject (200), email (254 + shape check), message (16000) before calling the API. Inline error renders via the existingerror_boxpath. The form view gains matchingmaxlength=attributes so the browser bounds input before submission.BUNYIP-122: tier settings model already carried Stripe
*_price_idand*_product_idfields and the API'sUpdateTierConfigRequestalready accepted them, but the bunyip-web tier form rendered only the four slot/trial inputs. Added six new inputs (free_price / lifetime_product / early_adopter_price / early_adopter_product / standard_price / standard_product) plus the matching three fields to web'sTierConfigResponseshape.TierFormcarries them as optional strings; the JSON body omits empty values so the API's tri-state-by-omission semantics apply (a blank field leaves the persisted value untouched; explicit clear is left for v2 once the API grows the shape).New dependency:
url = "2"inbunyip-web/Cargo.toml(already in tree transitively via reqwest, declared so the validator can::url::Url::parsefor icon_url / source_code_url / health_check_url / webhook_url shape checks).Tests: 9 new validator tests + 1 new create-app body test covering the BUNYIP-112 acceptance cases.
cargo test -p bunyip-weball green. The pre-existingbunyip-domain::config::tests::test_config_defaultsfailure is on main, not introduced by this PR.#BUNYIP-112
#BUNYIP-113
#BUNYIP-115
#BUNYIP-122