feat(backend): make field limits configurable

This commit is contained in:
sam 2025-02-28 16:37:15 +01:00
parent 7ea6c62d67
commit 218c756a70
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
7 changed files with 133 additions and 149 deletions

View file

@ -99,6 +99,11 @@ public class Config
{
public int MaxMemberCount { get; init; } = 1000;
public int MaxFields { get; init; } = 25;
public int MaxFieldNameLength { get; init; } = 100;
public int MaxFieldEntryTextLength { get; init; } = 100;
public int MaxFieldEntries { get; init; } = 100;
public int MaxUsernameLength { get; init; } = 40;
public int MaxMemberNameLength { get; init; } = 100;
public int MaxDisplayNameLength { get; init; } = 100;

View file

@ -81,13 +81,13 @@ public class MembersController(
("display_name", validationService.ValidateDisplayName(req.DisplayName)),
("bio", validationService.ValidateBio(req.Bio)),
("avatar", validationService.ValidateAvatar(req.Avatar)),
.. ValidationUtils.ValidateFields(req.Fields, CurrentUser!.CustomPreferences),
.. ValidationUtils.ValidateFieldEntries(
.. validationService.ValidateFields(req.Fields, CurrentUser!.CustomPreferences),
.. validationService.ValidateFieldEntries(
req.Names?.ToArray(),
CurrentUser!.CustomPreferences,
"names"
),
.. ValidationUtils.ValidatePronouns(
.. validationService.ValidatePronouns(
req.Pronouns?.ToArray(),
CurrentUser!.CustomPreferences
),
@ -191,7 +191,7 @@ public class MembersController(
if (req.Names != null)
{
errors.AddRange(
ValidationUtils.ValidateFieldEntries(
validationService.ValidateFieldEntries(
req.Names,
CurrentUser!.CustomPreferences,
"names"
@ -203,7 +203,7 @@ public class MembersController(
if (req.Pronouns != null)
{
errors.AddRange(
ValidationUtils.ValidatePronouns(req.Pronouns, CurrentUser!.CustomPreferences)
validationService.ValidatePronouns(req.Pronouns, CurrentUser!.CustomPreferences)
);
member.Pronouns = req.Pronouns.ToList();
}
@ -211,7 +211,10 @@ public class MembersController(
if (req.Fields != null)
{
errors.AddRange(
ValidationUtils.ValidateFields(req.Fields.ToList(), CurrentUser!.CustomPreferences)
validationService.ValidateFields(
req.Fields.ToList(),
CurrentUser!.CustomPreferences
)
);
member.Fields = req.Fields.ToList();
}

View file

@ -91,7 +91,7 @@ public class UsersController(
if (req.Names != null)
{
errors.AddRange(
ValidationUtils.ValidateFieldEntries(
validationService.ValidateFieldEntries(
req.Names,
CurrentUser!.CustomPreferences,
"names"
@ -103,7 +103,7 @@ public class UsersController(
if (req.Pronouns != null)
{
errors.AddRange(
ValidationUtils.ValidatePronouns(req.Pronouns, CurrentUser!.CustomPreferences)
validationService.ValidatePronouns(req.Pronouns, CurrentUser!.CustomPreferences)
);
user.Pronouns = req.Pronouns.ToList();
}
@ -111,7 +111,10 @@ public class UsersController(
if (req.Fields != null)
{
errors.AddRange(
ValidationUtils.ValidateFields(req.Fields.ToList(), CurrentUser!.CustomPreferences)
validationService.ValidateFields(
req.Fields.ToList(),
CurrentUser!.CustomPreferences
)
);
user.Fields = req.Fields.ToList();
}

View file

@ -15,9 +15,9 @@
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
namespace Foxnouns.Backend.Utils;
namespace Foxnouns.Backend.Services;
public static partial class ValidationUtils
public partial class ValidationService
{
public static readonly string[] DefaultStatusOptions =
[
@ -28,7 +28,7 @@ public static partial class ValidationUtils
"avoid",
];
public static IEnumerable<(string, ValidationError?)> ValidateFields(
public IEnumerable<(string, ValidationError?)> ValidateFields(
List<Field>? fields,
IReadOnlyDictionary<Snowflake, User.CustomPreference> customPreferences
)
@ -37,7 +37,7 @@ public static partial class ValidationUtils
return [];
var errors = new List<(string, ValidationError?)>();
if (fields.Count > 25)
if (fields.Count > _limits.MaxFields)
{
errors.Add(
(
@ -45,7 +45,7 @@ public static partial class ValidationUtils
ValidationError.LengthError(
"Too many fields",
0,
Limits.FieldLimit,
_limits.MaxFields,
fields.Count
)
)
@ -53,39 +53,38 @@ public static partial class ValidationUtils
}
// No overwhelming this function, thank you
if (fields.Count > 100)
if (fields.Count > _limits.MaxFields + 50)
return errors;
foreach ((Field? field, int index) in fields.Select((field, index) => (field, index)))
{
switch (field.Name.Length)
if (field.Name.Length > _limits.MaxFieldNameLength)
{
case > Limits.FieldNameLimit:
errors.Add(
(
$"fields.{index}.name",
ValidationError.LengthError(
"Field name is too long",
1,
Limits.FieldNameLimit,
field.Name.Length
)
errors.Add(
(
$"fields.{index}.name",
ValidationError.LengthError(
"Field name is too long",
1,
_limits.MaxFieldNameLength,
field.Name.Length
)
);
break;
case < 1:
errors.Add(
(
$"fields.{index}.name",
ValidationError.LengthError(
"Field name is too short",
1,
Limits.FieldNameLimit,
field.Name.Length
)
)
);
}
else if (field.Name.Length < 1)
{
errors.Add(
(
$"fields.{index}.name",
ValidationError.LengthError(
"Field name is too short",
1,
_limits.MaxFieldNameLength,
field.Name.Length
)
);
break;
)
);
}
errors = errors
@ -102,7 +101,7 @@ public static partial class ValidationUtils
return errors;
}
public static IEnumerable<(string, ValidationError?)> ValidateFieldEntries(
public IEnumerable<(string, ValidationError?)> ValidateFieldEntries(
FieldEntry[]? entries,
IReadOnlyDictionary<Snowflake, User.CustomPreference> customPreferences,
string errorPrefix = "fields"
@ -112,7 +111,7 @@ public static partial class ValidationUtils
return [];
var errors = new List<(string, ValidationError?)>();
if (entries.Length > Limits.FieldEntriesLimit)
if (entries.Length > _limits.MaxFieldEntries)
{
errors.Add(
(
@ -120,7 +119,7 @@ public static partial class ValidationUtils
ValidationError.LengthError(
"Field has too many entries",
0,
Limits.FieldEntriesLimit,
_limits.MaxFieldEntries,
entries.Length
)
)
@ -128,7 +127,7 @@ public static partial class ValidationUtils
}
// Same as above, no overwhelming this function with a ridiculous amount of entries
if (entries.Length > Limits.FieldEntriesLimit + 50)
if (entries.Length > _limits.MaxFieldEntries + 50)
return errors;
string[] customPreferenceIds = customPreferences.Keys.Select(id => id.ToString()).ToArray();
@ -139,34 +138,33 @@ public static partial class ValidationUtils
)
)
{
switch (entry.Value.Length)
if (entry.Value.Length > _limits.MaxFieldEntryTextLength)
{
case > Limits.FieldEntryTextLimit:
errors.Add(
(
$"{errorPrefix}.{entryIdx}.value",
ValidationError.LengthError(
"Field value is too long",
1,
Limits.FieldEntryTextLimit,
entry.Value.Length
)
errors.Add(
(
$"{errorPrefix}.{entryIdx}.value",
ValidationError.LengthError(
"Field value is too long",
1,
_limits.MaxFieldEntryTextLength,
entry.Value.Length
)
);
break;
case < 1:
errors.Add(
(
$"{errorPrefix}.{entryIdx}.value",
ValidationError.LengthError(
"Field value is too short",
1,
Limits.FieldEntryTextLimit,
entry.Value.Length
)
)
);
}
else if (entry.Value.Length < 1)
{
errors.Add(
(
$"{errorPrefix}.{entryIdx}.value",
ValidationError.LengthError(
"Field value is too short",
1,
_limits.MaxFieldEntryTextLength,
entry.Value.Length
)
);
break;
)
);
}
if (
@ -186,7 +184,7 @@ public static partial class ValidationUtils
return errors;
}
public static IEnumerable<(string, ValidationError?)> ValidatePronouns(
public IEnumerable<(string, ValidationError?)> ValidatePronouns(
Pronoun[]? entries,
IReadOnlyDictionary<Snowflake, User.CustomPreference> customPreferences,
string errorPrefix = "pronouns"
@ -196,7 +194,7 @@ public static partial class ValidationUtils
return [];
var errors = new List<(string, ValidationError?)>();
if (entries.Length > Limits.FieldEntriesLimit)
if (entries.Length > _limits.MaxFieldEntries)
{
errors.Add(
(
@ -204,7 +202,7 @@ public static partial class ValidationUtils
ValidationError.LengthError(
"Too many pronouns",
0,
Limits.FieldEntriesLimit,
_limits.MaxFieldEntries,
entries.Length
)
)
@ -212,7 +210,7 @@ public static partial class ValidationUtils
}
// Same as above, no overwhelming this function with a ridiculous amount of entries
if (entries.Length > Limits.FieldEntriesLimit + 50)
if (entries.Length > _limits.MaxFieldEntries + 50)
return errors;
string[] customPreferenceIds = customPreferences.Keys.Select(id => id.ToString()).ToArray();
@ -221,66 +219,64 @@ public static partial class ValidationUtils
(Pronoun? entry, int entryIdx) in entries.Select((entry, entryIdx) => (entry, entryIdx))
)
{
switch (entry.Value.Length)
if (entry.Value.Length > _limits.MaxFieldEntryTextLength)
{
case > Limits.FieldEntryTextLimit:
errors.Add(
(
$"{errorPrefix}.{entryIdx}.value",
ValidationError.LengthError(
"Pronoun value is too long",
1,
Limits.FieldEntryTextLimit,
entry.Value.Length
)
errors.Add(
(
$"{errorPrefix}.{entryIdx}.value",
ValidationError.LengthError(
"Pronoun value is too long",
1,
_limits.MaxFieldEntryTextLength,
entry.Value.Length
)
);
break;
case < 1:
errors.Add(
(
$"{errorPrefix}.{entryIdx}.value",
ValidationError.LengthError(
"Pronoun value is too short",
1,
Limits.FieldEntryTextLimit,
entry.Value.Length
)
)
);
}
else if (entry.Value.Length < 1)
{
errors.Add(
(
$"{errorPrefix}.{entryIdx}.value",
ValidationError.LengthError(
"Pronoun value is too short",
1,
_limits.MaxFieldEntryTextLength,
entry.Value.Length
)
);
break;
)
);
}
if (entry.DisplayText != null)
{
switch (entry.DisplayText.Length)
if (entry.DisplayText.Length > _limits.MaxFieldEntryTextLength)
{
case > Limits.FieldEntryTextLimit:
errors.Add(
(
$"{errorPrefix}.{entryIdx}.display_text",
ValidationError.LengthError(
"Pronoun display text is too long",
1,
Limits.FieldEntryTextLimit,
entry.Value.Length
)
errors.Add(
(
$"{errorPrefix}.{entryIdx}.display_text",
ValidationError.LengthError(
"Pronoun display text is too long",
1,
_limits.MaxFieldEntryTextLength,
entry.Value.Length
)
);
break;
case < 1:
errors.Add(
(
$"{errorPrefix}.{entryIdx}.display_text",
ValidationError.LengthError(
"Pronoun display text is too short",
1,
Limits.FieldEntryTextLimit,
entry.Value.Length
)
)
);
}
else if (entry.DisplayText.Length < 1)
{
errors.Add(
(
$"{errorPrefix}.{entryIdx}.display_text",
ValidationError.LengthError(
"Pronoun display text is too short",
1,
_limits.MaxFieldEntryTextLength,
entry.Value.Length
)
);
break;
)
);
}
}

View file

@ -1,23 +0,0 @@
// Copyright (C) 2023-present sam/u1f320 (vulpine.solutions)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
namespace Foxnouns.Backend.Utils;
public static class Limits
{
public const int FieldLimit = 25;
public const int FieldNameLimit = 100;
public const int FieldEntryTextLimit = 100;
public const int FieldEntriesLimit = 100;
}

View file

@ -20,7 +20,7 @@ public static partial class ValidationUtils
public static ValidationError? ValidateReportContext(string? context) =>
context?.Length > MaximumReportContextLength
? ValidationError.GenericValidationError("Avatar is too large", null)
? ValidationError.GenericValidationError("Report context is too long", null)
: null;
public const int MinimumPasswordLength = 12;

View file

@ -2,7 +2,7 @@ using System.Diagnostics.CodeAnalysis;
using Dapper;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Utils;
using Foxnouns.Backend.Services;
using Foxnouns.DataMigrator.Models;
using NodaTime.Extensions;
using Npgsql;
@ -260,6 +260,6 @@ public class UserMigrator(
{
if (_preferenceIds.TryGetValue(id, out Snowflake preferenceId))
return preferenceId.ToString();
return ValidationUtils.DefaultStatusOptions.Contains(id) ? id : "okay";
return ValidationService.DefaultStatusOptions.Contains(id) ? id : "okay";
}
}