feat(backend): user lookup

This commit is contained in:
sam 2024-12-27 17:48:37 -05:00
parent 8713279d3d
commit 12eddb9949
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
4 changed files with 156 additions and 0 deletions

View file

@ -0,0 +1,90 @@
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Middleware;
using Foxnouns.Backend.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Foxnouns.Backend.Controllers.Moderation;
[Route("/api/v2/moderation/lookup")]
[Authorize("user.moderation")]
[Limit(RequireModerator = true)]
public class LookupController(
DatabaseContext db,
UserRendererService userRenderer,
ModerationService moderationService,
ModerationRendererService moderationRenderer
) : ApiControllerBase
{
[HttpPost]
public async Task<IActionResult> QueryUsersAsync(
[FromBody] QueryUsersRequest req,
CancellationToken ct = default
)
{
var query = db.Users.Select(u => new { u.Id, u.Username });
query = req.Fuzzy
? query.Where(u => u.Username.Contains(req.Query))
: query.Where(u => u.Username == req.Query);
var users = await query.OrderBy(u => u.Id).Take(100).ToListAsync(ct);
return Ok(users);
}
[HttpGet("{id}")]
public async Task<IActionResult> QueryUserAsync(Snowflake id, CancellationToken ct = default)
{
User user = await db.ResolveUserAsync(id, ct);
bool showSensitiveData = await moderationService.ShowSensitiveDataAsync(
CurrentUser!,
user,
ct
);
List<AuthMethod> authMethods = showSensitiveData
? await db
.AuthMethods.Where(a => a.UserId == user.Id)
.Include(a => a.FediverseApplication)
.ToListAsync(ct)
: [];
return Ok(
new QueryUserResponse(
User: await userRenderer.RenderUserAsync(
user,
renderMembers: false,
renderAuthMethods: false,
ct: ct
),
MemberListHidden: user.ListHidden,
LastActive: user.LastActive,
LastSidReroll: user.LastSidReroll,
Suspended: user is { Deleted: true, DeletedBy: not null },
Deleted: user.Deleted,
AuthMethods: showSensitiveData
? authMethods.Select(UserRendererService.RenderAuthMethod)
: null
)
);
}
[HttpPost("{id}/sensitive")]
public async Task<IActionResult> QuerySensitiveUserDataAsync(
Snowflake id,
[FromBody] QuerySensitiveUserDataRequest req
)
{
User user = await db.ResolveUserAsync(id);
AuditLogEntry entry = await moderationService.QuerySensitiveDataAsync(
CurrentUser!,
user,
req.Reason
);
return Ok(moderationRenderer.RenderAuditLogEntry(entry));
}
}

View file

@ -41,4 +41,5 @@ public enum AuditLogEntryType
WarnUser,
WarnUserAndClearProfile,
SuspendUser,
QuerySensitiveUserData,
}

View file

@ -18,6 +18,7 @@ using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NodaTime;
namespace Foxnouns.Backend.Dto;
@ -94,3 +95,18 @@ public enum FieldsToClear
Flags,
CustomPreferences,
}
public record QueryUsersRequest(string Query, bool Fuzzy);
public record QueryUserResponse(
UserResponse User,
bool MemberListHidden,
Instant LastActive,
Instant LastSidReroll,
bool Suspended,
bool Deleted,
[property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
IEnumerable<AuthMethodResponse>? AuthMethods
);
public record QuerySensitiveUserDataRequest(string Reason);

View file

@ -18,6 +18,7 @@ using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Dto;
using Foxnouns.Backend.Jobs;
using Humanizer;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace Foxnouns.Backend.Services;
@ -63,6 +64,54 @@ public class ModerationService(
return entry;
}
public async Task<AuditLogEntry> QuerySensitiveDataAsync(
User moderator,
User target,
string reason
)
{
_logger.Information(
"Moderator {ModeratorId} is querying sensitive data for {TargetId}",
moderator.Id,
target.Id
);
var entry = new AuditLogEntry
{
Id = snowflakeGenerator.GenerateSnowflake(),
ModeratorId = moderator.Id,
ModeratorUsername = moderator.Username,
TargetUserId = target.Id,
TargetUsername = target.Username,
Type = AuditLogEntryType.QuerySensitiveUserData,
Reason = reason,
};
db.AuditLog.Add(entry);
await db.SaveChangesAsync();
return entry;
}
public async Task<bool> ShowSensitiveDataAsync(
User moderator,
User target,
CancellationToken ct = default
)
{
Snowflake cutoff = snowflakeGenerator.GenerateSnowflake(
clock.GetCurrentInstant() - Duration.FromDays(1)
);
return await db.AuditLog.AnyAsync(
e =>
e.ModeratorId == moderator.Id
&& e.TargetUserId == target.Id
&& e.Type == AuditLogEntryType.QuerySensitiveUserData
&& e.Id > cutoff,
ct
);
}
public async Task<AuditLogEntry> ExecuteSuspensionAsync(
User moderator,
User target,