From dc9c11ec5209fe44de5212be58da4ea2c08b781c Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 27 Dec 2024 13:21:02 -0500 Subject: [PATCH 1/6] feat: return reports in audit log entries --- .../Moderation/AuditLogController.cs | 4 +++- Foxnouns.Backend/Dto/Moderation.cs | 13 +++++++++++- .../Services/ModerationRendererService.cs | 16 +++++++++++++- .../src/lib/api/models/moderation.ts | 12 ++++++++++- .../components/admin/AuditLogEntryCard.svelte | 21 ++++++++++++++++++- 5 files changed, 61 insertions(+), 5 deletions(-) diff --git a/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs b/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs index b2d0581..304cfa4 100644 --- a/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs +++ b/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs @@ -43,7 +43,9 @@ public class AuditLogController(DatabaseContext db, ModerationRendererService mo _ => limit, }; - IQueryable query = db.AuditLog.OrderByDescending(e => e.Id); + IQueryable query = db + .AuditLog.Include(e => e.Report) + .OrderByDescending(e => e.Id); if (before != null) query = query.Where(e => e.Id < before.Value); diff --git a/Foxnouns.Backend/Dto/Moderation.cs b/Foxnouns.Backend/Dto/Moderation.cs index c9489ed..7d6b5b8 100644 --- a/Foxnouns.Backend/Dto/Moderation.cs +++ b/Foxnouns.Backend/Dto/Moderation.cs @@ -41,12 +41,23 @@ public record AuditLogResponse( AuditLogEntity? TargetUser, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] AuditLogEntity? TargetMember, - [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] Snowflake? ReportId, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] PartialReport? Report, AuditLogEntryType Type, string? Reason, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] string[]? ClearedFields ); +public record PartialReport( + Snowflake Id, + Snowflake ReporterId, + Snowflake TargetUserId, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + Snowflake? TargetMemberId, + ReportReason Reason, + string? Context, + ReportTargetType TargetType +); + public record NotificationResponse( Snowflake Id, NotificationType Type, diff --git a/Foxnouns.Backend/Services/ModerationRendererService.cs b/Foxnouns.Backend/Services/ModerationRendererService.cs index 04ef46b..c1d259a 100644 --- a/Foxnouns.Backend/Services/ModerationRendererService.cs +++ b/Foxnouns.Backend/Services/ModerationRendererService.cs @@ -46,12 +46,26 @@ public class ModerationRendererService( public AuditLogResponse RenderAuditLogEntry(AuditLogEntry entry) { + PartialReport? report = null; + if (entry.Report != null) + { + report = new PartialReport( + entry.Report.Id, + entry.Report.ReporterId, + entry.Report.TargetUserId, + entry.Report.TargetMemberId, + entry.Report.Reason, + entry.Report.Context, + entry.Report.TargetType + ); + } + return new AuditLogResponse( Id: entry.Id, Moderator: ToEntity(entry.ModeratorId, entry.ModeratorUsername)!, TargetUser: ToEntity(entry.TargetUserId, entry.TargetUsername), TargetMember: ToEntity(entry.TargetMemberId, entry.TargetMemberName), - ReportId: entry.ReportId, + Report: report, Type: entry.Type, Reason: entry.Reason, ClearedFields: entry.ClearedFields diff --git a/Foxnouns.Frontend/src/lib/api/models/moderation.ts b/Foxnouns.Frontend/src/lib/api/models/moderation.ts index f0e112b..5fbf9fa 100644 --- a/Foxnouns.Frontend/src/lib/api/models/moderation.ts +++ b/Foxnouns.Frontend/src/lib/api/models/moderation.ts @@ -45,7 +45,7 @@ export type AuditLogEntry = { moderator: AuditLogEntity; target_user?: AuditLogEntity; target_member?: AuditLogEntity; - report_id?: string; + report?: PartialReport; type: AuditLogEntryType; reason: string | null; cleared_fields?: string[]; @@ -59,3 +59,13 @@ export enum AuditLogEntryType { WarnUserAndClearProfile = "WARN_USER_AND_CLEAR_PROFILE", SuspendUser = "SUSPEND_USER", } + +export type PartialReport = { + id: string; + reporter_id: string; + target_user_id: string; + target_member_id?: string; + reason: ReportReason; + context: string | null; + target_type: "USER" | "MEMBER"; +}; diff --git a/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte index 2391b57..45d1ada 100644 --- a/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte +++ b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte @@ -39,12 +39,31 @@ {date} + + {#if entry.type === "IGNORE_REPORT"} + {#if entry.report} +
+ Report +
    +
  • From: {entry.report.reporter_id}
  • +
  • Target: {entry.report.target_user_id}
  • +
  • Reason: {entry.report.reason}
  • + {#if entry.report.context} +
  • Context: {entry.report.context}
  • + {/if} +
+
+ {:else} +

(the ignored report has been deleted)

+ {/if} + {/if} + {#if reason}
Reason {@html reason}
{:else} - (no reason given) +

(no reason given)

{/if} From 8713279d3d925a11e21248b841e64566498dd14d Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 27 Dec 2024 13:34:54 -0500 Subject: [PATCH 2/6] raise member limit to 1000 --- Foxnouns.Backend/Controllers/MembersController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Foxnouns.Backend/Controllers/MembersController.cs b/Foxnouns.Backend/Controllers/MembersController.cs index c58af0e..09db30e 100644 --- a/Foxnouns.Backend/Controllers/MembersController.cs +++ b/Foxnouns.Backend/Controllers/MembersController.cs @@ -65,7 +65,7 @@ public class MembersController( return Ok(memberRenderer.RenderMember(member, CurrentToken)); } - public const int MaxMemberCount = 500; + public const int MaxMemberCount = 1000; [HttpPost("/api/v2/users/@me/members")] [ProducesResponseType(StatusCodes.Status200OK)] From 12eddb99490e2c6331f874c1393d36c7d66283c9 Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 27 Dec 2024 17:48:37 -0500 Subject: [PATCH 3/6] feat(backend): user lookup --- .../Moderation/LookupController.cs | 90 +++++++++++++++++++ .../Database/Models/AuditLogEntry.cs | 1 + Foxnouns.Backend/Dto/Moderation.cs | 16 ++++ .../Services/ModerationService.cs | 49 ++++++++++ 4 files changed, 156 insertions(+) create mode 100644 Foxnouns.Backend/Controllers/Moderation/LookupController.cs diff --git a/Foxnouns.Backend/Controllers/Moderation/LookupController.cs b/Foxnouns.Backend/Controllers/Moderation/LookupController.cs new file mode 100644 index 0000000..ba5018c --- /dev/null +++ b/Foxnouns.Backend/Controllers/Moderation/LookupController.cs @@ -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 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 QueryUserAsync(Snowflake id, CancellationToken ct = default) + { + User user = await db.ResolveUserAsync(id, ct); + + bool showSensitiveData = await moderationService.ShowSensitiveDataAsync( + CurrentUser!, + user, + ct + ); + + List 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 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)); + } +} diff --git a/Foxnouns.Backend/Database/Models/AuditLogEntry.cs b/Foxnouns.Backend/Database/Models/AuditLogEntry.cs index c65e675..84e1a43 100644 --- a/Foxnouns.Backend/Database/Models/AuditLogEntry.cs +++ b/Foxnouns.Backend/Database/Models/AuditLogEntry.cs @@ -41,4 +41,5 @@ public enum AuditLogEntryType WarnUser, WarnUserAndClearProfile, SuspendUser, + QuerySensitiveUserData, } diff --git a/Foxnouns.Backend/Dto/Moderation.cs b/Foxnouns.Backend/Dto/Moderation.cs index 7d6b5b8..266c275 100644 --- a/Foxnouns.Backend/Dto/Moderation.cs +++ b/Foxnouns.Backend/Dto/Moderation.cs @@ -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? AuthMethods +); + +public record QuerySensitiveUserDataRequest(string Reason); diff --git a/Foxnouns.Backend/Services/ModerationService.cs b/Foxnouns.Backend/Services/ModerationService.cs index 5444657..ff86a05 100644 --- a/Foxnouns.Backend/Services/ModerationService.cs +++ b/Foxnouns.Backend/Services/ModerationService.cs @@ -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 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 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 ExecuteSuspensionAsync( User moderator, User target, From 9d3d46bf332d0b6180a316472992ee579e18d83e Mon Sep 17 00:00:00 2001 From: sam Date: Fri, 27 Dec 2024 17:49:29 -0500 Subject: [PATCH 4/6] feat(frontend): show "query sensitive data" in audit log --- Foxnouns.Frontend/src/lib/api/models/moderation.ts | 1 + .../src/lib/components/admin/AuditLogEntryCard.svelte | 6 ++---- Foxnouns.Frontend/src/routes/admin/+page.svelte | 4 ++++ .../src/routes/admin/audit-log/+page.svelte | 10 ++++++++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Foxnouns.Frontend/src/lib/api/models/moderation.ts b/Foxnouns.Frontend/src/lib/api/models/moderation.ts index 5fbf9fa..47aa3c8 100644 --- a/Foxnouns.Frontend/src/lib/api/models/moderation.ts +++ b/Foxnouns.Frontend/src/lib/api/models/moderation.ts @@ -58,6 +58,7 @@ export enum AuditLogEntryType { WarnUser = "WARN_USER", WarnUserAndClearProfile = "WARN_USER_AND_CLEAR_PROFILE", SuspendUser = "SUSPEND_USER", + QuerySensitiveUserData = "QUERY_SENSITIVE_USER_DATA", } export type PartialReport = { diff --git a/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte index 45d1ada..02872d4 100644 --- a/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte +++ b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte @@ -12,10 +12,6 @@ let date = $derived(idTimestamp(entry.id).toLocaleString(DateTime.DATETIME_MED)); - - Audit log - -
@@ -26,6 +22,8 @@ warned {:else if entry.type === "SUSPEND_USER"} suspended + {:else if entry.type === "QUERY_SENSITIVE_USER_DATA"} + looked up sensitive data of {:else} (unknown action {entry.type}) {/if} diff --git a/Foxnouns.Frontend/src/routes/admin/+page.svelte b/Foxnouns.Frontend/src/routes/admin/+page.svelte index 79df014..bf0c8f7 100644 --- a/Foxnouns.Frontend/src/routes/admin/+page.svelte +++ b/Foxnouns.Frontend/src/routes/admin/+page.svelte @@ -6,6 +6,10 @@ let { data }: Props = $props(); + + Admin dashboard • pronouns.cc + +

Dashboard

diff --git a/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte b/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte index a0e182d..0c1cf72 100644 --- a/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte +++ b/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte @@ -45,6 +45,10 @@ }; + + Audit log • pronouns.cc + +

Audit log

@@ -68,6 +72,12 @@ Suspend user + + Query sensitive user data + {#if data.type} Remove filter {/if} From db22e35f0dd3ccea276c1875cd9300c3062af2af Mon Sep 17 00:00:00 2001 From: sam Date: Sat, 28 Dec 2024 11:39:22 -0500 Subject: [PATCH 5/6] feat(frontend): partial user lookup --- .../src/lib/api/models/moderation.ts | 12 +++- .../components/settings/AuthMethodRow.svelte | 7 ++- .../src/routes/admin/+layout.svelte | 7 +++ .../src/routes/admin/lookup/+page.server.ts | 27 ++++++++ .../src/routes/admin/lookup/+page.svelte | 33 ++++++++++ .../routes/admin/lookup/[id]/+page.server.ts | 11 ++++ .../src/routes/admin/lookup/[id]/+page.svelte | 63 +++++++++++++++++++ 7 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts create mode 100644 Foxnouns.Frontend/src/routes/admin/lookup/+page.svelte create mode 100644 Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.server.ts create mode 100644 Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.svelte diff --git a/Foxnouns.Frontend/src/lib/api/models/moderation.ts b/Foxnouns.Frontend/src/lib/api/models/moderation.ts index 47aa3c8..852358e 100644 --- a/Foxnouns.Frontend/src/lib/api/models/moderation.ts +++ b/Foxnouns.Frontend/src/lib/api/models/moderation.ts @@ -1,5 +1,5 @@ import type { Member } from "./member"; -import type { PartialMember, PartialUser, User } from "./user"; +import type { AuthMethod, PartialMember, PartialUser, User } from "./user"; export type CreateReportRequest = { reason: ReportReason; @@ -70,3 +70,13 @@ export type PartialReport = { context: string | null; target_type: "USER" | "MEMBER"; }; + +export type QueriedUser = { + user: User; + member_list_hidden: boolean; + last_active: string; + last_sid_reroll: string; + suspended: boolean; + deleted: boolean; + auth_methods?: AuthMethod[]; +}; diff --git a/Foxnouns.Frontend/src/lib/components/settings/AuthMethodRow.svelte b/Foxnouns.Frontend/src/lib/components/settings/AuthMethodRow.svelte index 692146a..f1c7964 100644 --- a/Foxnouns.Frontend/src/lib/components/settings/AuthMethodRow.svelte +++ b/Foxnouns.Frontend/src/lib/components/settings/AuthMethodRow.svelte @@ -2,8 +2,8 @@ import { t } from "$lib/i18n"; import type { AuthMethod } from "$api/models"; - type Props = { method: AuthMethod; canRemove: boolean }; - let { method, canRemove }: Props = $props(); + type Props = { method: AuthMethod; canRemove: boolean; showType?: boolean }; + let { method, canRemove, showType }: Props = $props(); let name = $derived( method.type === "EMAIL" ? method.remote_id : (method.remote_username ?? method.remote_id), @@ -14,6 +14,9 @@
+ {#if showType} + {method.type}: + {/if} {name} {#if showId}({method.remote_id}){/if}
diff --git a/Foxnouns.Frontend/src/routes/admin/+layout.svelte b/Foxnouns.Frontend/src/routes/admin/+layout.svelte index 0c0b247..c40279e 100644 --- a/Foxnouns.Frontend/src/routes/admin/+layout.svelte +++ b/Foxnouns.Frontend/src/routes/admin/+layout.svelte @@ -41,6 +41,13 @@ > Audit log + + Lookup +
diff --git a/Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts b/Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts new file mode 100644 index 0000000..90a284e --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts @@ -0,0 +1,27 @@ +import { apiRequest } from "$api"; +import { redirect } from "@sveltejs/kit"; + +export const actions = { + default: async ({ request, fetch, cookies }) => { + const body = await request.formData(); + const query = body.get("query") as string; + const fuzzy = body.get("fuzzy") === "yes"; + + const users = await apiRequest>( + "POST", + "/moderation/lookup", + { + fetch, + cookies, + body: { + query, + fuzzy, + }, + }, + ); + + if (!fuzzy && users.length > 0) redirect(303, `/admin/lookup/${users[0].id}`); + + return { users }; + }, +}; diff --git a/Foxnouns.Frontend/src/routes/admin/lookup/+page.svelte b/Foxnouns.Frontend/src/routes/admin/lookup/+page.svelte new file mode 100644 index 0000000..b3c8a8d --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/lookup/+page.svelte @@ -0,0 +1,33 @@ + + + + Look up a user • pronouns.cc + + +

Look up a user

+ +
+
+ + +
+
+ + +
+
+ +
+ {#each form?.users || [] as user (user.id)} + + {user.username} ({user.id}) + + {:else} +
No results
+ {/each} +
diff --git a/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.server.ts b/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.server.ts new file mode 100644 index 0000000..130ae90 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.server.ts @@ -0,0 +1,11 @@ +import { apiRequest } from "$api"; +import type { QueriedUser } from "$api/models/moderation"; + +export const load = async ({ params, fetch, cookies }) => { + const user = await apiRequest("GET", `/moderation/lookup/${params.id}`, { + fetch, + cookies, + }); + + return { user }; +}; diff --git a/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.svelte b/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.svelte new file mode 100644 index 0000000..e3cf389 --- /dev/null +++ b/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.svelte @@ -0,0 +1,63 @@ + + + + Looking up @{data.user.user.username} • pronouns.cc + + +

Basic profile

+ + + + + +

Extra information

+ + + + + + + + + + + + + + + + +
Created at{createdAt.toLocaleString(DateTime.DATETIME_MED)}
Last active{lastActive.toLocaleString(DateTime.DATETIME_MED)}
Last SID reroll{lastSidReroll.toLocaleString(DateTime.DATETIME_MED)}
+ +{#if authMethods} +

Authentication methods

+
+ {#each authMethods as method (method.id)} + + {/each} +
+{/if} From 8edbc8bf1daa6215305ca3ff010330c5156c37be Mon Sep 17 00:00:00 2001 From: sam Date: Sun, 29 Dec 2024 16:34:11 -0500 Subject: [PATCH 6/6] feat(backend): only one sensitive data request per 24 hours --- Foxnouns.Backend/Controllers/Moderation/LookupController.cs | 6 ++++++ Foxnouns.Backend/Dto/Moderation.cs | 1 + 2 files changed, 7 insertions(+) diff --git a/Foxnouns.Backend/Controllers/Moderation/LookupController.cs b/Foxnouns.Backend/Controllers/Moderation/LookupController.cs index ba5018c..9e9fa7f 100644 --- a/Foxnouns.Backend/Controllers/Moderation/LookupController.cs +++ b/Foxnouns.Backend/Controllers/Moderation/LookupController.cs @@ -64,6 +64,7 @@ public class LookupController( LastSidReroll: user.LastSidReroll, Suspended: user is { Deleted: true, DeletedBy: not null }, Deleted: user.Deleted, + ShowSensitiveData: showSensitiveData, AuthMethods: showSensitiveData ? authMethods.Select(UserRendererService.RenderAuthMethod) : null @@ -79,6 +80,11 @@ public class LookupController( { User user = await db.ResolveUserAsync(id); + // Don't let mods accidentally spam the audit log + bool alreadyAuthorized = await moderationService.ShowSensitiveDataAsync(CurrentUser!, user); + if (alreadyAuthorized) + return NoContent(); + AuditLogEntry entry = await moderationService.QuerySensitiveDataAsync( CurrentUser!, user, diff --git a/Foxnouns.Backend/Dto/Moderation.cs b/Foxnouns.Backend/Dto/Moderation.cs index 266c275..58a38a6 100644 --- a/Foxnouns.Backend/Dto/Moderation.cs +++ b/Foxnouns.Backend/Dto/Moderation.cs @@ -105,6 +105,7 @@ public record QueryUserResponse( Instant LastSidReroll, bool Suspended, bool Deleted, + bool ShowSensitiveData, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] IEnumerable? AuthMethods );