From 4dca17d39ca552d794a97e7d02a089694f8570ad Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 22 Jun 2026 14:56:57 +0200 Subject: [PATCH] Add claim extension methods for user authentication Introduce `ReceiverClaimExtensions` and `SenderClaimExtensions` classes in the `EnvelopeGenerator.API.Extensions` namespace. These classes provide methods to extract specific claims from a `ClaimsPrincipal` object, aiding in user authentication. In `ReceiverClaimExtensions.cs`, add methods to retrieve envelope-specific claims such as `EnvelopeUuid`, `ReceiverSignature`, `ReceiverMail`, `EnvelopeId`, and `ReceiverId`. Implement `GetRequiredClaimValue` to handle missing claims. In `SenderClaimExtensions.cs`, add methods to extract sender-related claims like `GetId`, `GetUsername`, `GetName`, `GetPrename`, and `GetEmail`. Implement `GetRequiredClaimOfSender` for handling missing claims. Both classes include XML documentation for clarity on method usage and exceptions. --- .../Extensions/ReceiverClaimExtensions.cs | 96 +++++++++++++++++++ .../Extensions/SenderClaimExtensions.cs | 95 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Extensions/ReceiverClaimExtensions.cs create mode 100644 EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Extensions/SenderClaimExtensions.cs diff --git a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Extensions/ReceiverClaimExtensions.cs b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Extensions/ReceiverClaimExtensions.cs new file mode 100644 index 00000000..d851c1de --- /dev/null +++ b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Extensions/ReceiverClaimExtensions.cs @@ -0,0 +1,96 @@ +using DigitalData.Auth.Claims; +using Microsoft.IdentityModel.JsonWebTokens; +using System.Security.Claims; + +namespace EnvelopeGenerator.API.Extensions; + +/// +/// Provides helper methods for working with envelope-specific authentication claims. +/// +public static class ReceiverClaimExtensions +{ + /// + /// + /// + /// + /// + /// + /// + 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); + } + + /// + /// Gets the authenticated envelope UUID from the claims. + /// + public static string EnvelopeUuid(this ClaimsPrincipal user) + => user.GetRequiredClaimValue(EnvelopeClaimNames.EnvelopeUuid); + + /// + /// Gets the authenticated receiver signature from the claims. + /// + public static string ReceiverSignature(this ClaimsPrincipal user) + => user.GetRequiredClaimValue(EnvelopeClaimNames.ReceiverSignature); + + /// + /// Gets the authenticated receiver email address from the claims. + /// + public static string ReceiverMail(this ClaimsPrincipal user) + => user.GetRequiredClaimValue(JwtRegisteredClaimNames.Email); + + /// + /// Gets the authenticated envelope identifier from the claims. + /// + 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."); + } + + /// + /// Gets the authenticated receiver identifier from the claims. + /// + /// + /// + /// + 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."); + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Extensions/SenderClaimExtensions.cs b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Extensions/SenderClaimExtensions.cs new file mode 100644 index 00000000..262968ed --- /dev/null +++ b/EnvelopeGenerator.WebUI/EnvelopeGenerator.WebUI/Extensions/SenderClaimExtensions.cs @@ -0,0 +1,95 @@ +using System.Security.Claims; + +namespace EnvelopeGenerator.API.Extensions +{ + /// + /// Provides extension methods for extracting user information from a . + /// + public static class SenderClaimExtensions + { + private static string GetRequiredClaimOfSender(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 GetRequiredClaimOfSender(this ClaimsPrincipal user, params string[] claimTypes) + { + string? value = null; + + foreach (var claimType in claimTypes) + { + 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 among [{string.Join(", ", claimTypes)}] is missing for user '{principalName}' (auth: {authType}). Available claims: [{availableClaims}]."; + throw new InvalidOperationException(message); + } + + /// + /// Retrieves the user's ID from the claims. Throws an exception if the ID is missing or invalid. + /// + /// The representing the user. + /// The user's ID as an integer. + /// Thrown if the user ID claim is missing or invalid. + public static int GetId(this ClaimsPrincipal user) + { + var idValue = user.GetRequiredClaimOfSender(ClaimTypes.NameIdentifier, "sub"); + + if (!int.TryParse(idValue, out var result)) + { + throw new InvalidOperationException("User ID claim is missing or invalid. This may indicate a misconfigured or forged JWT token."); + } + + return result; + } + + /// + /// Retrieves the username from the claims. + /// + /// The representing the user. + /// The username as a string. + public static string GetUsername(this ClaimsPrincipal user) + => user.GetRequiredClaimOfSender(ClaimTypes.Name); + + /// + /// Retrieves the user's surname (last name) from the claims. + /// + /// The representing the user. + /// The surname as a string. + public static string GetName(this ClaimsPrincipal user) + => user.GetRequiredClaimOfSender(ClaimTypes.Surname); + + /// + /// Retrieves the user's given name (first name) from the claims. + /// + /// The representing the user. + /// The given name as a string. + public static string GetPrename(this ClaimsPrincipal user) + => user.GetRequiredClaimOfSender(ClaimTypes.GivenName); + + /// + /// Retrieves the user's email address from the claims. + /// + /// The representing the user. + /// The email address as a string. + public static string GetEmail(this ClaimsPrincipal user) + => user.GetRequiredClaimOfSender(ClaimTypes.Email); + } +} \ No newline at end of file