Introduced a new feature to allow users to reject envelopes with an optional reason. Added a `RejectAsync` method in `AnnotationService` to handle rejection requests. Updated `ReceiverPage.razor` to include a "Reject" button in the PDF toolbar and a rejection popup for user input. Implemented state management for the rejection popup, including visibility, submission status, and validation messages. Updated `AnnotationController` to accept `envelopeKey` as a route parameter for the rejection endpoint. Modified `ReceiverClaimExtensions` to support multiple email claim types. Added new CSS styles for the "Reject" button, including hover effects and dark theme support. Enhanced error handling and logging for the rejection process.
124 lines
4.8 KiB
C#
124 lines
4.8 KiB
C#
using DigitalData.Auth.Claims;
|
|
using DigitalData.UserManager.Domain.Entities;
|
|
using EnvelopeGenerator.Domain.Constants;
|
|
using Microsoft.IdentityModel.JsonWebTokens;
|
|
using System.Security.Claims;
|
|
|
|
namespace EnvelopeGenerator.Server.Extensions;
|
|
|
|
/// <summary>
|
|
/// Provides helper methods for working with envelope-specific authentication claims.
|
|
/// </summary>
|
|
public static class ReceiverClaimExtensions
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="user"></param>
|
|
/// <param name="claimType"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
private static string GetRequiredClaimValue(this ClaimsPrincipal user, string claimType)
|
|
{
|
|
var value = user.FindFirstValue(claimType);
|
|
if (value is not null)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
var identity = user.Identity;
|
|
var principalName = identity?.Name ?? "(anonymous)";
|
|
var authType = identity?.AuthenticationType ?? "(none)";
|
|
var availableClaims = string.Join(", ", user.Claims.Select(c => $"{c.Type}={c.Value}"));
|
|
var message = $"Required claim '{claimType}' is missing for user '{principalName}' (auth: {authType}). Available claims: [{availableClaims}].";
|
|
throw new InvalidOperationException(message);
|
|
}
|
|
|
|
private static string GetRequiredClaimValue(this ClaimsPrincipal user, params string[] claimTypes)
|
|
{
|
|
foreach (var claimType in claimTypes.Where(t => !string.IsNullOrWhiteSpace(t)).Distinct())
|
|
{
|
|
var value = user.FindFirstValue(claimType);
|
|
if (!string.IsNullOrWhiteSpace(value))
|
|
return value;
|
|
}
|
|
|
|
var identity = user.Identity;
|
|
var principalName = identity?.Name ?? "(anonymous)";
|
|
var authType = identity?.AuthenticationType ?? "(none)";
|
|
var availableClaims = string.Join(", ", user.Claims.Select(c => $"{c.Type}={c.Value}"));
|
|
var message = $"Required claim(s) '{string.Join("', '", claimTypes)}' are missing for user '{principalName}' (auth: {authType}). Available claims: [{availableClaims}].";
|
|
throw new InvalidOperationException(message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated envelope UUID from the claims.
|
|
/// </summary>
|
|
public static string EnvelopeUuid(this ClaimsPrincipal user)
|
|
=> user.GetRequiredClaimValue(EnvelopeClaimNames.EnvelopeUuid);
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated receiver signature from the claims.
|
|
/// </summary>
|
|
public static string ReceiverSignature(this ClaimsPrincipal user)
|
|
=> user.GetRequiredClaimValue(EnvelopeClaimNames.ReceiverSignature);
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated receiver email address from the claims.
|
|
/// </summary>
|
|
public static string ReceiverMail(this ClaimsPrincipal user)
|
|
=> user.GetRequiredClaimValue(JwtRegisteredClaimNames.Email, ClaimTypes.Email);
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated envelope identifier from the claims.
|
|
/// </summary>
|
|
public static int EnvelopeId(this ClaimsPrincipal user)
|
|
{
|
|
var envIdStr = user.GetRequiredClaimValue(EnvelopeClaimNames.EnvelopeId);
|
|
if (int.TryParse(envIdStr, out var envId))
|
|
return envId;
|
|
else
|
|
throw new InvalidOperationException($"Claim '{EnvelopeClaimNames.EnvelopeId}' is not a valid integer.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated receiver identifier from the claims.
|
|
/// </summary>
|
|
/// <param name="user"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public static int ReceiverId(this ClaimsPrincipal user)
|
|
{
|
|
var rcvIdStr = user.GetRequiredClaimValue(EnvelopeClaimNames.ReceiverId);
|
|
if (int.TryParse(rcvIdStr, out var rcvId))
|
|
return rcvId;
|
|
else
|
|
throw new InvalidOperationException($"Claim '{EnvelopeClaimNames.ReceiverId}' is not a valid integer.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated sender identifier from the 'sub' claim.
|
|
/// </summary>
|
|
/// <param name="user"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public static int SenderId(this ClaimsPrincipal user)
|
|
{
|
|
var senderIdStr = user.GetRequiredClaimValue(JwtRegisteredClaimNames.Sub, ClaimTypes.NameIdentifier);
|
|
if (int.TryParse(senderIdStr, out var senderId))
|
|
return senderId;
|
|
else
|
|
throw new InvalidOperationException($"Claim '{JwtRegisteredClaimNames.Sub}' is not a valid integer.");
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="user"></param>
|
|
/// <returns></returns>
|
|
public static bool IsReceiver(this ClaimsPrincipal user)
|
|
{
|
|
return Role.Receiver.Authorized.Any(user.IsInRole);
|
|
}
|
|
}
|