diff --git a/Iceshrimp.Backend/Controllers/AuthController.cs b/Iceshrimp.Backend/Controllers/AuthController.cs index ebda85cb..69d5d497 100644 --- a/Iceshrimp.Backend/Controllers/AuthController.cs +++ b/Iceshrimp.Backend/Controllers/AuthController.cs @@ -1,4 +1,3 @@ -using System.Net; using System.Net.Mime; using Iceshrimp.Backend.Controllers.Schemas; using Iceshrimp.Backend.Core.Database; @@ -19,16 +18,15 @@ namespace Iceshrimp.Backend.Controllers; [Route("/api/iceshrimp/v1/auth")] public class AuthController(DatabaseContext db, UserService userSvc) : Controller { [HttpGet] - [Authentication(false)] + [Authenticate] [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AuthResponse))] public IActionResult GetAuthStatus() { var session = Request.HttpContext.GetSession(); - if (session == null) { + if (session == null) return Ok(new AuthResponse { Status = AuthStatusEnum.Guest }); - } return Ok(new AuthResponse { Status = session.Active ? AuthStatusEnum.Authenticated : AuthStatusEnum.TwoFactor, diff --git a/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs b/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs index 08dd2059..f7797d77 100644 --- a/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/ServiceExtensions.cs @@ -39,6 +39,7 @@ public static class ServiceExtensions { services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // Hosted services = long running background tasks // Note: These need to be added as a singleton as well to ensure data consistency diff --git a/Iceshrimp.Backend/Core/Extensions/WebApplicationExtensions.cs b/Iceshrimp.Backend/Core/Extensions/WebApplicationExtensions.cs index ffaab3c5..9c11180b 100644 --- a/Iceshrimp.Backend/Core/Extensions/WebApplicationExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/WebApplicationExtensions.cs @@ -12,6 +12,7 @@ public static class WebApplicationExtensions { return app.UseMiddleware() .UseMiddleware() .UseMiddleware() + .UseMiddleware() .UseMiddleware(); } diff --git a/Iceshrimp.Backend/Core/Middleware/AuthenticationMiddleware.cs b/Iceshrimp.Backend/Core/Middleware/AuthenticationMiddleware.cs index 37d05c3e..95e77125 100644 --- a/Iceshrimp.Backend/Core/Middleware/AuthenticationMiddleware.cs +++ b/Iceshrimp.Backend/Core/Middleware/AuthenticationMiddleware.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Microsoft.AspNetCore.Http.Features; @@ -6,20 +5,15 @@ using Microsoft.EntityFrameworkCore; namespace Iceshrimp.Backend.Core.Middleware; -public class AuthenticationMiddleware( - [SuppressMessage("ReSharper", "SuggestBaseTypeForParameterInConstructor")] - DatabaseContext db -) : IMiddleware { +public class AuthenticationMiddleware(DatabaseContext db) : IMiddleware { public async Task InvokeAsync(HttpContext ctx, RequestDelegate next) { var endpoint = ctx.Features.Get()?.Endpoint; - var attribute = endpoint?.Metadata.GetMetadata(); + var attribute = endpoint?.Metadata.GetMetadata(); if (attribute != null) { var request = ctx.Request; var header = request.Headers.Authorization.ToString(); if (!header.ToLowerInvariant().StartsWith("bearer ")) { - if (attribute.Required) - throw GracefulException.Unauthorized("Missing bearer token in authorization header"); await next(ctx); return; } @@ -27,11 +21,10 @@ public class AuthenticationMiddleware( var token = header[7..]; var session = await db.Sessions.Include(p => p.User).FirstOrDefaultAsync(p => p.Token == token); if (session == null) { - if (attribute.Required) - throw GracefulException.Forbidden("Bearer token is invalid"); await next(ctx); return; } + ctx.SetSession(session); } @@ -39,9 +32,7 @@ public class AuthenticationMiddleware( } } -public class AuthenticationAttribute(bool required = true) : Attribute { - public bool Required { get; } = required; -} +public class AuthenticateAttribute : Attribute; public static class HttpContextExtensions { private const string Key = "session"; @@ -49,7 +40,7 @@ public static class HttpContextExtensions { internal static void SetSession(this HttpContext ctx, Session session) { ctx.Items.Add(Key, session); } - + public static Session? GetSession(this HttpContext ctx) { ctx.Items.TryGetValue(Key, out var session); return session as Session; diff --git a/Iceshrimp.Backend/Core/Middleware/AuthorizationMiddleware.cs b/Iceshrimp.Backend/Core/Middleware/AuthorizationMiddleware.cs new file mode 100644 index 00000000..dfe924d4 --- /dev/null +++ b/Iceshrimp.Backend/Core/Middleware/AuthorizationMiddleware.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Http.Features; + +namespace Iceshrimp.Backend.Core.Middleware; + +public class AuthorizationMiddleware : IMiddleware { + public async Task InvokeAsync(HttpContext ctx, RequestDelegate next) { + var endpoint = ctx.Features.Get()?.Endpoint; + var attribute = endpoint?.Metadata.GetMetadata(); + + if (attribute != null) + if (ctx.GetSession() is not { Active: true }) + throw GracefulException.Forbidden("This method requires an authenticated user"); + + await next(ctx); + } +} + +public class AuthorizeAttribute : Attribute; \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Middleware/ErrorHandlerMiddleware.cs b/Iceshrimp.Backend/Core/Middleware/ErrorHandlerMiddleware.cs index 34eab437..d79132b6 100644 --- a/Iceshrimp.Backend/Core/Middleware/ErrorHandlerMiddleware.cs +++ b/Iceshrimp.Backend/Core/Middleware/ErrorHandlerMiddleware.cs @@ -77,7 +77,7 @@ public class GracefulException(HttpStatusCode statusCode, string error, string m public static GracefulException Forbidden(string message, string? details = null) { return new GracefulException(HttpStatusCode.Forbidden, message, details); } - + public static GracefulException Unauthorized(string message, string? details = null) { return new GracefulException(HttpStatusCode.Unauthorized, message, details); }