using System.Linq;
using System.Security.Claims;
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
namespace EnvelopeGenerator.API.Extensions;
///
/// Provides helper methods for working with envelope-specific authentication claims.
///
public static class ReceiverClaimExtensions
{
private static readonly string[] EnvelopeIdClaimTypes = [EnvelopeClaimTypes.Id, "envelope_id", "EnvelopeId"];
private static readonly string[] ReceiverIdClaimTypes = ["receiver_id", "ReceiverId"];
private static readonly string[] EnvelopeUuidClaimTypes = [ClaimTypes.NameIdentifier, "envelope_uuid", "EnvelopeUuid"];
private static readonly string[] ReceiverSignatureClaimTypes = [ClaimTypes.Hash, "receiver_sig", "ReceiverSignature"];
private static string GetRequiredClaimOfReceiver(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 GetRequiredClaimOfReceiver(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);
}
///
/// Gets the authenticated envelope UUID from the claims.
///
public static string GetEnvelopeUuidOfReceiver(this ClaimsPrincipal user) => user.GetRequiredClaimOfReceiver(EnvelopeUuidClaimTypes);
///
/// Gets the authenticated receiver signature from the claims.
///
public static string GetReceiverSignatureOfReceiver(this ClaimsPrincipal user) => user.GetRequiredClaimOfReceiver(ReceiverSignatureClaimTypes);
///
/// Gets the authenticated receiver display name from the claims.
///
public static string GetReceiverNameOfReceiver(this ClaimsPrincipal user) => user.GetRequiredClaimOfReceiver(ClaimTypes.Name);
///
/// Gets the authenticated receiver email address from the claims.
///
public static string GetReceiverMailOfReceiver(this ClaimsPrincipal user) => user.GetRequiredClaimOfReceiver(ClaimTypes.Email);
///
/// Gets the authenticated envelope title from the claims.
///
public static string GetEnvelopeTitleOfReceiver(this ClaimsPrincipal user) => user.GetRequiredClaimOfReceiver(EnvelopeClaimTypes.Title);
///
/// Gets the authenticated envelope identifier from the claims.
///
public static int GetEnvelopeIdOfReceiver(this ClaimsPrincipal user)
{
var envIdStr = user.GetRequiredClaimOfReceiver(EnvelopeIdClaimTypes);
if (!int.TryParse(envIdStr, out var envId))
{
throw new InvalidOperationException($"Claim '{"envelope_id"}' is not a valid integer.");
}
return envId;
}
///
///
///
///
///
///
public static int GetReceiverIdOfReceiver(this ClaimsPrincipal user)
{
var rcvIdStr = user.GetRequiredClaimOfReceiver(ReceiverIdClaimTypes);
if (!int.TryParse(rcvIdStr, out var rcvId))
{
throw new InvalidOperationException($"Claim '{"receiver_id"}' is not a valid integer.");
}
return rcvId;
}
///
/// Signs in an envelope receiver using cookie authentication and attaches envelope claims.
///
/// The current HTTP context.
/// Envelope receiver DTO to extract claims from.
/// Role to attach to the authentication ticket.
public static async Task SignInEnvelopeAsync(this HttpContext context, EnvelopeReceiverDto envelopeReceiver, string receiverRole)
{
var claims = new List
{
new(ClaimTypes.NameIdentifier, envelopeReceiver.Envelope!.Uuid),
new(ClaimTypes.Hash, envelopeReceiver.Receiver!.Signature),
new(ClaimTypes.Name, envelopeReceiver.Name ?? string.Empty),
new(ClaimTypes.Email, envelopeReceiver.Receiver.EmailAddress),
new(EnvelopeClaimTypes.Title, envelopeReceiver.Envelope.Title),
new(EnvelopeClaimTypes.Id, envelopeReceiver.Envelope.Id.ToString()),
new(ClaimTypes.Role, receiverRole)
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties
{
AllowRefresh = false,
IsPersistent = false
};
await context.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
authProperties);
}
}