Implemented api/check endpoint. Added api error middleware

This commit is contained in:
Masu-Baumgartner
2024-10-01 14:27:09 +02:00
parent ef2e6c9a20
commit e32e35d3af
8 changed files with 258 additions and 79 deletions

View File

@@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using Moonlight.ApiServer.Attributes;
using Moonlight.ApiServer.Helpers.Authentication;
using Moonlight.ApiServer.Services;
using Moonlight.Shared.Http.Requests.Auth;
using Moonlight.Shared.Http.Responses.Auth;
@@ -41,4 +43,19 @@ public class AuthController : Controller
Token = await AuthService.GenerateToken(user)
};
}
[HttpGet("check")]
[RequirePermission("meta.authenticated")]
public async Task<CheckResponse> Check()
{
var perm = HttpContext.User as PermClaimsPrinciple;
var user = perm!.CurrentModel;
return new CheckResponse()
{
Email = user.Email,
Username = user.Username,
Permissions = perm.Permissions
};
}
}

View File

@@ -0,0 +1,72 @@
using System.Diagnostics;
using System.Net.Sockets;
using MoonCore.Exceptions;
namespace Moonlight.ApiServer.Http.Middleware;
public class ApiErrorMiddleware
{
private readonly RequestDelegate Next;
public ApiErrorMiddleware(RequestDelegate next)
{
Next = next;
}
public async Task Invoke(HttpContext context)
{
try
{
await Next(context);
}
catch (HttpApiException httpApiException)
{
await Results.Problem(
title: httpApiException.Title,
detail: httpApiException.Detail,
statusCode: httpApiException.Status,
type: "moonlight/general-api-error"
).ExecuteAsync(context);
}
catch (HttpRequestException e)
{
var logger = context.RequestServices.GetRequiredService<ILogger<ApiErrorMiddleware>>();
if (e.InnerException is SocketException)
{
logger.LogCritical("An unhandled socket exception occured. [{method}] {path}: {e}", context.Request.Method, context.Request.Path, e);
await Results.Problem(
title: "An socket exception occured on the api server",
detail: "Check the api server logs for more details",
statusCode: 502,
type: "moonlight/remote-api-connection-error"
).ExecuteAsync(context);
return;
}
logger.LogCritical("An unhandled exception occured. [{method}] {path}: {e}", context.Request.Method, context.Request.Path, e.Demystify());
await Results.Problem(
title: "An http request exception occured on the api server",
detail: "Check the api server logs for more details",
statusCode: 500,
type: "moonlight/remote-api-request-error"
).ExecuteAsync(context);
}
catch (Exception e)
{
var logger = context.RequestServices.GetRequiredService<ILogger<ApiErrorMiddleware>>();
logger.LogCritical("An unhandled exception occured. [{method}] {path}: {e}", context.Request.Method, context.Request.Path, e);
await Results.Problem(
title: "An unhanded exception occured on the api server",
detail: "Check the api server logs for more details",
statusCode: 500,
type: "moonlight/critical-api-error"
).ExecuteAsync(context);
}
}
}

View File

@@ -1,76 +0,0 @@
using Microsoft.AspNetCore.Mvc.Controllers;
using Moonlight.ApiServer.Attributes;
namespace Moonlight.ApiServer.Http.Middleware;
public class AuthorisationMiddleware
{
private readonly RequestDelegate Next;
private readonly ILogger<AuthorisationMiddleware> Logger;
public AuthorisationMiddleware(RequestDelegate next, ILogger<AuthorisationMiddleware> logger)
{
Next = next;
Logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
await Next(context);
}
private async Task Authorize(HttpContext context)
{
}
private string[] ResolveRequiredPermissions(HttpContext context)
{
// Basic handling
var endpoint = context.GetEndpoint();
if (endpoint == null)
return [];
var metadata = endpoint
.Metadata
.GetMetadata<ControllerActionDescriptor>();
if (metadata == null)
return [];
// Retrieve attribute infos
var controllerAttrInfo = metadata
.ControllerTypeInfo
.CustomAttributes
.FirstOrDefault(x => x.AttributeType == typeof(RequirePermissionAttribute));
var methodAttrInfo = metadata
.MethodInfo
.CustomAttributes
.FirstOrDefault(x => x.AttributeType == typeof(RequirePermissionAttribute));
// Retrieve permissions from attribute infos
var controllerPermission = controllerAttrInfo != null
? controllerAttrInfo.ConstructorArguments.First().Value as string
: null;
var methodPermission = methodAttrInfo != null
? methodAttrInfo.ConstructorArguments.First().Value as string
: null;
// If both have a permission flag, return both
if (controllerPermission != null && methodPermission != null)
return [controllerPermission, methodPermission];
// If either of them have a permission set, return it
if (controllerPermission != null)
return [controllerPermission];
if (methodPermission != null)
return [methodPermission];
// If both have no permission set, allow everyone to access it
return [];
}
}

View File

@@ -0,0 +1,144 @@
using Microsoft.AspNetCore.Mvc.Controllers;
using Moonlight.ApiServer.Attributes;
using Moonlight.ApiServer.Exceptions;
using Moonlight.ApiServer.Helpers.Authentication;
namespace Moonlight.ApiServer.Http.Middleware;
public class AuthorizationMiddleware
{
private readonly RequestDelegate Next;
private readonly ILogger<AuthorizationMiddleware> Logger;
public AuthorizationMiddleware(RequestDelegate next, ILogger<AuthorizationMiddleware> logger)
{
Next = next;
Logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
if (await Authorize(context))
{
try
{
await Next(context);
}
catch (MissingPermissionException e)
{
if (e.Permission == "meta.authenticated")
{
await Results.Problem(
title: "This endpoint requires a user authenticated token",
statusCode: 401
).ExecuteAsync(context);
}
else
{
await Results.Problem(
title: "You dont have the required permission",
detail: e.Permission,
statusCode: 403
).ExecuteAsync(context);
}
}
}
}
private async Task<bool> Authorize(HttpContext context)
{
var requiredPermissions = ResolveRequiredPermissions(context);
if (requiredPermissions.Length == 0)
return true;
// Check if no context => permissions have been loaded
if (context.User is not PermClaimsPrinciple permClaimsPrinciple)
{
await Results.Problem(
title: "An unauthenticated request is not allowed to use this endpoint",
statusCode: 401
).ExecuteAsync(context);
return false;
}
// Check if one of the required permissions is to be logged in
if (requiredPermissions.Any(x => x == "meta.authenticated") && permClaimsPrinciple.CurrentModelNullable == null)
{
await Results.Problem(
title: "This endpoint requires a user authenticated token",
statusCode: 401
).ExecuteAsync(context);
return false;
}
foreach (var permission in requiredPermissions)
{
if(permission == "meta.authenticated") // We already verified that
continue;
if (!permClaimsPrinciple.HasPermission(permission))
{
await Results.Problem(
title: "You dont have the required permission",
detail: permission,
statusCode: 403
).ExecuteAsync(context);
}
}
return true;
}
private string[] ResolveRequiredPermissions(HttpContext context)
{
// Basic handling
var endpoint = context.GetEndpoint();
if (endpoint == null)
return [];
var metadata = endpoint
.Metadata
.GetMetadata<ControllerActionDescriptor>();
if (metadata == null)
return [];
// Retrieve attribute infos
var controllerAttrInfo = metadata
.ControllerTypeInfo
.CustomAttributes
.FirstOrDefault(x => x.AttributeType == typeof(RequirePermissionAttribute));
var methodAttrInfo = metadata
.MethodInfo
.CustomAttributes
.FirstOrDefault(x => x.AttributeType == typeof(RequirePermissionAttribute));
// Retrieve permissions from attribute infos
var controllerPermission = controllerAttrInfo != null
? controllerAttrInfo.ConstructorArguments.First().Value as string
: null;
var methodPermission = methodAttrInfo != null
? methodAttrInfo.ConstructorArguments.First().Value as string
: null;
// If both have a permission flag, return both
if (controllerPermission != null && methodPermission != null)
return [controllerPermission, methodPermission];
// If either of them have a permission set, return it
if (controllerPermission != null)
return [controllerPermission];
if (methodPermission != null)
return [methodPermission];
// If both have no permission set, allow everyone to access it
return [];
}
}