feat(backend): user lookup
This commit is contained in:
parent
8713279d3d
commit
12eddb9949
4 changed files with 156 additions and 0 deletions
90
Foxnouns.Backend/Controllers/Moderation/LookupController.cs
Normal file
90
Foxnouns.Backend/Controllers/Moderation/LookupController.cs
Normal 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));
|
||||
}
|
||||
}
|
|
@ -41,4 +41,5 @@ public enum AuditLogEntryType
|
|||
WarnUser,
|
||||
WarnUserAndClearProfile,
|
||||
SuspendUser,
|
||||
QuerySensitiveUserData,
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue