Refactored EnvelopeAuthExtensions to require presence of all key authentication claims. Added GetRequiredClaim helper that throws detailed exceptions if claims are missing or invalid, replacing nullable return types with non-nullable ones. This ensures authentication logic fails fast and provides clearer error messages when claims are misconfigured or tampered with.
109 lines
4.5 KiB
C#
109 lines
4.5 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Provides helper methods for working with envelope-specific authentication claims.
|
|
/// </summary>
|
|
public static class EnvelopeAuthExtensions
|
|
{
|
|
private static string GetRequiredClaim(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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a claim value by type.
|
|
/// </summary>
|
|
/// <param name="user">The current claims principal.</param>
|
|
/// <param name="claimType">The claim type to resolve.</param>
|
|
/// <returns>The claim value.</returns>
|
|
public static string GetClaimValue(this ClaimsPrincipal user, string claimType) => user.GetRequiredClaim(claimType);
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated envelope UUID from the claims.
|
|
/// </summary>
|
|
public static string GetAuthEnvelopeUuid(this ClaimsPrincipal user) => user.GetRequiredClaim(ClaimTypes.NameIdentifier);
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated receiver signature from the claims.
|
|
/// </summary>
|
|
public static string GetAuthReceiverSignature(this ClaimsPrincipal user) => user.GetRequiredClaim(ClaimTypes.Hash);
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated receiver display name from the claims.
|
|
/// </summary>
|
|
public static string GetAuthReceiverName(this ClaimsPrincipal user) => user.GetRequiredClaim(ClaimTypes.Name);
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated receiver email address from the claims.
|
|
/// </summary>
|
|
public static string GetAuthReceiverMail(this ClaimsPrincipal user) => user.GetRequiredClaim(ClaimTypes.Email);
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated envelope title from the claims.
|
|
/// </summary>
|
|
public static string GetAuthEnvelopeTitle(this ClaimsPrincipal user) => user.GetRequiredClaim(EnvelopeClaimTypes.Title);
|
|
|
|
/// <summary>
|
|
/// Gets the authenticated envelope identifier from the claims.
|
|
/// </summary>
|
|
public static int GetAuthEnvelopeId(this ClaimsPrincipal user)
|
|
{
|
|
var envIdStr = user.GetRequiredClaim(EnvelopeClaimTypes.Id);
|
|
if (!int.TryParse(envIdStr, out var envId))
|
|
{
|
|
throw new InvalidOperationException($"Claim '{EnvelopeClaimTypes.Id}' is not a valid integer.");
|
|
}
|
|
|
|
return envId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Signs in an envelope receiver using cookie authentication and attaches envelope claims.
|
|
/// </summary>
|
|
/// <param name="context">The current HTTP context.</param>
|
|
/// <param name="envelopeReceiver">Envelope receiver DTO to extract claims from.</param>
|
|
/// <param name="receiverRole">Role to attach to the authentication ticket.</param>
|
|
public static async Task SignInEnvelopeAsync(this HttpContext context, EnvelopeReceiverDto envelopeReceiver, string receiverRole)
|
|
{
|
|
var claims = new List<Claim>
|
|
{
|
|
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);
|
|
}
|
|
} |