feat(backend): validate links, allow setting links in POST /users/@me/members

This commit is contained in:
sam 2024-09-27 15:29:33 +02:00
parent a3cbdc1a08
commit 8fe8755183
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
5 changed files with 37 additions and 5 deletions

View file

@ -54,7 +54,8 @@ public class MembersController(
("avatar", ValidationUtils.ValidateAvatar(req.Avatar)), ("avatar", ValidationUtils.ValidateAvatar(req.Avatar)),
.. ValidationUtils.ValidateFields(req.Fields, CurrentUser!.CustomPreferences), .. ValidationUtils.ValidateFields(req.Fields, CurrentUser!.CustomPreferences),
.. ValidationUtils.ValidateFieldEntries(req.Names?.ToArray(), CurrentUser!.CustomPreferences, "names"), .. ValidationUtils.ValidateFieldEntries(req.Names?.ToArray(), CurrentUser!.CustomPreferences, "names"),
.. ValidationUtils.ValidatePronouns(req.Pronouns?.ToArray(), CurrentUser!.CustomPreferences) .. ValidationUtils.ValidatePronouns(req.Pronouns?.ToArray(), CurrentUser!.CustomPreferences),
.. ValidationUtils.ValidateLinks(req.Links)
]); ]);
var member = new Member var member = new Member
@ -64,6 +65,7 @@ public class MembersController(
Name = req.Name, Name = req.Name,
DisplayName = req.DisplayName, DisplayName = req.DisplayName,
Bio = req.Bio, Bio = req.Bio,
Links = req.Links ?? [],
Fields = req.Fields ?? [], Fields = req.Fields ?? [],
Names = req.Names ?? [], Names = req.Names ?? [],
Pronouns = req.Pronouns ?? [], Pronouns = req.Pronouns ?? [],
@ -113,6 +115,7 @@ public class MembersController(
string? Bio, string? Bio,
string? Avatar, string? Avatar,
bool? Unlisted, bool? Unlisted,
string[]? Links,
List<FieldEntry>? Names, List<FieldEntry>? Names,
List<Pronoun>? Pronouns, List<Pronoun>? Pronouns,
List<Field>? Fields); List<Field>? Fields);

View file

@ -64,7 +64,7 @@ public class UsersController(
if (req.HasProperty(nameof(req.Links))) if (req.HasProperty(nameof(req.Links)))
{ {
// TODO: validate link length errors.AddRange(ValidationUtils.ValidateLinks(req.Links));
user.Links = req.Links ?? []; user.Links = req.Links ?? [];
} }
@ -238,7 +238,7 @@ public class UsersController(
.SetProperty(u => u.Sid, _ => db.FindFreeUserSid()) .SetProperty(u => u.Sid, _ => db.FindFreeUserSid())
.SetProperty(u => u.LastSidReroll, clock.GetCurrentInstant()) .SetProperty(u => u.LastSidReroll, clock.GetCurrentInstant())
.SetProperty(u => u.LastActive, clock.GetCurrentInstant())); .SetProperty(u => u.LastActive, clock.GetCurrentInstant()));
var user = await db.ResolveUserAsync(CurrentUser.Id); var user = await db.ResolveUserAsync(CurrentUser.Id);
return Ok(await userRenderer.RenderUserAsync(user, CurrentUser, CurrentToken, renderMembers: false)); return Ok(await userRenderer.RenderUserAsync(user, CurrentUser, CurrentToken, renderMembers: false));
} }

View file

@ -21,7 +21,7 @@ public class DatabaseContext : DbContext
public DbSet<Token> Tokens { get; set; } public DbSet<Token> Tokens { get; set; }
public DbSet<Application> Applications { get; set; } public DbSet<Application> Applications { get; set; }
public DbSet<TemporaryKey> TemporaryKeys { get; set; } public DbSet<TemporaryKey> TemporaryKeys { get; set; }
public DbSet<PrideFlag> PrideFlags { get; set; } public DbSet<PrideFlag> PrideFlags { get; set; }
public DbSet<UserFlag> UserFlags { get; set; } public DbSet<UserFlag> UserFlags { get; set; }
public DbSet<MemberFlag> MemberFlags { get; set; } public DbSet<MemberFlag> MemberFlags { get; set; }

View file

@ -37,7 +37,8 @@ public class MemberRendererService(DatabaseContext db, Config config)
private UserRendererService.PartialUser RenderPartialUser(User user) => private UserRendererService.PartialUser RenderPartialUser(User user) =>
new(user.Id, user.Sid, user.Username, user.DisplayName, AvatarUrlFor(user), user.CustomPreferences); new(user.Id, user.Sid, user.Username, user.DisplayName, AvatarUrlFor(user), user.CustomPreferences);
public PartialMember RenderPartialMember(Member member, bool renderUnlisted = false) => new(member.Id, member.Sid, member.Name, public PartialMember RenderPartialMember(Member member, bool renderUnlisted = false) => new(member.Id, member.Sid,
member.Name,
member.DisplayName, member.Bio, AvatarUrlFor(member), member.Names, member.Pronouns, member.DisplayName, member.Bio, AvatarUrlFor(member), member.Names, member.Pronouns,
renderUnlisted ? member.Unlisted : null); renderUnlisted ? member.Unlisted : null);

View file

@ -99,6 +99,34 @@ public static class ValidationUtils
}; };
} }
private const int MaxLinks = 25;
private const int MaxLinkLength = 256;
public static IEnumerable<(string, ValidationError?)> ValidateLinks(string[]? links)
{
if (links == null) return [];
if (links.Length > MaxLinks)
return [("links", ValidationError.LengthError("Too many links", 0, MaxLinks, links.Length))];
var errors = new List<(string, ValidationError?)>();
foreach (var (link, idx) in links.Select((l, i) => (l, i)))
{
switch (link.Length)
{
case 0:
errors.Add(($"links.{idx}",
ValidationError.LengthError("Link cannot be empty", 1, 256, 0)));
break;
case > MaxLinkLength:
errors.Add(($"links.{idx}",
ValidationError.LengthError("Link is too long", 1, MaxLinkLength, link.Length)));
break;
}
}
return errors;
}
public static ValidationError? ValidateBio(string? bio) public static ValidationError? ValidateBio(string? bio)
{ {
return bio?.Length switch return bio?.Length switch