feat: make some limits configurable
This commit is contained in:
parent
74800b46ef
commit
373d97e70a
11 changed files with 312 additions and 218 deletions
|
@ -29,7 +29,8 @@ public class AuthService(
|
|||
ILogger logger,
|
||||
DatabaseContext db,
|
||||
ISnowflakeGenerator snowflakeGenerator,
|
||||
UserRendererService userRenderer
|
||||
UserRendererService userRenderer,
|
||||
ValidationService validationService
|
||||
)
|
||||
{
|
||||
private readonly ILogger _logger = logger.ForContext<AuthService>();
|
||||
|
@ -49,7 +50,7 @@ public class AuthService(
|
|||
// Validate username and whether it's not taken
|
||||
ValidationUtils.Validate(
|
||||
[
|
||||
("username", ValidationUtils.ValidateUsername(username)),
|
||||
("username", validationService.ValidateUsername(username)),
|
||||
("password", ValidationUtils.ValidatePassword(password)),
|
||||
]
|
||||
);
|
||||
|
@ -97,7 +98,7 @@ public class AuthService(
|
|||
AssertValidAuthType(authType, instance);
|
||||
|
||||
// Validate username and whether it's not taken
|
||||
ValidationUtils.Validate([("username", ValidationUtils.ValidateUsername(username))]);
|
||||
ValidationUtils.Validate([("username", validationService.ValidateUsername(username))]);
|
||||
if (await db.Users.AnyAsync(u => u.Username == username, ct))
|
||||
throw new ApiError.BadRequest("Username is already taken", "username", username);
|
||||
|
||||
|
|
256
Foxnouns.Backend/Services/ValidationService.Strings.cs
Normal file
256
Foxnouns.Backend/Services/ValidationService.Strings.cs
Normal file
|
@ -0,0 +1,256 @@
|
|||
// 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/>.
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Foxnouns.Backend.Services;
|
||||
|
||||
public partial class ValidationService
|
||||
{
|
||||
private static readonly string[] InvalidUsernames =
|
||||
[
|
||||
"..",
|
||||
"admin",
|
||||
"administrator",
|
||||
"mod",
|
||||
"moderator",
|
||||
"api",
|
||||
"page",
|
||||
"pronouns",
|
||||
"settings",
|
||||
"pronouns.cc",
|
||||
"pronounscc",
|
||||
];
|
||||
|
||||
private static readonly string[] InvalidMemberNames =
|
||||
[
|
||||
// these break routing outright
|
||||
".",
|
||||
"..",
|
||||
// the user edit page lives at `/@{username}/edit`, so a member named "edit" would be inaccessible
|
||||
"edit",
|
||||
];
|
||||
|
||||
public ValidationError? ValidateUsername(string username)
|
||||
{
|
||||
if (!UsernameRegex().IsMatch(username))
|
||||
{
|
||||
if (username.Length < 2)
|
||||
{
|
||||
return ValidationError.LengthError(
|
||||
"Username is too short",
|
||||
2,
|
||||
_limits.MaxUsernameLength,
|
||||
username.Length
|
||||
);
|
||||
}
|
||||
|
||||
if (username.Length > _limits.MaxUsernameLength)
|
||||
{
|
||||
return ValidationError.LengthError(
|
||||
"Username is too long",
|
||||
2,
|
||||
_limits.MaxUsernameLength,
|
||||
username.Length
|
||||
);
|
||||
}
|
||||
|
||||
return ValidationError.GenericValidationError(
|
||||
"Username is invalid, can only contain alphanumeric characters, dashes, underscores, and periods",
|
||||
username
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
InvalidUsernames.Any(u =>
|
||||
string.Equals(u, username, StringComparison.InvariantCultureIgnoreCase)
|
||||
)
|
||||
)
|
||||
{
|
||||
return ValidationError.GenericValidationError("Username is not allowed", username);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ValidationError? ValidateMemberName(string memberName)
|
||||
{
|
||||
if (!MemberRegex().IsMatch(memberName))
|
||||
{
|
||||
if (memberName.Length < 1)
|
||||
{
|
||||
return ValidationError.LengthError(
|
||||
"Name is too short",
|
||||
1,
|
||||
_limits.MaxMemberNameLength,
|
||||
memberName.Length
|
||||
);
|
||||
}
|
||||
|
||||
if (memberName.Length > _limits.MaxMemberNameLength)
|
||||
{
|
||||
return ValidationError.LengthError(
|
||||
"Name is too long",
|
||||
1,
|
||||
_limits.MaxMemberNameLength,
|
||||
memberName.Length
|
||||
);
|
||||
}
|
||||
|
||||
return ValidationError.GenericValidationError(
|
||||
"Member name cannot contain any of the following: "
|
||||
+ " @, ?, !, #, /, \\, [, ], \", ', $, %, &, (, ), {, }, +, <, =, >, ^, |, ~, `, , "
|
||||
+ "and cannot be one or two periods",
|
||||
memberName
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
InvalidMemberNames.Any(u =>
|
||||
string.Equals(u, memberName, StringComparison.InvariantCultureIgnoreCase)
|
||||
)
|
||||
)
|
||||
{
|
||||
return ValidationError.GenericValidationError("Name is not allowed", memberName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ValidationError? ValidateDisplayName(string? displayName)
|
||||
{
|
||||
if (displayName?.Length == 0)
|
||||
{
|
||||
return ValidationError.LengthError(
|
||||
"Display name is too short",
|
||||
1,
|
||||
_limits.MaxDisplayNameLength,
|
||||
displayName.Length
|
||||
);
|
||||
}
|
||||
|
||||
if (displayName?.Length > _limits.MaxDisplayNameLength)
|
||||
{
|
||||
return ValidationError.LengthError(
|
||||
"Display name is too long",
|
||||
1,
|
||||
_limits.MaxDisplayNameLength,
|
||||
displayName.Length
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IEnumerable<(string, ValidationError?)> ValidateLinks(string[]? links)
|
||||
{
|
||||
if (links == null)
|
||||
return [];
|
||||
if (links.Length > _limits.MaxLinks)
|
||||
{
|
||||
return
|
||||
[
|
||||
(
|
||||
"links",
|
||||
ValidationError.LengthError("Too many links", 0, _limits.MaxLinks, links.Length)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
var errors = new List<(string, ValidationError?)>();
|
||||
foreach ((string link, int idx) in links.Select((l, i) => (l, i)))
|
||||
{
|
||||
if (link.Length == 0)
|
||||
{
|
||||
errors.Add(
|
||||
(
|
||||
$"links.{idx}",
|
||||
ValidationError.LengthError(
|
||||
"Link cannot be empty",
|
||||
1,
|
||||
_limits.MaxLinkLength,
|
||||
0
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
else if (link.Length > _limits.MaxLinkLength)
|
||||
{
|
||||
errors.Add(
|
||||
(
|
||||
$"links.{idx}",
|
||||
ValidationError.LengthError(
|
||||
"Link is too long",
|
||||
1,
|
||||
_limits.MaxLinkLength,
|
||||
link.Length
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
public ValidationError? ValidateBio(string? bio)
|
||||
{
|
||||
if (bio?.Length == 0)
|
||||
{
|
||||
return ValidationError.LengthError(
|
||||
"Bio is too short",
|
||||
1,
|
||||
_limits.MaxBioLength,
|
||||
bio.Length
|
||||
);
|
||||
}
|
||||
|
||||
if (bio?.Length > _limits.MaxBioLength)
|
||||
{
|
||||
return ValidationError.LengthError(
|
||||
"Bio is too long",
|
||||
1,
|
||||
_limits.MaxBioLength,
|
||||
bio.Length
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ValidationError? ValidateAvatar(string? avatar)
|
||||
{
|
||||
if (avatar?.Length == 0)
|
||||
{
|
||||
return ValidationError.GenericValidationError("Avatar cannot be empty", null);
|
||||
}
|
||||
|
||||
if (avatar?.Length > _limits.MaxAvatarLength)
|
||||
{
|
||||
return ValidationError.GenericValidationError("Avatar is too large", null);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^[a-zA-Z_0-9\-\.]{2,40}$", RegexOptions.IgnoreCase, "en-US")]
|
||||
private static partial Regex UsernameRegex();
|
||||
|
||||
[GeneratedRegex(
|
||||
"""^[^@'$%&()+<=>^|~`,*!#/\\\[\]""\{\}\?]{1,100}$""",
|
||||
RegexOptions.IgnoreCase,
|
||||
"en-US"
|
||||
)]
|
||||
private static partial Regex MemberRegex();
|
||||
}
|
6
Foxnouns.Backend/Services/ValidationService.cs
Normal file
6
Foxnouns.Backend/Services/ValidationService.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Foxnouns.Backend.Services;
|
||||
|
||||
public partial class ValidationService(Config config)
|
||||
{
|
||||
private readonly Config.LimitsConfig _limits = config.Limits;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue