Compare commits
	
		
			2 Commits
		
	
	
		
			026c405cd4
			...
			3a978441b6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3a978441b6 | |||
| a8503735d1 | 
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization;
 | 
				
			|||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
using NodaTime;
 | 
					using NodaTime;
 | 
				
			||||||
 | 
					using Org.BouncyCastle.Utilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace DysonNetwork.Sphere.Account;
 | 
					namespace DysonNetwork.Sphere.Account;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -421,6 +422,43 @@ public class AccountCurrentController(
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public class AuthorizedDevice
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        public string? Label { get; set; }
 | 
				
			||||||
 | 
					        public string UserAgent { get; set; } = null!;
 | 
				
			||||||
 | 
					        public string DeviceId { get; set; } = null!;
 | 
				
			||||||
 | 
					        public List<Session> Sessions { get; set; } = new();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    [HttpGet("devices")]
 | 
				
			||||||
 | 
					    [Authorize]
 | 
				
			||||||
 | 
					    public async Task<ActionResult<List<AuthorizedDevice>>> GetDevices()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (HttpContext.Items["CurrentUser"] is not Account currentUser)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return Unauthorized();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Group sessions by the related DeviceId, then create an AuthorizedDevice for each group.
 | 
				
			||||||
 | 
					        var deviceGroups = await db.AuthSessions
 | 
				
			||||||
 | 
					            .Where(s => s.Account.Id == currentUser.Id)
 | 
				
			||||||
 | 
					            // Include the challenge if you need it to access DeviceId
 | 
				
			||||||
 | 
					            .Include(s => s.Challenge)
 | 
				
			||||||
 | 
					            .GroupBy(s => s.Challenge.DeviceId!)
 | 
				
			||||||
 | 
					            .Select(g => new AuthorizedDevice
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                DeviceId = g.Key!,
 | 
				
			||||||
 | 
					                UserAgent = g.First(x => x.Challenge.UserAgent != null).Challenge.UserAgent!,
 | 
				
			||||||
 | 
					                Label = g.Where(x => string.IsNullOrWhiteSpace(x.Label)).Select(x => x.Label).FirstOrDefault(),
 | 
				
			||||||
 | 
					                Sessions = g
 | 
				
			||||||
 | 
					                    .OrderByDescending(x => x.LastGrantedAt)
 | 
				
			||||||
 | 
					                    .ToList()
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .ToListAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Ok(deviceGroups);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    [HttpGet("sessions")]
 | 
					    [HttpGet("sessions")]
 | 
				
			||||||
    [Authorize]
 | 
					    [Authorize]
 | 
				
			||||||
    public async Task<ActionResult<List<Session>>> GetSessions(
 | 
					    public async Task<ActionResult<List<Session>>> GetSessions(
 | 
				
			||||||
@@ -434,8 +472,7 @@ public class AccountCurrentController(
 | 
				
			|||||||
        var query = db.AuthSessions
 | 
					        var query = db.AuthSessions
 | 
				
			||||||
            .Include(session => session.Account)
 | 
					            .Include(session => session.Account)
 | 
				
			||||||
            .Include(session => session.Challenge)
 | 
					            .Include(session => session.Challenge)
 | 
				
			||||||
            .Where(session => session.Account.Id == currentUser.Id)
 | 
					            .Where(session => session.Account.Id == currentUser.Id);
 | 
				
			||||||
            .OrderByDescending(session => session.CreatedAt);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var total = await query.CountAsync();
 | 
					        var total = await query.CountAsync();
 | 
				
			||||||
        Response.Headers.Append("X-Total", total.ToString());
 | 
					        Response.Headers.Append("X-Total", total.ToString());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,6 @@
 | 
				
			|||||||
using System.ComponentModel.DataAnnotations;
 | 
					using System.ComponentModel.DataAnnotations;
 | 
				
			||||||
 | 
					using System.Globalization;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
using System.Text.RegularExpressions;
 | 
					using System.Text.RegularExpressions;
 | 
				
			||||||
using DysonNetwork.Sphere.Permission;
 | 
					using DysonNetwork.Sphere.Permission;
 | 
				
			||||||
using DysonNetwork.Sphere.Storage;
 | 
					using DysonNetwork.Sphere.Storage;
 | 
				
			||||||
@@ -142,6 +144,8 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ
 | 
				
			|||||||
    public async Task<ActionResult> SendMessage([FromBody] SendMessageRequest request, Guid roomId)
 | 
					    public async Task<ActionResult> SendMessage([FromBody] SendMessageRequest request, Guid roomId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
 | 
					        if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        request.Content = TextSanitizer.Sanitize(request.Content);
 | 
				
			||||||
        if (string.IsNullOrWhiteSpace(request.Content) &&
 | 
					        if (string.IsNullOrWhiteSpace(request.Content) &&
 | 
				
			||||||
            (request.AttachmentsId == null || request.AttachmentsId.Count == 0))
 | 
					            (request.AttachmentsId == null || request.AttachmentsId.Count == 0))
 | 
				
			||||||
            return BadRequest("You cannot send an empty message.");
 | 
					            return BadRequest("You cannot send an empty message.");
 | 
				
			||||||
@@ -218,6 +222,8 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
 | 
					        if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        request.Content = TextSanitizer.Sanitize(request.Content);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var message = await db.ChatMessages
 | 
					        var message = await db.ChatMessages
 | 
				
			||||||
            .Include(m => m.Sender)
 | 
					            .Include(m => m.Sender)
 | 
				
			||||||
            .Include(m => m.Sender.Account)
 | 
					            .Include(m => m.Sender.Account)
 | 
				
			||||||
@@ -230,6 +236,10 @@ public partial class ChatController(AppDatabase db, ChatService cs, ChatRoomServ
 | 
				
			|||||||
        if (message.Sender.AccountId != currentUser.Id)
 | 
					        if (message.Sender.AccountId != currentUser.Id)
 | 
				
			||||||
            return StatusCode(403, "You can only edit your own messages.");
 | 
					            return StatusCode(403, "You can only edit your own messages.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (string.IsNullOrWhiteSpace(request.Content) &&
 | 
				
			||||||
 | 
					            (request.AttachmentsId == null || request.AttachmentsId.Count == 0))
 | 
				
			||||||
 | 
					            return BadRequest("You cannot send an empty message.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (request.RepliedMessageId.HasValue)
 | 
					        if (request.RepliedMessageId.HasValue)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var repliedMessage = await db.ChatMessages
 | 
					            var repliedMessage = await db.ChatMessages
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ using System.Text.Json;
 | 
				
			|||||||
using DysonNetwork.Sphere.Account;
 | 
					using DysonNetwork.Sphere.Account;
 | 
				
			||||||
using DysonNetwork.Sphere.Permission;
 | 
					using DysonNetwork.Sphere.Permission;
 | 
				
			||||||
using DysonNetwork.Sphere.Publisher;
 | 
					using DysonNetwork.Sphere.Publisher;
 | 
				
			||||||
 | 
					using DysonNetwork.Sphere.Storage;
 | 
				
			||||||
using Microsoft.AspNetCore.Authorization;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
using Microsoft.AspNetCore.Mvc;
 | 
					using Microsoft.AspNetCore.Mvc;
 | 
				
			||||||
using Microsoft.EntityFrameworkCore;
 | 
					using Microsoft.EntityFrameworkCore;
 | 
				
			||||||
@@ -149,6 +150,7 @@ public class PostController(
 | 
				
			|||||||
        [FromHeader(Name = "X-Pub")] string? publisherName
 | 
					        [FromHeader(Name = "X-Pub")] string? publisherName
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        request.Content = TextSanitizer.Sanitize(request.Content);
 | 
				
			||||||
        if (string.IsNullOrWhiteSpace(request.Content) && request.Attachments is { Count: 0 })
 | 
					        if (string.IsNullOrWhiteSpace(request.Content) && request.Attachments is { Count: 0 })
 | 
				
			||||||
            return BadRequest("Content is required.");
 | 
					            return BadRequest("Content is required.");
 | 
				
			||||||
        if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
 | 
					        if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
 | 
				
			||||||
@@ -290,6 +292,7 @@ public class PostController(
 | 
				
			|||||||
    [HttpPatch("{id:guid}")]
 | 
					    [HttpPatch("{id:guid}")]
 | 
				
			||||||
    public async Task<ActionResult<Post>> UpdatePost(Guid id, [FromBody] PostRequest request)
 | 
					    public async Task<ActionResult<Post>> UpdatePost(Guid id, [FromBody] PostRequest request)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        request.Content = TextSanitizer.Sanitize(request.Content);
 | 
				
			||||||
        if (string.IsNullOrWhiteSpace(request.Content) && request.Attachments is { Count: 0 })
 | 
					        if (string.IsNullOrWhiteSpace(request.Content) && request.Attachments is { Count: 0 })
 | 
				
			||||||
            return BadRequest("Content is required.");
 | 
					            return BadRequest("Content is required.");
 | 
				
			||||||
        if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
 | 
					        if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										24
									
								
								DysonNetwork.Sphere/Storage/TextSanitizer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								DysonNetwork.Sphere/Storage/TextSanitizer.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					using System.Globalization;
 | 
				
			||||||
 | 
					using System.Text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace DysonNetwork.Sphere.Storage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public abstract class TextSanitizer
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    public static string? Sanitize(string? text)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (string.IsNullOrEmpty(text)) return text;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var filtered = new StringBuilder();
 | 
				
			||||||
 | 
					        foreach (var ch in from ch in text
 | 
				
			||||||
 | 
					                 let category = CharUnicodeInfo.GetUnicodeCategory(ch)
 | 
				
			||||||
 | 
					                 where category is not (UnicodeCategory.Control or UnicodeCategory.Format
 | 
				
			||||||
 | 
					                     or UnicodeCategory.NonSpacingMark)
 | 
				
			||||||
 | 
					                 select ch)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            filtered.Append(ch);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return filtered.ToString(); 
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user