DEV: Limit and validate category settings inputs (#20135)

We recently had a bug which caused auto-bumping to "not work". The problem was that the value had been set to 0.5, which when coerced to an integer turned into 0. So the feature is "working as intended", but there's a possibility of misconfiguration.

When looking into this, I noticed that the inputs on the category settings page doesn't have any particular sanitisation in the front-end, and also one or two validations missing in the back-end.

This change:

- Takes an existing component, NumberField and enhances that by only allowing numeric input, essentially turning it into a managed input using the same approach as our PasswordField.
- Changes the numeric inputs on category settings page to use this component.
- Adds appropriate min constraints to the fields to disallow out-of-range values.
- Adds missing back-end validations to relevant fields.
This commit is contained in:
Ted Johansson 2023-02-07 12:27:38 +08:00 committed by GitHub
parent 25f2fb61b8
commit 676d5fadab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 50 additions and 6 deletions

View File

@ -2,9 +2,37 @@ import I18n from "I18n";
import TextField from "discourse/components/text-field";
import discourseComputed from "discourse-common/utils/decorators";
const ALLOWED_KEYS = [
"Enter",
"Backspace",
"Tab",
"Delete",
"ArrowLeft",
"ArrowUp",
"ArrowRight",
"ArrowDown",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
];
export default TextField.extend({
classNameBindings: ["invalid"],
keyDown: function (e) {
return (
ALLOWED_KEYS.includes(e.key) ||
(e.key === "-" && parseInt(this.get("min"), 10) < 0)
);
},
@discourseComputed("number")
value: {
get(number) {

View File

@ -4,11 +4,12 @@
<label for="category-position">
{{i18n "category.position"}}
</label>
<TextField
@value={{this.category.position}}
<NumberField
@number={{this.category.position}}
@id="category-position"
@class="position-input"
@type="number"
@min="0"
/>
</section>
{{/if}}
@ -34,10 +35,11 @@
{{i18n "category.num_featured_topics"}}
{{/if}}
</label>
<TextField
@value={{this.category.num_featured_topics}}
<NumberField
@number={{this.category.num_featured_topics}}
@id="category-number-featured-topics"
@type="number"
@min="1"
/>
</section>
@ -185,10 +187,11 @@
<label for="category-number-daily-bump">
{{i18n "category.num_auto_bump_daily"}}
</label>
<TextField
@value={{this.category.custom_fields.num_auto_bump_daily}}
<NumberField
@number={{this.category.custom_fields.num_auto_bump_daily}}
@id="category-number-daily-bump"
@type="number"
@min="0"
/>
</section>
</section>

View File

@ -21,6 +21,7 @@
<NumberField
@number={{readonly cat.position}}
@change={{action "change" cat}}
@min="0"
/>
<DButton
@class="btn-default no-text"

View File

@ -73,6 +73,12 @@ class Category < ActiveRecord::Base
validate :ensure_slug
validate :permissions_compatibility_validator
validates :default_slow_mode_seconds,
numericality: {
only_integer: true,
greater_than: 0,
},
allow_nil: true
validates :auto_close_hours,
numericality: {
greater_than: 0,

View File

@ -7,6 +7,12 @@ RSpec.describe Category do
it { is_expected.to validate_presence_of :user_id }
it { is_expected.to validate_presence_of :name }
it do
is_expected.to validate_numericality_of(:default_slow_mode_seconds).is_greater_than(
0,
).only_integer
end
it "validates uniqueness of name" do
Fabricate(:category_with_definition)
is_expected.to validate_uniqueness_of(:name).scoped_to(:parent_category_id).case_insensitive