Newer
Older
percord / extra / admin-api / Spacebar.AdminAPI / Middleware / AuthenticationMiddleware.cs
@Emma [it/its]@Rory& Emma [it/its]@Rory& on 5 Oct 2 KB Further admin api work
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using ArcaneLibs.Extensions;
using Microsoft.IdentityModel.Tokens;
using Spacebar.AdminAPI.Services;
using Spacebar.Db.Contexts;
using Spacebar.Db.Models;

namespace Spacebar.AdminAPI.Middleware;

public class AuthenticationMiddleware(RequestDelegate next) {
    private static Dictionary<string, User> _userCache = new();
    private static Dictionary<string, DateTime> _userCacheExpiry = new();

    public async Task InvokeAsync(HttpContext context, IServiceProvider sp) {
        if (context.Request.Path.StartsWithSegments("/ping")) {
            await next(context);
            return;
        }

        if (!context.Request.Headers.ContainsKey("Authorization")) {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Authorization header is missing");
            return;
        }

        var token = context.Request.Headers["Authorization"].ToString().Split(' ').Last();

        var handler = new JwtSecurityTokenHandler();
        var secretFile = File.ReadAllText("../../../jwt.key.pub");
        var key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
        key.ImportFromPem(secretFile);

        var res = await handler.ValidateTokenAsync(token, new TokenValidationParameters {
            IssuerSigningKey = new ECDsaSecurityKey(key),
            ValidAlgorithms = new[] { "ES512" },
            LogValidationExceptions = true,
            // These are required to be false for the token to be valid as they aren't provided by the token
            ValidateIssuer = false,
            ValidateLifetime = false,
            ValidateAudience = false,
        });

        if (!res.IsValid) {
            context.Response.StatusCode = 401;
            await context.Response.WriteAsync("Invalid token");
            return;
        }

        User user;
        if (_userCacheExpiry.ContainsKey(token) && _userCacheExpiry[token] < DateTime.Now) {
            _userCache.Remove(token);
            _userCacheExpiry.Remove(token);
        }

        if (!_userCache.ContainsKey(token)) {
            var db = sp.GetRequiredService<SpacebarDbContext>();
            var config = sp.GetRequiredService<Configuration>();
            user = await db.Users.FindAsync(config.OverrideUid ?? res.ClaimsIdentity.Claims.First(x => x.Type == "id").Value)
                   ?? throw new InvalidOperationException();
            _userCache[token] = user;
            _userCacheExpiry[token] = DateTime.Now.AddMinutes(5);
        }

        user = _userCache[token];
        if (user.Disabled) {
            context.Response.StatusCode = 403;
            await context.Response.WriteAsync("User is disabled");
            return;
        }
        
        if (user.Deleted) {
            context.Response.StatusCode = 403;
            await context.Response.WriteAsync("User is deleted");
            return;
        }
        
        await next(context);
    }
}