84 lines
2.5 KiB
C#
84 lines
2.5 KiB
C#
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.Middleware;
|
|
|
|
public class ClientAuthenticationMiddleware(
|
|
IdentityContext db,
|
|
IClock clock
|
|
) : IMiddleware
|
|
{
|
|
public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
|
|
{
|
|
var endpoint = ctx.GetEndpoint();
|
|
var metadata = endpoint?.Metadata.GetMetadata<ClientAuthenticateAttribute>();
|
|
|
|
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;
|
|
}
|
|
|
|
public static Application GetApplicationOrThrow(this HttpContext context)
|
|
{
|
|
var token = context.GetToken();
|
|
if (token is not { Account: null }) throw new ApiError.Forbidden("This endpoint requires a client token.");
|
|
return token.Application;
|
|
}
|
|
}
|
|
|
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
|
|
public class ClientAuthenticateAttribute : Attribute;
|