too many things to list (notably, user avatar update)

This commit is contained in:
sam 2024-07-08 19:03:04 +02:00
parent a7950671e1
commit d6c9345dba
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
20 changed files with 341 additions and 47 deletions

View file

@ -2,6 +2,7 @@ using System.Security.Cryptography;
using Foxnouns.Backend.Database;
using Foxnouns.Backend.Database.Models;
using Foxnouns.Backend.Utils;
using Hangfire.Dashboard;
using Microsoft.EntityFrameworkCore;
using NodaTime;
@ -21,7 +22,7 @@ public class AuthenticationMiddleware(DatabaseContext db, IClock clock) : IMiddl
}
var header = ctx.Request.Headers.Authorization.ToString();
if (!OauthUtils.TryFromBase64String(header, out var rawToken))
if (!AuthUtils.TryFromBase64String(header, out var rawToken))
{
await next(ctx);
return;
@ -63,4 +64,33 @@ public static class HttpContextExtensions
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthenticateAttribute : Attribute;
public class AuthenticateAttribute : Attribute;
/// <summary>
/// Authentication filter for the Hangfire dashboard. Uses the cookie created by the frontend
/// (and otherwise only read <i>by</i> the frontend) to only allow admins to use it.
/// </summary>
public class HangfireDashboardAuthorizationFilter(IServiceProvider services) : IDashboardAsyncAuthorizationFilter
{
public async Task<bool> AuthorizeAsync(DashboardContext context)
{
await using var scope = services.CreateAsyncScope();
await using var db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
var clock = scope.ServiceProvider.GetRequiredService<IClock>();
var httpContext = context.GetHttpContext();
if (!httpContext.Request.Cookies.TryGetValue("pronounscc-token", out var cookie)) return false;
if (!AuthUtils.TryFromBase64String(cookie!, out var rawToken)) return false;
var hash = SHA512.HashData(rawToken);
var oauthToken = await db.Tokens
.Include(t => t.Application)
.Include(t => t.User)
.FirstOrDefaultAsync(t => t.Hash == hash && t.ExpiresAt > clock.GetCurrentInstant() && !t.ManuallyExpired);
return oauthToken?.User.Role == UserRole.Admin;
}
}

View file

@ -4,7 +4,7 @@ using Newtonsoft.Json.Converters;
namespace Foxnouns.Backend.Middleware;
public class ErrorHandlerMiddleware(ILogger baseLogger) : IMiddleware
public class ErrorHandlerMiddleware(ILogger baseLogger, IHub sentry) : IMiddleware
{
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
{
@ -22,6 +22,18 @@ public class ErrorHandlerMiddleware(ILogger baseLogger) : IMiddleware
{
logger.Error(e, "Error in {ClassName} ({Path}) after response started being sent", typeName,
ctx.Request.Path);
sentry.CaptureException(e, scope =>
{
var user = ctx.GetUser();
if (user != null)
scope.User = new SentryUser
{
Id = user.Id.ToString(),
Username = user.Username
};
});
return;
}
@ -59,6 +71,17 @@ public class ErrorHandlerMiddleware(ILogger baseLogger) : IMiddleware
{
logger.Error(e, "Exception in {ClassName} ({Path})", typeName, ctx.Request.Path);
}
var errorId = sentry.CaptureException(e, scope =>
{
var user = ctx.GetUser();
if (user != null)
scope.User = new SentryUser
{
Id = user.Id.ToString(),
Username = user.Username
};
});
ctx.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
ctx.Response.Headers.RequestId = ctx.TraceIdentifier;
@ -67,6 +90,7 @@ public class ErrorHandlerMiddleware(ILogger baseLogger) : IMiddleware
{
Status = (int)HttpStatusCode.InternalServerError,
Code = ErrorCode.InternalServerError,
ErrorId = errorId.ToString(),
Message = "Internal server error",
}));
}
@ -79,6 +103,7 @@ public record HttpApiError
[JsonConverter(typeof(StringEnumConverter))]
public required ErrorCode Code { get; init; }
public string? ErrorId { get; init; }
public required string Message { get; init; }