[backend/core] Add 401/403 response examples programmatically
This commit is contained in:
parent
72bc5e1090
commit
ba0e041bad
12 changed files with 83 additions and 55 deletions
|
@ -15,8 +15,6 @@ namespace Iceshrimp.Backend.Controllers;
|
||||||
[Authorize("role:admin")]
|
[Authorize("role:admin")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("/api/v1/iceshrimp/admin")]
|
[Route("/api/v1/iceshrimp/admin")]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(ErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(ErrorResponse))]
|
|
||||||
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor",
|
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor",
|
||||||
Justification = "We only have a DatabaseContext in our DI pool, not the base type")]
|
Justification = "We only have a DatabaseContext in our DI pool, not the base type")]
|
||||||
public class AdminController(DatabaseContext db, ActivityPubController apController) : ControllerBase
|
public class AdminController(DatabaseContext db, ActivityPubController apController) : ControllerBase
|
||||||
|
|
|
@ -115,7 +115,6 @@ public class AuthController(DatabaseContext db, UserService userSvc) : Controlle
|
||||||
[Consumes(MediaTypeNames.Application.Json)]
|
[Consumes(MediaTypeNames.Application.Json)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AuthResponse))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AuthResponse))]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorResponse))]
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorResponse))]
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(ErrorResponse))]
|
|
||||||
[SuppressMessage("ReSharper.DPA", "DPA0011: High execution time of MVC action",
|
[SuppressMessage("ReSharper.DPA", "DPA0011: High execution time of MVC action",
|
||||||
Justification = "Argon2 is execution time-heavy by design")]
|
Justification = "Argon2 is execution time-heavy by design")]
|
||||||
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
|
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest request)
|
||||||
|
@ -124,7 +123,7 @@ public class AuthController(DatabaseContext db, UserService userSvc) : Controlle
|
||||||
var userProfile = await db.UserProfiles.FirstOrDefaultAsync(p => p.User == user);
|
var userProfile = await db.UserProfiles.FirstOrDefaultAsync(p => p.User == user);
|
||||||
if (userProfile is not { Password: not null }) throw new GracefulException("userProfile?.Password was null");
|
if (userProfile is not { Password: not null }) throw new GracefulException("userProfile?.Password was null");
|
||||||
if (!AuthHelpers.ComparePassword(request.OldPassword, userProfile.Password))
|
if (!AuthHelpers.ComparePassword(request.OldPassword, userProfile.Password))
|
||||||
throw GracefulException.Forbidden("old_password is invalid");
|
throw GracefulException.BadRequest("old_password is invalid");
|
||||||
|
|
||||||
userProfile.Password = AuthHelpers.HashPassword(request.NewPassword);
|
userProfile.Password = AuthHelpers.HashPassword(request.NewPassword);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
|
@ -33,8 +33,6 @@ public class AccountController(
|
||||||
[HttpGet("verify_credentials")]
|
[HttpGet("verify_credentials")]
|
||||||
[Authorize("read:accounts")]
|
[Authorize("read:accounts")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AccountEntity))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AccountEntity))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
public async Task<IActionResult> VerifyUserCredentials()
|
public async Task<IActionResult> VerifyUserCredentials()
|
||||||
{
|
{
|
||||||
var user = HttpContext.GetUserOrFail();
|
var user = HttpContext.GetUserOrFail();
|
||||||
|
@ -56,8 +54,6 @@ public class AccountController(
|
||||||
[HttpPost("{id}/follow")]
|
[HttpPost("{id}/follow")]
|
||||||
[Authorize("write:follows")]
|
[Authorize("write:follows")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
//TODO: [FromHybrid] request (bool reblogs, bool notify, bool languages)
|
//TODO: [FromHybrid] request (bool reblogs, bool notify, bool languages)
|
||||||
public async Task<IActionResult> FollowUser(string id)
|
public async Task<IActionResult> FollowUser(string id)
|
||||||
{
|
{
|
||||||
|
@ -108,8 +104,6 @@ public class AccountController(
|
||||||
[HttpPost("{id}/unfollow")]
|
[HttpPost("{id}/unfollow")]
|
||||||
[Authorize("write:follows")]
|
[Authorize("write:follows")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
public async Task<IActionResult> UnfollowUser(string id)
|
public async Task<IActionResult> UnfollowUser(string id)
|
||||||
{
|
{
|
||||||
var user = HttpContext.GetUserOrFail();
|
var user = HttpContext.GetUserOrFail();
|
||||||
|
@ -149,8 +143,6 @@ public class AccountController(
|
||||||
[HttpGet("relationships")]
|
[HttpGet("relationships")]
|
||||||
[Authorize("read:follows")]
|
[Authorize("read:follows")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity[]))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity[]))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
public async Task<IActionResult> GetRelationships([FromQuery(Name = "id")] List<string> ids)
|
public async Task<IActionResult> GetRelationships([FromQuery(Name = "id")] List<string> ids)
|
||||||
{
|
{
|
||||||
var user = HttpContext.GetUserOrFail();
|
var user = HttpContext.GetUserOrFail();
|
||||||
|
@ -186,8 +178,6 @@ public class AccountController(
|
||||||
[Authorize("read:statuses")]
|
[Authorize("read:statuses")]
|
||||||
[LinkPagination(20, 40)]
|
[LinkPagination(20, 40)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<StatusEntity>))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<StatusEntity>))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
public async Task<IActionResult> GetUserStatuses(
|
public async Task<IActionResult> GetUserStatuses(
|
||||||
string id, AccountSchemas.AccountStatusesRequest request, MastodonPaginationQuery query
|
string id, AccountSchemas.AccountStatusesRequest request, MastodonPaginationQuery query
|
||||||
)
|
)
|
||||||
|
@ -275,8 +265,6 @@ public class AccountController(
|
||||||
[Authorize("read:follows")]
|
[Authorize("read:follows")]
|
||||||
[LinkPagination(40, 80)]
|
[LinkPagination(40, 80)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<AccountEntity>))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<AccountEntity>))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
public async Task<IActionResult> GetFollowRequests(MastodonPaginationQuery query)
|
public async Task<IActionResult> GetFollowRequests(MastodonPaginationQuery query)
|
||||||
{
|
{
|
||||||
var user = HttpContext.GetUserOrFail();
|
var user = HttpContext.GetUserOrFail();
|
||||||
|
@ -293,8 +281,6 @@ public class AccountController(
|
||||||
[HttpPost("/api/v1/follow_requests/{id}/authorize")]
|
[HttpPost("/api/v1/follow_requests/{id}/authorize")]
|
||||||
[Authorize("write:follows")]
|
[Authorize("write:follows")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
||||||
public async Task<IActionResult> AcceptFollowRequest(string id)
|
public async Task<IActionResult> AcceptFollowRequest(string id)
|
||||||
{
|
{
|
||||||
|
@ -338,8 +324,6 @@ public class AccountController(
|
||||||
[HttpPost("/api/v1/follow_requests/{id}/reject")]
|
[HttpPost("/api/v1/follow_requests/{id}/reject")]
|
||||||
[Authorize("write:follows")]
|
[Authorize("write:follows")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RelationshipEntity))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
||||||
public async Task<IActionResult> RejectFollowRequest(string id)
|
public async Task<IActionResult> RejectFollowRequest(string id)
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,8 +23,6 @@ namespace Iceshrimp.Backend.Controllers.Mastodon;
|
||||||
[EnableRateLimiting("sliding")]
|
[EnableRateLimiting("sliding")]
|
||||||
[EnableCors("mastodon")]
|
[EnableCors("mastodon")]
|
||||||
[Produces(MediaTypeNames.Application.Json)]
|
[Produces(MediaTypeNames.Application.Json)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
public class ListController(DatabaseContext db, UserRenderer userRenderer) : ControllerBase
|
public class ListController(DatabaseContext db, UserRenderer userRenderer) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
|
|
@ -17,8 +17,6 @@ namespace Iceshrimp.Backend.Controllers.Mastodon;
|
||||||
[EnableRateLimiting("sliding")]
|
[EnableRateLimiting("sliding")]
|
||||||
[Produces(MediaTypeNames.Application.Json)]
|
[Produces(MediaTypeNames.Application.Json)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AttachmentEntity))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AttachmentEntity))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
public class MediaController(DriveService driveSvc) : ControllerBase
|
public class MediaController(DriveService driveSvc) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpPost("/api/v1/media")]
|
[HttpPost("/api/v1/media")]
|
||||||
|
|
|
@ -85,8 +85,6 @@ public class StatusController(
|
||||||
[HttpPost("{id}/favourite")]
|
[HttpPost("{id}/favourite")]
|
||||||
[Authorize("write:favourites")]
|
[Authorize("write:favourites")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
||||||
public async Task<IActionResult> LikeNote(string id)
|
public async Task<IActionResult> LikeNote(string id)
|
||||||
{
|
{
|
||||||
|
@ -104,8 +102,6 @@ public class StatusController(
|
||||||
[HttpPost("{id}/unfavourite")]
|
[HttpPost("{id}/unfavourite")]
|
||||||
[Authorize("write:favourites")]
|
[Authorize("write:favourites")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
||||||
public async Task<IActionResult> UnlikeNote(string id)
|
public async Task<IActionResult> UnlikeNote(string id)
|
||||||
{
|
{
|
||||||
|
@ -123,8 +119,6 @@ public class StatusController(
|
||||||
[HttpPost("{id}/reblog")]
|
[HttpPost("{id}/reblog")]
|
||||||
[Authorize("write:favourites")]
|
[Authorize("write:favourites")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
||||||
public async Task<IActionResult> Renote(string id, [FromHybrid] string? visibility)
|
public async Task<IActionResult> Renote(string id, [FromHybrid] string? visibility)
|
||||||
{
|
{
|
||||||
|
@ -153,8 +147,6 @@ public class StatusController(
|
||||||
[HttpPost("{id}/unreblog")]
|
[HttpPost("{id}/unreblog")]
|
||||||
[Authorize("write:favourites")]
|
[Authorize("write:favourites")]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(StatusEntity))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
||||||
public async Task<IActionResult> UndoRenote(string id)
|
public async Task<IActionResult> UndoRenote(string id)
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,8 +23,6 @@ namespace Iceshrimp.Backend.Controllers.Mastodon;
|
||||||
[EnableRateLimiting("sliding")]
|
[EnableRateLimiting("sliding")]
|
||||||
[EnableCors("mastodon")]
|
[EnableCors("mastodon")]
|
||||||
[Produces(MediaTypeNames.Application.Json)]
|
[Produces(MediaTypeNames.Application.Json)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(MastodonErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(MastodonErrorResponse))]
|
|
||||||
public class TimelineController(DatabaseContext db, NoteRenderer noteRenderer, IDistributedCache cache) : ControllerBase
|
public class TimelineController(DatabaseContext db, NoteRenderer noteRenderer, IDistributedCache cache) : ControllerBase
|
||||||
{
|
{
|
||||||
[Authorize("read:statuses")]
|
[Authorize("read:statuses")]
|
||||||
|
|
|
@ -41,8 +41,6 @@ public class NoteController(DatabaseContext db, NoteService noteSvc, NoteRendere
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[Consumes(MediaTypeNames.Application.Json)]
|
[Consumes(MediaTypeNames.Application.Json)]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(NoteResponse))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(NoteResponse))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(ErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(ErrorResponse))]
|
|
||||||
public async Task<IActionResult> CreateNote(NoteCreateRequest request)
|
public async Task<IActionResult> CreateNote(NoteCreateRequest request)
|
||||||
{
|
{
|
||||||
var user = HttpContext.GetUserOrFail();
|
var user = HttpContext.GetUserOrFail();
|
||||||
|
|
|
@ -23,8 +23,6 @@ public class TimelineController(DatabaseContext db, IDistributedCache cache, Not
|
||||||
[Authenticate]
|
[Authenticate]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<NoteResponse>))]
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<NoteResponse>))]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(ErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status403Forbidden, Type = typeof(ErrorResponse))]
|
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))]
|
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))]
|
||||||
public async Task<IActionResult> GetHomeTimeline(PaginationQuery pq)
|
public async Task<IActionResult> GetHomeTimeline(PaginationQuery pq)
|
||||||
{
|
{
|
||||||
|
|
|
@ -147,14 +147,6 @@ public static class ServiceExtensions
|
||||||
Type = SecuritySchemeType.Http,
|
Type = SecuritySchemeType.Http,
|
||||||
Scheme = "bearer"
|
Scheme = "bearer"
|
||||||
});
|
});
|
||||||
options.AddSecurityDefinition("admin",
|
|
||||||
new OpenApiSecurityScheme
|
|
||||||
{
|
|
||||||
Name = "Authorization token",
|
|
||||||
In = ParameterLocation.Header,
|
|
||||||
Type = SecuritySchemeType.Http,
|
|
||||||
Scheme = "bearer"
|
|
||||||
});
|
|
||||||
options.AddSecurityDefinition("mastodon",
|
options.AddSecurityDefinition("mastodon",
|
||||||
new OpenApiSecurityScheme
|
new OpenApiSecurityScheme
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,6 +3,7 @@ using System.Reflection;
|
||||||
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
|
using Iceshrimp.Backend.Controllers.Mastodon.Attributes;
|
||||||
using Iceshrimp.Backend.Core.Middleware;
|
using Iceshrimp.Backend.Core.Middleware;
|
||||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||||
|
using Microsoft.OpenApi.Any;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
|
@ -45,19 +46,25 @@ public static class SwaggerGenOptionsExtensions
|
||||||
if (context.MethodInfo.DeclaringType is null)
|
if (context.MethodInfo.DeclaringType is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//TODO: separate admin & user authorize attributes
|
var authenticateAttribute = context.MethodInfo.GetCustomAttributes(true)
|
||||||
var hasAuthenticate = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
|
.OfType<AuthenticateAttribute>()
|
||||||
.OfType<AuthenticateAttribute>()
|
.FirstOrDefault() ??
|
||||||
.Any() ||
|
context.MethodInfo.DeclaringType.GetCustomAttributes(true)
|
||||||
context.MethodInfo.GetCustomAttributes(true)
|
.OfType<AuthenticateAttribute>()
|
||||||
.OfType<AuthenticateAttribute>()
|
.FirstOrDefault();
|
||||||
.Any();
|
|
||||||
|
if (authenticateAttribute == null) return;
|
||||||
|
|
||||||
var isMastodonController = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
|
var isMastodonController = context.MethodInfo.DeclaringType.GetCustomAttributes(true)
|
||||||
.OfType<MastodonApiControllerAttribute>()
|
.OfType<MastodonApiControllerAttribute>()
|
||||||
.Any();
|
.Any();
|
||||||
|
|
||||||
if (!hasAuthenticate) return;
|
var authorizeAttribute = context.MethodInfo.GetCustomAttributes(true)
|
||||||
|
.OfType<AuthorizeAttribute>()
|
||||||
|
.FirstOrDefault() ??
|
||||||
|
context.MethodInfo.DeclaringType.GetCustomAttributes(true)
|
||||||
|
.OfType<AuthorizeAttribute>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
var schema = new OpenApiSecurityScheme
|
var schema = new OpenApiSecurityScheme
|
||||||
{
|
{
|
||||||
|
@ -68,6 +75,72 @@ public static class SwaggerGenOptionsExtensions
|
||||||
};
|
};
|
||||||
|
|
||||||
operation.Security = new List<OpenApiSecurityRequirement> { new() { [schema] = Array.Empty<string>() } };
|
operation.Security = new List<OpenApiSecurityRequirement> { new() { [schema] = Array.Empty<string>() } };
|
||||||
|
|
||||||
|
if (authorizeAttribute == null) return;
|
||||||
|
|
||||||
|
const string web401 =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"statusCode": 401,
|
||||||
|
"error": "Unauthorized",
|
||||||
|
"message": "This method requires an authenticated user"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string web403 =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"statusCode": 403,
|
||||||
|
"error": "Forbidden",
|
||||||
|
"message": "This action is outside the authorized scopes"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string masto401 =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"error": "This method requires an authenticated user"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string masto403 =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"message": "This action is outside the authorized scopes"
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
var example401 = new OpenApiString(isMastodonController ? masto401 : web401);
|
||||||
|
|
||||||
|
var res401 = new OpenApiResponse
|
||||||
|
{
|
||||||
|
Description = "Unauthorized",
|
||||||
|
Content = new Dictionary<string, OpenApiMediaType>
|
||||||
|
{
|
||||||
|
{ "application/json", new OpenApiMediaType { Example = example401 } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
operation.Responses.Remove("401");
|
||||||
|
operation.Responses.Add("401", res401);
|
||||||
|
|
||||||
|
if (authorizeAttribute is { AdminRole: false, ModeratorRole: false, Scopes.Length: 0 } &&
|
||||||
|
authenticateAttribute is { AdminRole: false, ModeratorRole: false, Scopes.Length: 0 })
|
||||||
|
return;
|
||||||
|
|
||||||
|
operation.Responses.Remove("403");
|
||||||
|
|
||||||
|
var example403 = new OpenApiString(isMastodonController ? masto403 : web403);
|
||||||
|
|
||||||
|
var res403 = new OpenApiResponse
|
||||||
|
{
|
||||||
|
Description = "Forbidden",
|
||||||
|
Content = new Dictionary<string, OpenApiMediaType>
|
||||||
|
{
|
||||||
|
{ "application/json", new OpenApiMediaType { Example = example403 } }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
operation.Responses.Add("403", res403);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ public class AuthorizationMiddleware : IMiddleware
|
||||||
{
|
{
|
||||||
var session = ctx.GetSession();
|
var session = ctx.GetSession();
|
||||||
if (session is not { Active: true })
|
if (session is not { Active: true })
|
||||||
throw GracefulException.Forbidden("This method requires an authenticated user");
|
throw GracefulException.Unauthorized("This method requires an authenticated user");
|
||||||
if (attribute.AdminRole && !session.User.IsAdmin)
|
if (attribute.AdminRole && !session.User.IsAdmin)
|
||||||
throw GracefulException.Forbidden("This action is outside the authorized scopes");
|
throw GracefulException.Forbidden("This action is outside the authorized scopes");
|
||||||
if (attribute.ModeratorRole && session.User is { IsAdmin: false, IsModerator: false })
|
if (attribute.ModeratorRole && session.User is { IsAdmin: false, IsModerator: false })
|
||||||
|
|
Loading…
Add table
Reference in a new issue