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, |     WarnUser, | ||||||
|     WarnUserAndClearProfile, |     WarnUserAndClearProfile, | ||||||
|     SuspendUser, |     SuspendUser, | ||||||
|  |     QuerySensitiveUserData, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ using Foxnouns.Backend.Database; | ||||||
| using Foxnouns.Backend.Database.Models; | using Foxnouns.Backend.Database.Models; | ||||||
| using Newtonsoft.Json; | using Newtonsoft.Json; | ||||||
| using Newtonsoft.Json.Linq; | using Newtonsoft.Json.Linq; | ||||||
|  | using NodaTime; | ||||||
| 
 | 
 | ||||||
| namespace Foxnouns.Backend.Dto; | namespace Foxnouns.Backend.Dto; | ||||||
| 
 | 
 | ||||||
|  | @ -94,3 +95,18 @@ public enum FieldsToClear | ||||||
|     Flags, |     Flags, | ||||||
|     CustomPreferences, |     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.Dto; | ||||||
| using Foxnouns.Backend.Jobs; | using Foxnouns.Backend.Jobs; | ||||||
| using Humanizer; | using Humanizer; | ||||||
|  | using Microsoft.EntityFrameworkCore; | ||||||
| using NodaTime; | using NodaTime; | ||||||
| 
 | 
 | ||||||
| namespace Foxnouns.Backend.Services; | namespace Foxnouns.Backend.Services; | ||||||
|  | @ -63,6 +64,54 @@ public class ModerationService( | ||||||
|         return entry; |         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( |     public async Task<AuditLogEntry> ExecuteSuspensionAsync( | ||||||
|         User moderator, |         User moderator, | ||||||
|         User target, |         User target, | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue