Close data leaks: remove anonymous user creation, hide private playlists, validate uploads and clamp gateway ranges

This commit is contained in:
2026-09-24 05:00:21 +02:00
parent c8fe28b07b
commit c8e9c7790e
34 changed files with 374 additions and 553 deletions
@@ -92,7 +92,7 @@ public class CreatePlayListCommandHandler(
}
var validation = await uploadIntentValidator.ValidateAndLoadAsync(
uploadIntentConfiguration, request.PictureIntentId.Value, request.UserId, cancellationToken);
uploadIntentConfiguration, request.PictureIntentId.Value, request.UserId, UploadIntentPurpose.PlayListPicture, cancellationToken);
if (validation.IsError)
return validation.Errors;
@@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore;
using Musify.Application.Contracts;
using Musify.Application.Shared;
using Musify.Application.Tracks.Responses;
using Musify.Domain.ValueObjects;
using X.PagedList;
using X.PagedList.EF;
@@ -12,7 +13,8 @@ namespace Musify.Application.PlayLists;
public record GetPlayListTracksQuery(
Guid PlayListId,
int PageNumber,
int PageSize)
int PageSize,
long? ViewerId = null)
: IQuery<ErrorOr<PaginatedResponse<TrackApplicationResponse>>>;
public class GetPlayListTracksQueryHandler(IDatabase database)
@@ -22,7 +24,10 @@ public class GetPlayListTracksQueryHandler(IDatabase database)
{
var playListExists = await database.PlayLists
.AsNoTracking()
.AnyAsync(p => p.Id == request.PlayListId, cancellationToken);
.AnyAsync(
p => p.Id == request.PlayListId
&& (p.Visibility == PlaylistVisibility.Public || p.UserId == request.ViewerId),
cancellationToken);
if (!playListExists)
return Error.NotFound();
@@ -1,42 +0,0 @@
using ErrorOr;
using Mediator;
using Microsoft.EntityFrameworkCore;
using Musify.Application.Contracts;
using Musify.Application.PlayLists.Responses;
using Musify.Application.Shared;
using X.PagedList;
using X.PagedList.EF;
namespace Musify.Application.PlayLists;
public record GetPlayListsQuery(int PageNumber, int PageSize)
: IQuery<ErrorOr<PaginatedResponse<PlayListApplicationResponse>>>;
public class GetPlayListsQueryHandler(IDatabase database)
: IQueryHandler<GetPlayListsQuery, ErrorOr<PaginatedResponse<PlayListApplicationResponse>>>
{
public async ValueTask<ErrorOr<PaginatedResponse<PlayListApplicationResponse>>> Handle(GetPlayListsQuery request, CancellationToken cancellationToken)
{
var totalCount = await database.PlayLists.CountAsync(cancellationToken);
var pagedEntities = await database.PlayLists
.AsNoTracking()
.OrderBy(p => p.Id)
.Select(p => new
{
PlayList = p,
CoverTrackIds = p.PlayListTracks
.OrderBy(plt => plt.Position)
.Take(PlayListApplicationResponse.CoverTrackCount)
.Select(plt => plt.TrackId)
.ToList()
})
.ToPagedListAsync(request.PageNumber, request.PageSize, totalCount, cancellationToken);
var pagedPlayLists = new StaticPagedList<PlayListApplicationResponse>(
pagedEntities.Select(entry => PlayListApplicationResponse.FromEntity(entry.PlayList, entry.CoverTrackIds)),
pagedEntities.PageNumber, pagedEntities.PageSize, pagedEntities.TotalItemCount);
return PaginatedResponse<PlayListApplicationResponse>.FromPagedList(pagedPlayLists);
}
}
@@ -15,7 +15,7 @@ public record GetPlayListsByUserIdQuery(
string? Name,
int PageNumber,
int PageSize,
bool OnlyPublic = false)
long? ViewerId = null)
: IQuery<ErrorOr<PaginatedResponse<PlayListApplicationResponse>>>;
public class GetPlayListsByUserIdQueryHandler(IDatabase database)
@@ -25,12 +25,8 @@ public class GetPlayListsByUserIdQueryHandler(IDatabase database)
{
var playListsQuery = database.PlayLists
.AsNoTracking()
.Where(p => p.UserId == request.UserId);
if (request.OnlyPublic)
{
playListsQuery = playListsQuery.Where(p => p.Visibility == PlaylistVisibility.Public);
}
.Where(p => p.UserId == request.UserId
&& (p.Visibility == PlaylistVisibility.Public || p.UserId == request.ViewerId));
if (!string.IsNullOrEmpty(request.Name))
{
@@ -70,7 +70,7 @@ public class UpdatePlayListCommandHandler(
{
var validation = await uploadIntentValidator.ValidateAndLoadAsync(
uploadIntentConfiguration,
request.NewPictureIntentId.Value, request.UserId, cancellationToken);
request.NewPictureIntentId.Value, request.UserId, UploadIntentPurpose.PlayListPicture, cancellationToken);
if (validation.IsError)
return validation.Errors;