Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ public class ApiAuthenticationError : ApiError
public const string NoPermissionsForCreationWhen = "You lack the permissions to create this type of resource.";
public static readonly ApiAuthenticationError NoPermissionsForCreation = new(NoPermissionsForCreationWhen);

public const string ReadOnlyErrorWhen = "The server is currently in read-only mode.";
public static readonly ApiAuthenticationError ReadOnlyError = new(ReadOnlyErrorWhen);

public const string NotAuthenticatedWhen = "You are not authenticated.";
public static readonly ApiAuthenticationError NotAuthenticated = new(NotAuthenticatedWhen);

Expand Down
21 changes: 17 additions & 4 deletions Refresh.Interfaces.APIv3/Endpoints/CommentApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Bunkum.Core.RateLimit;
using Bunkum.Protocols.Http;
using Refresh.Common.Constants;
using Refresh.Core.Configuration;
using Refresh.Core.RateLimits.Comments;
using Refresh.Core.RateLimits.Relations;
using Refresh.Core.Types.Data;
Expand Down Expand Up @@ -51,8 +52,11 @@ public ApiListResponse<ApiProfileCommentResponse> GetCommentsOnProfile(RequestCo
public ApiResponse<ApiProfileCommentResponse> PostCommentOnProfile(RequestContext context,
DataContext dataContext, GameUser user, ApiCommentPostRequest body,
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType)
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameUser? profile = dataContext.Database.GetUserByIdAndType(idType, id);
if (profile == null) return ApiNotFoundError.UserMissingError;

Expand Down Expand Up @@ -99,8 +103,11 @@ public ApiOkResponse DeleteProfileComment(RequestContext context, DataContext da
[RateLimitSettings(CommonRelationEndpointLimits.TimeoutDuration, CommonRelationEndpointLimits.RequestAmount,
CommonRelationEndpointLimits.BlockDuration, CommonRelationEndpointLimits.RequestBucket)]
public ApiOkResponse RateProfileComment(RequestContext context, DataContext dataContext, GameUser user, int id,
[DocSummary("The user's new rating for the comment. -1 = dislike, 0 = neutral, 1 = like.")] string rawRating)
[DocSummary("The user's new rating for the comment. -1 = dislike, 0 = neutral, 1 = like.")] string rawRating, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameProfileComment? comment = dataContext.Database.GetProfileCommentById(id);
if (comment == null) return ApiNotFoundError.CommentMissingError;

Expand Down Expand Up @@ -138,8 +145,11 @@ public ApiListResponse<ApiLevelCommentResponse> GetCommentsOnLevel(RequestContex
[RateLimitSettings(CommentUploadEndpointLimits.TimeoutDuration, CommentUploadEndpointLimits.RequestAmount,
CommentUploadEndpointLimits.BlockDuration, CommentUploadEndpointLimits.RequestBucket)]
public ApiResponse<ApiLevelCommentResponse> PostCommentOnLevel(RequestContext context,
DataContext dataContext, int id, GameUser user, ApiCommentPostRequest body)
DataContext dataContext, int id, GameUser user, ApiCommentPostRequest body, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameLevel? level = dataContext.Database.GetLevelById(id);
if (level == null) return ApiNotFoundError.LevelMissingError;

Expand Down Expand Up @@ -186,8 +196,11 @@ public ApiOkResponse DeleteLevelComment(RequestContext context, DataContext data
[RateLimitSettings(CommonRelationEndpointLimits.TimeoutDuration, CommonRelationEndpointLimits.RequestAmount,
CommonRelationEndpointLimits.BlockDuration, CommonRelationEndpointLimits.RequestBucket)]
public ApiOkResponse RateLevelComment(RequestContext context, DataContext dataContext, GameUser user, int id,
[DocSummary("The user's new rating for the comment. -1 = dislike, 0 = neutral, 1 = like.")] string rawRating)
[DocSummary("The user's new rating for the comment. -1 = dislike, 0 = neutral, 1 = like.")] string rawRating, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameLevelComment? comment = dataContext.Database.GetLevelCommentById(id);
if (comment == null) return ApiNotFoundError.CommentMissingError;

Expand Down
22 changes: 16 additions & 6 deletions Refresh.Interfaces.APIv3/Endpoints/LevelApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Refresh.Common.Constants;
using Refresh.Common.Verification;
using Refresh.Core.Authentication.Permission;
using Refresh.Core.Configuration;
using Refresh.Core.RateLimits.Levels;
using Refresh.Core.RateLimits.Playlists;
using Refresh.Core.RateLimits.Presence;
Expand Down Expand Up @@ -63,9 +64,12 @@ public ApiResponse<ApiGameLevelResponse> GetLevelByRootResource(RequestContext c
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)]
[DocError(typeof(ApiAuthenticationError), ApiAuthenticationError.NoPermissionsForObjectWhen)]
[RateLimitSettings(420, 8, 300, "level-update-api")]
public ApiResponse<ApiGameLevelResponse> EditLevelById(RequestContext context,
[DocSummary("The ID of the level")] int id, ApiEditLevelRequest body, DataContext dataContext)
public ApiResponse<ApiGameLevelResponse> EditLevelById(RequestContext context, GameUser user,
[DocSummary("The ID of the level")] int id, ApiEditLevelRequest body, DataContext dataContext, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameLevel? level = dataContext.Database.GetLevelById(id);
if (level == null) return ApiNotFoundError.LevelMissingError;

Expand Down Expand Up @@ -162,9 +166,12 @@ public ApiResponse<ApiGameLevelOwnRelationsResponse> GetLevelRelationsOfUser(Req
[DocError(typeof(ApiNotFoundError), ApiNotFoundError.LevelMissingErrorWhen)]
[RateLimitSettings(CommonRelationEndpointLimits.TimeoutDuration, CommonRelationEndpointLimits.RequestAmount,
CommonRelationEndpointLimits.BlockDuration, CommonRelationEndpointLimits.RequestBucket)]
public ApiOkResponse FavouriteLevel(RequestContext context, GameDatabaseContext database, GameUser user,
[DocSummary("The ID of the level")] int id, DataContext dataContext)
public ApiOkResponse HeartLevel(RequestContext context, GameDatabaseContext database, GameUser user,
[DocSummary("The ID of the level")] int id, DataContext dataContext, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameLevel? level = database.GetLevelById(id);
if (level == null) return ApiNotFoundError.LevelMissingError;

Expand All @@ -179,8 +186,11 @@ public ApiOkResponse FavouriteLevel(RequestContext context, GameDatabaseContext
[RateLimitSettings(CommonRelationEndpointLimits.TimeoutDuration, CommonRelationEndpointLimits.RequestAmount,
CommonRelationEndpointLimits.BlockDuration, CommonRelationEndpointLimits.RequestBucket)]
public ApiOkResponse UnheartLevel(RequestContext context, GameDatabaseContext database, GameUser user,
[DocSummary("The ID of the level")] int id, DataContext dataContext)
[DocSummary("The ID of the level")] int id, DataContext dataContext, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameLevel? level = database.GetLevelById(id);
if (level == null) return ApiNotFoundError.LevelMissingError;

Expand Down Expand Up @@ -232,7 +242,7 @@ public ApiOkResponse DequeueLevel(RequestContext context, GameDatabaseContext da
[RateLimitSettings(CommonRelationEndpointLimits.TimeoutDuration, CommonRelationEndpointLimits.RequestAmount,
CommonRelationEndpointLimits.BlockDuration, CommonRelationEndpointLimits.RequestBucket)]
public ApiOkResponse ClearQueuedLevels(RequestContext context, GameDatabaseContext database,
IDataStore dataStore, GameUser user, DataContext dataContext)
GameUser user, DataContext dataContext)
{
database.ClearQueue(user);
dataContext.Cache.ClearQueueByUser(user);
Expand Down
31 changes: 25 additions & 6 deletions Refresh.Interfaces.APIv3/Endpoints/PlaylistApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Refresh.Interfaces.APIv3.Endpoints.DataTypes.Response.Playlists;
using Refresh.Interfaces.APIv3.Extensions;
using Refresh.Core.RateLimits.Playlists;
using Refresh.Core.Configuration;

namespace Refresh.Interfaces.APIv3.Endpoints;

Expand Down Expand Up @@ -54,8 +55,11 @@ public class PlaylistApiEndpoints : EndpointGroup
[DocQueryParam("parentId", "If set, the new playlist will be added to the playlist specified by ID here instead of the root playlist. "
+ "If the specified playlist doesn't exist or is not owned by the user calling this endpoint, nothing will happen.")]
public ApiResponse<ApiGamePlaylistResponse> CreatePlaylist(RequestContext context, DataContext dataContext,
GameUser user, ApiPlaylistCreationRequest body)
GameUser user, ApiPlaylistCreationRequest body, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

ApiError? error = this.ValidatePlaylist(body, dataContext);
if (error != null) return error;

Expand Down Expand Up @@ -96,8 +100,11 @@ public ApiResponse<ApiGamePlaylistResponse> CreatePlaylist(RequestContext contex
[RateLimitSettings(PlaylistCreationEndpointLimits.UploadTimeoutDuration, PlaylistCreationEndpointLimits.MaxCreateAmount,
PlaylistCreationEndpointLimits.UploadBlockDuration, PlaylistCreationEndpointLimits.CreateBucket)]
public ApiResponse<ApiGamePlaylistResponse> UpdatePlaylist(RequestContext context, DataContext dataContext,
GameUser user, ApiPlaylistCreationRequest body, int id)
GameUser user, ApiPlaylistCreationRequest body, int id, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GamePlaylist? playlist = dataContext.Database.GetPlaylistById(id);
if (playlist == null) return ApiNotFoundError.PlaylistMissingError;

Expand Down Expand Up @@ -149,8 +156,11 @@ public ApiResponse<ApiGamePlaylistResponse> GetPlaylistById(RequestContext conte
[RateLimitSettings(PlaylistModificationEndpointLimits.TimeoutDuration, PlaylistModificationEndpointLimits.RequestAmount,
PlaylistModificationEndpointLimits.BlockDuration, PlaylistModificationEndpointLimits.RequestBucket)]
public ApiOkResponse AddLevelToPlaylist(RequestContext context, DataContext dataContext,
GameUser user, int playlistId, int levelId)
GameUser user, int playlistId, int levelId, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GamePlaylist? playlist = dataContext.Database.GetPlaylistById(playlistId);
if (playlist == null) return ApiNotFoundError.ParentPlaylistMissingError;

Expand All @@ -172,8 +182,11 @@ public ApiOkResponse AddLevelToPlaylist(RequestContext context, DataContext data
[RateLimitSettings(PlaylistModificationEndpointLimits.TimeoutDuration, PlaylistModificationEndpointLimits.RequestAmount,
PlaylistModificationEndpointLimits.BlockDuration, PlaylistModificationEndpointLimits.RequestBucket)]
public ApiOkResponse RemoveLevelFromPlaylist(RequestContext context, DataContext dataContext,
GameUser user, int playlistId, int levelId)
GameUser user, int playlistId, int levelId, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GamePlaylist? playlist = dataContext.Database.GetPlaylistById(playlistId);
if (playlist == null) return ApiNotFoundError.ParentPlaylistMissingError;

Expand All @@ -195,8 +208,11 @@ public ApiOkResponse RemoveLevelFromPlaylist(RequestContext context, DataContext
[RateLimitSettings(PlaylistModificationEndpointLimits.TimeoutDuration, PlaylistModificationEndpointLimits.RequestAmount,
PlaylistModificationEndpointLimits.BlockDuration, PlaylistModificationEndpointLimits.RequestBucket)]
public ApiOkResponse AddPlaylistToPlaylist(RequestContext context, DataContext dataContext,
GameUser user, int playlistId, int subPlaylistId)
GameUser user, int playlistId, int subPlaylistId, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GamePlaylist? parent = dataContext.Database.GetPlaylistById(playlistId);
if (parent == null) return ApiNotFoundError.ParentPlaylistMissingError;

Expand All @@ -218,8 +234,11 @@ public ApiOkResponse AddPlaylistToPlaylist(RequestContext context, DataContext d
[RateLimitSettings(PlaylistModificationEndpointLimits.TimeoutDuration, PlaylistModificationEndpointLimits.RequestAmount,
PlaylistModificationEndpointLimits.BlockDuration, PlaylistModificationEndpointLimits.RequestBucket)]
public ApiOkResponse RemovePlaylistFromPlaylist(RequestContext context, DataContext dataContext,
GameUser user, int playlistId, int subPlaylistId)
GameUser user, int playlistId, int subPlaylistId, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GamePlaylist? parent = dataContext.Database.GetPlaylistById(playlistId);
if (parent == null) return ApiNotFoundError.ParentPlaylistMissingError;

Expand Down
3 changes: 1 addition & 2 deletions Refresh.Interfaces.APIv3/Endpoints/ResourceApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ public ApiResponse<ApiGameAssetResponse> GetPspAssetInfo(RequestContext context,
[DocError(typeof(ApiValidationError), ApiValidationError.BodyTooLongErrorWhen)]
[DocError(typeof(ApiValidationError), ApiValidationError.CannotReadAssetErrorWhen)]
[DocError(typeof(ApiValidationError), ApiValidationError.BodyMustBeImageErrorWhen)]
[DocError(typeof(ApiAuthenticationError), ApiAuthenticationError.NoPermissionsForCreationWhen)]
[RateLimitSettings(420, 10, 300, "image-upload-api")]
public ApiResponse<ApiGameAssetResponse> UploadImageAsset(RequestContext context, GameDatabaseContext database,
IDataStore dataStore, AssetImporter importer, GameServerConfig config,
Expand All @@ -176,7 +175,7 @@ IntegrationConfig integration
// If we're blocking asset uploads, throw unless the user is an admin.
// We also have the ability to block asset uploads for trusted users (when they would normally bypass this)
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.NoPermissionsForCreation;
return ApiAuthenticationError.ReadOnlyError;

if (!CommonPatterns.Sha1Regex().IsMatch(hash)) return ApiValidationError.HashInvalidError;

Expand Down
16 changes: 13 additions & 3 deletions Refresh.Interfaces.APIv3/Endpoints/ReviewApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Bunkum.Core.Storage;
using Bunkum.Protocols.Http;
using Refresh.Common.Constants;
using Refresh.Core.Configuration;
using Refresh.Core.RateLimits.Relations;
using Refresh.Core.RateLimits.Reviews;
using Refresh.Core.Types.Data;
Expand Down Expand Up @@ -101,8 +102,11 @@ private List<Label> ValidateLabels(List<Label> input)
ReviewUploadEndpointLimits.BlockDuration, ReviewUploadEndpointLimits.RequestBucket)]
public ApiResponse<ApiGameReviewResponse> PostReviewToLevel(RequestContext context,
GameDatabaseContext database, IDataStore dataStore, GameUser user,
[DocSummary("The ID of the level")] int id, ApiSubmitReviewRequest body, DataContext dataContext)
[DocSummary("The ID of the level")] int id, ApiSubmitReviewRequest body, DataContext dataContext, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameLevel? level = database.GetLevelById(id);
if (level == null) return ApiNotFoundError.LevelMissingError;

Expand Down Expand Up @@ -147,8 +151,11 @@ public ApiResponse<ApiGameReviewResponse> PostReviewToLevel(RequestContext conte
ReviewUploadEndpointLimits.BlockDuration, ReviewUploadEndpointLimits.RequestBucket)]
public ApiResponse<ApiGameReviewResponse> UpdateReviewById(RequestContext context,
GameDatabaseContext database, IDataStore dataStore, GameUser user,
[DocSummary("The ID of the review")] int id, ApiSubmitReviewRequest body, DataContext dataContext)
[DocSummary("The ID of the review")] int id, ApiSubmitReviewRequest body, DataContext dataContext, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameReview? review = database.GetReviewById(id);
if (review == null) return ApiNotFoundError.ReviewMissingError;

Expand Down Expand Up @@ -209,8 +216,11 @@ public ApiOkResponse DeleteReviewById(RequestContext context,
public ApiOkResponse RateReviewById(RequestContext context,
GameDatabaseContext database, IDataStore dataStore, GameUser user,
[DocSummary("The ID of the review")] int id, DataContext dataContext,
[DocSummary("The user's new rating. -1 = dislike, 0 = neutral, 1 = like.")] string rawRating)
[DocSummary("The user's new rating. -1 = dislike, 0 = neutral, 1 = like.")] string rawRating, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameReview? review = database.GetReviewById(id);
if (review == null) return ApiNotFoundError.ReviewMissingError;

Expand Down
10 changes: 8 additions & 2 deletions Refresh.Interfaces.APIv3/Endpoints/UserApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ public ApiResponse<ApiGameUserResponse> GetUser(RequestContext context, GameData
CommonRelationEndpointLimits.BlockDuration, CommonRelationEndpointLimits.RequestBucket)]
public ApiOkResponse HeartUser(RequestContext context, GameDatabaseContext database,
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType, DataContext dataContext, GameUser user)
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType, DataContext dataContext, GameUser user, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameUser? target = database.GetUserByIdAndType(idType, id);
if(target == null) return ApiNotFoundError.UserMissingError;

Expand All @@ -72,8 +75,11 @@ public ApiOkResponse HeartUser(RequestContext context, GameDatabaseContext datab
CommonRelationEndpointLimits.BlockDuration, CommonRelationEndpointLimits.RequestBucket)]
public ApiOkResponse UnheartUser(RequestContext context, GameDatabaseContext database,
[DocSummary(SharedParamDescriptions.UserIdParam)] string id,
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType, DataContext dataContext, GameUser user)
[DocSummary(SharedParamDescriptions.UserIdTypeParam)] string idType, DataContext dataContext, GameUser user, GameServerConfig config)
{
if (user.IsWriteBlocked(config))
return ApiAuthenticationError.ReadOnlyError;

GameUser? target = database.GetUserByIdAndType(idType, id);
if(target == null) return ApiNotFoundError.UserMissingError;

Expand Down
Loading
Loading