using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Localization; using PrivaPub.ClientModels; using PrivaPub.Extensions; using PrivaPub.Resources; using PrivaPub.StaticServices; using System.Security.Claims; using System.Text; using System.Text.Json; namespace PrivaPub.Services { public class JwtEvents : JwtBearerEvents { static readonly string[] PolicyClaims = { Policies.IsUser, Policies.IsModerator, Policies.IsAdmin }; static readonly JsonSerializerOptions Web = new(JsonSerializerDefaults.Web); ILogger _logger { get; set; } const string contentType = "application/json"; //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().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; } // a password recovery ends every session made before it (RootSessions) if (root.CredentialsChangedAt is { } changed && context.SecurityToken.ValidFrom < changed.AddSeconds(-1)) { context.Fail("The account's password was changed."); return; } 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")); } } public override async Task Challenge(JwtBearerChallengeContext context) { context.HandleResponse(); context.Response.StatusCode = StatusCodes.Status401Unauthorized; context.Response.Headers.WWWAuthenticate = context.AuthenticateFailure == default ? "Bearer" : "Bearer error=\"invalid_token\""; try { var localizer = context.HttpContext.RequestServices.GetRequiredService>(); var webResult = new WebResult().Invalidate(localizer["Unauthorized."], StatusCodes.Status401Unauthorized); context.Response.ContentType = contentType; await context.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(webResult, Web))); } catch (Exception ex) { _logger = context.HttpContext.RequestServices.GetRequiredService>(); _logger.LogError(ex, "Error at Challenge()"); } } public override async Task Forbidden(ForbiddenContext context) { try { var localizer = context.HttpContext.RequestServices.GetRequiredService>(); var webResult = new WebResult().Invalidate(localizer["Forbidden."], StatusCodes.Status403Forbidden); context.Response.ContentType = contentType; await context.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(webResult, Web))); } catch (Exception ex) { _logger = context.HttpContext.RequestServices.GetRequiredService>(); _logger.LogError(ex, "Error at Forbidden()"); } } } }