using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using DigitalData.Auth.Claims; using EnvelopeGenerator.Domain.Constants; using EnvelopeGenerator.Server.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Options; namespace EnvelopeGenerator.Server.Services; /// /// Authorizes receiver access for interactive server pages without calling a controller endpoint. /// public class EnvelopeReceiverAuthorizationService( IHttpContextAccessor httpContextAccessor, IAuthorizationService authorizationService, IOptions authTokenKeyOptions, IOptionsMonitor jwtBearerOptionsMonitor, ILogger logger) { private readonly AuthTokenKeys _authTokenKeys = authTokenKeyOptions.Value; /// /// Returns the authenticated receiver principal for the specified envelope key when authorization succeeds. /// public async Task AuthorizeAsync(string envelopeKey, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(envelopeKey)) return null; var httpContext = httpContextAccessor.HttpContext; if (httpContext is null) return null; if (await IsAuthorizedReceiverAsync(httpContext.User, envelopeKey, cancellationToken)) return httpContext.User; var cookieName = CookieNames.GetEnvelopeReceiverCookieName(_authTokenKeys.Cookie, envelopeKey); if (!httpContext.Request.Cookies.TryGetValue(cookieName, out var token) || string.IsNullOrWhiteSpace(token)) { logger.LogDebug("Receiver cookie '{CookieName}' was not found for envelope '{EnvelopeKey}'.", cookieName, envelopeKey); return null; } var principal = ValidateReceiverToken(token); if (principal is null) return null; if (!await IsAuthorizedReceiverAsync(principal, envelopeKey, cancellationToken)) return null; httpContext.User = principal; return principal; } /// /// Checks whether the current request is authorized for the specified envelope key. /// public async Task IsAuthorizedAsync(string envelopeKey, CancellationToken cancellationToken = default) => await AuthorizeAsync(envelopeKey, cancellationToken) is not null; private async Task IsAuthorizedReceiverAsync(ClaimsPrincipal? principal, string envelopeKey, CancellationToken cancellationToken) { if (principal?.Identity?.IsAuthenticated != true) return false; var authorizationResult = await authorizationService.AuthorizeAsync(principal, AuthPolicy.Receiver); if (!authorizationResult.Succeeded) return false; var subject = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? principal.FindFirst("sub")?.Value; return string.Equals(subject, envelopeKey, StringComparison.Ordinal); } private ClaimsPrincipal? ValidateReceiverToken(string token) { try { var tokenValidationParameters = jwtBearerOptionsMonitor.Get(AuthScheme.Receiver).TokenValidationParameters.Clone(); var tokenHandler = new JwtSecurityTokenHandler(); return tokenHandler.ValidateToken(token, tokenValidationParameters, out _); } catch (Exception ex) { logger.LogDebug(ex, "Receiver token validation failed."); return null; } } }