Files
EnvelopeGenerator/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverAuthorizationService.cs
TekH 489d2808a1 Refactor EnvelopeReceiverPage for modular data handling
Refactored `EnvelopeReceiverPage.razor` to use new services for receiver authentication and data retrieval. Introduced `EnvelopeReceiverAuthorizationService` for handling JWT-based authorization and `EnvelopeReceiverPageDataService` for centralized data access and caching. Updated dependency injection in `Program.cs` to register these services.

Replaced direct service calls with `PageDataService` methods for document, signature, and receiver data retrieval. Improved logging with `ILogger` and added debug logs for token validation. Enhanced modularity, maintainability, and performance by consolidating logic and reducing coupling between components.
2026-06-29 01:26:43 +02:00

94 lines
3.6 KiB
C#

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;
/// <summary>
/// Authorizes receiver access for interactive server pages without calling a controller endpoint.
/// </summary>
public class EnvelopeReceiverAuthorizationService(
IHttpContextAccessor httpContextAccessor,
IAuthorizationService authorizationService,
IOptions<AuthTokenKeys> authTokenKeyOptions,
IOptionsMonitor<JwtBearerOptions> jwtBearerOptionsMonitor,
ILogger<EnvelopeReceiverAuthorizationService> logger)
{
private readonly AuthTokenKeys _authTokenKeys = authTokenKeyOptions.Value;
/// <summary>
/// Returns the authenticated receiver principal for the specified envelope key when authorization succeeds.
/// </summary>
public async Task<ClaimsPrincipal?> 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;
}
/// <summary>
/// Checks whether the current request is authorized for the specified envelope key.
/// </summary>
public async Task<bool> IsAuthorizedAsync(string envelopeKey, CancellationToken cancellationToken = default)
=> await AuthorizeAsync(envelopeKey, cancellationToken) is not null;
private async Task<bool> 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;
}
}
}