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
@@ -19,11 +19,6 @@ public static class PlayListEndpoints
var group = app.MapGroup("/playlists")
.WithTags("PlayLists");
group.MapGet("/", GetPlayLists)
.WithName("GetPlayLists")
.WithSummary("Get Paginated PlayLists.")
.Produces<PaginatedResponse<PlayListApplicationResponse>>();
group.MapGet("/{id}", GetPlayListById)
.WithName("GetPlayListById")
.WithSummary("Get A PlayList By Id.")
@@ -32,7 +27,7 @@ public static class PlayListEndpoints
group.MapGet("/users/{userId}", GetPlayListsByUserId)
.WithName("GetPlayListsByUserId")
.WithSummary("Get Paginated PlayLists For A User.")
.WithSummary("Get Paginated PlayLists For A User. Private PlayLists Are Only Returned To Their Owner.")
.Produces<PaginatedResponse<PlayListApplicationResponse>>();
group.MapGet("/{playlistId}/tracks", GetPlayListTracks)
@@ -105,16 +100,6 @@ public static class PlayListEndpoints
return app;
}
private static async Task<IResult> GetPlayLists(
IMediator mediator,
CancellationToken cancellationToken,
int pageNumber = 1,
int pageSize = 10)
{
var result = await mediator.Send(new GetPlayListsQuery(pageNumber, pageSize), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> GetPlayListById(
IMediator mediator,
CurrentUser currentUser,
@@ -127,25 +112,26 @@ public static class PlayListEndpoints
private static async Task<IResult> GetPlayListsByUserId(
IMediator mediator,
CurrentUser currentUser,
long userId,
CancellationToken cancellationToken,
string? name,
int pageNumber = 1,
int pageSize = 10,
bool onlyPublic = false)
int pageSize = 10)
{
var result = await mediator.Send(new GetPlayListsByUserIdQuery(userId, name, pageNumber, pageSize, onlyPublic), cancellationToken);
var result = await mediator.Send(new GetPlayListsByUserIdQuery(userId, name, pageNumber, pageSize, currentUser.Id), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> GetPlayListTracks(
IMediator mediator,
CurrentUser currentUser,
Guid playlistId,
CancellationToken cancellationToken,
int pageNumber = 1,
int pageSize = 10)
{
var result = await mediator.Send(new GetPlayListTracksQuery(playlistId, pageNumber, pageSize), cancellationToken);
var result = await mediator.Send(new GetPlayListTracksQuery(playlistId, pageNumber, pageSize, currentUser.Id), cancellationToken);
return result.ToHttpResult();
}
@@ -1,8 +1,6 @@
using Mediator;
using Musify.Api.Authentication;
using Musify.Api.DataTransferObjects.Users;
using Musify.Api.Extensions;
using Musify.Api.Filters;
using Musify.Application.Shared;
using Musify.Application.Tracks;
using Musify.Application.Tracks.Responses;
@@ -38,7 +36,10 @@ public static class UserEndpoints
group.MapGet("/{id}/listening-stats", GetListeningStats)
.WithName("GetListeningStats")
.WithSummary("Get A User'S Listening Stats For The Current Week And Streak.")
.Produces<ListeningStatsResponse>();
.RequireAuthorization()
.Produces<ListeningStatsResponse>()
.Produces(StatusCodes.Status401Unauthorized)
.Produces(StatusCodes.Status403Forbidden);
group.MapGet("/{id}/last-listened-track", GetLastTrackListenedByUserId)
.WithName("GetLastTrackListenedByUserId")
@@ -88,14 +89,6 @@ public static class UserEndpoints
.Produces(StatusCodes.Status204NoContent)
.Produces(StatusCodes.Status401Unauthorized);
group.MapPost("/", CreateUser)
.WithName("CreateUser")
.WithSummary("Create A New User.")
.AddEndpointFilter<ValidationFilter<CreateUserRequest>>()
.Produces<UserApplicationResponse>(StatusCodes.Status201Created)
.ProducesValidationProblem()
.Produces(StatusCodes.Status409Conflict);
return app;
}
@@ -128,12 +121,16 @@ public static class UserEndpoints
return await mediator.Send(new GetListeningHistoryQuery(id), cancellationToken);
}
private static async Task<ListeningStatsResponse> GetListeningStats(
private static async Task<IResult> GetListeningStats(
IMediator mediator,
CurrentUser currentUser,
long id,
CancellationToken cancellationToken)
{
return await mediator.Send(new GetListeningStatsQuery(id), cancellationToken);
if (currentUser.RequiredId != id)
return Results.StatusCode(StatusCodes.Status403Forbidden);
return Results.Ok(await mediator.Send(new GetListeningStatsQuery(id), cancellationToken));
}
private static async Task<IResult> GetLastTrackListenedByUserId(
@@ -207,16 +204,4 @@ public static class UserEndpoints
var result = await mediator.Send(new UnfollowUserCommand(currentUser.RequiredId, id), cancellationToken);
return result.ToHttpResult();
}
private static async Task<IResult> CreateUser(
IMediator mediator,
CreateUserRequest request,
CancellationToken cancellationToken)
{
var result = await mediator.Send(
new CreateUserCommand(request.Id, request.Name, request.FirstName, request.SecondName),
cancellationToken);
return result.ToCreatedResult(user => $"/users/{user.Id}");
}
}