using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using PrivaPub.Extensions; using PrivaPub.Models.User; using PrivaPub.StaticServices; using System.Security.Claims; using System.Text; namespace PrivaPub.Services { // The /clientapi token: how it is validated (JwtBearer through AddPrivaPubAuth, and the persona token exchange on // /oauth/token), and when a valid one can no longer be used. One place, so the two never drift apart. public static class RootJwt { public static TokenValidationParameters Parameters(IConfiguration configuration) => new() { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = configuration["AppConfiguration:Jwt:Issuer"], ValidAudience = configuration["AppConfiguration:Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["AppConfiguration:Jwt:Key"])) }; //why a token that validated can no longer be used, or default when it can: a token outlives a ban, a removal and a //password recovery (RootSessions gives the root a new stamp), so the database has the last word public static string Refusal(RootUser root, ClaimsPrincipal principal) { if (root is not { IsBanned: false, DeletedAt: null }) return "The account can no longer be used."; if ((root.SessionStamp ?? string.Empty) != (principal.FindFirst(AuthTokenManager.SessionStamp)?.Value ?? string.Empty)) return "The account's sessions were ended."; return default; } //the root a token belongs to, when the token is valid and can still be used; default otherwise, never an exception public static async Task Root(string jwt, IConfiguration configuration, DbEntities dbEntities, CancellationToken token) { if (string.IsNullOrEmpty(jwt)) return default; var result = await new JsonWebTokenHandler { MapInboundClaims = false }.ValidateTokenAsync(jwt, Parameters(configuration)); if (!result.IsValid) return default; var principal = new ClaimsPrincipal(result.ClaimsIdentity); var rootId = principal.GetUserId(); var root = string.IsNullOrEmpty(rootId) ? default : await dbEntities.RootUsers.MatchID(rootId).ExecuteFirstAsync(token); return Refusal(root, principal) == default ? root : default; } } }