diff --git a/Foxnouns.Backend/Controllers/MembersController.cs b/Foxnouns.Backend/Controllers/MembersController.cs index 09db30e..c58af0e 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 = 1000; + public const int MaxMemberCount = 500; [HttpPost("/api/v2/users/@me/members")] [ProducesResponseType(StatusCodes.Status200OK)] diff --git a/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs b/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs index 304cfa4..b2d0581 100644 --- a/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs +++ b/Foxnouns.Backend/Controllers/Moderation/AuditLogController.cs @@ -43,9 +43,7 @@ public class AuditLogController(DatabaseContext db, ModerationRendererService mo _ => limit, }; - IQueryable query = db - .AuditLog.Include(e => e.Report) - .OrderByDescending(e => e.Id); + IQueryable query = db.AuditLog.OrderByDescending(e => e.Id); if (before != null) query = query.Where(e => e.Id < before.Value); diff --git a/Foxnouns.Backend/Controllers/Moderation/LookupController.cs b/Foxnouns.Backend/Controllers/Moderation/LookupController.cs deleted file mode 100644 index 9e9fa7f..0000000 --- a/Foxnouns.Backend/Controllers/Moderation/LookupController.cs +++ /dev/null @@ -1,96 +0,0 @@ -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, - ShowSensitiveData: showSensitiveData, - 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); - - // 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, - req.Reason - ); - - return Ok(moderationRenderer.RenderAuditLogEntry(entry)); - } -} diff --git a/Foxnouns.Backend/Database/Models/AuditLogEntry.cs b/Foxnouns.Backend/Database/Models/AuditLogEntry.cs index 84e1a43..c65e675 100644 --- a/Foxnouns.Backend/Database/Models/AuditLogEntry.cs +++ b/Foxnouns.Backend/Database/Models/AuditLogEntry.cs @@ -41,5 +41,4 @@ public enum AuditLogEntryType WarnUser, WarnUserAndClearProfile, SuspendUser, - QuerySensitiveUserData, } diff --git a/Foxnouns.Backend/Dto/Moderation.cs b/Foxnouns.Backend/Dto/Moderation.cs index 58a38a6..c9489ed 100644 --- a/Foxnouns.Backend/Dto/Moderation.cs +++ b/Foxnouns.Backend/Dto/Moderation.cs @@ -18,7 +18,6 @@ using Foxnouns.Backend.Database; using Foxnouns.Backend.Database.Models; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using NodaTime; namespace Foxnouns.Backend.Dto; @@ -42,23 +41,12 @@ public record AuditLogResponse( AuditLogEntity? TargetUser, [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] AuditLogEntity? TargetMember, - [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] PartialReport? Report, + [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] Snowflake? ReportId, 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, @@ -95,19 +83,3 @@ 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, - bool ShowSensitiveData, - [property: JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - IEnumerable? AuthMethods -); - -public record QuerySensitiveUserDataRequest(string Reason); diff --git a/Foxnouns.Backend/Services/ModerationRendererService.cs b/Foxnouns.Backend/Services/ModerationRendererService.cs index c1d259a..04ef46b 100644 --- a/Foxnouns.Backend/Services/ModerationRendererService.cs +++ b/Foxnouns.Backend/Services/ModerationRendererService.cs @@ -46,26 +46,12 @@ 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), - Report: report, + ReportId: entry.ReportId, Type: entry.Type, Reason: entry.Reason, ClearedFields: entry.ClearedFields diff --git a/Foxnouns.Backend/Services/ModerationService.cs b/Foxnouns.Backend/Services/ModerationService.cs index ff86a05..5444657 100644 --- a/Foxnouns.Backend/Services/ModerationService.cs +++ b/Foxnouns.Backend/Services/ModerationService.cs @@ -18,7 +18,6 @@ using Foxnouns.Backend.Database.Models; using Foxnouns.Backend.Dto; using Foxnouns.Backend.Jobs; using Humanizer; -using Microsoft.EntityFrameworkCore; using NodaTime; namespace Foxnouns.Backend.Services; @@ -64,54 +63,6 @@ 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, diff --git a/Foxnouns.Frontend/src/lib/api/models/moderation.ts b/Foxnouns.Frontend/src/lib/api/models/moderation.ts index 852358e..f0e112b 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 { AuthMethod, PartialMember, PartialUser, User } from "./user"; +import type { PartialMember, PartialUser, User } from "./user"; export type CreateReportRequest = { reason: ReportReason; @@ -45,7 +45,7 @@ export type AuditLogEntry = { moderator: AuditLogEntity; target_user?: AuditLogEntity; target_member?: AuditLogEntity; - report?: PartialReport; + report_id?: string; type: AuditLogEntryType; reason: string | null; cleared_fields?: string[]; @@ -58,25 +58,4 @@ export enum AuditLogEntryType { WarnUser = "WARN_USER", WarnUserAndClearProfile = "WARN_USER_AND_CLEAR_PROFILE", SuspendUser = "SUSPEND_USER", - QuerySensitiveUserData = "QUERY_SENSITIVE_USER_DATA", } - -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"; -}; - -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/admin/AuditLogEntryCard.svelte b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte index 02872d4..2391b57 100644 --- a/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte +++ b/Foxnouns.Frontend/src/lib/components/admin/AuditLogEntryCard.svelte @@ -12,6 +12,10 @@ let date = $derived(idTimestamp(entry.id).toLocaleString(DateTime.DATETIME_MED)); + + Audit log + +
@@ -22,8 +26,6 @@ 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} @@ -37,31 +39,12 @@ {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}
diff --git a/Foxnouns.Frontend/src/lib/components/settings/AuthMethodRow.svelte b/Foxnouns.Frontend/src/lib/components/settings/AuthMethodRow.svelte index f1c7964..692146a 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; showType?: boolean }; - let { method, canRemove, showType }: Props = $props(); + type Props = { method: AuthMethod; canRemove: boolean }; + let { method, canRemove }: Props = $props(); let name = $derived( method.type === "EMAIL" ? method.remote_id : (method.remote_username ?? method.remote_id), @@ -14,9 +14,6 @@
- {#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 c40279e..0c0b247 100644 --- a/Foxnouns.Frontend/src/routes/admin/+layout.svelte +++ b/Foxnouns.Frontend/src/routes/admin/+layout.svelte @@ -41,13 +41,6 @@ > Audit log - - Lookup -
diff --git a/Foxnouns.Frontend/src/routes/admin/+page.svelte b/Foxnouns.Frontend/src/routes/admin/+page.svelte index bf0c8f7..79df014 100644 --- a/Foxnouns.Frontend/src/routes/admin/+page.svelte +++ b/Foxnouns.Frontend/src/routes/admin/+page.svelte @@ -6,10 +6,6 @@ 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 0c1cf72..a0e182d 100644 --- a/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte +++ b/Foxnouns.Frontend/src/routes/admin/audit-log/+page.svelte @@ -45,10 +45,6 @@ }; - - Audit log • pronouns.cc - -

Audit log

@@ -72,12 +68,6 @@ Suspend user - - Query sensitive user data - {#if data.type} Remove filter {/if} diff --git a/Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts b/Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts deleted file mode 100644 index 90a284e..0000000 --- a/Foxnouns.Frontend/src/routes/admin/lookup/+page.server.ts +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index b3c8a8d..0000000 --- a/Foxnouns.Frontend/src/routes/admin/lookup/+page.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - - - 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 deleted file mode 100644 index 130ae90..0000000 --- a/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.server.ts +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index e3cf389..0000000 --- a/Foxnouns.Frontend/src/routes/admin/lookup/[id]/+page.svelte +++ /dev/null @@ -1,63 +0,0 @@ - - - - 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}