From 0f53d0509e37a67fd91e892e9dee1a56a2d84570 Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 28 Sep 2026 09:51:51 +0200 Subject: [PATCH 1/2] remove depr API --- EnvelopeGenerator.API/AuthScheme.cs | 17 - .../Controllers/AnnotationController.cs | 132 ------- .../Controllers/AuthController.cs | 117 ------ .../Controllers/CacheController.cs | 84 ----- .../Controllers/ConfigController.cs | 31 -- .../DocReceiverElementController.cs | 57 --- .../Controllers/DocumentController.cs | 84 ----- .../Controllers/EmailTemplateController.cs | 69 ---- .../Controllers/EnvelopeController.cs | 111 ------ .../Controllers/EnvelopeReceiverController.cs | 281 -------------- .../Controllers/EnvelopeTypeController.cs | 39 -- .../Controllers/HistoryController.cs | 118 ------ .../Controllers/Interfaces/IAuthController.cs | 38 -- .../Controllers/LocalizationController.cs | 121 ------ .../Controllers/ReadOnlyController.cs | 91 ----- .../Controllers/ReceiverController.cs | 47 --- .../Controllers/TfaRegistrationController.cs | 129 ------- .../Documentation/AuthProxyDocumentFilter.cs | 123 ------ .../EnvelopeGenerator.API.csproj | 84 ----- .../Extensions/ReceiverClaimExtensions.cs | 96 ----- .../Extensions/SenderClaimExtensions.cs | 95 ----- EnvelopeGenerator.API/Jenkinsfile | 10 - .../Middleware/ExceptionHandlingMiddleware.cs | 84 ----- EnvelopeGenerator.API/Models/Auth.cs | 14 - EnvelopeGenerator.API/Models/AuthTokenKeys.cs | 28 -- .../Models/ConnectionString.cs | 12 - EnvelopeGenerator.API/Models/ContactLink.cs | 60 --- EnvelopeGenerator.API/Models/Culture.cs | 17 - EnvelopeGenerator.API/Models/Cultures.cs | 12 - EnvelopeGenerator.API/Models/CustomImages.cs | 6 - .../Models/EnvelopeReceiverLogin.cs | 7 - .../Models/ErrorViewModel.cs | 10 - EnvelopeGenerator.API/Models/Image.cs | 10 - EnvelopeGenerator.API/Models/Login.cs | 13 - EnvelopeGenerator.API/Models/MainViewModel.cs | 6 - .../Models/PsPdfKitAnnotation/Annotation.cs | 93 ----- .../PsPdfKitAnnotation/AnnotationParams.cs | 80 ---- .../Models/PsPdfKitAnnotation/Background.cs | 58 --- .../Models/PsPdfKitAnnotation/Color.cs | 10 - .../Models/PsPdfKitAnnotation/Extensions.cs | 8 - .../Models/PsPdfKitAnnotation/IAnnotation.cs | 22 -- EnvelopeGenerator.API/Models/TFARegParams.cs | 17 - EnvelopeGenerator.API/Options/CacheOptions.cs | 18 - EnvelopeGenerator.API/Program.cs | 355 ------------------ .../PublishProfiles/IISProfileNet8.pubxml | 20 - .../Properties/launchSettings.json | 51 --- .../appsettings.Development.json | 29 -- EnvelopeGenerator.API/appsettings.json | 256 ------------- EnvelopeGenerator.API/yarp.json | 185 --------- EnvelopeGenerator.sln | 7 - 50 files changed, 3462 deletions(-) delete mode 100644 EnvelopeGenerator.API/AuthScheme.cs delete mode 100644 EnvelopeGenerator.API/Controllers/AnnotationController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/AuthController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/CacheController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/ConfigController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/DocReceiverElementController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/DocumentController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/EmailTemplateController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/EnvelopeController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/EnvelopeReceiverController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/EnvelopeTypeController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/HistoryController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/Interfaces/IAuthController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/LocalizationController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/ReadOnlyController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/ReceiverController.cs delete mode 100644 EnvelopeGenerator.API/Controllers/TfaRegistrationController.cs delete mode 100644 EnvelopeGenerator.API/Documentation/AuthProxyDocumentFilter.cs delete mode 100644 EnvelopeGenerator.API/EnvelopeGenerator.API.csproj delete mode 100644 EnvelopeGenerator.API/Extensions/ReceiverClaimExtensions.cs delete mode 100644 EnvelopeGenerator.API/Extensions/SenderClaimExtensions.cs delete mode 100644 EnvelopeGenerator.API/Jenkinsfile delete mode 100644 EnvelopeGenerator.API/Middleware/ExceptionHandlingMiddleware.cs delete mode 100644 EnvelopeGenerator.API/Models/Auth.cs delete mode 100644 EnvelopeGenerator.API/Models/AuthTokenKeys.cs delete mode 100644 EnvelopeGenerator.API/Models/ConnectionString.cs delete mode 100644 EnvelopeGenerator.API/Models/ContactLink.cs delete mode 100644 EnvelopeGenerator.API/Models/Culture.cs delete mode 100644 EnvelopeGenerator.API/Models/Cultures.cs delete mode 100644 EnvelopeGenerator.API/Models/CustomImages.cs delete mode 100644 EnvelopeGenerator.API/Models/EnvelopeReceiverLogin.cs delete mode 100644 EnvelopeGenerator.API/Models/ErrorViewModel.cs delete mode 100644 EnvelopeGenerator.API/Models/Image.cs delete mode 100644 EnvelopeGenerator.API/Models/Login.cs delete mode 100644 EnvelopeGenerator.API/Models/MainViewModel.cs delete mode 100644 EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Annotation.cs delete mode 100644 EnvelopeGenerator.API/Models/PsPdfKitAnnotation/AnnotationParams.cs delete mode 100644 EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Background.cs delete mode 100644 EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Color.cs delete mode 100644 EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Extensions.cs delete mode 100644 EnvelopeGenerator.API/Models/PsPdfKitAnnotation/IAnnotation.cs delete mode 100644 EnvelopeGenerator.API/Models/TFARegParams.cs delete mode 100644 EnvelopeGenerator.API/Options/CacheOptions.cs delete mode 100644 EnvelopeGenerator.API/Program.cs delete mode 100644 EnvelopeGenerator.API/Properties/PublishProfiles/IISProfileNet8.pubxml delete mode 100644 EnvelopeGenerator.API/Properties/launchSettings.json delete mode 100644 EnvelopeGenerator.API/appsettings.Development.json delete mode 100644 EnvelopeGenerator.API/appsettings.json delete mode 100644 EnvelopeGenerator.API/yarp.json diff --git a/EnvelopeGenerator.API/AuthScheme.cs b/EnvelopeGenerator.API/AuthScheme.cs deleted file mode 100644 index 96e3b90d..00000000 --- a/EnvelopeGenerator.API/AuthScheme.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace EnvelopeGenerator.API; - -/// -/// -/// -public static class AuthScheme -{ - /// - /// Scheme name used for per-envelope receiver JWT authentication. - /// - public const string Receiver = "EnvelopeGenerator.API.ReceiverJWT"; - - /// - /// Scheme name used for per-envelope sender JWT authentication. - /// - public const string Sender = "EnvelopeGenerator.API.SenderJWT"; -} diff --git a/EnvelopeGenerator.API/Controllers/AnnotationController.cs b/EnvelopeGenerator.API/Controllers/AnnotationController.cs deleted file mode 100644 index e0698b84..00000000 --- a/EnvelopeGenerator.API/Controllers/AnnotationController.cs +++ /dev/null @@ -1,132 +0,0 @@ -using DigitalData.Core.Abstraction.Application.DTO; -using DigitalData.Core.Exceptions; -using EnvelopeGenerator.API.Extensions; -using EnvelopeGenerator.Application.Common.Dto; -using EnvelopeGenerator.Application.Common.Extensions; -using EnvelopeGenerator.Application.Common.Interfaces.Services; -using EnvelopeGenerator.Application.Common.Notifications.DocSigned; -using EnvelopeGenerator.Application.Common.Notifications.RemoveSignature; -using EnvelopeGenerator.Application.EnvelopeReceivers.Queries; -using EnvelopeGenerator.Application.Histories.Queries; -using EnvelopeGenerator.Domain.Constants; -using MediatR; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Manages annotations and signature lifecycle for envelopes. -/// -[Authorize(Policy = AuthPolicy.Receiver)] -[ApiController] -[Route("api/[controller]")] -public class AnnotationController : ControllerBase -{ - [Obsolete("Use MediatR")] - private readonly IEnvelopeHistoryService _historyService; - - [Obsolete("Use MediatR")] - private readonly IEnvelopeReceiverService _envelopeReceiverService; - - private readonly IMediator _mediator; - - private readonly ILogger _logger; - - /// - /// Initializes a new instance of . - /// - [Obsolete("Use MediatR")] - public AnnotationController( - ILogger logger, - IEnvelopeHistoryService envelopeHistoryService, - IEnvelopeReceiverService envelopeReceiverService, - IMediator mediator) - { - _historyService = envelopeHistoryService; - _envelopeReceiverService = envelopeReceiverService; - _mediator = mediator; - _logger = logger; - } - - /// - /// Creates or updates annotations for the authenticated envelope receiver. - /// - /// Annotation payload. - /// Cancellation token. - [Authorize(Policy = AuthPolicy.Receiver)] - [HttpPost] - [Obsolete("PSPDF Kit will no longer be used.")] - public async Task CreateOrUpdate([FromBody] PsPdfKitAnnotation? psPdfKitAnnotation = null, CancellationToken cancel = default) - { - var signature = User.ReceiverSignature(); - var uuid = User.EnvelopeUuid(); - - var envelopeReceiver = await _mediator.ReadEnvelopeReceiverAsync(uuid, signature, cancel).ThrowIfNull(Exceptions.NotFound); - - if (!envelopeReceiver.Envelope!.ReadOnly && psPdfKitAnnotation is null) - return BadRequest(); - - if (await _mediator.IsSignedAsync(uuid, signature, cancel)) - return Problem(statusCode: StatusCodes.Status409Conflict); - else if (await _mediator.AnyHistoryAsync(uuid, new[] { EnvelopeStatus.EnvelopeRejected, EnvelopeStatus.DocumentRejected }, cancel)) - return Problem(statusCode: StatusCodes.Status423Locked); - - var envelopeReceiverDto = await _mediator.ReadEnvelopeReceiverAsync(uuid, signature, cancel); - var docSignedNotification = envelopeReceiverDto is not null - ? new DocSignedNotification { EnvelopeReceiver = envelopeReceiverDto, PsPdfKitAnnotation = psPdfKitAnnotation } - : throw new NotFoundException("Envelope receiver is not found."); - - try - { - await _mediator.Publish(docSignedNotification, cancel); - } - catch (Exception) - { - await _mediator.Publish(new RemoveSignatureNotification() - { - EnvelopeId = docSignedNotification.EnvelopeReceiver.EnvelopeId, - ReceiverId = docSignedNotification.EnvelopeReceiver.ReceiverId - }, cancel); - throw; - } - await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); - - return Ok(); - } - - /// - /// Rejects the document for the current receiver. - /// - /// Optional rejection reason. - [Authorize(Policy = AuthPolicy.Receiver)] - [HttpPost("reject")] - [Obsolete("Use MediatR")] - public async Task Reject([FromBody] string? reason = null) - { - var signature = User.ReceiverSignature(); - var uuid = User.EnvelopeUuid(); - var mail = User.ReceiverMail(); - - var envRcvRes = await _envelopeReceiverService.ReadByUuidSignatureAsync(uuid: uuid, signature: signature); - - if (envRcvRes.IsFailed) - { - _logger.LogNotice(envRcvRes.Notices); - return Unauthorized("you are not authorized"); - } - - var histRes = await _historyService.RecordAsync(envRcvRes.Data.EnvelopeId, userReference: mail, EnvelopeStatus.DocumentRejected, comment: reason); - if (histRes.IsSuccess) - { - await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); - return NoContent(); - } - - _logger.LogEnvelopeError(uuid: uuid, signature: signature, message: "Unexpected error happened in api/envelope/reject"); - _logger.LogNotice(histRes.Notices); - return StatusCode(500, histRes.Messages); - } -} diff --git a/EnvelopeGenerator.API/Controllers/AuthController.cs b/EnvelopeGenerator.API/Controllers/AuthController.cs deleted file mode 100644 index 56e710d8..00000000 --- a/EnvelopeGenerator.API/Controllers/AuthController.cs +++ /dev/null @@ -1,117 +0,0 @@ -using DigitalData.Auth.Claims; -using EnvelopeGenerator.API.Controllers.Interfaces; -using EnvelopeGenerator.API.Models; -using EnvelopeGenerator.Domain.Constants; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Controller verantwortlich für die Benutzer-Authentifizierung, einschließlich Anmelden, Abmelden und Überprüfung des Authentifizierungsstatus. -/// -[Route("api/[controller]")] -[ApiController] -public partial class AuthController(IOptions authTokenKeyOptions, IAuthorizationService authService) : ControllerBase, IAuthController -{ - private readonly AuthTokenKeys authTokenKeys = authTokenKeyOptions.Value; - - /// - /// - /// - public IAuthorizationService AuthService { get; } = authService; - - /// - /// Entfernt das Authentifizierungs-Cookie des Benutzers (AuthCookie) - /// - /// - /// Gibt eine HTTP 200 oder 401. - /// - /// - /// Sample request: - /// - /// POST /api/auth/logout - /// - /// - /// Erfolgreich gelöscht, wenn der Benutzer ein berechtigtes Cookie hat. - /// Wenn es kein zugelassenes Cookie gibt, wird „nicht zugelassen“ zurückgegeben. - [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] - [Authorize(AuthenticationSchemes = AuthScheme.Sender)] - [HttpPost("logout")] - public async Task Logout() - { - if (await this.IsUserInPolicyAsync(AuthPolicy.Sender)) - Response.Cookies.Delete(authTokenKeys.Cookie); - else if (await this.IsUserInPolicyAsync(AuthPolicy.ReceiverOrReceiverTFA)) - await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); - else - return Unauthorized(); - - return Ok(); - } - - /// - /// Prüft, ob der Benutzer ein autorisiertes Token hat. - /// - /// Wenn ein autorisiertes Token vorhanden ist HTTP 200 asynchron 401 - /// - /// Sample request: - /// - /// GET /api/auth - /// - /// - /// Wenn es einen autorisierten Cookie gibt. - /// Wenn kein Cookie vorhanden ist oder nicht autorisierte. - [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] - [HttpGet("check")] - [Authorize(AuthenticationSchemes = AuthScheme.Sender)] - public IActionResult Check(string? role = null) - => role is not null && !User.IsInRole(role) - ? Unauthorized() - : Ok(); - - /// - /// Checks whether the caller holds a valid per-envelope receiver token for the given envelope key. - /// The request must carry a cookie named AuthTokenSignFLOWReceiver.{envelopeKey}. - /// - /// The unique envelope key extracted from the route. - /// Valid per-envelope token found. - /// Token is missing, expired or invalid. - [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] - [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] - [Authorize(Policy = AuthPolicy.Receiver)] - [HttpGet("check/envelope/{envelopeKey}")] - public IActionResult CheckEnvelopeReceiver([FromRoute] string envelopeKey) => Ok(); - - /// - /// Removes the per-envelope receiver cookie for the given envelope key. - /// - /// The unique envelope key whose cookie should be deleted. - /// Cookie successfully deleted. - [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] - [HttpPost("logout/envelope/{envelopeKey}")] - public IActionResult LogoutEnvelopeReceiver([FromRoute] string envelopeKey) - { - var cookieName = CookieNames.GetEnvelopeReceiverCookieName(authTokenKeys.Cookie, envelopeKey); - Response.Cookies.Delete(cookieName); - return Ok(); - } - - /// - /// Removes all per-envelope receiver cookies from the current request. - /// - /// All envelope receiver cookies successfully deleted. - [ProducesResponseType(typeof(void), StatusCodes.Status200OK)] - [HttpPost("logout/envelope")] - public IActionResult LogoutAllEnvelopeReceivers() - { - foreach (var cookieName in Request.Cookies.Keys.Where(k => CookieNames.IsEnvelopeReceiverCookie(k, authTokenKeys.Cookie))) - Response.Cookies.Delete(cookieName); - return Ok(); - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Controllers/CacheController.cs b/EnvelopeGenerator.API/Controllers/CacheController.cs deleted file mode 100644 index 8379d4eb..00000000 --- a/EnvelopeGenerator.API/Controllers/CacheController.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Caching.Distributed; -using Microsoft.Extensions.Options; -using System.Text.Json; -using EnvelopeGenerator.API.Options; -using EnvelopeGenerator.Domain.Constants; -using EnvelopeGenerator.API.Extensions; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Manages cached data for receivers using distributed cache. -/// -[ApiController] -[Route("api/[controller]")] -[Authorize(Policy = AuthPolicy.Receiver)] -public class CacheController( - IDistributedCache cache, - IOptions cacheOptions) : ControllerBase -{ - private const string SignatureCacheKeyPrefix = "envelope-generator.receiver-ui.signature:"; - - /// - /// Stores a receiver's signature in cache for the specified envelope. - /// - [Authorize(Policy = AuthPolicy.Receiver)] - [HttpPost("SignatureCapture/{envelopeKey}")] - public async Task SaveSignature( - [FromRoute] string envelopeKey, - [FromBody] SignatureCacheRequest request, - CancellationToken cancel) - { - var cacheKey = $"{SignatureCacheKeyPrefix}{User.ReceiverSignature()}"; - var json = JsonSerializer.Serialize(request); - - var options = cacheOptions.Value.SignatureCacheExpiration.HasValue - ? new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = cacheOptions.Value.SignatureCacheExpiration.Value } - : null; - - await cache.SetStringAsync(cacheKey, json, options ?? new DistributedCacheEntryOptions(), cancel); - - return Ok(); - } - - /// - /// Retrieves a cached signature for the specified envelope. - /// - [Authorize(Policy = AuthPolicy.Receiver)] - [HttpGet("SignatureCapture/{envelopeKey}")] - public async Task GetSignature([FromRoute] string envelopeKey, CancellationToken cancel) - { - var cacheKey = $"{SignatureCacheKeyPrefix}{User.ReceiverSignature()}"; - var json = await cache.GetStringAsync(cacheKey, cancel); - - if (json is null) - return NotFound(); - - var signature = JsonSerializer.Deserialize(json); - return Ok(signature); - } - - /// - /// Deletes a cached signature for the specified envelope. - /// - [Authorize(Policy = AuthPolicy.Receiver)] - [HttpDelete("SignatureCapture/{envelopeKey}")] - public async Task DeleteSignature([FromRoute] string envelopeKey, CancellationToken cancel) - { - var cacheKey = $"{SignatureCacheKeyPrefix}{User.ReceiverSignature()}"; - await cache.RemoveAsync(cacheKey, cancel); - - return Ok(); - } -} - -/// -/// Request model for caching signature data. -/// -public sealed record SignatureCacheRequest( - string DataUrl, - string FullName, - string Place, - string? Position = null); \ No newline at end of file diff --git a/EnvelopeGenerator.API/Controllers/ConfigController.cs b/EnvelopeGenerator.API/Controllers/ConfigController.cs deleted file mode 100644 index 117252a2..00000000 --- a/EnvelopeGenerator.API/Controllers/ConfigController.cs +++ /dev/null @@ -1,31 +0,0 @@ -using EnvelopeGenerator.API.Models.PsPdfKitAnnotation; -using EnvelopeGenerator.Domain.Constants; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Exposes configuration data required by the client applications. -/// -/// -/// Initializes a new instance of . -/// -[Route("api/[controller]")] -[ApiController] -[Authorize(Policy = AuthPolicy.SenderOrReceiver)] -public class ConfigController(IOptionsMonitor annotationParamsOptions) : ControllerBase -{ - private readonly AnnotationParams _annotationParams = annotationParamsOptions.CurrentValue; - - /// - /// Returns annotation configuration that was previously rendered by MVC. - /// - [HttpGet("Annotations")] - [Obsolete("PSPDF Kit will no longer be used.")] - public IActionResult GetAnnotationParams() - { - return Ok(_annotationParams.AnnotationJSObject); - } -} diff --git a/EnvelopeGenerator.API/Controllers/DocReceiverElementController.cs b/EnvelopeGenerator.API/Controllers/DocReceiverElementController.cs deleted file mode 100644 index 6c234117..00000000 --- a/EnvelopeGenerator.API/Controllers/DocReceiverElementController.cs +++ /dev/null @@ -1,57 +0,0 @@ -using EnvelopeGenerator.API.Extensions; -using EnvelopeGenerator.Application.Common.Dto; -using EnvelopeGenerator.Application.Common.Extensions; -using EnvelopeGenerator.Application.Documents.Queries; -using EnvelopeGenerator.Domain.Constants; -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// -/// -[Authorize(Policy = AuthPolicy.Receiver)] -[ApiController] -[Route("api/[controller]")] -public class DocReceiverElementController : ControllerBase -{ - private readonly IMediator _mediator; - - /// - /// Initializes a new instance of . - /// - public DocReceiverElementController(IMediator mediator) - { - _mediator = mediator; - } - - //TODO: update to use signature query - /// - /// - /// - /// - /// - /// - [Authorize(Policy = AuthPolicy.Receiver)] - [HttpGet("{envelopeKey}")] - public async Task Get(string envelopeKey, CancellationToken cancel) - { - int envelopeId = User.EnvelopeId(); - - int receiverId = User.ReceiverId(); - - var doc = await _mediator.Send(new ReadDocumentQuery() { EnvelopeId = envelopeId }, cancel); - - if (doc.Elements is not IEnumerable docSignatures) - return NotFound("Document is empty."); - - var rcvSignatures = docSignatures.Where(s => s.ReceiverId == receiverId).ToList(); - - if (rcvSignatures is null) - return NotFound("No signatures found for the current receiver."); - else - return Ok(rcvSignatures); - } -} diff --git a/EnvelopeGenerator.API/Controllers/DocumentController.cs b/EnvelopeGenerator.API/Controllers/DocumentController.cs deleted file mode 100644 index f8468f81..00000000 --- a/EnvelopeGenerator.API/Controllers/DocumentController.cs +++ /dev/null @@ -1,84 +0,0 @@ -using DigitalData.Auth.Claims; -using EnvelopeGenerator.API.Controllers.Interfaces; -using EnvelopeGenerator.API.Extensions; -using EnvelopeGenerator.Application.Documents.Queries; -using EnvelopeGenerator.Domain.Constants; -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Provides access to envelope documents for authenticated receivers. -/// -/// -/// Initializes a new instance of the class. -/// -[ApiController] -[Route("api/[controller]")] -public class DocumentController(IMediator mediator, IAuthorizationService authService, ILogger logger) : ControllerBase, IAuthController -{ - /// - /// - /// - public IAuthorizationService AuthService => authService; - - /// - /// Returns the document bytes receiver. - /// - /// Encoded envelope key. - /// Cancellation token. - [HttpGet] - [Authorize(Policy = AuthPolicy.SenderOrReceiver)] - public async Task GetDocument(CancellationToken cancel, [FromQuery] ReadDocumentQuery? query = null) - { - // Sender: expects query with envelope key - if (await this.IsUserInPolicyAsync(AuthPolicy.Sender)) - { - if (query is null) - return BadRequest("Missing document query."); - - var senderDoc = await mediator.Send(query, cancel); - return senderDoc.ByteData is byte[] senderDocByte - ? File(senderDocByte, "application/octet-stream") - : NotFound("Document is empty."); - } - - // Receiver: resolve envelope id from claims - if (await this.IsUserInPolicyAsync(AuthPolicy.Receiver)) - { - if (query is not null) - return BadRequest("Query parameters are not allowed for receiver role."); - - var envelopeId = User.EnvelopeId(); - var receiverDoc = await mediator.Send(new ReadDocumentQuery { EnvelopeId = envelopeId }, cancel); - return receiverDoc.ByteData is byte[] receiverDocByte - ? File(receiverDocByte, "application/octet-stream") - : NotFound("Document is empty."); - } - - return Unauthorized(); - } - - /// - /// Gets the document for the specified envelope key. - /// - /// - /// - /// - [Authorize(Policy = AuthPolicy.Receiver)] - [HttpGet("{envelopeKey}")] - public async Task GetDocumentOfReceiver(string envelopeKey, CancellationToken cancel) - { - int envelopeId = User.EnvelopeId(); - - var senderDoc = await mediator.Send(new ReadDocumentQuery() { EnvelopeId = envelopeId }, cancel); - - if (senderDoc.ByteData is not byte[] senderDocByte) - return NotFound("Document is empty."); - - Response.Headers.ContentDisposition = $"inline; filename=\"{envelopeKey}.pdf\""; - return File(senderDocByte, "application/pdf"); - } -} diff --git a/EnvelopeGenerator.API/Controllers/EmailTemplateController.cs b/EnvelopeGenerator.API/Controllers/EmailTemplateController.cs deleted file mode 100644 index be670e42..00000000 --- a/EnvelopeGenerator.API/Controllers/EmailTemplateController.cs +++ /dev/null @@ -1,69 +0,0 @@ -using AutoMapper; -using EnvelopeGenerator.Application.EmailTemplates.Commands; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using MediatR; -using EnvelopeGenerator.Application.Common.Dto; -using DigitalData.Core.Abstraction.Application.Repository; -using EnvelopeGenerator.Domain.Entities; -using Microsoft.EntityFrameworkCore; -using EnvelopeGenerator.Domain.Constants; -using EnvelopeGenerator.Application.EmailTemplates.Queries; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Controller for managing temp templates. -/// Steuerung zur Verwaltung von E-Mail-Vorlagen. -/// -/// -/// Initialisiert eine neue Instanz der -Klasse. -/// -/// -/// Die Mediator-Instanz, die zum Senden von Befehlen und Abfragen verwendet wird. -/// -[Route("api/[controller]")] -[ApiController] -[Authorize(Policy = AuthPolicy.Sender)] -public class EmailTemplateController(IMediator mediator) : ControllerBase -{ - /// - /// Ruft E-Mail-Vorlagen basierend auf der angegebenen Abfrage ab. - /// Gibt alles zurück, wenn keine Id- oder Typ-Informationen eingegeben wurden. - /// - /// Die Abfrageparameter zum Abrufen von E-Mail-Vorlagen. - /// - /// Gibt HTTP-Antwort zurück - /// - /// Sample request: - /// GET /api/EmailTemplate?emailTemplateId=123 - /// - /// Wenn die E-Mail-Vorlagen erfolgreich abgerufen werden. - /// Wenn die Abfrageparameter ungültig sind. - /// Wenn der Benutzer nicht authentifiziert ist. - /// Wenn die gesuchte Abfrage nicht gefunden wird. - [HttpGet] - public async Task Get([FromQuery] ReadEmailTemplateQuery emailTemplate, CancellationToken cancel) - { - var result = await mediator.Send(emailTemplate, cancel); - return Ok(result); - } - - /// - /// Updates an temp template or resets it if no update command is provided. - /// Aktualisiert eine E-Mail-Vorlage oder setzt sie zurück, wenn kein Aktualisierungsbefehl angegeben ist. - /// - /// - /// - /// - /// Wenn die E-Mail-Vorlage erfolgreich aktualisiert oder zurückgesetzt wird. - /// Wenn die Abfrage ohne einen String gesendet wird. - /// Wenn der Benutzer nicht authentifiziert ist. - /// Wenn die gesuchte Abfrage nicht gefunden wird. - [HttpPut] - public async Task Update([FromBody] UpdateEmailTemplateCommand update, CancellationToken cancel) - { - await mediator.Send(update, cancel); - return Ok(); - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Controllers/EnvelopeController.cs b/EnvelopeGenerator.API/Controllers/EnvelopeController.cs deleted file mode 100644 index c1bb54d2..00000000 --- a/EnvelopeGenerator.API/Controllers/EnvelopeController.cs +++ /dev/null @@ -1,111 +0,0 @@ -using EnvelopeGenerator.API.Extensions; -using EnvelopeGenerator.Application.Envelopes.Commands; -using EnvelopeGenerator.Application.Envelopes.Queries; -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Dieser Controller stellt Endpunkte für die Verwaltung von Umschlägen bereit. -/// -/// -/// Die API ermöglicht das Abrufen und Verwalten von Umschlägen basierend auf Benutzerinformationen und Statusfiltern. -/// -/// Mögliche Antworten: -/// - 200 OK: Die Anfrage war erfolgreich, und die angeforderten Daten werden zurückgegeben. -/// - 400 Bad Request: Die Anfrage war fehlerhaft oder unvollständig. -/// - 401 Unauthorized: Der Benutzer ist nicht authentifiziert. -/// - 403 Forbidden: Der Benutzer hat keine Berechtigung, auf die Ressource zuzugreifen. -/// - 404 Not Found: Die angeforderte Ressource wurde nicht gefunden. -/// - 500 Internal Server Error: Ein unerwarteter Fehler ist aufgetreten. -/// -[Route("api/[controller]")] -[ApiController] -[Authorize] -public class EnvelopeController : ControllerBase -{ - private readonly ILogger _logger; - private readonly IMediator _mediator; - - /// - /// Erstellt eine neue Instanz des EnvelopeControllers. - /// - /// Der Logger, der für das Protokollieren von Informationen verwendet wird. - /// - public EnvelopeController(ILogger logger, IMediator mediator) - { - _logger = logger; - _mediator = mediator; - } - - /// - /// Ruft eine Liste von Umschlägen basierend auf dem Benutzer und den angegebenen Statusfiltern ab. - /// - /// - /// Eine IActionResult-Instanz, die die abgerufenen Umschläge oder einen Fehlerstatus enthält. - /// Die Anfrage war erfolgreich, und die Umschläge werden zurückgegeben. - /// Die Anfrage war fehlerhaft oder unvollständig. - /// Der Benutzer ist nicht authentifiziert. - /// Der Benutzer hat keine Berechtigung, auf die Ressource zuzugreifen. - /// Ein unerwarteter Fehler ist aufgetreten. - [Authorize(AuthenticationSchemes = AuthScheme.Sender)] - [HttpGet] - public async Task GetAsync([FromQuery] ReadEnvelopeQuery envelope) - { - var result = await _mediator.Send(envelope.Authorize(User.GetId())); - return result.Any() ? Ok(result) : NotFound(); - } - - /// - /// Ruft das Ergebnis eines Dokuments basierend auf der ID ab. - /// - /// - /// Gibt an, ob das Dokument inline angezeigt werden soll (true) oder als Download bereitgestellt wird (false). - /// Eine IActionResult-Instanz, die das Dokument oder einen Fehlerstatus enthält. - /// Das Dokument wurde erfolgreich abgerufen. - /// Das Dokument wurde nicht gefunden oder ist nicht verfügbar. - /// Ein unerwarteter Fehler ist aufgetreten. - [HttpGet("doc-result")] - public async Task GetDocResultAsync([FromQuery] ReadEnvelopeQuery query, [FromQuery] bool view = false) - { - var envelopes = await _mediator.Send(query.Authorize(User.GetId())); - var envelope = envelopes.FirstOrDefault(); - - if (envelope is null) - return NotFound("Envelope not available."); - if (envelope.DocResult is null) - return NotFound("The document has not been fully signed or the result has not yet been released."); - - if (view) - { - Response.Headers.Append("Content-Disposition", "inline; filename=\"" + envelope.Uuid + ".pdf\""); - return File(envelope.DocResult, "application/pdf"); - } - - return File(envelope.DocResult, "application/pdf", $"{envelope.Uuid}.pdf"); - } - - /// - /// - /// - /// - /// - [NonAction] - [Authorize] - [HttpPost] - public async Task CreateAsync([FromBody] CreateEnvelopeCommand command) - { - var res = await _mediator.Send(command.WithAuth(User.GetId())); - - if (res is null) - { - _logger.LogError("Failed to create envelope. Envelope details: {EnvelopeDetails}", JsonConvert.SerializeObject(command)); - return StatusCode(StatusCodes.Status500InternalServerError); - } - else - return Ok(res); - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Controllers/EnvelopeReceiverController.cs b/EnvelopeGenerator.API/Controllers/EnvelopeReceiverController.cs deleted file mode 100644 index 55ee0f71..00000000 --- a/EnvelopeGenerator.API/Controllers/EnvelopeReceiverController.cs +++ /dev/null @@ -1,281 +0,0 @@ -using AutoMapper; -using EnvelopeGenerator.Application.EnvelopeReceivers.Commands; -using EnvelopeGenerator.Application.EnvelopeReceivers.Queries; -using EnvelopeGenerator.Application.Envelopes.Queries; -using EnvelopeGenerator.Domain.Entities; -using EnvelopeGenerator.API.Models; -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Data.SqlClient; -using Microsoft.Extensions.Options; -using System.Data; -using EnvelopeGenerator.Application.Common.SQL; -using EnvelopeGenerator.Application.Common.Dto.Receiver; -using EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor; -using EnvelopeGenerator.API.Extensions; -using EnvelopeGenerator.Domain.Constants; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Controller für die Verwaltung von Umschlagempfängern. -/// -/// -/// Dieser Controller bietet Endpunkte für das Abrufen und Verwalten von Umschlagempfängerdaten. -/// -[Route("api/[controller]")] -[Authorize] -[ApiController] -public class EnvelopeReceiverController : ControllerBase -{ - private readonly ILogger _logger; - private readonly IMediator _mediator; - private readonly IMapper _mapper; - private readonly IEnvelopeExecutor _envelopeExecutor; - private readonly IEnvelopeReceiverExecutor _erExecutor; - private readonly IDocumentExecutor _documentExecutor; - private readonly string _cnnStr; - - /// - /// Konstruktor für den EnvelopeReceiverController. - /// - public EnvelopeReceiverController(ILogger logger, IMediator mediator, IMapper mapper, IEnvelopeExecutor envelopeExecutor, IEnvelopeReceiverExecutor erExecutor, IDocumentExecutor documentExecutor, IOptions csOpt) - { - _logger = logger; - _mediator = mediator; - _mapper = mapper; - _envelopeExecutor = envelopeExecutor; - _erExecutor = erExecutor; - _documentExecutor = documentExecutor; - _cnnStr = csOpt.Value.Value; - } - - /// - /// Ruft eine Liste von Umschlagempfängern basierend auf den angegebenen Abfrageparametern ab. - /// - /// Die Abfrageparameter für die Filterung von Umschlagempfängern. - /// Eine HTTP-Antwort mit der Liste der gefundenen Umschlagempfänger oder einem Fehlerstatus. - /// - /// Dieser Endpunkt ermöglicht es, Umschlagempfänger basierend auf dem Benutzernamen und optionalen Statusfiltern abzurufen. - /// Wenn der Benutzername nicht ermittelt werden kann, wird ein Serverfehler zurückgegeben. - /// - /// Die Liste der Umschlagempfänger wurde erfolgreich abgerufen. - /// Wenn kein autorisierter Token vorhanden ist - /// Ein unerwarteter Fehler ist aufgetreten. - [Authorize] - [HttpGet] - public async Task GetEnvelopeReceiver([FromQuery] ReadEnvelopeReceiverQuery envelopeReceiver) - { - envelopeReceiver = envelopeReceiver with { Username = User.GetUsername() }; - - var result = await _mediator.Send(envelopeReceiver); - - return Ok(result); - } - - /// - /// - /// - /// - /// - /// - [Authorize(Policy = AuthPolicy.Receiver)] - [HttpGet("{envelopeKey}")] - public async Task GetEnvelopeReceiverOfReceiver([FromRoute] string envelopeKey, CancellationToken cancel) - { - var er = await _mediator.Send(new ReadEnvelopeReceiverQuery() - { - Key = envelopeKey - }, cancel); - - return Ok(er.SingleOrDefault()); - } - - /// - /// Ruft den Namen des zuletzt verwendeten Empfängers basierend auf der angegebenen E-Mail-Adresse ab. - /// - /// Abfrage, bei der nur eine der Angaben ID, Signatur oder E-Mail-Adresse des Empfängers eingegeben werden muss. - /// Eine HTTP-Antwort mit dem Namen des Empfängers oder einem Fehlerstatus. - /// - /// Dieser Endpunkt ermöglicht es, den Namen des zuletzt verwendeten Empfängers basierend auf der E-Mail-Adresse abzurufen. - /// - /// Der Name des Empfängers wurde erfolgreich abgerufen. - /// Wenn kein autorisierter Token vorhanden ist - /// Kein Empfänger gefunden. - /// Ein unerwarteter Fehler ist aufgetreten. - [Authorize] - [HttpGet("salute")] - public async Task GetReceiverName([FromQuery] ReadReceiverNameQuery receiver) - { - var name = await _mediator.Send(receiver); - return name is null ? NotFound() : Ok(name); - } - - /// - /// Datenübertragungsobjekt mit Informationen zu Umschlägen, Empfängern und Unterschriften. - /// - /// - /// - /// HTTP-Antwort - /// - /// Sample request: - /// - /// POST /api/envelope - /// { - /// "title": "Vertragsdokument", - /// "message": "Bitte unterschreiben Sie dieses Dokument.", - /// "document": { - /// "dataAsBase64": "dGVzdC1iYXNlNjQtZGF0YQ==" - /// }, - /// "receivers": [ - /// { - /// "emailAddress": "example@example.com", - /// "signatures": [ - /// { - /// "x": 100, - /// "y": 200, - /// "page": 1 - /// } - /// ], - /// "name": "Max Mustermann", - /// "phoneNumber": "+49123456789" - /// } - /// ], - /// "tfaEnabled": false - /// } - /// - /// - /// Envelope-Erstellung und Sendeprozessbefehl erfolgreich - /// Wenn ein Fehler im HTTP-Body auftritt - /// Wenn kein autorisierter Token vorhanden ist - /// Es handelt sich um einen unerwarteten Fehler. Die Protokolle sollten überprüft werden. - [Authorize] - [HttpPost] - public async Task CreateAsync([FromBody] CreateEnvelopeReceiverCommand request, CancellationToken cancel) - { - #region Create Envelope - var envelope = await _envelopeExecutor.CreateEnvelopeAsync(User.GetId(), request.Title, request.Message, request.TFAEnabled, cancel); - #endregion - - #region Add receivers - List sentReceivers = new(); - List unsentReceivers = new(); - - foreach (var receiver in request.Receivers) - { - var envelopeReceiver = await _erExecutor.AddEnvelopeReceiverAsync(envelope.Uuid, receiver.EmailAddress, receiver.Salution, receiver.PhoneNumber, cancel); - - if (envelopeReceiver is null) - unsentReceivers.Add(receiver); - else - sentReceivers.Add(envelopeReceiver); - } - - var res = _mapper.Map(envelope); - res.UnsentReceivers = unsentReceivers; - res.SentReceiver = _mapper.Map>(sentReceivers.Select(er => er.Receiver)); - #endregion - - #region Add document - var document = await _documentExecutor.CreateDocumentAsync(request.Document.DataAsBase64, envelope.Uuid, cancel); - - if (document is null) - return StatusCode(StatusCodes.Status500InternalServerError, "Document creation is failed."); - #endregion - - #region Add document element - // @DOC_ID, @RECEIVER_ID, @POSITION_X, @POSITION_Y, @PAGE - string sql = @" - DECLARE @OUT_SUCCESS bit; - - EXEC [dbo].[PRSIG_API_ADD_DOC_RECEIVER_ELEM] - {0}, - {1}, - {2}, - {3}, - {4}, - @OUT_SUCCESS OUTPUT; - - SELECT @OUT_SUCCESS as [@OUT_SUCCESS];"; - - foreach (var rcv in res.SentReceiver) - foreach (var sign in request.Receivers.Where(r => r.EmailAddress == rcv.EmailAddress).FirstOrDefault()?.DocReceiverElements ?? Enumerable.Empty()) - { - using SqlConnection conn = new(_cnnStr); - conn.Open(); - - var formattedSQL = string.Format(sql, document.Id.ToSqlParam(), rcv.Id.ToSqlParam(), sign.X.ToSqlParam(), sign.Y.ToSqlParam(), sign.Page.ToSqlParam()); - - using SqlCommand cmd = new(formattedSQL, conn); - cmd.CommandType = CommandType.Text; - - using SqlDataReader reader = cmd.ExecuteReader(); - if (reader.Read()) - { - bool outSuccess = reader.GetBoolean(0); - if (!outSuccess) - _logger.LogWarning( - "PRSIG_API_ADD_DOC_RECEIVER_ELEM returned OUT_SUCCESS=false. DOC_ID={DocId}, RECEIVER_ID={ReceiverId}, Page={Page}", - document.Id, rcv.Id, sign.Page); - } - } - #endregion - - #region Create history - // ENV_UID, STATUS_ID, USER_ID, - string sql_hist = @" - DECLARE @OUT_SUCCESS bit; - - EXEC [dbo].[PRSIG_API_ADD_HISTORY_STATE] - {0}, - {1}, - {2}, - @OUT_SUCCESS OUTPUT; - - SELECT @OUT_SUCCESS as [@OUT_SUCCESS];"; - - using (SqlConnection conn = new(_cnnStr)) - { - conn.Open(); - var formattedSQL_hist = string.Format(sql_hist, envelope.Uuid.ToSqlParam(), 1003.ToSqlParam(), User.GetId().ToSqlParam()); - using SqlCommand cmd = new(formattedSQL_hist, conn); - cmd.CommandType = CommandType.Text; - - using SqlDataReader reader = cmd.ExecuteReader(); - if (reader.Read()) - { - bool outSuccess = reader.GetBoolean(0); - if (!outSuccess) - _logger.LogWarning( - "PRSIG_API_ADD_HISTORY_STATE returned OUT_SUCCESS=false. EnvelopeUuid={EnvelopeUuid}", - envelope.Uuid); - } - } - #endregion - - return Ok(res); - } - - /// - /// - /// - /// - /// - public static bool IsBase64String(string input) - { - if (string.IsNullOrWhiteSpace(input)) - return false; - - try - { - Convert.FromBase64String(input); - return true; - } - catch (FormatException) - { - return false; - } - } - -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Controllers/EnvelopeTypeController.cs b/EnvelopeGenerator.API/Controllers/EnvelopeTypeController.cs deleted file mode 100644 index e6fc6095..00000000 --- a/EnvelopeGenerator.API/Controllers/EnvelopeTypeController.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediatR; -using EnvelopeGenerator.Application.EnvelopeTypes.Queries; -using Microsoft.AspNetCore.Mvc; - -namespace EnvelopeGenerator.GeneratorAPI.Controllers; - -/// -/// -/// -[ApiExplorerSettings(IgnoreApi = true)] -[Route("api/[controller]")] -[ApiController] -public class EnvelopeTypeController : ControllerBase -{ - private readonly ILogger _logger; - private readonly IMediator _mediator; - - /// - /// - /// - /// - /// - public EnvelopeTypeController(ILogger logger, IMediator mediator) - { - _logger = logger; - _mediator = mediator; - } - - /// - /// - /// - /// - [HttpGet] - public async Task GetAllAsync() - { - var result = await _mediator.Send(new ReadEnvelopeTypesQuery()); - return Ok(result); - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Controllers/HistoryController.cs b/EnvelopeGenerator.API/Controllers/HistoryController.cs deleted file mode 100644 index 16418fa3..00000000 --- a/EnvelopeGenerator.API/Controllers/HistoryController.cs +++ /dev/null @@ -1,118 +0,0 @@ -using MediatR; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Caching.Memory; -using EnvelopeGenerator.Application.Histories.Queries; -using EnvelopeGenerator.Domain.Constants; -using EnvelopeGenerator.Application.Common.Extensions; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Dieser Controller stellt Endpunkte für den Zugriff auf die Umschlaghistorie bereit. -/// -[Route("api/[controller]")] -[ApiController] -[Authorize] -public class HistoryController : ControllerBase -{ - private readonly IMemoryCache _memoryCache; - - private readonly IMediator _mediator; - - /// - /// Konstruktor für den HistoryController. - /// - /// - /// - public HistoryController(IMemoryCache memoryCache, IMediator mediator) - { - _memoryCache = memoryCache; - _mediator = mediator; - } - - /// - /// Gibt alle möglichen Verweise auf alle möglichen Include in einem Verlaufsdatensatz zurück. (z. B. DocumentSigned bezieht sich auf Receiver.) - /// Dies wird hinzugefügt, damit Client-Anwendungen sich selbst auf dem neuesten Stand halten können. - /// 1 - Sender: - /// Historische Datensätze über den Include der Empfänger. Diese haben Statuscodes, die mit 1* beginnen. - /// 2 - Receiver: - /// Historische Datensätze, die sich auf den Include des Absenders beziehen. Sie haben Statuscodes, die mit 2* beginnen. - /// 3 - System: - /// Historische Datensätze, die sich auf den allgemeinen Zustand des Umschlags beziehen. Diese haben Statuscodes, die mit 3* beginnen. - /// 4 - Unknown: - /// Ein unbekannter Datensatz weist auf einen möglichen Mangel oder eine Unstimmigkeit im Aktualisierungsprozess der Anwendung hin. - /// - /// - /// - [HttpGet("related")] - [Authorize] - public IActionResult GetReferenceTypes(ReferenceType? referenceType = null) - { - return referenceType is null - ? Ok(_memoryCache.GetEnumAsDictionary("gen.api", ReferenceType.Unknown)) - : Ok(referenceType.ToString()); - } - - /// - /// Gibt alle möglichen Include in einem Verlaufsdatensatz zurück. - /// Dies wird hinzugefügt, damit Client-Anwendungen sich selbst auf dem neuesten Stand halten können. - /// 1003: EnvelopeQueued - /// 1006: EnvelopeCompletelySigned - /// 1007: EnvelopeReportCreated - /// 1008: EnvelopeArchived - /// 1009: EnvelopeDeleted - /// 10007: EnvelopeRejected - /// 10009: EnvelopeWithdrawn - /// 2001: AccessCodeRequested - /// 2002: AccessCodeCorrect - /// 2003: AccessCodeIncorrect - /// 2004: DocumentOpened - /// 2005: DocumentSigned - /// 2006: DocumentForwarded - /// 2007: DocumentRejected - /// 2008: EnvelopeShared - /// 2009: EnvelopeViewed - /// 3001: MessageInvitationSent (Wird von Trigger verwendet) - /// 3002: MessageAccessCodeSent - /// 3003: MessageConfirmationSent - /// 3004: MessageDeletionSent - /// 3005: MessageCompletionSent - /// - /// - /// Abfrageparameter, der angibt, auf welche Referenz sich der Include bezieht. - /// 1 - Sender: Historische Datensätze, die sich auf den Include des Absenders beziehen. Sie haben Statuscodes, die mit 1* beginnen. - /// 2 - Receiver: Historische Datensätze über den Include der Empfänger. Diese haben Statuscodes, die mit 2* beginnen. - /// 3 - System: Diese werden durch Datenbank-Trigger aktualisiert und sind in den Tabellen EnvelopeHistory und EmailOut zu finden.Sie arbeiten - /// integriert mit der Anwendung EmailProfiler, um E-Mails zu versenden und haben die Codes, die mit 3* beginnen. - /// - /// Gibt die HTTP-Antwort zurück. - /// - [HttpGet("status")] - [Authorize] - public IActionResult GetEnvelopeStatus([FromQuery] EnvelopeStatus? status = null) - { - return status is null - ? Ok(_memoryCache.GetEnumAsDictionary("gen.api", Status.NonHist, Status.RelatedToFormApp)) - : Ok(status.ToString()); - } - - /// - /// Ruft die gesamte Umschlaghistorie basierend auf den angegebenen Abfrageparametern ab. - /// - /// Die Abfrageparameter, die die Filterkriterien für die Umschlaghistorie definieren. - /// - /// Eine Liste von Historieneinträgen, die den angegebenen Kriterien entsprechen, oder nur der letzte Eintrag. - /// Die Anfrage war erfolgreich, und die Umschlaghistorie wird zurückgegeben. - /// Die Anfrage war ungültig oder unvollständig. - /// Der Benutzer ist nicht authentifiziert. - /// Der Benutzer hat keine Berechtigung, auf die Ressource zuzugreifen. - /// Ein unerwarteter Fehler ist aufgetreten. - [HttpGet] - [Authorize] - public async Task GetAllAsync([FromQuery] ReadHistoryQuery historyQuery, CancellationToken cancel) - { - var history = await _mediator.Send(historyQuery, cancel).ThrowIfEmpty(Exceptions.NotFound); - return Ok((historyQuery.OnlyLast) ? history.MaxBy(h => h.AddedWhen) : history); - } -} diff --git a/EnvelopeGenerator.API/Controllers/Interfaces/IAuthController.cs b/EnvelopeGenerator.API/Controllers/Interfaces/IAuthController.cs deleted file mode 100644 index cf31d972..00000000 --- a/EnvelopeGenerator.API/Controllers/Interfaces/IAuthController.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Authorization; - -namespace EnvelopeGenerator.API.Controllers.Interfaces; - -/// -/// -/// -public interface IAuthController -{ - /// - /// - /// - IAuthorizationService AuthService { get; } - - /// - /// - /// - ClaimsPrincipal User { get; } -} - -/// -/// -/// -public static class AuthControllerExtensions -{ - /// - /// - /// - /// - /// - /// - public static async Task IsUserInPolicyAsync(this IAuthController controller, string policyName) - { - var result = await controller.AuthService.AuthorizeAsync(controller.User, policyName); - return result.Succeeded; - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Controllers/LocalizationController.cs b/EnvelopeGenerator.API/Controllers/LocalizationController.cs deleted file mode 100644 index e2e02de2..00000000 --- a/EnvelopeGenerator.API/Controllers/LocalizationController.cs +++ /dev/null @@ -1,121 +0,0 @@ -using DigitalData.Core.API; -using EnvelopeGenerator.Application.Resources; -using Microsoft.AspNetCore.Localization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Localization; -using EnvelopeGenerator.Application.Resources; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Controller für die Verwaltung der Lokalisierung und Spracheinstellungen. -/// -[ApiExplorerSettings(IgnoreApi = true)] -[Route("api/[controller]")] -[ApiController] -public class LocalizationController : ControllerBase -{ - private static readonly Guid L_KEY = Guid.NewGuid(); - - private readonly ILogger _logger; - private readonly IStringLocalizer _mLocalizer; - private readonly IStringLocalizer _localizer; - private readonly IMemoryCache _cache; - - /// - /// Konstruktor für den . - /// - /// Logger für die Protokollierung. - /// Lokalisierungsdienst für Ressourcen. - /// Speicher-Cache für die Zwischenspeicherung von Daten. - /// Lokalisierungsdienst für Modelle. - public LocalizationController( - ILogger logger, - IStringLocalizer localizer, - IMemoryCache memoryCache, - IStringLocalizer _modelLocalizer) - { - _logger = logger; - _localizer = localizer; - _cache = memoryCache; - _mLocalizer = _modelLocalizer; - } - - /// - /// Ruft alle lokalisierten Daten ab. - /// - /// Eine Liste aller lokalisierten Daten. - [HttpGet] - public IActionResult GetAll() => Ok(_cache.GetOrCreate(Language ?? string.Empty + L_KEY, _ => _mLocalizer.ToDictionary())); - - /// - /// Ruft die aktuelle Sprache ab. - /// - /// Die aktuelle Sprache oder ein NotFound-Ergebnis, wenn keine Sprache gesetzt ist. - [HttpGet("lang")] - public IActionResult GetLanguage() => Language is null ? NotFound() : Ok(Language); - - /// - /// Setzt die Sprache. - /// - /// Die zu setzende Sprache. - /// Ein Ok-Ergebnis, wenn die Sprache erfolgreich gesetzt wurde, oder ein BadRequest-Ergebnis, wenn die Eingabe ungültig ist. - [HttpPost("lang")] - public IActionResult SetLanguage([FromQuery] string language) - { - if (string.IsNullOrEmpty(language)) - return BadRequest(); - - Language = language; - return Ok(); - } - - /// - /// Löscht die aktuelle Sprache. - /// - /// Ein Ok-Ergebnis, wenn die Sprache erfolgreich gelöscht wurde. - [HttpDelete("lang")] - public IActionResult DeleteLanguage() - { - Language = null; - return Ok(); - } - - /// - /// Eigenschaft für die Verwaltung der aktuellen Sprache über Cookies. - /// - private string? Language - { - get - { - var cookieValue = Request.Cookies[CookieRequestCultureProvider.DefaultCookieName]; - - if (string.IsNullOrEmpty(cookieValue)) - return null; - - var culture = CookieRequestCultureProvider.ParseCookieValue(cookieValue)?.Cultures[0]; - return culture?.Value ?? null; - } - set - { - if (value is null) - Response.Cookies.Delete(CookieRequestCultureProvider.DefaultCookieName); - else - { - var cookieOptions = new CookieOptions() - { - Expires = DateTimeOffset.UtcNow.AddYears(1), - Secure = false, - SameSite = SameSiteMode.Strict, - HttpOnly = true - }; - - Response.Cookies.Append( - CookieRequestCultureProvider.DefaultCookieName, - CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(value)), - cookieOptions); - } - } - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Controllers/ReadOnlyController.cs b/EnvelopeGenerator.API/Controllers/ReadOnlyController.cs deleted file mode 100644 index db800c22..00000000 --- a/EnvelopeGenerator.API/Controllers/ReadOnlyController.cs +++ /dev/null @@ -1,91 +0,0 @@ -using DigitalData.Core.Abstraction.Application.DTO; -using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiverReadOnly; -using EnvelopeGenerator.Application.Common.Interfaces.Services; -using EnvelopeGenerator.Domain.Constants; -using EnvelopeGenerator.API.Extensions; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Manages read-only envelope sharing flows. -/// -[Route("api/[controller]")] -[ApiController] -public class ReadOnlyController : ControllerBase -{ - private readonly ILogger _logger; - private readonly IEnvelopeReceiverReadOnlyService _readOnlyService; - private readonly IEnvelopeMailService _mailService; - private readonly IEnvelopeHistoryService _historyService; - - /// - /// Initializes a new instance of the class. - /// - public ReadOnlyController(ILogger logger, IEnvelopeReceiverReadOnlyService readOnlyService, IEnvelopeMailService mailService, IEnvelopeHistoryService historyService) - { - _logger = logger; - _readOnlyService = readOnlyService; - _mailService = mailService; - _historyService = historyService; - } - - /// - /// Creates a new read-only receiver for the current envelope. - /// - /// Creation payload. - [HttpPost] - [Authorize(Policy = AuthPolicy.Receiver)] - [Obsolete("Use MediatR")] - public async Task CreateAsync([FromBody] EnvelopeReceiverReadOnlyCreateDto createDto) - { - var authReceiverMail = User.ReceiverMail(); - if (authReceiverMail is null) - { - _logger.LogError("EmailAddress claim is not found in envelope-receiver-read-only creation process. Create DTO is:\n {dto}", JsonConvert.SerializeObject(createDto)); - return Unauthorized(); - } - - var envelopeId = User.EnvelopeId(); - - createDto.AddedWho = authReceiverMail; - createDto.EnvelopeId = envelopeId; - - var creationRes = await _readOnlyService.CreateAsync(createDto: createDto); - - if (creationRes.IsFailed) - { - _logger.LogNotice(creationRes); - return StatusCode(StatusCodes.Status500InternalServerError); - } - - var readRes = await _readOnlyService.ReadByIdAsync(creationRes.Data.Id); - if (readRes.IsFailed) - { - _logger.LogNotice(creationRes); - return StatusCode(StatusCodes.Status500InternalServerError); - } - - var newReadOnly = readRes.Data; - - return await _mailService.SendAsync(newReadOnly).ThenAsync(SuccessAsync: async _ => - { - var histRes = await _historyService.RecordAsync((int)createDto.EnvelopeId, createDto.AddedWho, EnvelopeStatus.EnvelopeShared); - if (histRes.IsFailed) - { - _logger.LogError("Although the envelope was sent as read-only, the EnvelopeShared history could not be saved. Create DTO:\n{createDto}", JsonConvert.SerializeObject(createDto)); - _logger.LogNotice(histRes.Notices); - } - - return Ok(); - }, - - Fail: (msg, ntc) => - { - _logger.LogNotice(ntc); - return StatusCode(StatusCodes.Status500InternalServerError); - }); - } -} diff --git a/EnvelopeGenerator.API/Controllers/ReceiverController.cs b/EnvelopeGenerator.API/Controllers/ReceiverController.cs deleted file mode 100644 index 756b5eb3..00000000 --- a/EnvelopeGenerator.API/Controllers/ReceiverController.cs +++ /dev/null @@ -1,47 +0,0 @@ -using MediatR; -using EnvelopeGenerator.Application.Receivers.Queries; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; - -namespace EnvelopeGenerator.GeneratorAPI.Controllers; - -/// -/// Controller für die Verwaltung von Empfängern. -/// -/// -/// Dieser Controller bietet Endpunkte für das Abrufen von Empfängern basierend auf E-Mail-Adresse oder Signatur. -/// -[Route("api/[controller]")] -[ApiController] -[Authorize] -public class ReceiverController : ControllerBase -{ - private readonly IMediator _mediator; - - /// - /// Initialisiert eine neue Instanz des -Controllers. - /// - /// Mediator für Anfragen. - public ReceiverController(IMediator mediator) - { - _mediator = mediator; - } - - /// - /// Ruft eine Liste von Empfängern ab, basierend auf den angegebenen Abfrageparametern. - /// - /// Die Abfrageparameter, einschließlich E-Mail-Adresse und Signatur. - /// Eine Liste von Empfängern oder ein Fehlerstatus. - [HttpGet] - public async Task Get([FromQuery] ReadReceiverQuery receiver) - { - if (!receiver.HasAnyCriteria) - { - var all = await _mediator.Send(new ReadReceiverQuery()); - return Ok(all); - } - - var result = await _mediator.Send(receiver); - return result is null ? NotFound() : Ok(result); - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Controllers/TfaRegistrationController.cs b/EnvelopeGenerator.API/Controllers/TfaRegistrationController.cs deleted file mode 100644 index f365c0df..00000000 --- a/EnvelopeGenerator.API/Controllers/TfaRegistrationController.cs +++ /dev/null @@ -1,129 +0,0 @@ -using DigitalData.Core.Abstraction.Application.DTO; -using EnvelopeGenerator.Application.Common.Extensions; -using EnvelopeGenerator.Application.Common.Interfaces.Services; -using EnvelopeGenerator.Application.Resources; -using EnvelopeGenerator.Domain.Constants; -using EnvelopeGenerator.API.Models; -using Ganss.Xss; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Options; - -namespace EnvelopeGenerator.API.Controllers; - -/// -/// Exposes endpoints for registering and managing two-factor authentication for envelope receivers. -/// -[ApiController] -[Route("api/tfa")] -public class TfaRegistrationController : ControllerBase -{ - private readonly ILogger _logger; - private readonly IEnvelopeReceiverService _envelopeReceiverService; - private readonly IAuthenticator _authenticator; - private readonly IReceiverService _receiverService; - private readonly TFARegParams _parameters; - private readonly IStringLocalizer _localizer; - - /// - /// Initializes a new instance of the class. - /// - public TfaRegistrationController( - ILogger logger, - IEnvelopeReceiverService envelopeReceiverService, - IAuthenticator authenticator, - IReceiverService receiverService, - IOptions tfaRegParamsOptions, - IStringLocalizer localizer) - { - _logger = logger; - _envelopeReceiverService = envelopeReceiverService; - _authenticator = authenticator; - _receiverService = receiverService; - _parameters = tfaRegParamsOptions.Value; - _localizer = localizer; - } - - /// - /// Generates registration metadata (QR code and deadline) for a receiver. - /// - /// Encoded envelope receiver id. - [Authorize] - [HttpGet("{envelopeReceiverId}")] - public async Task RegisterAsync(string envelopeReceiverId) - { - try - { - var (uuid, signature) = envelopeReceiverId.DecodeEnvelopeReceiverId(); - - if (uuid is null || signature is null) - { - _logger.LogEnvelopeError(uuid: uuid, signature: signature, message: _localizer.WrongEnvelopeReceiverId()); - return Unauthorized(new { message = _localizer.WrongEnvelopeReceiverId() }); - } - - var secretResult = await _envelopeReceiverService.ReadWithSecretByUuidSignatureAsync(uuid: uuid, signature: signature); - if (secretResult.IsFailed) - { - _logger.LogNotice(secretResult.Notices); - return NotFound(new { message = _localizer.WrongEnvelopeReceiverId() }); - } - - var envelopeReceiver = secretResult.Data; - - if (!envelopeReceiver.Envelope!.TFAEnabled) - return Unauthorized(new { message = _localizer.WrongAccessCode() }); - - var receiver = envelopeReceiver.Receiver; - receiver!.TotpSecretkey = _authenticator.GenerateTotpSecretKey(); - await _receiverService.UpdateAsync(receiver); - var totpQr64 = _authenticator.GenerateTotpQrCode(userEmail: receiver.EmailAddress, secretKey: receiver.TotpSecretkey).ToBase64String(); - - if (receiver.TfaRegDeadline is null) - { - receiver.TfaRegDeadline = _parameters.Deadline; - await _receiverService.UpdateAsync(receiver); - } - else if (receiver.TfaRegDeadline <= DateTime.Now) - { - return StatusCode(StatusCodes.Status410Gone, new { message = _localizer.WrongAccessCode() }); - } - - return Ok(new - { - envelopeReceiver.EnvelopeId, - envelopeReceiver.Envelope!.Uuid, - envelopeReceiver.Receiver!.Signature, - receiver.TfaRegDeadline, - TotpQR64 = totpQr64 - }); - } - catch (Exception ex) - { - _logger.LogEnvelopeError(envelopeReceiverId: envelopeReceiverId, exception: ex, message: _localizer.WrongEnvelopeReceiverId()); - return StatusCode(StatusCodes.Status500InternalServerError, new { message = _localizer.UnexpectedError() }); - } - } - - /// - /// Logs out the envelope receiver from cookie authentication. - /// - [Authorize(Policy = AuthPolicy.Receiver)] - [HttpPost("auth/logout")] - public async Task LogOutAsync() - { - try - { - await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); - return Ok(); - } - catch (Exception ex) - { - _logger.LogError(ex, "{message}", ex.Message); - return StatusCode(StatusCodes.Status500InternalServerError, new { message = _localizer.UnexpectedError() }); - } - } -} diff --git a/EnvelopeGenerator.API/Documentation/AuthProxyDocumentFilter.cs b/EnvelopeGenerator.API/Documentation/AuthProxyDocumentFilter.cs deleted file mode 100644 index 8cb9c6c0..00000000 --- a/EnvelopeGenerator.API/Documentation/AuthProxyDocumentFilter.cs +++ /dev/null @@ -1,123 +0,0 @@ -using EnvelopeGenerator.API.Models; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace EnvelopeGenerator.API.Documentation; - -/// -/// -/// -public sealed class AuthProxyDocumentFilter : IDocumentFilter -{ - /// - /// - /// - /// - /// - public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) - { - AddLoginOperation(swaggerDoc, context); - AddEnvelopeReceiverLoginOperation(swaggerDoc, context); - } - - private static void AddLoginOperation(OpenApiDocument swaggerDoc, DocumentFilterContext context) - { - const string path = "/api/auth"; - - var loginSchema = context.SchemaGenerator.GenerateSchema(typeof(Login), context.SchemaRepository); - var loginExample = new OpenApiObject - { - ["password"] = new OpenApiString(""), - ["username"] = new OpenApiString("") - }; - - var operation = new OpenApiOperation - { - Summary = "Proxy login (auth-hub)", - Description = "Proxies the request to the auth service. Add query parameter `cookie=true|false`.", - Tags = [new() { Name = "Auth" }], - Parameters = - { - new OpenApiParameter - { - Name = "cookie", - In = ParameterLocation.Query, - Required = false, - Schema = new OpenApiSchema { Type = "boolean", Default = new OpenApiBoolean(true) }, - Example = new OpenApiBoolean(true), - Description = "If true, auth service sets the auth cookie." - } - }, - RequestBody = new OpenApiRequestBody - { - Required = true, - Content = - { - ["application/json"] = new OpenApiMediaType { Schema = loginSchema, Example = loginExample }, - ["multipart/form-data"] = new OpenApiMediaType { Schema = loginSchema, Example = loginExample } - } - }, - Responses = - { - ["200"] = new OpenApiResponse { Description = "OK (proxied response)" }, - ["401"] = new OpenApiResponse { Description = "Unauthorized" } - } - }; - - swaggerDoc.Paths[path] = new OpenApiPathItem - { - Operations = - { - [OperationType.Post] = operation - } - }; - } - - private static void AddEnvelopeReceiverLoginOperation(OpenApiDocument swaggerDoc, DocumentFilterContext context) - { - const string path = "/api/Auth/envelope-receiver/{key}"; - - var bodySchema = context.SchemaGenerator.GenerateSchema(typeof(EnvelopeReceiverLogin), context.SchemaRepository); - - var operation = new OpenApiOperation - { - Summary = "Envelope receiver login (auth-hub proxy)", - Description = "Proxies the envelope receiver login to the auth service. " + - "The `cookie` query parameter is always forwarded as `true` so the auth service sets the per-envelope cookie automatically.", - Tags = [new() { Name = "Auth" }], - Parameters = - { - new OpenApiParameter - { - Name = "key", - In = ParameterLocation.Path, - Required = true, - Schema = new OpenApiSchema { Type = "string" }, - Description = "The unique envelope receiver key." - } - }, - RequestBody = new OpenApiRequestBody - { - Required = false, - Content = - { - ["multipart/form-data"] = new OpenApiMediaType { Schema = bodySchema } - } - }, - Responses = - { - ["200"] = new OpenApiResponse { Description = "OK – per-envelope cookie set by auth service." }, - ["401"] = new OpenApiResponse { Description = "Unauthorized – invalid or missing access code." } - } - }; - - swaggerDoc.Paths[path] = new OpenApiPathItem - { - Operations = - { - [OperationType.Post] = operation - } - }; - } -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/EnvelopeGenerator.API.csproj b/EnvelopeGenerator.API/EnvelopeGenerator.API.csproj deleted file mode 100644 index 96154a76..00000000 --- a/EnvelopeGenerator.API/EnvelopeGenerator.API.csproj +++ /dev/null @@ -1,84 +0,0 @@ - - - - net8.0 - enable - enable - true - EnvelopeGenerator.GeneratorAPI - - Digital Data GmbH - Digital Data GmbH - EnvelopeGenerator.GeneratorAPI - 1.4.0 - 1.4.0 - 1.4.0 - Copyright © 2025 Digital Data GmbH. All rights reserved. - bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/EnvelopeGenerator.API/Extensions/ReceiverClaimExtensions.cs b/EnvelopeGenerator.API/Extensions/ReceiverClaimExtensions.cs deleted file mode 100644 index d851c1de..00000000 --- a/EnvelopeGenerator.API/Extensions/ReceiverClaimExtensions.cs +++ /dev/null @@ -1,96 +0,0 @@ -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.API/Extensions/SenderClaimExtensions.cs b/EnvelopeGenerator.API/Extensions/SenderClaimExtensions.cs deleted file mode 100644 index 262968ed..00000000 --- a/EnvelopeGenerator.API/Extensions/SenderClaimExtensions.cs +++ /dev/null @@ -1,95 +0,0 @@ -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 diff --git a/EnvelopeGenerator.API/Jenkinsfile b/EnvelopeGenerator.API/Jenkinsfile deleted file mode 100644 index 3546ad67..00000000 --- a/EnvelopeGenerator.API/Jenkinsfile +++ /dev/null @@ -1,10 +0,0 @@ -pipeline { - agent any - stages { - stage('Build') { - steps { - sh 'dotnet build' - } - } - } -} diff --git a/EnvelopeGenerator.API/Middleware/ExceptionHandlingMiddleware.cs b/EnvelopeGenerator.API/Middleware/ExceptionHandlingMiddleware.cs deleted file mode 100644 index 08d6c392..00000000 --- a/EnvelopeGenerator.API/Middleware/ExceptionHandlingMiddleware.cs +++ /dev/null @@ -1,84 +0,0 @@ -namespace EnvelopeGenerator.API.Middleware; - -using DigitalData.Core.Exceptions; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; -using System.Net; -using System.Text.Json; - -/// -/// Middleware for handling exceptions globally in the application. -/// Captures exceptions thrown during the request pipeline execution, -/// logs them, and returns an appropriate HTTP response with a JSON error message. -/// -public class ExceptionHandlingMiddleware -{ - private readonly RequestDelegate _next; - private readonly ILogger _logger; - - /// - /// Initializes a new instance of the class. - /// - /// The next middleware in the request pipeline. - /// The logger instance for logging exceptions. - public ExceptionHandlingMiddleware(RequestDelegate next, ILogger logger) - { - _next = next; - _logger = logger; - } - - /// - /// Invokes the middleware to handle the HTTP request. - /// - /// The HTTP context of the current request. - /// A task that represents the asynchronous operation. - public async Task InvokeAsync(HttpContext context) - { - try - { - await _next(context); // Continue down the pipeline - } - catch (Exception ex) - { - await HandleExceptionAsync(context, ex, _logger); - } - } - - /// - /// Handles exceptions by logging them and writing an appropriate JSON response. - /// - /// The HTTP context of the current request. - /// The exception that occurred. - /// The logger instance for logging the exception. - /// A task that represents the asynchronous operation. - private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger) - { - context.Response.ContentType = "application/json"; - - string message; - - switch (exception) - { - case BadRequestException badRequestEx: - context.Response.StatusCode = (int)HttpStatusCode.BadRequest; - message = badRequestEx.Message; - break; - - case NotFoundException notFoundEx: - context.Response.StatusCode = (int)HttpStatusCode.NotFound; - message = notFoundEx.Message; - break; - - default: - logger.LogError(exception, "Unhandled exception occurred."); - context.Response.StatusCode = (int)HttpStatusCode.InternalServerError; - message = "An unexpected error occurred."; - break; - } - - await context.Response.WriteAsync(JsonSerializer.Serialize(new - { - message - })); - } -} diff --git a/EnvelopeGenerator.API/Models/Auth.cs b/EnvelopeGenerator.API/Models/Auth.cs deleted file mode 100644 index f08b6c95..00000000 --- a/EnvelopeGenerator.API/Models/Auth.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace EnvelopeGenerator.API.Models; - -public record Auth(string? AccessCode = null, string? SmsCode = null, string? AuthenticatorCode = null, bool UserSelectSMS = default) -{ - public bool HasAccessCode => AccessCode is not null; - - public bool HasSmsCode => SmsCode is not null; - - public bool HasAuthenticatorCode => AuthenticatorCode is not null; - - public bool HasMulti => new[] { HasAccessCode, HasSmsCode, HasAuthenticatorCode }.Count(state => state) > 1; - - public bool HasNone => !(HasAccessCode || HasSmsCode || HasAuthenticatorCode); -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Models/AuthTokenKeys.cs b/EnvelopeGenerator.API/Models/AuthTokenKeys.cs deleted file mode 100644 index 1952e384..00000000 --- a/EnvelopeGenerator.API/Models/AuthTokenKeys.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace EnvelopeGenerator.API.Models; - -/// -/// Represents the keys and default values used for authentication token handling -/// within the Envelope Generator API. -/// -public class AuthTokenKeys -{ - /// - /// Gets the name of the cookie used to store the authentication token. - /// - public string Cookie { get; init; } = "AuthToken"; - - /// - /// Gets the name of the query string parameter used to pass the authentication token. - /// - public string QueryString { get; init; } = "AuthToken"; - - /// - /// Gets the expected issuer value for the authentication token. - /// - public string Issuer { get; init; } = "auth.digitaldata.works"; - - /// - /// Gets the expected audience value for the authentication token. - /// - public string Audience { get; init; } = "sign-flow.digitaldata.works"; -} diff --git a/EnvelopeGenerator.API/Models/ConnectionString.cs b/EnvelopeGenerator.API/Models/ConnectionString.cs deleted file mode 100644 index 90280671..00000000 --- a/EnvelopeGenerator.API/Models/ConnectionString.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace EnvelopeGenerator.API.Models; - -/// -/// -/// -public class ConnectionString -{ - /// - /// - /// - public string Value { get; set; } = string.Empty; -} diff --git a/EnvelopeGenerator.API/Models/ContactLink.cs b/EnvelopeGenerator.API/Models/ContactLink.cs deleted file mode 100644 index 691dbc83..00000000 --- a/EnvelopeGenerator.API/Models/ContactLink.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace EnvelopeGenerator.API.Models -{ - /// - /// Represents a hyperlink for contact purposes with various HTML attributes. - /// - public class ContactLink - { - /// - /// Gets or sets the label of the hyperlink. - /// - public string Label { get; init; } = "Contact"; - - /// - /// Gets or sets the URL that the hyperlink points to. - /// - public string Href { get; set; } = string.Empty; - - /// - /// Gets or sets the target where the hyperlink should open. - /// Commonly used values are "_blank", "_self", "_parent", "_top". - /// - public string Target { get; set; } = "_blank"; - - /// - /// Gets or sets the relationship of the linked URL as space-separated link types. - /// Examples include "nofollow", "noopener", "noreferrer". - /// - public string Rel { get; set; } = string.Empty; - - /// - /// Gets or sets the filename that should be downloaded when clicking the hyperlink. - /// This attribute will only have an effect if the href attribute is set. - /// - public string Download { get; set; } = string.Empty; - - /// - /// Gets or sets the language of the linked resource. Useful when linking to - /// content in another language. - /// - public string HrefLang { get; set; } = "en"; - - /// - /// Gets or sets the MIME type of the linked URL. Helps browsers to handle - /// the type correctly when the link is clicked. - /// - public string Type { get; set; } = string.Empty; - - /// - /// Gets or sets additional information about the hyperlink, typically viewed - /// as a tooltip when the mouse hovers over the link. - /// - public string Title { get; set; } = string.Empty; - - /// - /// Gets or sets an identifier for the hyperlink, unique within the HTML document. - /// - public string Id { get; set; } = string.Empty; - } - -} diff --git a/EnvelopeGenerator.API/Models/Culture.cs b/EnvelopeGenerator.API/Models/Culture.cs deleted file mode 100644 index b1386088..00000000 --- a/EnvelopeGenerator.API/Models/Culture.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Globalization; - -namespace EnvelopeGenerator.API.Models; - -public class Culture -{ - private string _language = string.Empty; - public string Language { get => _language; - init { - _language = value; - Info = new(value); - } - } - public string FIClass { get; init; } = string.Empty; - - public CultureInfo? Info { get; init; } -} diff --git a/EnvelopeGenerator.API/Models/Cultures.cs b/EnvelopeGenerator.API/Models/Cultures.cs deleted file mode 100644 index 7b293c13..00000000 --- a/EnvelopeGenerator.API/Models/Cultures.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace EnvelopeGenerator.API.Models; - -public class Cultures : List -{ - public IEnumerable Languages => this.Select(c => c.Language); - - public IEnumerable FIClasses => this.Select(c => c.FIClass); - - public Culture Default => this.First(); - - public Culture? this[string? language] => language is null ? null : this.Where(c => c.Language == language).FirstOrDefault(); -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Models/CustomImages.cs b/EnvelopeGenerator.API/Models/CustomImages.cs deleted file mode 100644 index fd212acd..00000000 --- a/EnvelopeGenerator.API/Models/CustomImages.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace EnvelopeGenerator.API.Models; - -public class CustomImages : Dictionary -{ - public new Image this[string key] => TryGetValue(key, out var img) && img is not null ? img : new(); -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Models/EnvelopeReceiverLogin.cs b/EnvelopeGenerator.API/Models/EnvelopeReceiverLogin.cs deleted file mode 100644 index fa53ec61..00000000 --- a/EnvelopeGenerator.API/Models/EnvelopeReceiverLogin.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace EnvelopeGenerator.API.Models; - -/// -/// Request body for the envelope-receiver login endpoint. -/// -/// The access code sent to the receiver. -public record EnvelopeReceiverLogin(string? AccessCode = null); diff --git a/EnvelopeGenerator.API/Models/ErrorViewModel.cs b/EnvelopeGenerator.API/Models/ErrorViewModel.cs deleted file mode 100644 index b09ffded..00000000 --- a/EnvelopeGenerator.API/Models/ErrorViewModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace EnvelopeGenerator.API.Models; - -public class ErrorViewModel -{ - public string Title { get; init; } = "404"; - - public string Subtitle { get; init; } = "Hmmm..."; - - public string Body { get; init; } = "It looks like one of the developers fell asleep"; -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Models/Image.cs b/EnvelopeGenerator.API/Models/Image.cs deleted file mode 100644 index d7abd148..00000000 --- a/EnvelopeGenerator.API/Models/Image.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace EnvelopeGenerator.API.Models; - -public class Image -{ - public string Src { get; init; } = string.Empty; - - public Dictionary Classes { get; init; } = new(); - - public string GetClassIn(string page) => Classes.TryGetValue(page, out var cls) && cls is not null ? cls : string.Empty; -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Models/Login.cs b/EnvelopeGenerator.API/Models/Login.cs deleted file mode 100644 index 926b1811..00000000 --- a/EnvelopeGenerator.API/Models/Login.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace EnvelopeGenerator.API.Models; - -/// -/// Repräsentiert ein Login-Modell mit erforderlichem Passwort und optionaler ID und Benutzername. -/// -/// Das erforderliche Passwort für das Login. -/// Die optionale ID des Benutzers. -/// Der optionale Benutzername. -public record Login([Required] string Password, int? UserId = null, string? Username = null) -{ -} diff --git a/EnvelopeGenerator.API/Models/MainViewModel.cs b/EnvelopeGenerator.API/Models/MainViewModel.cs deleted file mode 100644 index d49d323b..00000000 --- a/EnvelopeGenerator.API/Models/MainViewModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace EnvelopeGenerator.API.Models; - -public class MainViewModel -{ - public string? Title { get; init; } -} diff --git a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Annotation.cs b/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Annotation.cs deleted file mode 100644 index 5dbb1aed..00000000 --- a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Annotation.cs +++ /dev/null @@ -1,93 +0,0 @@ -using EnvelopeGenerator.API.Models.PsPdfKitAnnotation; -using System.Text.Json.Serialization; - -namespace EnvelopeGenerator.API.Models.PsPdfKitAnnotation; - -public record Annotation : IAnnotation -{ - public required string Name { get; init; } - - #region Bound Annotation - [JsonIgnore] - public string? HorBoundAnnotName { get; init; } - - [JsonIgnore] - public string? VerBoundAnnotName { get; init; } - #endregion - - #region Layout - [JsonIgnore] - public double? MarginLeft { get; set; } - - [JsonIgnore] - public double MarginLeftRatio { get; init; } = 1; - - [JsonIgnore] - public double? MarginTop { get; set; } - - [JsonIgnore] - public double MarginTopRatio { get; init; } = 1; - - public double? Width { get; set; } - - [JsonIgnore] - public double WidthRatio { get; init; } = 1; - - public double? Height { get; set; } - - [JsonIgnore] - public double HeightRatio { get; init; } = 1; - #endregion - - #region Position - public double Left => (MarginLeft ?? 0) + (HorBoundAnnot?.HorBoundary ?? 0); - - public double Top => (MarginTop ?? 0) + (VerBoundAnnot?.VerBoundary ?? 0); - #endregion - - #region Boundary - [JsonIgnore] - public double HorBoundary => Left + (Width ?? 0); - - [JsonIgnore] - public double VerBoundary => Top + (Height ?? 0); - #endregion - - #region BoundAnnot - [JsonIgnore] - public Annotation? HorBoundAnnot { get; set; } - - [JsonIgnore] - public Annotation? VerBoundAnnot { get; set; } - #endregion - - public Color? BackgroundColor { get; init; } - - #region Border - public Color? BorderColor { get; init; } - - public string? BorderStyle { get; init; } - - public int? BorderWidth { get; set; } - #endregion - - [JsonIgnore] - internal Annotation Default - { - set - { - // To set null value, annotation must have null (0) value but null must has non-null value - if (MarginLeft == null && value.MarginLeft != null) - MarginLeft = value.MarginLeft * MarginLeftRatio; - - if (MarginTop == null && value.MarginTop != null) - MarginTop = value.MarginTop * MarginTopRatio; - - if (Width == null && value.Width != null) - Width = value.Width * WidthRatio; - - if (Height == null && value.Height != null) - Height = value.Height * HeightRatio; - } - } -}; \ No newline at end of file diff --git a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/AnnotationParams.cs b/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/AnnotationParams.cs deleted file mode 100644 index 019d872e..00000000 --- a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/AnnotationParams.cs +++ /dev/null @@ -1,80 +0,0 @@ -using EnvelopeGenerator.API.Models.PsPdfKitAnnotation; -using System.Text.Json.Serialization; - -namespace EnvelopeGenerator.API.Models.PsPdfKitAnnotation; - -public class AnnotationParams -{ - public AnnotationParams() - { - _AnnotationJSObjectInitor = new(CreateAnnotationJSObject); - } - - public Background? Background { get; init; } - - #region Annotation - [JsonIgnore] - public Annotation? DefaultAnnotation { get; init; } - - private readonly List _annots = new List(); - - public bool TryGet(string name, out Annotation annotation) - { -#pragma warning disable CS8601 // Possible null reference assignment. - annotation = _annots.FirstOrDefault(a => a.Name == name); -#pragma warning restore CS8601 // Possible null reference assignment. - return annotation is not null; - } - - public required IEnumerable Annotations - { - get => _annots; - init - { - _annots = value.ToList(); - - if (DefaultAnnotation is not null) - foreach (var annot in _annots) - annot.Default = DefaultAnnotation; - - for (int i = 0; i < _annots.Count; i++) - { - #region set bound annotations - // horizontal - if (_annots[i].HorBoundAnnotName is string horBoundAnnotName) - if (TryGet(horBoundAnnotName, out var horBoundAnnot)) - _annots[i].HorBoundAnnot = horBoundAnnot; - else - throw new InvalidOperationException($"{horBoundAnnotName} added as bound anotation. However, it is not defined."); - - // vertical - if (_annots[i].VerBoundAnnotName is string verBoundAnnotName) - if (TryGet(verBoundAnnotName, out var verBoundAnnot)) - _annots[i].VerBoundAnnot = verBoundAnnot; - else - throw new InvalidOperationException($"{verBoundAnnotName} added as bound anotation. However, it is not defined."); - #endregion - } - } - } - #endregion - - #region AnnotationJSObject - private Dictionary CreateAnnotationJSObject() - { - var dict = _annots.ToDictionary(a => a.Name.ToLower(), a => a as IAnnotation); - - if (Background is not null) - { - Background.Locate(_annots); - dict.Add(Background.Name.ToLower(), Background); - } - - return dict; - } - - private readonly Lazy> _AnnotationJSObjectInitor; - - public Dictionary AnnotationJSObject => _AnnotationJSObjectInitor.Value; - #endregion -} diff --git a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Background.cs b/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Background.cs deleted file mode 100644 index 19298196..00000000 --- a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Background.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Text.Json.Serialization; - -namespace EnvelopeGenerator.API.Models.PsPdfKitAnnotation; - -/// -/// The Background is an annotation for the PSPDF Kit. However, it has no function. -/// It is only the first annotation as a background for other annotations. -/// -public record Background : IAnnotation -{ - [JsonIgnore] - public double Margin { get; init; } - - public string Name { get; } = "Background"; - - public double? Width { get; set; } - - public double? Height { get; set; } - - public double Left { get; set; } - - public double Top { get; set; } - - public Color? BackgroundColor { get; init; } - - #region Border - public Color? BorderColor { get; init; } - - public string? BorderStyle { get; init; } - - public int? BorderWidth { get; set; } - #endregion - - public void Locate(IEnumerable annotations) - { - // set Top - if (annotations.MinBy(a => a.Top)?.Top is double minTop) - Top = minTop; - - // set Left - if (annotations.MinBy(a => a.Left)?.Left is double minLeft) - Left = minLeft; - - // set Width - if(annotations.MaxBy(a => a.GetRight())?.GetRight() is double maxRight) - Width = maxRight - Left; - - // set Height - if (annotations.MaxBy(a => a.GetBottom())?.GetBottom() is double maxBottom) - Height = maxBottom - Top; - - // add margins - Top -= Margin; - Left -= Margin; - Width += Margin * 2; - Height += Margin * 2; - } -} diff --git a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Color.cs b/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Color.cs deleted file mode 100644 index 0d3d4056..00000000 --- a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Color.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace EnvelopeGenerator.API.Models.PsPdfKitAnnotation; - -public record Color -{ - public int R { get; init; } = 0; - - public int G { get; init; } = 0; - - public int B { get; init; } = 0; -} diff --git a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Extensions.cs b/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Extensions.cs deleted file mode 100644 index 1cc508fd..00000000 --- a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Extensions.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace EnvelopeGenerator.API.Models.PsPdfKitAnnotation; - -public static class Extensions -{ - public static double GetRight(this IAnnotation annotation) => annotation.Left + annotation?.Width ?? 0; - - public static double GetBottom(this IAnnotation annotation) => annotation.Top + annotation?.Height ?? 0; -} diff --git a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/IAnnotation.cs b/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/IAnnotation.cs deleted file mode 100644 index 735bf573..00000000 --- a/EnvelopeGenerator.API/Models/PsPdfKitAnnotation/IAnnotation.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace EnvelopeGenerator.API.Models.PsPdfKitAnnotation; - -public interface IAnnotation -{ - string Name { get; } - - double? Width { get; } - - double? Height { get; } - - double Left { get; } - - double Top { get; } - - Color? BackgroundColor { get; } - - Color? BorderColor { get; } - - string? BorderStyle { get; } - - int? BorderWidth { get; } -} diff --git a/EnvelopeGenerator.API/Models/TFARegParams.cs b/EnvelopeGenerator.API/Models/TFARegParams.cs deleted file mode 100644 index ff363fdf..00000000 --- a/EnvelopeGenerator.API/Models/TFARegParams.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace EnvelopeGenerator.API.Models; - -/// -/// Represents the parameters for two-factor authentication (2FA) registration. -/// -public class TFARegParams -{ - /// - /// The maximum allowed time for completing the registration process. - /// - public TimeSpan TimeLimit { get; init; } = new(0, 30, 0); - - /// - /// The deadline for registration, calculated as the current time plus the . - /// - public DateTime Deadline => DateTime.Now.AddTicks(TimeLimit.Ticks); -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Options/CacheOptions.cs b/EnvelopeGenerator.API/Options/CacheOptions.cs deleted file mode 100644 index c39da3d7..00000000 --- a/EnvelopeGenerator.API/Options/CacheOptions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace EnvelopeGenerator.API.Options; - -/// -/// Configuration options for distributed caching. -/// -public sealed class CacheOptions -{ - /// - /// Configuration section name in appsettings.json. - /// - public const string SectionName = "Cache"; - - /// - /// Signature cache expiration time. - /// If null, signatures will not expire automatically. - /// - public TimeSpan? SignatureCacheExpiration { get; set; } -} diff --git a/EnvelopeGenerator.API/Program.cs b/EnvelopeGenerator.API/Program.cs deleted file mode 100644 index 8e0b570e..00000000 --- a/EnvelopeGenerator.API/Program.cs +++ /dev/null @@ -1,355 +0,0 @@ -using DigitalData.Core.API; -using DigitalData.Core.Application; -using EnvelopeGenerator.Infrastructure; -using EnvelopeGenerator.Domain.Constants; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Localization; -using Microsoft.EntityFrameworkCore; -using System.Globalization; -using Scalar.AspNetCore; -using Microsoft.OpenApi.Models; -using DigitalData.UserManager.DependencyInjection; -using EnvelopeGenerator.Application; -using DigitalData.Auth.Client; -using DigitalData.Core.Abstractions; -using EnvelopeGenerator.API.Models; -using Microsoft.IdentityModel.Tokens; -using DigitalData.Core.Abstractions.Security.Extensions; -using EnvelopeGenerator.API.Middleware; -using EnvelopeGenerator.API.Options; -using NLog.Web; -using NLog; -using DigitalData.Auth.Claims; -using EnvelopeGenerator.API; -using Microsoft.AspNetCore.Authentication.JwtBearer; - -var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); -logger.Info("Logging initialized!"); - -try -{ - var builder = WebApplication.CreateBuilder(args); - - builder.Configuration.AddJsonFile("yarp.json", optional: true, reloadOnChange: true); - - builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); - - if (!builder.Environment.IsDevelopment()) - { - builder.Logging.ClearProviders(); - builder.Host.UseNLog(); - } - - var config = builder.Configuration; - - var deferredProvider = new DeferredServiceProvider(); - - builder.Services.AddControllers() - .AddJsonOptions(options => - { - options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles; - }); - builder.Services.AddHttpClient(); - builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); - - // CORS Policy - var allowedOrigins = config.GetSection("AllowedOrigins").Get() ?? - throw new InvalidOperationException("AllowedOrigins section is missing in the configuration."); - builder.Services.AddCors(options => - { - options.AddPolicy("AllowSpecificOriginsPolicy", builder => - { - builder.WithOrigins(allowedOrigins) - .SetIsOriginAllowedToAllowWildcardSubdomains() - .AllowAnyMethod() - .AllowAnyHeader() - .AllowCredentials(); - }); - }); - - // Swagger - builder.Services.AddEndpointsApiExplorer(); - builder.Services.AddSwaggerGen(options => - { - options.SwaggerDoc("v1", new OpenApiInfo - { - Version = "v1", - Title = "signFLOW Absender-API", - Description = "Eine API zur Verwaltung der Erstellung, des Versands und der Nachverfolgung von Umschlägen in der signFLOW-Anwendung.", - Contact = new OpenApiContact - { - Name = "Digital Data GmbH", - Url = new Uri("https://digitaldata.works/digitale-signatur#kontakt"), - Email = "info-flow@digitaldata.works" - }, - }); - - options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme - { - Name = "Authorization", - Type = SecuritySchemeType.Http, - Scheme = "bearer", - BearerFormat = "JWT", - In = ParameterLocation.Header, - Description = "JWT-Autorisierungs-Header unter Verwendung des Bearer-Schemas.", - }); - - options.AddSecurityRequirement(new OpenApiSecurityRequirement - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme, - Id = "Bearer" - } - }, - Array.Empty() - } - }); - - var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml"); - foreach (var xmlFile in xmlFiles) - { - options.IncludeXmlComments(xmlFile); - } - - options.DocumentFilter(); - }); -#if NET9_0_OR_GREATER - builder.Services.AddOpenApi(); -#endif - - //Add EF Core dbcontext - var useDbMigration = Environment.GetEnvironmentVariable("MIGRATION_TEST_MODE") == true.ToString() || config.GetValue("UseDbMigration"); - var cnnStrName = useDbMigration ? "DbMigrationTest" : "Default"; - var connStr = config.GetConnectionString(cnnStrName) - ?? throw new InvalidOperationException($"Connection string '{cnnStrName}' is missing in the application configuration."); - - builder.Services.Configure(cs => cs.Value = connStr); - - builder.Services.AddDbContext(options => options.UseSqlServer(connStr)); - - builder.Services.AddAuthHubClient(config.GetSection("AuthClientParams")); - - var authTokenKeys = config.GetOrDefault(); - - builder.Services.AddAuthentication(options => - { - options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; - }) - .AddJwtBearer(AuthScheme.Sender, opt => - { - opt.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuerSigningKey = true, - IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) => - { - var clientParams = deferredProvider.GetOptions(); - var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience); - return [publicKey.SecurityKey]; - }, - ValidateIssuer = true, - ValidIssuer = authTokenKeys.Issuer, - ValidateAudience = true, - ValidAudience = authTokenKeys.Audience, - }; - - opt.Events = new JwtBearerEvents - { - OnMessageReceived = context => - { - // if there is no token read related cookie or query string - if (context.Token is null) // if there is no token - { - if (context.Request.Cookies.TryGetValue(authTokenKeys.Cookie, out var cookieToken) && cookieToken is not null) - context.Token = cookieToken; - else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken)) - context.Token = queryStrToken; - } - return Task.CompletedTask; - } - }; - }) - // Per-envelope receiver scheme: reads the JWT from the cookie named - // AuthTokenSignFLOWReceiver.{envelope_key} where envelope_key is the - // last path segment of the request URL. - // This enables simultaneous authentication for multiple envelopes - // within the same browser session. - .AddJwtBearer(AuthScheme.Receiver, opt => - { - opt.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuerSigningKey = true, - IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) => - { - var clientParams = deferredProvider.GetOptions(); - var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience); - return [publicKey.SecurityKey]; - }, - ValidateIssuer = true, - ValidIssuer = authTokenKeys.Issuer, - ValidateAudience = true, - ValidAudience = authTokenKeys.Audience, - }; - - opt.Events = new JwtBearerEvents - { - OnMessageReceived = context => - { - var paths = context.Request.Path.Value?.Split('/', StringSplitOptions.RemoveEmptyEntries); - - // Derive the envelope key from the last route segment: /{envelope_key} - var envelopeKey = paths?.LastOrDefault(); - - if (envelopeKey is not null) - { - var cookieName = CookieNames.GetEnvelopeReceiverCookieName(authTokenKeys.Cookie, envelopeKey); - if (context.Request.Cookies.TryGetValue(cookieName, out var cookieToken) && cookieToken is not null) - context.Token = cookieToken; - } - - return Task.CompletedTask; - }, - OnTokenValidated = context => - { - var paths = context.Request.Path.Value?.Split('/', StringSplitOptions.RemoveEmptyEntries); - var envelopeKey = paths?.LastOrDefault(); - - var sub = context.Principal?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value - ?? context.Principal?.FindFirst("sub")?.Value; - - if (envelopeKey is null || sub != envelopeKey) - context.Fail("Envelope key in the path does not match the token subject."); - - return Task.CompletedTask; - } - }; - }); - - // Authentication - builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) - .AddCookie(options => - { - options.Cookie.HttpOnly = true; // Makes the cookie inaccessible to client-side scripts for security - options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Ensures cookies are sent over HTTPS only - options.Cookie.SameSite = SameSiteMode.Strict; // Protects against CSRF attacks by restricting how cookies are sent with requests from external sites - options.LoginPath = "/api/auth/login"; - options.LogoutPath = "/api/auth/logout"; - options.SlidingExpiration = true; - }); - - builder.Services.AddAuthorizationBuilder() - .AddPolicy(AuthPolicy.SenderOrReceiver, policy => policy - .RequireRole(Role.Sender, Role.Receiver.Full) - .AddAuthenticationSchemes(AuthScheme.Sender, AuthScheme.Receiver)) - .AddPolicy(AuthPolicy.Sender, policy => policy - .RequireRole(Role.Sender) - .AddAuthenticationSchemes(AuthScheme.Sender)) - .AddPolicy(AuthPolicy.Receiver, policy => policy - .AddAuthenticationSchemes(AuthScheme.Receiver) - .RequireAuthenticatedUser() - .RequireRole(Role.Receiver.Full, "receiver")) - .AddPolicy(AuthPolicy.ReceiverTFA, policy => policy.RequireRole(Role.Receiver.TFA)); - - // User manager -#pragma warning disable CS0618 // Type or member is obsolete - builder.Services.AddUserManager(); -#pragma warning restore CS0618 // Type or member is obsolete - - // LDAP - builder.ConfigureBySection(); - builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions")); - - // Localizer - builder.Services.AddCookieBasedLocalizer(); - - // Cache options - builder.Services.Configure(config.GetSection(CacheOptions.SectionName)); - - // Distributed Cache - SQL Server - builder.Services.AddDistributedSqlServerCache(options => - { - config.GetSection("Cache:SqlServer").Bind(options); - - if (string.IsNullOrWhiteSpace(options.ConnectionString)) - { - options.ConnectionString = connStr; - } - }); - - // Envelope generator serives -#pragma warning disable CS0618 // Type or member is obsolete - builder.Services - .AddEnvelopeGeneratorInfrastructureServices(opt => - { - opt.AddDbTriggerParams(config); - opt.AddDbContext((provider, options) => - { - var logger = provider.GetRequiredService>(); - options.UseSqlServer(connStr) - .LogTo(log => logger.LogInformation("{log}", log), Microsoft.Extensions.Logging.LogLevel.Trace) - .EnableSensitiveDataLogging() - .EnableDetailedErrors(); - }); - opt.AddSQLExecutor(executor => executor.ConnectionString = connStr); - }) - .AddEnvelopeGeneratorServices(config); -#pragma warning restore CS0618 // Type or member is obsolete - - var app = builder.Build(); - - deferredProvider.Factory = () => app.Services; - - app.UseMiddleware(); - -#if NET9_0_OR_GREATER - app.MapOpenApi(); -#endif - - // Configure the HTTP request pipeline. - if (app.Environment.IsDevelopment() || (app.IsDevOrDiP() && config.GetValue("UseSwagger"))) - { - app.UseSwagger(); - app.UseSwaggerUI(); - app.MapScalarApiReference(); - } - - // Set CORS policy - app.UseCors("AllowSpecificOriginsPolicy"); - - // Localizer - string[] supportedCultureNames = ["de-DE", "en-US"]; - IList list = [.. supportedCultureNames.Select(cn => new CultureInfo(cn))]; - var cultureInfo = list.FirstOrDefault() ?? throw new InvalidOperationException("There is no supported culture."); - var requestLocalizationOptions = new RequestLocalizationOptions - { - SupportedCultures = list, - SupportedUICultures = list - }; - requestLocalizationOptions.RequestCultureProviders.Add(new QueryStringRequestCultureProvider()); - app.UseRequestLocalization(requestLocalizationOptions); - - - app.UseHttpsRedirection(); - - app.UseDefaultFiles(); - app.UseStaticFiles(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.MapControllers(); - - // Catch-all YARP proxy — only forward requests that are not swagger/scalar/openapi paths. - app.MapReverseProxy(); - - app.Run(); -} -catch (Exception ex) -{ - logger.Error(ex, "Stopped program because of exception"); - throw; -} \ No newline at end of file diff --git a/EnvelopeGenerator.API/Properties/PublishProfiles/IISProfileNet8.pubxml b/EnvelopeGenerator.API/Properties/PublishProfiles/IISProfileNet8.pubxml deleted file mode 100644 index 039b567b..00000000 --- a/EnvelopeGenerator.API/Properties/PublishProfiles/IISProfileNet8.pubxml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - Package - Release - Any CPU - - true - false - 5e0e17c0-ff5a-4246-bf87-1add85376a27 - M:\App&Service\0 DD - Smart UP\signFLOW\PreRelease\API\net8\$(Version)\EnvelopeGenerator.API.zip - true - EnvelopeGenerator - <_TargetId>IISWebDeployPackage - net8.0 - - \ No newline at end of file diff --git a/EnvelopeGenerator.API/Properties/launchSettings.json b/EnvelopeGenerator.API/Properties/launchSettings.json deleted file mode 100644 index 365fa0b2..00000000 --- a/EnvelopeGenerator.API/Properties/launchSettings.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:4176", - "sslPort": 44300 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "swagger", - "applicationUrl": "http://localhost:5131", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "sender", - "applicationUrl": "https://localhost:8088;http://localhost:5131", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "angular": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "launchUrl": "", - "applicationUrl": "https://localhost:7174;http://localhost:5131", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/EnvelopeGenerator.API/appsettings.Development.json b/EnvelopeGenerator.API/appsettings.Development.json deleted file mode 100644 index dce89096..00000000 --- a/EnvelopeGenerator.API/appsettings.Development.json +++ /dev/null @@ -1,29 +0,0 @@ -{ -"Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } -}, -"ReverseProxy": { - "Clusters": { - "receiver-ui": { - "Destinations": { - "primary": { - "Address": "https://localhost:52936" - } - } - } - } -}, - "AuthClientParams": { - "Url": "http://172.24.12.39:9090/auth-hub", - "PublicKeys": [ - { - "Issuer": "auth.digitaldata.works", - "Audience": "sign-flow.digitaldata.works" - } - ], - "RetryDelay": "00:00:05" - } -} diff --git a/EnvelopeGenerator.API/appsettings.json b/EnvelopeGenerator.API/appsettings.json deleted file mode 100644 index a3907871..00000000 --- a/EnvelopeGenerator.API/appsettings.json +++ /dev/null @@ -1,256 +0,0 @@ -{ - "UseSwagger": true, - "UseDbMigration": false, - "DiPMode": true, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "AllowedOrigins": [ "http://localhost:4200", "http://172.24.12.39:9090", "https://localhost:8088", "http://localhost:5131", "http://localhost:7192" ], - "ConnectionStrings": { - "Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;", - "DbMigrationTest": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM_DATA_MIGR_TEST;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;" - }, - "DirectorySearchOptions": { - "ServerName": "DD-VMP01-DC01", - "Root": "DC=dd-gan,DC=local,DC=digitaldata,DC=works", - "UserCacheExpirationDays": 1, - "CustomSearchFilters": { - "User": "(&(objectClass=user)(sAMAccountName=*))", - "Group": "(&(objectClass=group)(samAccountName=*))" - } - }, - "AuthClientParams": { - "Url": "http://172.24.12.39:9090/auth-hub", - "PublicKeys": [ - { - "Issuer": "auth.digitaldata.works", - "Audience": "sign-flow.digitaldata.works" - } - ], - "RetryDelay": "00:00:05" - }, - "AuthTokenKeys": { - "Cookie": "AuthToken", - "QueryString": "AuthToken", - "Issuer": "auth.digitaldata.works", - "Audience": "sign-flow.digitaldata.works" - }, - "PSPDFKitLicenseKey": "SXCtGGY9XA-31OGUXQK-r7c6AkdLGPm2ljuyDr1qu0kkhLvydg-Do-fxpNUF4Rq3fS_xAnZRNFRHbXpE6sQ2BMcCSVTcXVJO6tPviexjpiT-HnrDEySlUERJnnvh-tmeOWprxS6BySPnSILkmaVQtUfOIUS-cUbvvEYHTvQBKbSF8di4XHQFyfv49ihr51axm3NVV3AXwh2EiKL5C5XdqBZ4sQ4O7vXBjM2zvxdPxlxdcNYmiU83uAzw7B83O_jubPzya4CdUHh_YH7Nlp2gP56MeG1Sw2JhMtfG3Rj14Sg4ctaeL9p6AEWca5dDjJ2li5tFIV2fQSsw6A_cowLu0gtMm5i8IfJXeIcQbMC2-0wGv1oe9hZYJvFMdzhTM_FiejM0agemxt3lJyzuyP8zbBSOgp7Si6A85krLWPZptyZBTG7pp7IHboUHfPMxCXqi-zMsqewOJtQBE2mjntU-lPryKnssOpMPfswwQX7QSkJYV5EMqNmEhQX6mEkp2wcqFzMC7bJQew1aO4pOpvChUaMvb1vgRek0HxLag0nwQYX2YrYGh7F_xXJs-8HNwJe8H0-eW4x4faayCgM5rB5772CCCsD9ThZcvXFrjNHHLGJ8WuBUFm6LArvSfFQdii_7j-_sqHMpeKZt26NFgivj1A==", - "Content-Security-Policy": [ // The first format parameter {0} will be replaced by the nonce value. - "default-src 'self'", - "script-src 'self' 'nonce-{0}' 'unsafe-eval'", - "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com:*", - "img-src 'self' data: https: blob:", - "font-src 'self' https://fonts.gstatic.com:*", - "connect-src 'self' https://nominatim.openstreetmap.org:* http://localhost:* https://localhost:* ws://localhost:* wss://localhost:* blob:", - "frame-src 'self'", - "media-src 'self'", - "object-src 'self'" - ], - "NLog": { - "throwConfigExceptions": true, - "variables": { - "logDirectory": "E:\\LogFiles\\Digital Data\\signFlow", - "logFileNamePrefix": "${shortdate}-ECM.EnvelopeGenerator.Web" - }, - "targets": { - "infoLogs": { - "type": "File", - "fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log", - "maxArchiveDays": 30 - }, - "errorLogs": { - "type": "File", - "fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log", - "maxArchiveDays": 30 - }, - "criticalLogs": { - "type": "File", - "fileName": "${logDirectory}\\${logFileNamePrefix}-Critical.log", - "maxArchiveDays": 30 - } - }, - // Trace, Debug, Info, Warn, Error and *Fatal* - "rules": [ - { - "logger": "*", - "minLevel": "Info", - "maxLevel": "Warn", - "writeTo": "infoLogs" - }, - { - "logger": "*", - "level": "Error", - "writeTo": "errorLogs" - }, - { - "logger": "*", - "level": "Fatal", - "writeTo": "criticalLogs" - } - ] - }, - "ContactLink": { - "Label": "Kontakt", - "Href": "https://digitaldata.works/", - "HrefLang": "de", - "Target": "_blank", - "Title": "Digital Data GmbH" - }, - /* Resx naming format is -> Resource.language.resx (eg: Resource.de_DE.resx). - To add a new language, first you should write the required resx file. - first is the default culture name. */ - "Cultures": [ - { - "Language": "de-DE", - "FIClass": "fi-de" - }, - { - "Language": "en-US", - "FIClass": "fi-us" - } - ], - "DisableMultiLanguage": false, - "Regexes": [ - { - "Pattern": "/^\\p{L}+(?:([\\ \\-\\']|(\\.\\ ))\\p{L}+)*$/u", - "Name": "City", - "Platforms": [ ".NET" ] - }, - { - "Pattern": "/^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$/", - "Name": "City", - "Platforms": [ "javascript" ] - } - ], - "CustomImages": { - "App": { - "Src": "/img/DD_signFLOW_LOGO.png", - "Classes": { - "Main": "signFlow-logo" - } - }, - "Company": { - "Src": "/img/digital_data.svg", - "Classes": { - "Show": "dd-show-logo", - "Locked": "dd-locked-logo" - } - } - }, - "DispatcherParams": { - "SendingProfile": 1, - "AddedWho": "DDEnvelopGenerator", - "ReminderTypeId": 202377, - "EmailAttmt1": "" - }, - "MailParams": { - "Placeholders": { - "[NAME_PORTAL]": "signFlow", - "[SIGNATURE_TYPE]": "signieren", - "[REASON]": "" - } - }, - "GtxMessagingParams": { - "Uri": "https://rest.gtx-messaging.net", - "Path": "smsc/sendsms/f566f7e5-bdf2-4a9a-bf52-ed88215a432e/json", - "Headers": {}, - "QueryParams": { - "from": "signFlow" - } - }, - "TFARegParams": { - "TimeLimit": "00:30:00" - }, - "DbTriggerParams": { - "Envelope": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ], - "EnvelopeHistory": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ], - "EmailOut": [ "TBEMLP_EMAIL_OUT_AFT_INS", "TBEMLP_EMAIL_OUT_AFT_UPD" ], - "EnvelopeReceiverReadOnly": [ "TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD" ], - "Receiver": [], - "EmailTemplate": [ "TBSIG_EMAIL_TEMPLATE_AFT_UPD" ] - }, - "Cache": { - "SignatureCacheExpiration": null, - "SqlServer": { - "ConnectionString": null, - "SchemaName": "dbo", - "TableName": "TBDD_CACHE" - } - }, - "MainPageTitle": null, - "AnnotationParams": { - "Background": { - "Margin": 0.20, - "BackgroundColor": { - "R": 222, - "G": 220, - "B": 215 - }, - "BorderColor": { - "R": 204, - "G": 202, - "B": 198 - }, - "BorderStyle": "underline", - "BorderWidth": 4 - }, - "DefaultAnnotation": { - "Width": 1, - "Height": 0.5, - "MarginTop": 1 - }, - "Annotations": [ - { - "Name": "Signature", - "MarginTop": 0 - }, - { - "Name": "PositionLabel", - "VerBoundAnnotName": "Signature", - "WidthRatio": 1.2, - "HeightRatio": 0.5, - "MarginTopRatio": 0.22 - }, - { - "Name": "Position", - "VerBoundAnnotName": "PositionLabel", - "WidthRatio": 1.2, - "HeightRatio": 0.5, - "MarginTopRatio": -0.05 - }, - { - "Name": "CityLabel", - "VerBoundAnnotName": "Position", - "WidthRatio": 1.2, - "HeightRatio": 0.5, - "MarginTopRatio": 0.05 - }, - { - "Name": "City", - "VerBoundAnnotName": "CityLabel", - "WidthRatio": 1.2, - "HeightRatio": 0.5, - "MarginTopRatio": -0.05 - }, - { - "Name": "DateLabel", - "VerBoundAnnotName": "City", - "WidthRatio": 1.55, - "HeightRatio": 0.5, - "MarginTopRatio": 0.05 - }, - { - "Name": "Date", - "VerBoundAnnotName": "DateLabel", - "WidthRatio": 1.55, - "HeightRatio": 0.5, - "MarginTopRatio": -0.1 - } - ] - } -} diff --git a/EnvelopeGenerator.API/yarp.json b/EnvelopeGenerator.API/yarp.json deleted file mode 100644 index aba392dd..00000000 --- a/EnvelopeGenerator.API/yarp.json +++ /dev/null @@ -1,185 +0,0 @@ -{ -"ReverseProxy": { - "Routes": { - "receiver-ui-root": { - "ClusterId": "receiver-ui", - "Order": 300, - "Match": { - "Path": "/", - "Methods": [ "GET", "HEAD" ] - }, - "Transforms": [ - { "PathSet": "/index.html" } - ] - }, - "receiver-ui-sender": { - "ClusterId": "receiver-ui", - "Order": 100, - "Match": { - "Path": "/sender/{**catch-all}", - "Methods": [ "GET", "HEAD" ] - }, - "Transforms": [ - { "PathSet": "/index.html" } - ] - }, - "receiver-ui-envelope": { - "ClusterId": "receiver-ui", - "Order": 100, - "Match": { - "Path": "/envelope/{EnvelopeKey}", - "Methods": [ "GET", "HEAD" ] - }, - "Transforms": [ - { "PathSet": "/index.html" } - ] - }, - "receiver-ui-envelope-dxreportviewer": { - "ClusterId": "receiver-ui", - "Order": 90, - "Match": { - "Path": "/envelope/{EnvelopeKey}/DxReportViewer", - "Methods": [ "GET", "HEAD" ] - }, - "Transforms": [ - { "PathSet": "/index.html" } - ] - }, - "receiver-ui-blazor-framework": { - "ClusterId": "receiver-ui", - "Order": 50, - "Match": { - "Path": "/_framework/{**catch-all}", - "Methods": [ "GET", "HEAD" ] - } - }, - "receiver-ui-blazor-content": { - "ClusterId": "receiver-ui", - "Order": 50, - "Match": { - "Path": "/_content/{**catch-all}", - "Methods": [ "GET", "HEAD" ] - } - }, - "receiver-ui-static-css": { - "ClusterId": "receiver-ui", - "Order": 200, - "Match": { - "Path": "/css/{**catch-all}", - "Methods": [ "GET", "HEAD" ] - }, - "Transforms": [ - { - "ResponseHeader": "Cache-Control", - "Set": "no-cache, no-store, must-revalidate", - "When": "Always" - } - ] - }, - "receiver-ui-static-js": { - "ClusterId": "receiver-ui", - "Order": 200, - "Match": { - "Path": "/js/{**catch-all}", - "Methods": [ "GET", "HEAD" ] - }, - "Transforms": [ - { - "ResponseHeader": "Cache-Control", - "Set": "no-cache, no-store, must-revalidate", - "When": "Always" - } - ] - }, - "receiver-ui-fake-data": { - "ClusterId": "receiver-ui", - "Order": 200, - "Match": { - "Path": "/fake-data/{**catch-all}", - "Methods": [ "GET", "HEAD" ] - } - }, - "receiver-ui-appsettings": { - "ClusterId": "receiver-ui", - "Order": 50, - "Match": { - "Path": "/appsettings.json", - "Methods": [ "GET", "HEAD" ] - } - }, - "receiver-ui-appsettings-dev": { - "ClusterId": "receiver-ui", - "Order": 50, - "Match": { - "Path": "/appsettings.Development.json", - "Methods": [ "GET", "HEAD" ] - } - }, - "receiver-ui-styles": { - "ClusterId": "receiver-ui", - "Order": 50, - "Match": { - "Path": "/EnvelopeGenerator.ReceiverUI.styles.css", - "Methods": [ "GET", "HEAD" ] - } - }, - "receiver-ui-fonts": { - "ClusterId": "receiver-ui", - "Order": 200, - "Match": { - "Path": "/fonts/{**catch-all}", - "Methods": [ "GET", "HEAD" ] - } - }, - "receiver-ui-images": { - "ClusterId": "receiver-ui", - "Order": 200, - "Match": { - "Path": "/images/{**catch-all}", - "Methods": [ "GET", "HEAD" ] - } - }, - "auth-login": { - "ClusterId": "auth-hub", - "Match": { - "Path": "/api/auth", - "Methods": [ "POST" ] - }, - "Transforms": [ - { "PathSet": "/api/auth/sign-flow" } - ] - }, - "auth-envelope-receiver-login": { - "ClusterId": "auth-hub", - "Match": { - "Path": "/api/Auth/envelope-receiver/{key}", - "Methods": [ "POST" ] - }, - "Transforms": [ - { "PathPattern": "/api/auth/envelope-receiver/{key}" }, - { - "QueryValueParameter": "cookie", - "Set": "true" - } - ] - } - }, - "Clusters": { - "receiver-ui": { - "Destinations": { - "primary": { - "Address": "https://localhost:52936" - } - } - }, - "auth-hub": { - "Destinations": { - "primary": { - "Address": "http://172.24.12.39:9090" - } - } - } - } - } -} - diff --git a/EnvelopeGenerator.sln b/EnvelopeGenerator.sln index 4b7867c0..cca97ec9 100644 --- a/EnvelopeGenerator.sln +++ b/EnvelopeGenerator.sln @@ -36,8 +36,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.PdfEditor EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Tests", "EnvelopeGenerator.Tests\EnvelopeGenerator.Tests.csproj", "{224C4845-1CDE-22B7-F3A9-1FF9297F70E8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.API", "EnvelopeGenerator.API\EnvelopeGenerator.API.csproj", "{EC768913-6270-14F4-1DD3-69C87A659462}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.DependencyInjection", "EnvelopeGenerator.DependencyInjection\EnvelopeGenerator.DependencyInjection.csproj", "{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EnvelopeGenerator.Server", "EnvelopeGenerator.Server", "{BF1700D5-592E-4FFA-84E8-5480E289A1F0}" @@ -95,10 +93,6 @@ Global {224C4845-1CDE-22B7-F3A9-1FF9297F70E8}.Debug|Any CPU.Build.0 = Debug|Any CPU {224C4845-1CDE-22B7-F3A9-1FF9297F70E8}.Release|Any CPU.ActiveCfg = Release|Any CPU {224C4845-1CDE-22B7-F3A9-1FF9297F70E8}.Release|Any CPU.Build.0 = Release|Any CPU - {EC768913-6270-14F4-1DD3-69C87A659462}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EC768913-6270-14F4-1DD3-69C87A659462}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.Build.0 = Release|Any CPU {5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -136,7 +130,6 @@ Global {6D56C01F-D6CB-4D8A-BD3D-4FD34326998C} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} {211619F5-AE25-4BA5-A552-BACAFE0632D3} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB} {224C4845-1CDE-22B7-F3A9-1FF9297F70E8} = {0CBC2432-A561-4440-89BC-671B66A24146} - {EC768913-6270-14F4-1DD3-69C87A659462} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} {5DCCF9A1-C03F-90E6-87D3-E96DB25250C2} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} {BF1700D5-592E-4FFA-84E8-5480E289A1F0} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB} {4E6C54DA-576D-0955-2564-9EC890BB8279} = {BF1700D5-592E-4FFA-84E8-5480E289A1F0} From 380a5f4c0cdcd33796d16fb505e938bb1d30dfac Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 28 Sep 2026 12:21:44 +0200 Subject: [PATCH 2/2] Add signature submission feature and pipeline updates Introduced a new feature for submitting signatures, including backend and frontend changes. Added `SignatureSubmitDto` and `SubmitAsync` in `SignatureService` to handle signature data submission. Implemented a new `Submit` endpoint in `SignatureController` to validate and process signature payloads. Updated pipeline behaviors to include `SaveSignatureBehavior` and adjusted the execution order. Enhanced query logic in `EnvelopeReceiverResolutionBehavior` and `SaveSignatureBehavior` to align with the updated data model. Refactored `EGDbContext` to improve trigger handling and logging. Updated `ReceiverSignedPage.razor` to integrate `SignatureService` and handle submission errors gracefully. Adjusted application URLs in `launchSettings.json` and added new database triggers in `appsettings.json`. --- .../Common/Dto/Signature.cs | 4 +- .../DependencyInjection.cs | 11 +++-- .../EnvelopeReceiverResolutionBehavior.cs | 6 ++- .../Behaviors/SaveSignatureBehavior.cs | 4 +- .../EGDbContext.cs | 24 ++++++--- .../Models/SignatureSubmitDto.cs | 10 ++++ .../Services/SignatureService.cs | 29 +++++++++++ .../Components/Pages/ReceiverSignedPage.razor | 42 +++++++++++++++- .../Controllers/SignatureController.cs | 49 ++++++++++++++++++- .../Properties/launchSettings.json | 2 +- .../EnvelopeGenerator.Server/appsettings.json | 2 + 11 files changed, 162 insertions(+), 21 deletions(-) create mode 100644 EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Models/SignatureSubmitDto.cs diff --git a/EnvelopeGenerator.Application/Common/Dto/Signature.cs b/EnvelopeGenerator.Application/Common/Dto/Signature.cs index 9a934934..71af71eb 100644 --- a/EnvelopeGenerator.Application/Common/Dto/Signature.cs +++ b/EnvelopeGenerator.Application/Common/Dto/Signature.cs @@ -49,7 +49,7 @@ public sealed record Signature public string? Position { get => _position; - init => _position = string.IsNullOrWhiteSpace(value) ? value : null; + init => _position = string.IsNullOrWhiteSpace(value) ? null : value; } /// @@ -62,4 +62,4 @@ public sealed record Signature /// Example: "Berlin" ? rendered as "Berlin, 26.01.2025" /// public required string Place { get; init; } -} \ No newline at end of file +} diff --git a/EnvelopeGenerator.Application/DependencyInjection.cs b/EnvelopeGenerator.Application/DependencyInjection.cs index 23715f65..bf41d504 100644 --- a/EnvelopeGenerator.Application/DependencyInjection.cs +++ b/EnvelopeGenerator.Application/DependencyInjection.cs @@ -65,18 +65,21 @@ public static class DependencyInjection cfg.AddBehavior, EnvelopeReceiverResolutionBehavior>(); // 1. AnnotationBehavior - Saves annotations (executes second) + cfg.AddBehavior, SaveSignatureBehavior>(); + + // 2. AnnotationBehavior - Saves annotations for legacy web flow cfg.AddBehavior, AnnotationBehavior>(); - // 2. DocStatusBehavior - Creates document status (executes third) + // 3. DocStatusBehavior - Creates document status cfg.AddBehavior, DocStatusBehavior>(); - // 3. HistoryBehavior - Records history (executes fourth) + // 4. HistoryBehavior - Records history cfg.AddBehavior, HistoryBehavior>(); - // 4. SendSignedMailBehavior - Sends notification email (executes LAST, only if all previous succeed) + // 5. SendSignedMailBehavior - Sends notification email (executes LAST) cfg.AddBehavior, SendSignedMailBehavior>(); }); return services; } -} \ No newline at end of file +} diff --git a/EnvelopeGenerator.Application/DocReceiverElements/Behaviors/EnvelopeReceiverResolutionBehavior.cs b/EnvelopeGenerator.Application/DocReceiverElements/Behaviors/EnvelopeReceiverResolutionBehavior.cs index 8eeb1669..5a1946d4 100644 --- a/EnvelopeGenerator.Application/DocReceiverElements/Behaviors/EnvelopeReceiverResolutionBehavior.cs +++ b/EnvelopeGenerator.Application/DocReceiverElements/Behaviors/EnvelopeReceiverResolutionBehavior.cs @@ -43,7 +43,11 @@ public class EnvelopeReceiverResolutionBehavior : IPipelineBehavior x.Envelope) + .Include(x => x.Receiver) + .SingleOrDefaultAsync(cancellationToken) ?? throw new NotFoundException("EnvelopeReceiver not found"); request.SetEnvelopeReceiver(_mapper.Map(er)); diff --git a/EnvelopeGenerator.Application/DocReceiverElements/Behaviors/SaveSignatureBehavior.cs b/EnvelopeGenerator.Application/DocReceiverElements/Behaviors/SaveSignatureBehavior.cs index e4aecec0..f1207ae3 100644 --- a/EnvelopeGenerator.Application/DocReceiverElements/Behaviors/SaveSignatureBehavior.cs +++ b/EnvelopeGenerator.Application/DocReceiverElements/Behaviors/SaveSignatureBehavior.cs @@ -50,8 +50,8 @@ public class SaveSignatureBehavior : IPipelineBehavior throw new BadRequestException($"Signatures are required for saving signature behavior."); var elements = await _elementRepo - .Where(e => e.Document.EnvelopeId == request.Envelope.Id) - .Where(e => e.ReceiverId == request.Receiver.Id) + .Where(e => e.Document.EnvelopeId == request.EnvelopeReceiver.EnvelopeId) + .Where(e => e.ReceiverId == request.EnvelopeReceiver.ReceiverId) .ToListAsync(cancel); foreach (var element in elements) diff --git a/EnvelopeGenerator.Infrastructure/EGDbContext.cs b/EnvelopeGenerator.Infrastructure/EGDbContext.cs index 2b6bdee0..b66f856f 100644 --- a/EnvelopeGenerator.Infrastructure/EGDbContext.cs +++ b/EnvelopeGenerator.Infrastructure/EGDbContext.cs @@ -204,16 +204,24 @@ public abstract class EGDbContextBase : DbContext #region Trigger // Configure entities to handle database triggers - void AddTrigger() where T : class => _triggers - .Where(t => t.Key == typeof(T).Name) - .SelectMany(t => t.Value) - .ForEach(tName => + void AddTrigger() where T : class + { + var entityName = typeof(T).Name; + + var configuredTriggerNames = _triggers + .Where(t => t.Key == entityName) + .SelectMany(t => t.Value) + .Distinct() + .ToList(); + + foreach (var triggerName in configuredTriggerNames) { #if NET - modelBuilder.Entity().ToTable(tb => tb.HasTrigger(tName)); + modelBuilder.Entity().ToTable(tb => tb.HasTrigger(triggerName)); #endif - _logger?.LogInformation("Trigger '{triggerName}' has been added to the '{entityName}' entity.", tName, typeof(T).Name); - }); + _logger?.LogInformation("Trigger '{triggerName}' has been added to the '{entityName}' entity.", triggerName, entityName); + } + } // TODO: call add trigger methods with attributes and reflection AddTrigger(); @@ -243,4 +251,4 @@ public abstract class EGDbContextBase : DbContext #if NETFRAMEWORK } -#endif \ No newline at end of file +#endif diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Models/SignatureSubmitDto.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Models/SignatureSubmitDto.cs new file mode 100644 index 00000000..78d32317 --- /dev/null +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Models/SignatureSubmitDto.cs @@ -0,0 +1,10 @@ +namespace EnvelopeGenerator.Server.Client.Models; + +public sealed record SignatureSubmitDto +{ + public required int Id { get; init; } + public required string DataUrl { get; init; } + public required string FullName { get; init; } + public string? Position { get; init; } + public required string Place { get; init; } +} diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/SignatureService.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/SignatureService.cs index a8f76d22..7b64dffb 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/SignatureService.cs +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server.Client/Services/SignatureService.cs @@ -1,6 +1,7 @@ using System.Net.Http; using System.Net.Http.Json; using System.Text.Json; +using EnvelopeGenerator.Application.Common.Dto; using EnvelopeGenerator.Server.Client.Models; namespace EnvelopeGenerator.Server.Client.Services; @@ -21,4 +22,32 @@ public class SignatureService(IHttpClientFactory httpClientFactory) var result = await response.Content.ReadFromJsonAsync>(_jsonOptions, cancel); return result ?? []; } + + public async Task SubmitAsync(string envelopeKey, IEnumerable signatures, CancellationToken cancel = default) + { + using var http = httpClientFactory.CreateClient("EnvelopeGenerator.Server"); + + var payload = signatures + .Select(signature => new Signature + { + Id = signature.Id, + DataUrl = signature.DataUrl, + FullName = signature.FullName, + Position = signature.Position, + Place = signature.Place + }) + .ToList(); + + var response = await http.PostAsJsonAsync( + $"/api/Signature/submit/{Uri.EscapeDataString(envelopeKey)}", + payload, + cancel); + + if (!response.IsSuccessStatusCode) + { + var body = await response.Content.ReadAsStringAsync(cancel); + throw new HttpRequestException( + $"Failed to submit signature for envelope {envelopeKey}: {(int)response.StatusCode} {response.StatusCode} - {body}"); + } + } } diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/ReceiverSignedPage.razor b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/ReceiverSignedPage.razor index 0a26cc4b..63119a0c 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/ReceiverSignedPage.razor +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Components/Pages/ReceiverSignedPage.razor @@ -12,6 +12,7 @@ @inject EnvelopeGenerator.Server.Client.Services.AuthService AuthService @inject EnvelopeGenerator.Server.Services.EnvelopeReceiverAuthorizationService ReceiverAuthorizationService @inject EnvelopeGenerator.Server.Services.EnvelopeReceiverPageDataService PageDataService +@inject SignatureService SignatureService @inject AppVersionService AppVersion @inject IMemoryCache MemoryCache @inject ILogger Logger @@ -122,6 +123,12 @@

+ @if (!string.IsNullOrWhiteSpace(_submitErrorMessage)) + { + + }
@@ -161,17 +168,48 @@ // ----- Submit / logout state ----- bool _isLoggingOut = false; bool _submitConfirmVisible = false; + string? _submitErrorMessage; void OpenSubmitConfirmPopup() => _submitConfirmVisible = true; async Task SubmitAndLogoutAsync() { if (_isLoggingOut) return; + + if (string.IsNullOrWhiteSpace(EnvelopeKey) || _sig is null) + return; + _isLoggingOut = true; _submitConfirmVisible = false; + _submitErrorMessage = null; await InvokeAsync(StateHasChanged); - await AuthService.LogoutEnvelopeReceiverAsync(EnvelopeKey!); - Navigation.NavigateTo("/", forceLoad: true); + + try + { + if (_signatures.Count == 0) + throw new InvalidOperationException("No signature fields found for receiver."); + + var payload = _signatures.Select(field => new SignatureSubmitDto + { + Id = field.Id, + DataUrl = _sig.DataUrl, + FullName = _sig.FullName, + Position = string.IsNullOrWhiteSpace(_sig.Position) ? null : _sig.Position, + Place = _sig.Place + }).ToList(); + + await SignatureService.SubmitAsync(EnvelopeKey, payload); + + await AuthService.LogoutEnvelopeReceiverAsync(EnvelopeKey); + Navigation.NavigateTo("/", forceLoad: true); + } + catch (Exception ex) + { + Logger.LogError(ex, "Failed to finalize signature for {EnvelopeKey}", EnvelopeKey); + _submitErrorMessage = "Die Unterschrift konnte nicht abgeschlossen werden. Bitte versuchen Sie es erneut."; + _isLoggingOut = false; + await InvokeAsync(StateHasChanged); + } } protected override async Task OnInitializedAsync() diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/SignatureController.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/SignatureController.cs index 2c951983..9e9a33cd 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/SignatureController.cs +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/SignatureController.cs @@ -1,7 +1,10 @@ using EnvelopeGenerator.Server.Extensions; using EnvelopeGenerator.Application.Common.Dto; using EnvelopeGenerator.Application.Common.Extensions; +using EnvelopeGenerator.Application.DocReceiverElements.Commands; using EnvelopeGenerator.Application.Documents.Queries; +using EnvelopeGenerator.Application.EnvelopeReceivers.Queries; +using EnvelopeGenerator.Application.Histories.Queries; using EnvelopeGenerator.Domain.Constants; using MediatR; using Microsoft.AspNetCore.Authorization; @@ -18,13 +21,15 @@ namespace EnvelopeGenerator.Server.Controllers; public class SignatureController : ControllerBase { private readonly IMediator _mediator; + private readonly ILogger _logger; /// /// Initializes a new instance of . /// - public SignatureController(IMediator mediator) + public SignatureController(IMediator mediator, ILogger logger) { _mediator = mediator; + _logger = logger; } //TODO: update to use signature query @@ -54,4 +59,46 @@ public class SignatureController : ControllerBase else return Ok(rcvSignatures); } + + /// + /// Finalizes the receiver signing flow and persists signature data to TBSIG_DOCUMENT_RECEIVER_ELEMENT. + /// Also triggers status/history/mail side effects through SigningCommand pipeline. + /// + [Authorize(Policy = AuthPolicy.Receiver)] + [HttpPost("submit/{envelopeKey}")] + public async Task Submit( + [FromRoute] string envelopeKey, + [FromBody] IEnumerable? signatures, + CancellationToken cancel) + { + if (signatures is null) + return BadRequest("Signature payload is required."); + + var uuid = User.EnvelopeUuid(); + var receiverSignature = User.ReceiverSignature(); + + if (await _mediator.IsSignedAsync(uuid, receiverSignature, cancel)) + return Problem(statusCode: StatusCodes.Status409Conflict); + + if (await _mediator.AnyHistoryAsync(uuid, new[] { EnvelopeStatus.EnvelopeRejected, EnvelopeStatus.DocumentRejected }, cancel)) + return Problem(statusCode: StatusCodes.Status423Locked); + + try + { + await _mediator.Send(new SigningCommand + { + Envelope = new() { Uuid = uuid }, + Receiver = new() { Signature = receiverSignature }, + ReceiverAppType = ReceiverAppType.ReceiverUI, + Signatures = signatures.ToList() + }, cancel); + + return Ok(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to submit signatures for envelope {EnvelopeKey}", envelopeKey); + throw; + } + } } diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Properties/launchSettings.json b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Properties/launchSettings.json index 39f160ad..64b21d1a 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Properties/launchSettings.json +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Properties/launchSettings.json @@ -13,7 +13,7 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "https://localhost:8088;http://localhost:5131", + "applicationUrl": "https://localhost:8089;http://localhost:5132", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json index 9dadd643..060188ee 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/appsettings.json @@ -185,8 +185,10 @@ "TimeLimit": "00:30:00" }, "DbTriggerParams": { + "DocReceiverElement": [ "TBSIG_DOCUMENT_RECEIVER_ELEMENT_AFT_INS", "TBSIG_DOCUMENT_RECEIVER_ELEMENT_AFT_UPD" ], "Envelope": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ], "EnvelopeHistory": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ], + "History": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ], "EmailOut": [ "TBEMLP_EMAIL_OUT_AFT_INS", "TBEMLP_EMAIL_OUT_AFT_UPD" ], "EnvelopeReceiverReadOnly": [ "TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD" ], "Receiver": [],