init with code from Foxnouns.NET

This commit is contained in:
sam 2024-08-04 15:57:10 +02:00
commit e658366473
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
30 changed files with 1387 additions and 0 deletions

View file

@ -0,0 +1,66 @@
using System.Security.Cryptography;
using Hydra.Backend.Database;
using Hydra.Backend.Database.Models;
using Hydra.Backend.Utils;
using Microsoft.EntityFrameworkCore;
using NodaTime;
namespace Hydra.Backend.Middleware;
public class AuthenticationMiddleware(HydraContext db, IClock clock) : IMiddleware
{
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
{
var endpoint = ctx.GetEndpoint();
var metadata = endpoint?.Metadata.GetMetadata<AuthenticateAttribute>();
if (metadata == null)
{
await next(ctx);
return;
}
var header = ctx.Request.Headers.Authorization.ToString();
if (!AuthUtils.TryFromBase64String(header, out var rawToken))
{
await next(ctx);
return;
}
var hash = SHA512.HashData(rawToken);
var oauthToken = await db.Tokens
.Include(t => t.Application)
.Include(t => t.Account)
.FirstOrDefaultAsync(t => t.Hash == hash && t.ExpiresAt > clock.GetCurrentInstant() && !t.ManuallyExpired);
if (oauthToken == null)
{
await next(ctx);
return;
}
ctx.SetToken(oauthToken);
await next(ctx);
}
}
public static class HttpContextExtensions
{
private const string Key = "token";
public static void SetToken(this HttpContext ctx, Token token) => ctx.Items.Add(Key, token);
public static Account? GetAccount(this HttpContext ctx) => ctx.GetToken()?.Account;
public static Account GetAccountOrThrow(this HttpContext ctx) =>
ctx.GetAccount() ?? throw new ApiError.AuthenticationError("No account in HttpContext");
public static Token? GetToken(this HttpContext ctx)
{
if (ctx.Items.TryGetValue(Key, out var token))
return token as Token;
return null;
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthenticateAttribute : Attribute;

View file

@ -0,0 +1,34 @@
namespace Hydra.Backend.Middleware;
public class AuthorizationMiddleware : IMiddleware
{
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
{
var endpoint = ctx.GetEndpoint();
var attribute = endpoint?.Metadata.GetMetadata<AuthorizeAttribute>();
if (attribute == null)
{
await next(ctx);
return;
}
var token = ctx.GetToken();
if (token == null)
throw new ApiError.Unauthorized("This endpoint requires an authenticated user.");
if (attribute.Scopes.Length > 0 && attribute.Scopes.Except(token.Scopes).Any())
throw new ApiError.Forbidden("This endpoint requires ungranted scopes.",
attribute.Scopes.Except(token.Scopes));
await next(ctx);
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeAttribute(params string[] scopes) : Attribute
{
public readonly bool RequireAdmin = scopes.Contains(":admin");
public readonly bool RequireModerator = scopes.Contains(":moderator");
public readonly string[] Scopes = scopes.Except([":admin", ":moderator"]).ToArray();
}