using System.Security.Cryptography; using Foxchat.Core; using Foxchat.Core.Utils; using Foxchat.Identity.Database; using Foxchat.Identity.Database.Models; using Microsoft.EntityFrameworkCore; using NodaTime; namespace Foxchat.Identity.Authorization; public class AuthenticationMiddleware( IdentityContext db, IClock clock ) : IMiddleware { public async Task InvokeAsync(HttpContext ctx, RequestDelegate next) { var endpoint = ctx.GetEndpoint(); var metadata = endpoint?.Metadata.GetMetadata(); if (metadata == null) { await next(ctx); return; } var header = ctx.Request.Headers.Authorization.ToString(); if (!header.StartsWith("bearer ", StringComparison.InvariantCultureIgnoreCase)) { await next(ctx); return; } var token = header[7..]; if (!CryptoUtils.TryFromBase64String(token, out var rawToken)) { await next(ctx); return; } var hash = SHA512.HashData(rawToken); var oauthToken = await db.Tokens .Include(t => t.Account) .Include(t => t.Application) .FirstOrDefaultAsync(t => t.Hash == hash && t.Expires > clock.GetCurrentInstant()); 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;