Compare commits
47 Commits
a3afeb175f
...
refactor/a
| Author | SHA1 | Date | |
|---|---|---|---|
| 2fcea78574 | |||
| e8e428f935 | |||
| 9450ed3486 | |||
| 583a07c646 | |||
| 51ad4fbc2c | |||
| 50ac7570ea | |||
| 5465996563 | |||
| 1b840f4ae3 | |||
| 3923a3b403 | |||
| ada621ac46 | |||
| abbe6a26a9 | |||
| 3066dac541 | |||
| b1aa6d6639 | |||
| 31fe1c34f2 | |||
| d7644bfe07 | |||
| 4759b16a85 | |||
| cfdfb43631 | |||
| 6254bb6e3f | |||
| f995fa9fc3 | |||
| c2fefe798d | |||
| 849a282ec5 | |||
| 6b23dcdba7 | |||
| a60d0f63e2 | |||
| 2481059b49 | |||
| 6334097d5e | |||
| 9baa126c8c | |||
| 1ef46013cd | |||
| 72dffd1043 | |||
| eda30472b9 | |||
| 75846573da | |||
| f59c0d90ad | |||
| cf40449112 | |||
| a59d4836d4 | |||
| f475cf4ea9 | |||
| d39018ca39 | |||
| b49482137f | |||
| bd40404d97 | |||
| 6f16921a79 | |||
| 1afc95f9c6 | |||
| 6aed820196 | |||
| e17c4d02f8 | |||
| 8187924a8c | |||
| 1bf530f7e7 | |||
| 9cadc8e901 | |||
| 1d4ad13532 | |||
| 03a8154b1c | |||
| 20b8acd3fc |
118
EnvelopeGenerator.API/Controllers/AnnotationController.cs
Normal file
118
EnvelopeGenerator.API/Controllers/AnnotationController.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
using DigitalData.Core.Abstraction.Application.DTO;
|
||||
using DigitalData.Core.Exceptions;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
using EnvelopeGenerator.Application.Common.Notifications.DocSigned;
|
||||
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
||||
using EnvelopeGenerator.Application.Histories.Queries;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.API.Extensions;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace EnvelopeGenerator.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Manages annotations and signature lifecycle for envelopes.
|
||||
/// </summary>
|
||||
[Authorize(Roles = Role.Receiver.FullyAuth)]
|
||||
[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<AnnotationController> _logger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="AnnotationController"/>.
|
||||
/// </summary>
|
||||
[Obsolete("Use MediatR")]
|
||||
public AnnotationController(
|
||||
ILogger<AnnotationController> logger,
|
||||
IEnvelopeHistoryService envelopeHistoryService,
|
||||
IEnvelopeReceiverService envelopeReceiverService,
|
||||
IMediator mediator)
|
||||
{
|
||||
_historyService = envelopeHistoryService;
|
||||
_envelopeReceiverService = envelopeReceiverService;
|
||||
_mediator = mediator;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or updates annotations for the authenticated envelope receiver.
|
||||
/// </summary>
|
||||
/// <param name="psPdfKitAnnotation">Annotation payload.</param>
|
||||
/// <param name="cancel">Cancellation token.</param>
|
||||
[Authorize(Roles = Role.Receiver.FullyAuth)]
|
||||
[HttpPost]
|
||||
[Obsolete("PSPDF Kit will no longer be used.")]
|
||||
public async Task<IActionResult> CreateOrUpdate([FromBody] PsPdfKitAnnotation? psPdfKitAnnotation = null, CancellationToken cancel = default)
|
||||
{
|
||||
var signature = User.GetReceiverSignatureOfReceiver();
|
||||
var uuid = User.GetEnvelopeUuidOfReceiver();
|
||||
|
||||
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 docSignedNotification = await _mediator
|
||||
.ReadEnvelopeReceiverAsync(uuid, signature, cancel)
|
||||
.ToDocSignedNotification(psPdfKitAnnotation)
|
||||
?? throw new NotFoundException("Envelope receiver is not found.");
|
||||
|
||||
await _mediator.PublishSafely(docSignedNotification, cancel);
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rejects the document for the current receiver.
|
||||
/// </summary>
|
||||
/// <param name="reason">Optional rejection reason.</param>
|
||||
[Authorize(Roles = Role.Receiver.FullyAuth)]
|
||||
[HttpPost("reject")]
|
||||
[Obsolete("Use MediatR")]
|
||||
public async Task<IActionResult> Reject([FromBody] string? reason = null)
|
||||
{
|
||||
var signature = User.GetReceiverSignatureOfReceiver();
|
||||
var uuid = User.GetEnvelopeUuidOfReceiver();
|
||||
var mail = User.GetReceiverMailOfReceiver();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
59
EnvelopeGenerator.API/Controllers/AuthController.cs
Normal file
59
EnvelopeGenerator.API/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using EnvelopeGenerator.API.Models;
|
||||
|
||||
namespace EnvelopeGenerator.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Controller verantwortlich für die Benutzer-Authentifizierung, einschließlich Anmelden, Abmelden und Überprüfung des Authentifizierungsstatus.
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public partial class AuthController(ILogger<AuthController> logger) : ControllerBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Entfernt das Authentifizierungs-Cookie des Benutzers (AuthCookie)
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Gibt eine HTTP 200 oder 401.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// POST /api/auth/logout
|
||||
///
|
||||
/// </remarks>
|
||||
/// <response code="200">Erfolgreich gelöscht, wenn der Benutzer ein berechtigtes Cookie hat.</response>
|
||||
/// <response code="401">Wenn es kein zugelassenes Cookie gibt, wird „nicht zugelassen“ zurückgegeben.</response>
|
||||
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[Authorize]
|
||||
[HttpPost("logout")]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prüft, ob der Benutzer ein autorisiertes Token hat.
|
||||
/// </summary>
|
||||
/// <returns>Wenn ein autorisiertes Token vorhanden ist HTTP 200 asynchron 401</returns>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// GET /api/auth
|
||||
///
|
||||
/// </remarks>
|
||||
/// <response code="200">Wenn es einen autorisierten Cookie gibt.</response>
|
||||
/// <response code="401">Wenn kein Cookie vorhanden ist oder nicht autorisierte.</response>
|
||||
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[Authorize]
|
||||
[HttpGet]
|
||||
public IActionResult IsAuthenticated() => Ok();
|
||||
}
|
||||
30
EnvelopeGenerator.API/Controllers/ConfigController.cs
Normal file
30
EnvelopeGenerator.API/Controllers/ConfigController.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using EnvelopeGenerator.API.Models.PsPdfKitAnnotation;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace EnvelopeGenerator.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes configuration data required by the client applications.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of <see cref="ConfigController"/>.
|
||||
/// </remarks>
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[Authorize]
|
||||
public class ConfigController(IOptionsMonitor<AnnotationParams> annotationParamsOptions) : ControllerBase
|
||||
{
|
||||
private readonly AnnotationParams _annotationParams = annotationParamsOptions.CurrentValue;
|
||||
|
||||
/// <summary>
|
||||
/// Returns annotation configuration that was previously rendered by MVC.
|
||||
/// </summary>
|
||||
[HttpGet("Annotations")]
|
||||
[Obsolete("PSPDF Kit will no longer be used.")]
|
||||
public IActionResult GetAnnotationParams()
|
||||
{
|
||||
return Ok(_annotationParams.AnnotationJSObject);
|
||||
}
|
||||
}
|
||||
57
EnvelopeGenerator.API/Controllers/DocumentController.cs
Normal file
57
EnvelopeGenerator.API/Controllers/DocumentController.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to envelope documents for authenticated receivers.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="DocumentController"/> class.
|
||||
/// </remarks>
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class DocumentController(IMediator mediator, ILogger<DocumentController> logger) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the document bytes receiver.
|
||||
/// </summary>
|
||||
/// <param name="query">Encoded envelope key.</param>
|
||||
/// <param name="cancel">Cancellation token.</param>
|
||||
[HttpGet]
|
||||
[Authorize(Roles = $"{Role.Sender},{Role.Receiver.FullyAuth}")]
|
||||
public async Task<IActionResult> GetDocument(CancellationToken cancel, [FromQuery] ReadDocumentQuery? query = null)
|
||||
{
|
||||
// Sender: expects query with envelope key
|
||||
if (User.IsInRole(Role.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 (User.IsInRole(Role.Receiver.FullyAuth))
|
||||
{
|
||||
if (query is not null)
|
||||
return BadRequest("Query parameters are not allowed for receiver role.");
|
||||
|
||||
var envelopeId = User.GetEnvelopeIdOfReceiver();
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
|
||||
namespace EnvelopeGenerator.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Controller for managing temp templates.
|
||||
@@ -1,11 +1,12 @@
|
||||
using EnvelopeGenerator.Application.Envelopes.Commands;
|
||||
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.GeneratorAPI.Controllers;
|
||||
namespace EnvelopeGenerator.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Dieser Controller stellt Endpunkte für die Verwaltung von Umschlägen bereit.
|
||||
@@ -3,7 +3,7 @@ using EnvelopeGenerator.Application.EnvelopeReceivers.Commands;
|
||||
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
||||
using EnvelopeGenerator.Application.Envelopes.Queries;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using EnvelopeGenerator.GeneratorAPI.Models;
|
||||
using EnvelopeGenerator.API.Models;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -13,8 +13,9 @@ using System.Data;
|
||||
using EnvelopeGenerator.Application.Common.SQL;
|
||||
using EnvelopeGenerator.Application.Common.Dto.Receiver;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
||||
using EnvelopeGenerator.API.Extensions;
|
||||
|
||||
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
|
||||
namespace EnvelopeGenerator.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Controller für die Verwaltung von Umschlagempfängern.
|
||||
@@ -65,16 +66,7 @@ public class EnvelopeReceiverController : ControllerBase
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetEnvelopeReceiver([FromQuery] ReadEnvelopeReceiverQuery envelopeReceiver)
|
||||
{
|
||||
var username = User.GetUsernameOrDefault();
|
||||
|
||||
if (username is null)
|
||||
{
|
||||
_logger.LogError(@"Envelope Receiver dto cannot be sent because username claim is null. Potential authentication and authorization error. The value of other claims are [id: {id}], [username: {username}], [name: {name}], [prename: {prename}], [email: {email}].",
|
||||
User.GetId(), User.GetUsernameOrDefault(), User.GetNameOrDefault(), User.GetPrenameOrDefault(), User.GetEmailOrDefault());
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
|
||||
envelopeReceiver = envelopeReceiver with { Username = username };
|
||||
envelopeReceiver = envelopeReceiver with { Username = User.GetUsername() };
|
||||
|
||||
var result = await _mediator.Send(envelopeReceiver);
|
||||
|
||||
@@ -225,18 +217,14 @@ public class EnvelopeReceiverController : ControllerBase
|
||||
using (SqlConnection conn = new(_cnnStr))
|
||||
{
|
||||
conn.Open();
|
||||
var formattedSQL_hist = string.Format(sql_hist, envelope.Uuid.ToSqlParam(), 1003.ToSqlParam(), userId.ToSqlParam());
|
||||
using (SqlCommand cmd = new SqlCommand(formattedSQL_hist, conn))
|
||||
{
|
||||
cmd.CommandType = CommandType.Text;
|
||||
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);
|
||||
}
|
||||
}
|
||||
using SqlDataReader reader = cmd.ExecuteReader();
|
||||
if (reader.Read())
|
||||
{
|
||||
bool outSuccess = reader.GetBoolean(0);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
@@ -6,7 +6,7 @@ using EnvelopeGenerator.Application.Histories.Queries;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
|
||||
namespace EnvelopeGenerator.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Dieser Controller stellt Endpunkte für den Zugriff auf die Umschlaghistorie bereit.
|
||||
@@ -113,6 +113,6 @@ public class HistoryController : ControllerBase
|
||||
public async Task<IActionResult> GetAllAsync([FromQuery] ReadHistoryQuery historyQuery, CancellationToken cancel)
|
||||
{
|
||||
var history = await _mediator.Send(historyQuery, cancel).ThrowIfEmpty(Exceptions.NotFound);
|
||||
return Ok((historyQuery.OnlyLast ?? false) ? history.MaxBy(h => h.AddedWhen) : history);
|
||||
return Ok((historyQuery.OnlyLast) ? history.MaxBy(h => h.AddedWhen) : history);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using DigitalData.Core.API;
|
||||
using EnvelopeGenerator.Application.Resources;
|
||||
using EnvelopeGenerator.CommonServices;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using EnvelopeGenerator.Application.Resources;
|
||||
|
||||
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
|
||||
namespace EnvelopeGenerator.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Controller für die Verwaltung der Lokalisierung und Spracheinstellungen.
|
||||
@@ -19,7 +19,7 @@ public class LocalizationController : ControllerBase
|
||||
private static readonly Guid L_KEY = Guid.NewGuid();
|
||||
|
||||
private readonly ILogger<LocalizationController> _logger;
|
||||
private readonly IStringLocalizer<Model> _mLocalizer;
|
||||
private readonly IStringLocalizer<Resource> _mLocalizer;
|
||||
private readonly IStringLocalizer<Resource> _localizer;
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
@@ -34,7 +34,7 @@ public class LocalizationController : ControllerBase
|
||||
ILogger<LocalizationController> logger,
|
||||
IStringLocalizer<Resource> localizer,
|
||||
IMemoryCache memoryCache,
|
||||
IStringLocalizer<Model> _modelLocalizer)
|
||||
IStringLocalizer<Resource> _modelLocalizer)
|
||||
{
|
||||
_logger = logger;
|
||||
_localizer = localizer;
|
||||
90
EnvelopeGenerator.API/Controllers/ReadOnlyController.cs
Normal file
90
EnvelopeGenerator.API/Controllers/ReadOnlyController.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Manages read-only envelope sharing flows.
|
||||
/// </summary>
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ReadOnlyController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<ReadOnlyController> _logger;
|
||||
private readonly IEnvelopeReceiverReadOnlyService _readOnlyService;
|
||||
private readonly IEnvelopeMailService _mailService;
|
||||
private readonly IEnvelopeHistoryService _historyService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadOnlyController"/> class.
|
||||
/// </summary>
|
||||
public ReadOnlyController(ILogger<ReadOnlyController> logger, IEnvelopeReceiverReadOnlyService readOnlyService, IEnvelopeMailService mailService, IEnvelopeHistoryService historyService)
|
||||
{
|
||||
_logger = logger;
|
||||
_readOnlyService = readOnlyService;
|
||||
_mailService = mailService;
|
||||
_historyService = historyService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new read-only receiver for the current envelope.
|
||||
/// </summary>
|
||||
/// <param name="createDto">Creation payload.</param>
|
||||
[HttpPost]
|
||||
[Authorize(Roles = Role.Receiver.FullyAuth)]
|
||||
public async Task<IActionResult> CreateAsync([FromBody] EnvelopeReceiverReadOnlyCreateDto createDto)
|
||||
{
|
||||
var authReceiverMail = User.GetReceiverMailOfReceiver();
|
||||
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.GetEnvelopeIdOfReceiver();
|
||||
|
||||
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<int, IActionResult>(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);
|
||||
});
|
||||
}
|
||||
}
|
||||
129
EnvelopeGenerator.API/Controllers/TfaRegistrationController.cs
Normal file
129
EnvelopeGenerator.API/Controllers/TfaRegistrationController.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Exposes endpoints for registering and managing two-factor authentication for envelope receivers.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/tfa")]
|
||||
public class TfaRegistrationController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<TfaRegistrationController> _logger;
|
||||
private readonly IEnvelopeReceiverService _envelopeReceiverService;
|
||||
private readonly IAuthenticator _authenticator;
|
||||
private readonly IReceiverService _receiverService;
|
||||
private readonly TFARegParams _parameters;
|
||||
private readonly IStringLocalizer<Resource> _localizer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TfaRegistrationController"/> class.
|
||||
/// </summary>
|
||||
public TfaRegistrationController(
|
||||
ILogger<TfaRegistrationController> logger,
|
||||
IEnvelopeReceiverService envelopeReceiverService,
|
||||
IAuthenticator authenticator,
|
||||
IReceiverService receiverService,
|
||||
IOptions<TFARegParams> tfaRegParamsOptions,
|
||||
IStringLocalizer<Resource> localizer)
|
||||
{
|
||||
_logger = logger;
|
||||
_envelopeReceiverService = envelopeReceiverService;
|
||||
_authenticator = authenticator;
|
||||
_receiverService = receiverService;
|
||||
_parameters = tfaRegParamsOptions.Value;
|
||||
_localizer = localizer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates registration metadata (QR code and deadline) for a receiver.
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiverId">Encoded envelope receiver id.</param>
|
||||
[Authorize]
|
||||
[HttpGet("{envelopeReceiverId}")]
|
||||
public async Task<IActionResult> 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() });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs out the envelope receiver from cookie authentication.
|
||||
/// </summary>
|
||||
[Authorize(Roles = Role.FullyAuth)]
|
||||
[HttpPost("auth/logout")]
|
||||
public async Task<IActionResult> 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() });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using EnvelopeGenerator.API.Models;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
namespace EnvelopeGenerator.API.Documentation;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public sealed class AuthProxyDocumentFilter : IDocumentFilter
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="swaggerDoc"></param>
|
||||
/// <param name="context"></param>
|
||||
public void Apply(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
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
17
EnvelopeGenerator.API/EnvelopeClaimTypes.cs
Normal file
17
EnvelopeGenerator.API/EnvelopeClaimTypes.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace EnvelopeGenerator.API;
|
||||
|
||||
/// <summary>
|
||||
/// Provides custom claim types for envelope-related information.
|
||||
/// </summary>
|
||||
public static class EnvelopeClaimTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// Claim type for the title of an envelope.
|
||||
/// </summary>
|
||||
public static readonly string Title = $"Envelope{nameof(Title)}";
|
||||
|
||||
/// <summary>
|
||||
/// Claim type for the ID of an envelope.
|
||||
/// </summary>
|
||||
public static readonly string Id = $"Envelope{nameof(Id)}";
|
||||
}
|
||||
@@ -17,6 +17,17 @@
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="ClientApp\**" />
|
||||
<Content Remove="ClientApp\**" />
|
||||
<EmbeddedResource Remove="ClientApp\**" />
|
||||
<None Remove="ClientApp\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="yarp.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCore.Scalar" Version="1.1.8" />
|
||||
<PackageReference Include="DigitalData.Auth.Client" Version="1.3.7" />
|
||||
@@ -28,6 +39,7 @@
|
||||
<PackageReference Include="Scalar.AspNetCore" Version="2.2.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
|
||||
<PackageReference Include="DigitalData.EmailProfilerDispatcher.Abstraction" Version="3.2.0" />
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="2.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
@@ -49,41 +61,13 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="ClientApp\" />
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.CommonServices\EnvelopeGenerator.CommonServices.vbproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="ClientApp\envelope-generator-ui\dist\envelope-generator-ui\browser\chunk-35PBLQEP.js">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ClientApp\envelope-generator-ui\dist\envelope-generator-ui\browser\chunk-IUSOII6E.js">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ClientApp\envelope-generator-ui\dist\envelope-generator-ui\browser\favicon.ico">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ClientApp\envelope-generator-ui\dist\envelope-generator-ui\browser\index.html">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ClientApp\envelope-generator-ui\dist\envelope-generator-ui\browser\login\index.html">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ClientApp\envelope-generator-ui\dist\envelope-generator-ui\browser\main-ZN7C4PGY.js">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ClientApp\envelope-generator-ui\dist\envelope-generator-ui\browser\polyfills-N6LQB2YD.js">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="ClientApp\envelope-generator-ui\dist\envelope-generator-ui\browser\styles-S5ZWIC2V.css">
|
||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
101
EnvelopeGenerator.API/Extensions/ReceiverClaimExtensions.cs
Normal file
101
EnvelopeGenerator.API/Extensions/ReceiverClaimExtensions.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
|
||||
namespace EnvelopeGenerator.API.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides helper methods for working with envelope-specific authentication claims.
|
||||
/// </summary>
|
||||
public static class ReceiverClaimExtensions
|
||||
{
|
||||
private static string GetRequiredClaimOfReceiver(this ClaimsPrincipal user, string claimType)
|
||||
{
|
||||
var value = user.FindFirstValue(claimType);
|
||||
if (value is not null)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var identity = user.Identity;
|
||||
var principalName = identity?.Name ?? "(anonymous)";
|
||||
var authType = identity?.AuthenticationType ?? "(none)";
|
||||
var availableClaims = string.Join(", ", user.Claims.Select(c => $"{c.Type}={c.Value}"));
|
||||
var message = $"Required claim '{claimType}' is missing for user '{principalName}' (auth: {authType}). Available claims: [{availableClaims}].";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authenticated envelope UUID from the claims.
|
||||
/// </summary>
|
||||
public static string GetEnvelopeUuidOfReceiver(this ClaimsPrincipal user) => user.GetRequiredClaimOfReceiver(ClaimTypes.NameIdentifier);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authenticated receiver signature from the claims.
|
||||
/// </summary>
|
||||
public static string GetReceiverSignatureOfReceiver(this ClaimsPrincipal user) => user.GetRequiredClaimOfReceiver(ClaimTypes.Hash);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authenticated receiver display name from the claims.
|
||||
/// </summary>
|
||||
public static string GetReceiverNameOfReceiver(this ClaimsPrincipal user) => user.GetRequiredClaimOfReceiver(ClaimTypes.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authenticated receiver email address from the claims.
|
||||
/// </summary>
|
||||
public static string GetReceiverMailOfReceiver(this ClaimsPrincipal user) => user.GetRequiredClaimOfReceiver(ClaimTypes.Email);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authenticated envelope title from the claims.
|
||||
/// </summary>
|
||||
public static string GetEnvelopeTitleOfReceiver(this ClaimsPrincipal user) => user.GetRequiredClaimOfReceiver(EnvelopeClaimTypes.Title);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the authenticated envelope identifier from the claims.
|
||||
/// </summary>
|
||||
public static int GetEnvelopeIdOfReceiver(this ClaimsPrincipal user)
|
||||
{
|
||||
var envIdStr = user.GetRequiredClaimOfReceiver(EnvelopeClaimTypes.Id);
|
||||
if (!int.TryParse(envIdStr, out var envId))
|
||||
{
|
||||
throw new InvalidOperationException($"Claim '{EnvelopeClaimTypes.Id}' is not a valid integer.");
|
||||
}
|
||||
|
||||
return envId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Signs in an envelope receiver using cookie authentication and attaches envelope claims.
|
||||
/// </summary>
|
||||
/// <param name="context">The current HTTP context.</param>
|
||||
/// <param name="envelopeReceiver">Envelope receiver DTO to extract claims from.</param>
|
||||
/// <param name="receiverRole">Role to attach to the authentication ticket.</param>
|
||||
public static async Task SignInEnvelopeAsync(this HttpContext context, EnvelopeReceiverDto envelopeReceiver, string receiverRole)
|
||||
{
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.NameIdentifier, envelopeReceiver.Envelope!.Uuid),
|
||||
new(ClaimTypes.Hash, envelopeReceiver.Receiver!.Signature),
|
||||
new(ClaimTypes.Name, envelopeReceiver.Name ?? string.Empty),
|
||||
new(ClaimTypes.Email, envelopeReceiver.Receiver.EmailAddress),
|
||||
new(EnvelopeClaimTypes.Title, envelopeReceiver.Envelope.Title),
|
||||
new(EnvelopeClaimTypes.Id, envelopeReceiver.Envelope.Id.ToString()),
|
||||
new(ClaimTypes.Role, receiverRole)
|
||||
};
|
||||
|
||||
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
|
||||
var authProperties = new AuthenticationProperties
|
||||
{
|
||||
AllowRefresh = false,
|
||||
IsPersistent = false
|
||||
};
|
||||
|
||||
await context.SignInAsync(
|
||||
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
new ClaimsPrincipal(claimsIdentity),
|
||||
authProperties);
|
||||
}
|
||||
}
|
||||
95
EnvelopeGenerator.API/Extensions/SenderClaimExtensions.cs
Normal file
95
EnvelopeGenerator.API/Extensions/SenderClaimExtensions.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace EnvelopeGenerator.API.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for extracting user information from a <see cref="ClaimsPrincipal"/>.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the user's ID from the claims. Throws an exception if the ID is missing or invalid.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||
/// <returns>The user's ID as an integer.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the user ID claim is missing or invalid.</exception>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the username from the claims.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||
/// <returns>The username as a string.</returns>
|
||||
public static string GetUsername(this ClaimsPrincipal user)
|
||||
=> user.GetRequiredClaimOfSender(ClaimTypes.Name);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the user's surname (last name) from the claims.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||
/// <returns>The surname as a string.</returns>
|
||||
public static string GetName(this ClaimsPrincipal user)
|
||||
=> user.GetRequiredClaimOfSender(ClaimTypes.Surname);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the user's given name (first name) from the claims.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||
/// <returns>The given name as a string.</returns>
|
||||
public static string GetPrename(this ClaimsPrincipal user)
|
||||
=> user.GetRequiredClaimOfSender(ClaimTypes.GivenName);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the user's email address from the claims.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||
/// <returns>The email address as a string.</returns>
|
||||
public static string GetEmail(this ClaimsPrincipal user)
|
||||
=> user.GetRequiredClaimOfSender(ClaimTypes.Email);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace EnvelopeGenerator.GeneratorAPI.Middleware;
|
||||
namespace EnvelopeGenerator.API.Middleware;
|
||||
|
||||
using DigitalData.Core.Exceptions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
14
EnvelopeGenerator.API/Models/Auth.cs
Normal file
14
EnvelopeGenerator.API/Models/Auth.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace EnvelopeGenerator.GeneratorAPI.Models;
|
||||
namespace EnvelopeGenerator.API.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the keys and default values used for authentication token handling
|
||||
@@ -24,5 +24,5 @@ public class AuthTokenKeys
|
||||
/// <summary>
|
||||
/// Gets the expected audience value for the authentication token.
|
||||
/// </summary>
|
||||
public string Audience { get; init; } = "sign-flow-gen.digitaldata.works";
|
||||
public string Audience { get; init; } = "sign-flow.digitaldata.works";
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace EnvelopeGenerator.GeneratorAPI.Models;
|
||||
namespace EnvelopeGenerator.API.Models;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
60
EnvelopeGenerator.API/Models/ContactLink.cs
Normal file
60
EnvelopeGenerator.API/Models/ContactLink.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
namespace EnvelopeGenerator.API.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a hyperlink for contact purposes with various HTML attributes.
|
||||
/// </summary>
|
||||
public class ContactLink
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the label of the hyperlink.
|
||||
/// </summary>
|
||||
public string Label { get; init; } = "Contact";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the URL that the hyperlink points to.
|
||||
/// </summary>
|
||||
public string Href { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the target where the hyperlink should open.
|
||||
/// Commonly used values are "_blank", "_self", "_parent", "_top".
|
||||
/// </summary>
|
||||
public string Target { get; set; } = "_blank";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the relationship of the linked URL as space-separated link types.
|
||||
/// Examples include "nofollow", "noopener", "noreferrer".
|
||||
/// </summary>
|
||||
public string Rel { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public string Download { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the language of the linked resource. Useful when linking to
|
||||
/// content in another language.
|
||||
/// </summary>
|
||||
public string HrefLang { get; set; } = "en";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MIME type of the linked URL. Helps browsers to handle
|
||||
/// the type correctly when the link is clicked.
|
||||
/// </summary>
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets additional information about the hyperlink, typically viewed
|
||||
/// as a tooltip when the mouse hovers over the link.
|
||||
/// </summary>
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an identifier for the hyperlink, unique within the HTML document.
|
||||
/// </summary>
|
||||
public string Id { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
}
|
||||
17
EnvelopeGenerator.API/Models/Culture.cs
Normal file
17
EnvelopeGenerator.API/Models/Culture.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
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; }
|
||||
}
|
||||
12
EnvelopeGenerator.API/Models/Cultures.cs
Normal file
12
EnvelopeGenerator.API/Models/Cultures.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace EnvelopeGenerator.API.Models;
|
||||
|
||||
public class Cultures : List<Culture>
|
||||
{
|
||||
public IEnumerable<string> Languages => this.Select(c => c.Language);
|
||||
|
||||
public IEnumerable<string> 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();
|
||||
}
|
||||
6
EnvelopeGenerator.API/Models/CustomImages.cs
Normal file
6
EnvelopeGenerator.API/Models/CustomImages.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace EnvelopeGenerator.API.Models;
|
||||
|
||||
public class CustomImages : Dictionary<string, Image>
|
||||
{
|
||||
public new Image this[string key] => TryGetValue(key, out var img) && img is not null ? img : new();
|
||||
}
|
||||
10
EnvelopeGenerator.API/Models/ErrorViewModel.cs
Normal file
10
EnvelopeGenerator.API/Models/ErrorViewModel.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
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";
|
||||
}
|
||||
10
EnvelopeGenerator.API/Models/Image.cs
Normal file
10
EnvelopeGenerator.API/Models/Image.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace EnvelopeGenerator.API.Models;
|
||||
|
||||
public class Image
|
||||
{
|
||||
public string Src { get; init; } = string.Empty;
|
||||
|
||||
public Dictionary<string, string> Classes { get; init; } = new();
|
||||
|
||||
public string GetClassIn(string page) => Classes.TryGetValue(page, out var cls) && cls is not null ? cls : string.Empty;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace EnvelopeGenerator.GeneratorAPI.Models;
|
||||
namespace EnvelopeGenerator.API.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Repräsentiert ein Login-Modell mit erforderlichem Passwort und optionaler ID und Benutzername.
|
||||
6
EnvelopeGenerator.API/Models/MainViewModel.cs
Normal file
6
EnvelopeGenerator.API/Models/MainViewModel.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace EnvelopeGenerator.API.Models;
|
||||
|
||||
public class MainViewModel
|
||||
{
|
||||
public string? Title { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
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<Annotation> _annots = new List<Annotation>();
|
||||
|
||||
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<Annotation> 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<string, IAnnotation> 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<Dictionary<string, IAnnotation>> _AnnotationJSObjectInitor;
|
||||
|
||||
public Dictionary<string, IAnnotation> AnnotationJSObject => _AnnotationJSObjectInitor.Value;
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace EnvelopeGenerator.API.Models.PsPdfKitAnnotation;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<IAnnotation> 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;
|
||||
}
|
||||
}
|
||||
10
EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Color.cs
Normal file
10
EnvelopeGenerator.API/Models/PsPdfKitAnnotation/Color.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
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; }
|
||||
}
|
||||
17
EnvelopeGenerator.API/Models/TFARegParams.cs
Normal file
17
EnvelopeGenerator.API/Models/TFARegParams.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace EnvelopeGenerator.API.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the parameters for two-factor authentication (2FA) registration.
|
||||
/// </summary>
|
||||
public class TFARegParams
|
||||
{
|
||||
/// <summary>
|
||||
/// The maximum allowed time for completing the registration process.
|
||||
/// </summary>
|
||||
public TimeSpan TimeLimit { get; init; } = new(0, 30, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The deadline for registration, calculated as the current time plus the <see cref="TimeLimit"/>.
|
||||
/// </summary>
|
||||
public DateTime Deadline => DateTime.Now.AddTicks(TimeLimit.Ticks);
|
||||
}
|
||||
@@ -11,11 +11,11 @@ using DigitalData.UserManager.DependencyInjection;
|
||||
using EnvelopeGenerator.Application;
|
||||
using DigitalData.Auth.Client;
|
||||
using DigitalData.Core.Abstractions;
|
||||
using EnvelopeGenerator.GeneratorAPI.Models;
|
||||
using EnvelopeGenerator.API.Models;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using DigitalData.Core.Abstractions.Security.Extensions;
|
||||
using EnvelopeGenerator.GeneratorAPI.Middleware;
|
||||
using EnvelopeGenerator.API.Middleware;
|
||||
using NLog.Web;
|
||||
using NLog;
|
||||
|
||||
@@ -26,6 +26,8 @@ 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())
|
||||
@@ -39,6 +41,8 @@ try
|
||||
var deferredProvider = new DeferredServiceProvider();
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddHttpClient();
|
||||
builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
|
||||
|
||||
// CORS Policy
|
||||
var allowedOrigins = config.GetSection("AllowedOrigins").Get<string[]>() ??
|
||||
@@ -102,6 +106,8 @@ try
|
||||
{
|
||||
options.IncludeXmlComments(xmlFile);
|
||||
}
|
||||
|
||||
options.DocumentFilter<EnvelopeGenerator.API.Documentation.AuthProxyDocumentFilter>();
|
||||
});
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
@@ -241,6 +247,7 @@ try
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapReverseProxy();
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
@@ -6,11 +6,11 @@
|
||||
}
|
||||
},
|
||||
"AuthClientParams": {
|
||||
"Url": "https://localhost:7192/auth-hub",
|
||||
"Url": "http://172.24.12.39:9090/auth-hub",
|
||||
"PublicKeys": [
|
||||
{
|
||||
"Issuer": "auth.digitaldata.works",
|
||||
"Audience": "sign-flow-gen.digitaldata.works"
|
||||
"Audience": "sign-flow.digitaldata.works"
|
||||
}
|
||||
],
|
||||
"RetryDelay": "00:00:05"
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"AllowedOrigins": [ "http://localhost:4200" ],
|
||||
"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;"
|
||||
@@ -24,11 +24,11 @@
|
||||
}
|
||||
},
|
||||
"AuthClientParams": {
|
||||
"Url": "https://localhost:7192/auth-hub",
|
||||
"Url": "http://172.24.12.39:9090/auth-hub",
|
||||
"PublicKeys": [
|
||||
{
|
||||
"Issuer": "auth.digitaldata.works",
|
||||
"Audience": "sign-flow-gen.digitaldata.works"
|
||||
"Audience": "sign-flow.digitaldata.works"
|
||||
}
|
||||
],
|
||||
"RetryDelay": "00:00:05"
|
||||
@@ -37,7 +37,7 @@
|
||||
"Cookie": "AuthToken",
|
||||
"QueryString": "AuthToken",
|
||||
"Issuer": "auth.digitaldata.works",
|
||||
"Audience": "work-flow.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.
|
||||
25
EnvelopeGenerator.API/yarp.json
Normal file
25
EnvelopeGenerator.API/yarp.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"ReverseProxy": {
|
||||
"Routes": {
|
||||
"auth-login": {
|
||||
"ClusterId": "auth-hub",
|
||||
"Match": {
|
||||
"Path": "/api/auth",
|
||||
"Methods": [ "POST" ]
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathSet": "/api/auth/sign-flow" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"Clusters": {
|
||||
"auth-hub": {
|
||||
"Destinations": {
|
||||
"primary": {
|
||||
"Address": "http://172.24.12.39:9090"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,19 @@ using System.Text;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class LoggerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="envelopeReceiverId"></param>
|
||||
/// <param name="exception"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void LogEnvelopeError(this ILogger logger, string envelopeReceiverId, Exception? exception = null, string? message = null, params object?[] args)
|
||||
{
|
||||
var sb = new StringBuilder().AppendLine(envelopeReceiverId.DecodeEnvelopeReceiverId().ToTitle());
|
||||
@@ -18,6 +29,15 @@ namespace EnvelopeGenerator.Application.Common.Extensions
|
||||
logger.Log(LogLevel.Error, exception, sb.AppendLine(exception.Message).ToString(), args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="uuid"></param>
|
||||
/// <param name="signature"></param>
|
||||
/// <param name="exception"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="args"></param>
|
||||
public static void LogEnvelopeError(this ILogger logger, string? uuid, string? signature = null, Exception? exception = null, string? message = null, params object?[] args)
|
||||
{
|
||||
var sb = new StringBuilder($"Envelope Uuid: {uuid}");
|
||||
@@ -34,6 +54,11 @@ namespace EnvelopeGenerator.Application.Common.Extensions
|
||||
logger.Log(LogLevel.Error, exception, sb.ToString(), args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiverTuple"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToTitle(this (string? UUID, string? Signature) envelopeReceiverTuple)
|
||||
{
|
||||
return $"UUID is {envelopeReceiverTuple.UUID} and signature is {envelopeReceiverTuple.Signature}";
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
/// <summary>
|
||||
/// Extension methods for tasks
|
||||
/// </summary>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static class TaskExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -17,6 +18,7 @@ public static class TaskExtensions
|
||||
/// <param name="factory">Exception provider</param>
|
||||
/// <returns>The awaited result if not <c>null</c>.</returns>
|
||||
/// <exception>Thrown if the result is <c>null</c>.</exception>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static async Task<T> ThrowIfNull<T, TException>(this Task<T?> task, Func<TException> factory) where TException : Exception
|
||||
{
|
||||
var result = await task;
|
||||
@@ -33,6 +35,7 @@ public static class TaskExtensions
|
||||
/// <param name="factory">Exception provider</param>
|
||||
/// <returns>The awaited collection if it is not <c>null</c> or empty.</returns>
|
||||
/// <exception cref="NotFoundException">Thrown if the result is <c>null</c> or empty.</exception>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static async Task<IEnumerable<T>> ThrowIfEmpty<T, TException>(this Task<IEnumerable<T>> task, Func<TException> factory) where TException : Exception
|
||||
{
|
||||
var result = await task;
|
||||
@@ -47,11 +50,33 @@ public static class TaskExtensions
|
||||
/// <param name="task"></param>
|
||||
/// <param name="act"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static async Task<I> Then<T, I>(this Task<T> task, Func<T, I> act)
|
||||
{
|
||||
var res = await task;
|
||||
return act(res);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="task"></param>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static Task<T?> FirstOrDefaultAsync<T>(this Task<IEnumerable<T>> task) => task.Then(t => t.FirstOrDefault());
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TException"></typeparam>
|
||||
/// <param name="task"></param>
|
||||
/// <param name="factory"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<T> FirstAsync<T, TException>(this Task<IEnumerable<T>> task, Func<TException> factory)
|
||||
where TException : Exception
|
||||
=> task.Then(t => t.FirstOrDefault() ?? throw factory());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,11 +93,13 @@ public static class Exceptions
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static BadRequestException BadRequest() => new();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Obsolete("Implement Mediator behaviors in the Osolete .NET project.")]
|
||||
public static ForbiddenException Forbidden() => new();
|
||||
}
|
||||
@@ -2,16 +2,42 @@
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.Text.Encodings.Web;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions
|
||||
namespace EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class XSSExtensions
|
||||
{
|
||||
public static class XSSExtensions
|
||||
{
|
||||
public static string? TryEncode(this string? value, UrlEncoder encoder) => value is null ? value : encoder.Encode(value);
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="encoder"></param>
|
||||
/// <returns></returns>
|
||||
public static string? TryEncode(this string? value, UrlEncoder encoder) => value is null ? value : encoder.Encode(value);
|
||||
|
||||
public static string? TryEncode(this LocalizedString? value, UrlEncoder encoder) => value is null ? null : encoder.Encode(value);
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <param name="encoder"></param>
|
||||
/// <returns></returns>
|
||||
public static string? TryEncode(this LocalizedString? value, UrlEncoder encoder) => value is null ? null : encoder.Encode(value);
|
||||
|
||||
public static string? TrySanitize(this string? html, HtmlSanitizer sanitizer) => html is null ? html : sanitizer.Sanitize(html);
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="html"></param>
|
||||
/// <param name="sanitizer"></param>
|
||||
/// <returns></returns>
|
||||
public static string? TrySanitize(this string? html, HtmlSanitizer sanitizer) => html is null ? html : sanitizer.Sanitize(html);
|
||||
|
||||
public static string? TrySanitize(this LocalizedString? html, HtmlSanitizer sanitizer) => html is null ? null : sanitizer.Sanitize(html);
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="html"></param>
|
||||
/// <param name="sanitizer"></param>
|
||||
/// <returns></returns>
|
||||
public static string? TrySanitize(this LocalizedString? html, HtmlSanitizer sanitizer) => html is null ? null : sanitizer.Sanitize(html);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using EnvelopeGenerator.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using AutoMapper;
|
||||
using EnvelopeGenerator.Application.Common.Dto;
|
||||
using DigitalData.Core.Exceptions;
|
||||
|
||||
namespace EnvelopeGenerator.Application.Documents.Queries;
|
||||
|
||||
@@ -12,14 +13,14 @@ namespace EnvelopeGenerator.Application.Documents.Queries;
|
||||
/// </summary>
|
||||
/// <param name="Id">The unique identifier of the document. Optional.</param>
|
||||
/// <param name="EnvelopeId">The identifier of the envelope associated with the document. Optional.</param>
|
||||
public record ReadDocumentQuery(int? Id = null, int? EnvelopeId = null) : IRequest<DocumentDto?>
|
||||
public record ReadDocumentQuery(int? Id = null, int? EnvelopeId = null) : IRequest<DocumentDto>
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles queries for reading <see cref="Document"/> data based on either the document ID or the envelope ID.
|
||||
/// </summary>
|
||||
public class ReadDocumentQueryHandler : IRequestHandler<ReadDocumentQuery, DocumentDto?>
|
||||
public class ReadDocumentQueryHandler : IRequestHandler<ReadDocumentQuery, DocumentDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// TempRepo for accessing <see cref="Document"/> entities.
|
||||
@@ -50,20 +51,19 @@ public class ReadDocumentQueryHandler : IRequestHandler<ReadDocumentQuery, Docum
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when neither <see cref="ReadDocumentQuery.Id"/> nor <see cref="ReadDocumentQuery.EnvelopeId"/> is provided.
|
||||
/// </exception>
|
||||
public async Task<DocumentDto?> Handle(ReadDocumentQuery query, CancellationToken cancel)
|
||||
public async Task<DocumentDto> Handle(ReadDocumentQuery query, CancellationToken cancel)
|
||||
{
|
||||
if (query.Id is not null)
|
||||
{
|
||||
var doc = await _repo.ReadOnly().Where(d => d.Id == query.Id).FirstOrDefaultAsync(cancel);
|
||||
var doc = await _repo.Query.Where(d => d.Id == query.Id).FirstOrDefaultAsync(cancel);
|
||||
return _mapper.Map<DocumentDto>(doc);
|
||||
}
|
||||
else if (query.EnvelopeId is not null)
|
||||
{
|
||||
var doc = await _repo.ReadOnly().Where(d => d.EnvelopeId == query.EnvelopeId).FirstOrDefaultAsync(cancel);
|
||||
var doc = await _repo.Query.Where(d => d.EnvelopeId == query.EnvelopeId).FirstOrDefaultAsync(cancel);
|
||||
return _mapper.Map<DocumentDto>(doc);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Invalid {nameof(ReadDocumentQuery)}: either {nameof(query.Id)} or {nameof(query.EnvelopeId)} must be provided.");
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public class UpdateEmailTemplateCommandHandler : IRequestHandler<UpdateEmailTemp
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="repository"></param>
|
||||
/// <param name="mapper"></param>
|
||||
public UpdateEmailTemplateCommandHandler(IRepository<EmailTemplate> repository, IMapper mapper)
|
||||
{
|
||||
_repository = repository;
|
||||
|
||||
@@ -67,10 +67,11 @@ public static class Extensions
|
||||
/// <param name="key"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<EnvelopeReceiverDto?> ReadEnvelopeReceiverAsync(this IMediator mediator, string key, CancellationToken cancel = default)
|
||||
public static async Task<EnvelopeReceiverDto?> ReadEnvelopeReceiverAsync(this IMediator mediator, string key, CancellationToken cancel = default)
|
||||
{
|
||||
var q = new ReadEnvelopeReceiverQuery() { Key = key };
|
||||
return mediator.Send(q, cancel).Then(envRcvs => envRcvs.FirstOrDefault());
|
||||
var envRcvs = await mediator.Send(q, cancel);
|
||||
return envRcvs.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,12 +82,13 @@ public static class Extensions
|
||||
/// <param name="signature"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
public static Task<EnvelopeReceiverDto?> ReadEnvelopeReceiverAsync(this IMediator mediator, string uuid, string signature, CancellationToken cancel = default)
|
||||
public static async Task<EnvelopeReceiverDto?> ReadEnvelopeReceiverAsync(this IMediator mediator, string uuid, string signature, CancellationToken cancel = default)
|
||||
{
|
||||
var q = new ReadEnvelopeReceiverQuery();
|
||||
q.Envelope.Uuid = uuid;
|
||||
q.Receiver.Signature = signature;
|
||||
return mediator.Send(q, cancel).Then(envRcvs => envRcvs.FirstOrDefault());
|
||||
var envRcvs = await mediator.Send(q, cancel);
|
||||
return envRcvs.FirstOrDefault();
|
||||
}
|
||||
/// <summary>
|
||||
/// Verarbeitet <see cref="ReadEnvelopeReceiverQuery"/> und liefert passende <see cref="EnvelopeReceiverDto"/>-Ergebnisse.
|
||||
|
||||
@@ -68,6 +68,6 @@ public class ReceiverAlreadySignedQueryHandler : IRequestHandler<ReceiverAlready
|
||||
/// <returns></returns>
|
||||
public async Task<bool> Handle(ReceiverAlreadySignedQuery request, CancellationToken cancel = default)
|
||||
{
|
||||
return await _repo.ReadOnly().Where(request).Where(h => h.Status == EnvelopeStatus.DocumentSigned).AnyAsync(cancel);
|
||||
return await _repo.Query.Where(request).Where(h => h.Status == EnvelopeStatus.DocumentSigned).AnyAsync(cancel);
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ public class CreateHistoryCommandHandler : IRequestHandler<CreateHistoryCommand,
|
||||
if(request.UserReference is null)
|
||||
{
|
||||
var receivers = await _erRepo
|
||||
.ReadOnly()
|
||||
.Query
|
||||
.Where(request)
|
||||
.Include(er => er.Receiver)
|
||||
.ToListAsync(cancel);
|
||||
|
||||
@@ -67,6 +67,7 @@ public class CreateReceiverCommandHandler : IRequestHandler<CreateReceiverComman
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="mapper"></param>
|
||||
public CreateReceiverCommandHandler(IRepository<Receiver> repo, IMapper mapper)
|
||||
{
|
||||
_repo = repo;
|
||||
@@ -81,7 +82,7 @@ public class CreateReceiverCommandHandler : IRequestHandler<CreateReceiverComman
|
||||
/// <returns></returns>
|
||||
public async Task<(ReceiverDto Receiver, bool AlreadyExists)> Handle(CreateReceiverCommand request, CancellationToken cancel)
|
||||
{
|
||||
var receiver = await _repo.ReadOnly()
|
||||
var receiver = await _repo.Query
|
||||
.Where(r => r.EmailAddress == request.EmailAddress)
|
||||
.SingleOrDefaultAsync(cancel);
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
namespace EnvelopeGenerator.Domain.Constants
|
||||
{
|
||||
public static class ReceiverRole
|
||||
{
|
||||
public const string PreAuth = "PreAuth";
|
||||
public const string FullyAuth = "FullyAuth";
|
||||
}
|
||||
}
|
||||
23
EnvelopeGenerator.Domain/Constants/Role.cs
Normal file
23
EnvelopeGenerator.Domain/Constants/Role.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
#if NETFRAMEWORK
|
||||
using System;
|
||||
#endif
|
||||
|
||||
namespace EnvelopeGenerator.Domain.Constants
|
||||
{
|
||||
public static class Role
|
||||
{
|
||||
[Obsolete("Use Receiver.PreAuth or Receiver.FullyAuth")]
|
||||
public const string PreAuth = "PreAuth";
|
||||
|
||||
[Obsolete("Use Receiver.PreAuth or Receiver.FullyAuth")]
|
||||
public const string FullyAuth = "FullyAuth";
|
||||
|
||||
public static class Receiver
|
||||
{
|
||||
public const string PreAuth = "PreAuth";
|
||||
public const string FullyAuth = "FullyAuth";
|
||||
}
|
||||
|
||||
public const string Sender = "Sender";
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "9.0.3",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
],
|
||||
"rollForward": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
using DigitalData.Core.Abstraction.Application;
|
||||
using DigitalData.UserManager.Application.Contracts;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using EnvelopeGenerator.GeneratorAPI.Models;
|
||||
|
||||
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Controller verantwortlich für die Benutzer-Authentifizierung, einschließlich Anmelden, Abmelden und Überprüfung des Authentifizierungsstatus.
|
||||
/// </summary>
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public partial class AuthController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<AuthController> _logger;
|
||||
[Obsolete("Use MediatR")]
|
||||
private readonly IUserService _userService;
|
||||
private readonly IDirectorySearchService _dirSearchService;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AuthController"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger instance.</param>
|
||||
/// <param name="userService">The user service instance.</param>
|
||||
/// <param name="dirSearchService">The directory search service instance.</param>
|
||||
[Obsolete("Use MediatR")]
|
||||
public AuthController(ILogger<AuthController> logger, IUserService userService, IDirectorySearchService dirSearchService)
|
||||
{
|
||||
_logger = logger;
|
||||
_userService = userService;
|
||||
_dirSearchService = dirSearchService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authentifiziert einen Benutzer und generiert ein JWT-Token. Wenn 'cookie' wahr ist, wird das Token als HTTP-Only-Cookie zurückgegeben.
|
||||
/// </summary>
|
||||
/// <param name="login">Benutzeranmeldedaten (Benutzername und Passwort).</param>
|
||||
/// <param name="cookie">Wenn wahr, wird das JWT-Token auch als HTTP-Only-Cookie gesendet.</param>
|
||||
/// <returns>
|
||||
/// Gibt eine HTTP 200 oder 401.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// POST /api/auth?cookie=true
|
||||
/// {
|
||||
/// "username": "MaxMustermann",
|
||||
/// "password": "Geheim123!"
|
||||
/// }
|
||||
///
|
||||
/// POST /api/auth?cookie=true
|
||||
/// {
|
||||
/// "id": "1",
|
||||
/// "password": "Geheim123!"
|
||||
/// }
|
||||
///
|
||||
/// </remarks>
|
||||
/// <response code="200">Erfolgreiche Anmeldung. Gibt das JWT-Token im Antwortkörper oder als Cookie zurück, wenn 'cookie' wahr ist.</response>
|
||||
/// <response code="401">Unbefugt. Ungültiger Benutzername oder Passwort.</response>
|
||||
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[AllowAnonymous]
|
||||
[HttpPost]
|
||||
public Task<IActionResult> Login([FromBody] Login login, [FromQuery] bool cookie = false)
|
||||
{
|
||||
// added to configure open API (swagger and scalar)
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authentifiziert einen Benutzer und generiert ein JWT-Token. Das Token wird als HTTP-only-Cookie zurückgegeben.
|
||||
/// </summary>
|
||||
/// <param name="login">Benutzeranmeldedaten (Benutzername und Passwort).</param>
|
||||
/// <returns>
|
||||
/// Gibt eine HTTP 200 oder 401.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// POST /api/auth/form
|
||||
/// {
|
||||
/// "username": "MaxMustermann",
|
||||
/// "password": "Geheim123!"
|
||||
/// }
|
||||
///
|
||||
/// </remarks>
|
||||
/// <response code="200">Erfolgreiche Anmeldung. Gibt das JWT-Token im Antwortkörper oder als Cookie zurück, wenn 'cookie' wahr ist.</response>
|
||||
/// <response code="401">Unbefugt. Ungültiger Benutzername oder Passwort.</response>
|
||||
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[AllowAnonymous]
|
||||
[HttpPost]
|
||||
[Route("form")]
|
||||
public Task<IActionResult> Login([FromForm] Login login)
|
||||
{
|
||||
// added to configure open API (swagger and scalar)
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Entfernt das Authentifizierungs-Cookie des Benutzers (AuthCookie)
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Gibt eine HTTP 200 oder 401.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// POST /api/auth/logout
|
||||
///
|
||||
/// </remarks>
|
||||
/// <response code="200">Erfolgreich gelöscht, wenn der Benutzer ein berechtigtes Cookie hat.</response>
|
||||
/// <response code="401">Wenn es kein zugelassenes Cookie gibt, wird „nicht zugelassen“ zurückgegeben.</response>
|
||||
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[Authorize]
|
||||
[HttpPost("logout")]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prüft, ob der Benutzer ein autorisiertes Token hat.
|
||||
/// </summary>
|
||||
/// <returns>Wenn ein autorisiertes Token vorhanden ist HTTP 200 asynchron 401</returns>
|
||||
/// <remarks>
|
||||
/// Sample request:
|
||||
///
|
||||
/// GET /api/auth
|
||||
///
|
||||
/// </remarks>
|
||||
/// <response code="200">Wenn es einen autorisierten Cookie gibt.</response>
|
||||
/// <response code="401">Wenn kein Cookie vorhanden ist oder nicht autorisierte.</response>
|
||||
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[Authorize]
|
||||
[HttpGet]
|
||||
public IActionResult IsAuthenticated() => Ok();
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace EnvelopeGenerator.GeneratorAPI.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides extension methods for extracting user information from a <see cref="ClaimsPrincipal"/>.
|
||||
/// </summary>
|
||||
public static class ControllerExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Attempts to retrieve the user's ID from the claims. Returns null if the ID is not found or invalid.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||
/// <returns>The user's ID as an integer, or null if not found or invalid.</returns>
|
||||
public static int? GetIdOrDefault(this ClaimsPrincipal user)
|
||||
=> int.TryParse(user.FindFirstValue(ClaimTypes.NameIdentifier) ?? user.FindFirstValue("sub"), out int result)
|
||||
? result : null;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the user's ID from the claims. Throws an exception if the ID is missing or invalid.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||
/// <returns>The user's ID as an integer.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the user ID claim is missing or invalid.</exception>
|
||||
public static int GetId(this ClaimsPrincipal user)
|
||||
=> user.GetIdOrDefault()
|
||||
?? throw new InvalidOperationException("User ID claim is missing or invalid. This may indicate a misconfigured or forged JWT token.");
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the username from the claims, if available.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||
/// <returns>The username as a string, or null if not found.</returns>
|
||||
public static string? GetUsernameOrDefault(this ClaimsPrincipal user)
|
||||
=> user.FindFirst(ClaimTypes.Name)?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the user's surname (last name) from the claims, if available.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||
/// <returns>The surname as a string, or null if not found.</returns>
|
||||
public static string? GetNameOrDefault(this ClaimsPrincipal user)
|
||||
=> user.FindFirst(ClaimTypes.Surname)?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the user's given name (first name) from the claims, if available.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||
/// <returns>The given name as a string, or null if not found.</returns>
|
||||
public static string? GetPrenameOrDefault(this ClaimsPrincipal user)
|
||||
=> user.FindFirst(ClaimTypes.GivenName)?.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the user's email address from the claims, if available.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="ClaimsPrincipal"/> representing the user.</param>
|
||||
/// <returns>The email address as a string, or null if not found.</returns>
|
||||
public static string? GetEmailOrDefault(this ClaimsPrincipal user)
|
||||
=> user.FindFirst(ClaimTypes.Email)?.Value;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<WebPublishMethod>Package</WebPublishMethod>
|
||||
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
|
||||
<LastUsedPlatform>Any CPU</LastUsedPlatform>
|
||||
<SiteUrlToLaunchAfterPublish />
|
||||
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
|
||||
<ExcludeApp_Data>false</ExcludeApp_Data>
|
||||
<ProjectGuid>4fdae4ba-f512-444a-9e18-111047d3ef02</ProjectGuid>
|
||||
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\signFLOW\Gen\Api\net7\win64\$(Version)\$(Version).zip</DesktopBuildPackageLocation>
|
||||
<PackageAsSingleFile>true</PackageAsSingleFile>
|
||||
<DeployIisAppPath>SignFlowGen</DeployIisAppPath>
|
||||
<_TargetId>IISWebDeployPackage</_TargetId>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<WebPublishMethod>Package</WebPublishMethod>
|
||||
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
|
||||
<LastUsedPlatform>Any CPU</LastUsedPlatform>
|
||||
<SiteUrlToLaunchAfterPublish />
|
||||
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
|
||||
<ExcludeApp_Data>false</ExcludeApp_Data>
|
||||
<ProjectGuid>4fdae4ba-f512-444a-9e18-111047d3ef02</ProjectGuid>
|
||||
<DesktopBuildPackageLocation>P:\Install .Net\0 DD - Smart UP\signFLOW\Gen\Api\net9\win64\$(Version)\$(Version).zip</DesktopBuildPackageLocation>
|
||||
<PackageAsSingleFile>true</PackageAsSingleFile>
|
||||
<DeployIisAppPath>SignFlowGen</DeployIisAppPath>
|
||||
<_TargetId>IISWebDeployPackage</_TargetId>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>true</SelfContained>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="3em" height="2.5em" viewBox="0 0 24 24" style="stroke: #a9a8ad;">
|
||||
<path opacity="0.5" d="M15.9998 2L14.9998 2C12.1714 2 10.7576 2.00023 9.87891 2.87891C9.00023 3.75759 9.00023 5.1718 9.00023 8.00023L9.00023 16.0002C9.00023 18.8287 9.00023 20.2429 9.87891 21.1215C10.7574 22 12.1706 22 14.9976 22L14.9998 22L15.9998 22C18.8282 22 20.2424 22 21.1211 21.1213C21.9998 20.2426 21.9998 18.8284 21.9998 16L21.9998 8L21.9998 7.99998C21.9998 5.17157 21.9998 3.75736 21.1211 2.87868C20.2424 2 18.8282 2 15.9998 2Z" fill="#1C274C"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.25098 11.999C1.25098 11.5848 1.58676 11.249 2.00098 11.249L13.9735 11.249L12.0129 9.56845C11.6984 9.29889 11.662 8.82541 11.9315 8.51092C12.2011 8.19642 12.6746 8.16 12.9891 8.42957L16.4891 11.4296C16.6553 11.5721 16.751 11.7801 16.751 11.999C16.751 12.218 16.6553 12.426 16.4891 12.5685L12.9891 15.5685C12.6746 15.838 12.2011 15.8016 11.9315 15.4871C11.662 15.1726 11.6984 14.6991 12.0129 14.4296L13.9735 12.749L2.00098 12.749C1.58676 12.749 1.25098 12.4132 1.25098 11.999Z" fill="#1C274C"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 264 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -15,7 +15,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace EnvelopeGenerator.Web.Controllers;
|
||||
|
||||
[Authorize(Roles = ReceiverRole.FullyAuth)]
|
||||
[Authorize(Roles = Role.FullyAuth)]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AnnotationController : ControllerBase
|
||||
@@ -42,7 +42,7 @@ public class AnnotationController : ControllerBase
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[Authorize(Roles = ReceiverRole.FullyAuth)]
|
||||
[Authorize(Roles = Role.FullyAuth)]
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateOrUpdate([FromBody] PsPdfKitAnnotation? psPdfKitAnnotation = null, CancellationToken cancel = default)
|
||||
{
|
||||
@@ -80,7 +80,7 @@ public class AnnotationController : ControllerBase
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Roles = ReceiverRole.FullyAuth)]
|
||||
[Authorize(Roles = Role.FullyAuth)]
|
||||
[HttpPost("reject")]
|
||||
[Obsolete("Use DigitalData.Core.Exceptions and .Middleware")]
|
||||
public async Task<IActionResult> Reject([FromBody] string? reason = null)
|
||||
|
||||
@@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace EnvelopeGenerator.Web.Controllers;
|
||||
|
||||
[Authorize(Roles = ReceiverRole.FullyAuth)]
|
||||
[Authorize(Roles = Role.FullyAuth)]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class DocumentController : ControllerBase
|
||||
|
||||
@@ -107,7 +107,7 @@ public class EnvelopeController : ViewControllerBase
|
||||
return this.ViewEnvelopeNotFound();
|
||||
}
|
||||
var er_secret = er_secret_res.Data;
|
||||
await HttpContext.SignInEnvelopeAsync(er_secret, ReceiverRole.FullyAuth);
|
||||
await HttpContext.SignInEnvelopeAsync(er_secret, Role.FullyAuth);
|
||||
return await CreateShowEnvelopeView(er_secret);
|
||||
}
|
||||
#endregion UseAccessCode
|
||||
@@ -172,7 +172,7 @@ public class EnvelopeController : ViewControllerBase
|
||||
}
|
||||
|
||||
// show envelope if already logged in
|
||||
if (User.IsInRole(ReceiverRole.FullyAuth))
|
||||
if (User.IsInRole(Role.FullyAuth))
|
||||
return await CreateShowEnvelopeView(er_secret);
|
||||
|
||||
if (auth.HasMulti)
|
||||
@@ -206,7 +206,7 @@ public class EnvelopeController : ViewControllerBase
|
||||
.WithData("ErrorMessage", _localizer.WrongEnvelopeReceiverId());
|
||||
}
|
||||
|
||||
await HttpContext.SignInEnvelopeAsync(er_secret, ReceiverRole.FullyAuth);
|
||||
await HttpContext.SignInEnvelopeAsync(er_secret, Role.FullyAuth);
|
||||
|
||||
return await CreateShowEnvelopeView(er_secret);
|
||||
}
|
||||
@@ -225,9 +225,9 @@ public class EnvelopeController : ViewControllerBase
|
||||
&& uuidClaim == er.Envelope?.Uuid
|
||||
&& signatureClaim is not null
|
||||
&& signatureClaim == er.Receiver?.Signature
|
||||
&& User.IsInRole(ReceiverRole.FullyAuth))
|
||||
&& User.IsInRole(Role.FullyAuth))
|
||||
{
|
||||
await HttpContext.SignInEnvelopeAsync(er, ReceiverRole.FullyAuth);
|
||||
await HttpContext.SignInEnvelopeAsync(er, Role.FullyAuth);
|
||||
|
||||
//add PSPDFKit licence key
|
||||
ViewData["PSPDFKitLicenseKey"] = _configuration["PSPDFKitLicenseKey"];
|
||||
@@ -262,7 +262,7 @@ public class EnvelopeController : ViewControllerBase
|
||||
return this.ViewDocumentNotFound();
|
||||
}
|
||||
|
||||
await HttpContext.SignInEnvelopeAsync(er, ReceiverRole.FullyAuth);
|
||||
await HttpContext.SignInEnvelopeAsync(er, Role.FullyAuth);
|
||||
|
||||
ViewData["ReadAndConfirm"] = er.Envelope.ReadOnly;
|
||||
|
||||
@@ -334,7 +334,7 @@ public class EnvelopeController : ViewControllerBase
|
||||
await _rcvService.UpdateAsync(rcv);
|
||||
}
|
||||
|
||||
await HttpContext.SignInEnvelopeAsync(er_secret, ReceiverRole.PreAuth);
|
||||
await HttpContext.SignInEnvelopeAsync(er_secret, Role.PreAuth);
|
||||
|
||||
return await TFAViewAsync(auth.UserSelectSMS, er_secret, envelopeReceiverId);
|
||||
}
|
||||
@@ -348,7 +348,7 @@ public class EnvelopeController : ViewControllerBase
|
||||
if (er_secret.Receiver!.TotpSecretkey is null)
|
||||
throw new InvalidOperationException($"TotpSecretkey of DTO cannot validate without TotpSecretkey. Dto: {JsonConvert.SerializeObject(er_secret)}");
|
||||
|
||||
if (!User.IsInRole(ReceiverRole.PreAuth) || !_envSmsHandler.VerifyTotp(auth.SmsCode!, er_secret.Receiver.TotpSecretkey))
|
||||
if (!User.IsInRole(Role.PreAuth) || !_envSmsHandler.VerifyTotp(auth.SmsCode!, er_secret.Receiver.TotpSecretkey))
|
||||
{
|
||||
Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
ViewData["ErrorMessage"] = _localizer.WrongAccessCode();
|
||||
@@ -364,7 +364,7 @@ public class EnvelopeController : ViewControllerBase
|
||||
if (er_secret.Receiver!.TotpSecretkey is null)
|
||||
throw new InvalidOperationException($"TotpSecretkey of DTO cannot validate without TotpSecretkey. Dto: {JsonConvert.SerializeObject(er_secret)}");
|
||||
|
||||
if (!User.IsInRole(ReceiverRole.PreAuth) || !_authenticator.VerifyTotp(auth.AuthenticatorCode!, er_secret.Receiver.TotpSecretkey, window: VerificationWindow.RfcSpecifiedNetworkDelay))
|
||||
if (!User.IsInRole(Role.PreAuth) || !_authenticator.VerifyTotp(auth.AuthenticatorCode!, er_secret.Receiver.TotpSecretkey, window: VerificationWindow.RfcSpecifiedNetworkDelay))
|
||||
{
|
||||
Response.StatusCode = StatusCodes.Status401Unauthorized;
|
||||
ViewData["ErrorMessage"] = _localizer.WrongAccessCode();
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace EnvelopeGenerator.Web.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Roles = ReceiverRole.FullyAuth)]
|
||||
[Authorize(Roles = Role.FullyAuth)]
|
||||
[Obsolete("Use MediatR")]
|
||||
public async Task<IActionResult> CreateAsync([FromBody] EnvelopeReceiverReadOnlyCreateDto createDto)
|
||||
{
|
||||
|
||||
@@ -91,7 +91,7 @@ public class TFARegController : ViewControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize(Roles = ReceiverRole.FullyAuth)]
|
||||
[Authorize(Roles = Role.FullyAuth)]
|
||||
[HttpPost("auth/logout")]
|
||||
public async Task<IActionResult> LogOut()
|
||||
{
|
||||
|
||||
@@ -17,8 +17,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvelopeGenerator.Infrastru
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvelopeGenerator.Application", "EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj", "{5A9984F8-51A2-4558-A415-EC5FEED7CF7D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvelopeGenerator.GeneratorAPI", "EnvelopeGenerator.GeneratorAPI\EnvelopeGenerator.GeneratorAPI.csproj", "{E5E12BA4-60C1-48BA-9053-0F8B62B38124}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{9943209E-1744-4944-B1BA-4F87FC1A0EEB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{134D4164-B291-4E19-99B9-E4FA3AFAB62C}"
|
||||
@@ -29,8 +27,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "infrastructure", "infrastru
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "presentation", "presentation", "{E3C758DC-914D-4B7E-8457-0813F1FDB0CB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Terminal", "EnvelopeGenerator.Terminal\EnvelopeGenerator.Terminal.csproj", "{A9F9B431-BB9B-49B8-9E2C-0703634A653A}"
|
||||
EndProject
|
||||
Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "EnvelopeGenerator.Form", "EnvelopeGenerator.Form\EnvelopeGenerator.Form.vbproj", "{6D56C01F-D6CB-4D8A-BD3D-4FD34326998C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.PdfEditor", "EnvelopeGenerator.PdfEditor\EnvelopeGenerator.PdfEditor.csproj", "{211619F5-AE25-4BA5-A552-BACAFE0632D3}"
|
||||
@@ -41,6 +37,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Jobs", "E
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.WorkerService", "EnvelopeGenerator.WorkerService\EnvelopeGenerator.WorkerService.csproj", "{E3676510-7030-4E85-86E1-51E483E2A3B6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.API", "EnvelopeGenerator.API\EnvelopeGenerator.API.csproj", "{EC768913-6270-14F4-1DD3-69C87A659462}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -75,14 +73,6 @@ Global
|
||||
{5A9984F8-51A2-4558-A415-EC5FEED7CF7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5A9984F8-51A2-4558-A415-EC5FEED7CF7D}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5A9984F8-51A2-4558-A415-EC5FEED7CF7D}.Release|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E5E12BA4-60C1-48BA-9053-0F8B62B38124}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E5E12BA4-60C1-48BA-9053-0F8B62B38124}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E5E12BA4-60C1-48BA-9053-0F8B62B38124}.Release|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E5E12BA4-60C1-48BA-9053-0F8B62B38124}.Release|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9F9B431-BB9B-49B8-9E2C-0703634A653A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A9F9B431-BB9B-49B8-9E2C-0703634A653A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A9F9B431-BB9B-49B8-9E2C-0703634A653A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A9F9B431-BB9B-49B8-9E2C-0703634A653A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6D56C01F-D6CB-4D8A-BD3D-4FD34326998C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6D56C01F-D6CB-4D8A-BD3D-4FD34326998C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6D56C01F-D6CB-4D8A-BD3D-4FD34326998C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -103,6 +93,10 @@ Global
|
||||
{E3676510-7030-4E85-86E1-51E483E2A3B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E3676510-7030-4E85-86E1-51E483E2A3B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E3676510-7030-4E85-86E1-51E483E2A3B6}.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
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -115,16 +109,15 @@ Global
|
||||
{4F32A98D-E6F0-4A09-BD97-1CF26107E837} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
||||
{63E32615-0ECA-42DC-96E3-91037324B7C7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{5A9984F8-51A2-4558-A415-EC5FEED7CF7D} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
||||
{E5E12BA4-60C1-48BA-9053-0F8B62B38124} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||
{9943209E-1744-4944-B1BA-4F87FC1A0EEB} = {134D4164-B291-4E19-99B9-E4FA3AFAB62C}
|
||||
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {134D4164-B291-4E19-99B9-E4FA3AFAB62C}
|
||||
{E3C758DC-914D-4B7E-8457-0813F1FDB0CB} = {134D4164-B291-4E19-99B9-E4FA3AFAB62C}
|
||||
{A9F9B431-BB9B-49B8-9E2C-0703634A653A} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||
{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}
|
||||
{3D0514EA-2681-4B13-AD71-35CC6363DBD7} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
||||
{E3676510-7030-4E85-86E1-51E483E2A3B6} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
||||
{EC768913-6270-14F4-1DD3-69C87A659462} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {73E60370-756D-45AD-A19A-C40A02DACCC7}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user