2023-02-18 08:52:17 +01:00
|
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
|
|
|
using Microsoft.Extensions.Localization;
|
|
|
|
|
|
2023-02-19 00:43:43 +01:00
|
|
|
using PrivaPub.ClientModels;
|
2026-10-03 11:49:55 +02:00
|
|
|
using PrivaPub.Extensions;
|
2023-02-19 00:43:43 +01:00
|
|
|
using PrivaPub.Resources;
|
2026-10-03 11:49:55 +02:00
|
|
|
using PrivaPub.StaticServices;
|
2023-02-18 08:52:17 +01:00
|
|
|
|
2026-10-03 11:49:55 +02:00
|
|
|
using System.Security.Claims;
|
2023-02-18 08:52:17 +01:00
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.Json;
|
|
|
|
|
|
2023-02-19 00:43:43 +01:00
|
|
|
namespace PrivaPub.Services
|
2023-02-18 08:52:17 +01:00
|
|
|
{
|
|
|
|
|
public class JwtEvents : JwtBearerEvents
|
|
|
|
|
{
|
2026-10-03 11:49:55 +02:00
|
|
|
static readonly string[] PolicyClaims = { Policies.IsUser, Policies.IsModerator, Policies.IsAdmin };
|
|
|
|
|
static readonly JsonSerializerOptions Web = new(JsonSerializerDefaults.Web);
|
|
|
|
|
|
2023-02-18 08:52:17 +01:00
|
|
|
ILogger<JwtEvents> _logger { get; set; }
|
|
|
|
|
const string contentType = "application/json";
|
|
|
|
|
|
2026-10-03 11:49:55 +02:00
|
|
|
//a token outlives a ban, a removal or a demotion, so every request asks the database who the root is now
|
|
|
|
|
public override async Task TokenValidated(TokenValidatedContext context)
|
|
|
|
|
{
|
|
|
|
|
var rootId = context.Principal.GetUserId();
|
|
|
|
|
var root = string.IsNullOrEmpty(rootId)
|
|
|
|
|
? default
|
|
|
|
|
: await context.HttpContext.RequestServices.GetRequiredService<DbEntities>().RootUsers.MatchID(rootId)
|
|
|
|
|
.ExecuteFirstAsync(context.HttpContext.RequestAborted);
|
|
|
|
|
if (root is not { IsBanned: false, DeletedAt: null })
|
|
|
|
|
{
|
|
|
|
|
context.Fail("The account can no longer be used.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-10-04 03:48:32 +02:00
|
|
|
// a password recovery ends every session made before it (RootSessions gives the root a new stamp)
|
|
|
|
|
if ((root.SessionStamp ?? string.Empty) != (context.Principal.FindFirst(AuthTokenManager.SessionStamp)?.Value ?? string.Empty))
|
2026-10-04 03:16:59 +02:00
|
|
|
{
|
2026-10-04 03:48:32 +02:00
|
|
|
context.Fail("The account's sessions were ended.");
|
2026-10-04 03:16:59 +02:00
|
|
|
return;
|
|
|
|
|
}
|
2026-10-03 11:49:55 +02:00
|
|
|
if (context.Principal.Identity is not ClaimsIdentity identity)
|
|
|
|
|
return;
|
|
|
|
|
foreach (var policy in PolicyClaims)
|
|
|
|
|
{
|
|
|
|
|
foreach (var claim in identity.FindAll(policy).ToList())
|
|
|
|
|
identity.TryRemoveClaim(claim);
|
|
|
|
|
identity.AddClaim(new Claim(policy, root.Policies.Contains(policy) ? "true" : "false"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-03 10:46:20 +02:00
|
|
|
public override async Task Challenge(JwtBearerChallengeContext context)
|
2023-02-18 08:52:17 +01:00
|
|
|
{
|
2026-10-03 10:46:20 +02:00
|
|
|
context.HandleResponse();
|
|
|
|
|
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
|
|
|
|
context.Response.Headers.WWWAuthenticate = context.AuthenticateFailure == default ? "Bearer" : "Bearer error=\"invalid_token\"";
|
2023-02-18 08:52:17 +01:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var localizer = context.HttpContext.RequestServices.GetRequiredService<IStringLocalizer<GenericRes>>();
|
2026-10-01 11:52:31 +02:00
|
|
|
var webResult = new WebResult().Invalidate(localizer["Unauthorized."], StatusCodes.Status401Unauthorized);
|
2023-02-18 08:52:17 +01:00
|
|
|
context.Response.ContentType = contentType;
|
2026-10-03 11:49:55 +02:00
|
|
|
await context.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(webResult, Web)));
|
2023-02-18 08:52:17 +01:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtEvents>>();
|
2026-10-03 10:46:20 +02:00
|
|
|
_logger.LogError(ex, "Error at Challenge()");
|
2023-02-18 08:52:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override async Task Forbidden(ForbiddenContext context)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var localizer = context.HttpContext.RequestServices.GetRequiredService<IStringLocalizer<GenericRes>>();
|
2026-10-01 11:52:31 +02:00
|
|
|
var webResult = new WebResult().Invalidate(localizer["Forbidden."], StatusCodes.Status403Forbidden);
|
2023-02-18 08:52:17 +01:00
|
|
|
context.Response.ContentType = contentType;
|
2026-10-03 11:49:55 +02:00
|
|
|
await context.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(webResult, Web)));
|
2023-02-18 08:52:17 +01:00
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
_logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtEvents>>();
|
2026-10-03 10:46:20 +02:00
|
|
|
_logger.LogError(ex, "Error at Forbidden()");
|
2023-02-18 08:52:17 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|