Introduce a new static class `CookieNames` in the `DigitalData.Auth.Claims` namespace to centralize and standardize the construction of cookie names for envelope receiver tokens. - Added `GetEnvelopeReceiverCookieName` methods to generate cookie names with or without a default cookie name. - Updated `AuthController` to use the `CookieNames` helper for constructing cookie names, replacing direct concatenation logic. - Improved maintainability and consistency of cookie naming conventions across the application.
269 lines
10 KiB
C#
269 lines
10 KiB
C#
using DigitalData.Auth.API.Config;
|
|
using DigitalData.Auth.API.Entities;
|
|
using DigitalData.Auth.API.Models;
|
|
using DigitalData.Auth.API.Services.Contracts;
|
|
using DigitalData.Auth.Claims;
|
|
using DigitalData.Core.Abstraction.Application;
|
|
using DigitalData.Core.Abstraction.Application.DTO;
|
|
using DigitalData.Core.Abstractions.Security.Extensions;
|
|
using DigitalData.Core.Abstractions.Security.Services;
|
|
using DigitalData.UserManager.Application.Contracts;
|
|
using DigitalData.UserManager.Application.DTOs.User;
|
|
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
|
using EnvelopeGenerator.Application.Common.Extensions;
|
|
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
|
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
|
using EnvelopeGenerator.Application.Receivers.Queries;
|
|
using EnvelopeGenerator.Domain.Entities;
|
|
using MediatR;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.FileSystemGlobbing;
|
|
using Microsoft.Extensions.Options;
|
|
using System.Net;
|
|
using System.Text;
|
|
|
|
namespace DigitalData.Auth.API.Controllers
|
|
{
|
|
[Route("api/[controller]")]
|
|
[ApiController]
|
|
public class AuthController : ControllerBase
|
|
{
|
|
private readonly IJwtSignatureHandler<EnvelopeReceiverSecretDto> _erSignatureHandler;
|
|
|
|
private readonly IJwtSignatureHandler<UserReadDto> _userSignatureHandler;
|
|
|
|
private readonly IJwtSignatureHandler<Consumer> _consumerSignatureHandler;
|
|
|
|
private readonly AuthApiParams _apiParams;
|
|
|
|
private readonly IAsymmetricKeyPool _keyPool;
|
|
|
|
private readonly ILogger<AuthController> _logger;
|
|
|
|
private readonly IUserService _userService;
|
|
|
|
private readonly IDirectorySearchService _dirSearchService;
|
|
|
|
private readonly IConsumerService _consumerService;
|
|
|
|
private readonly IOptionsMonitor<BackdoorParams> _backdoorMonitor;
|
|
|
|
private readonly IMediator _mediator;
|
|
|
|
public AuthController(IJwtSignatureHandler<UserReadDto> userSignatureHandler, IOptions<AuthApiParams> cookieParamsOptions, IAsymmetricKeyPool keyPool, ILogger<AuthController> logger, IUserService userService, IDirectorySearchService dirSearchService, IConsumerService consumerService, IJwtSignatureHandler<Consumer> apiSignatureHandler, IOptionsMonitor<BackdoorParams> backdoorMonitor, IMediator mediator, IJwtSignatureHandler<EnvelopeReceiverSecretDto> erSignatureHandler)
|
|
{
|
|
_apiParams = cookieParamsOptions.Value;
|
|
_userSignatureHandler = userSignatureHandler;
|
|
_keyPool = keyPool;
|
|
_logger = logger;
|
|
_userService = userService;
|
|
_dirSearchService = dirSearchService;
|
|
_consumerService = consumerService;
|
|
_consumerSignatureHandler = apiSignatureHandler;
|
|
_backdoorMonitor = backdoorMonitor;
|
|
_mediator = mediator;
|
|
_erSignatureHandler = erSignatureHandler;
|
|
}
|
|
|
|
private async Task<IActionResult> CreateTokenAsync(UserLogin login, string consumerName, bool cookie = true)
|
|
{
|
|
DataResult<UserReadDto>? uRes;
|
|
if(login.Username is not null && login.UserId is not null)
|
|
return BadRequest("Both user ID and username cannot be provided.");
|
|
if (login.Username is not null)
|
|
{
|
|
var backDoorOpened = _backdoorMonitor.CurrentValue.Backdoors.TryGet(login.Username, out var backdoor)
|
|
&& backdoor.Verify(login.Password);
|
|
|
|
if(backDoorOpened)
|
|
_logger.LogInformation("Backdoor access granted for user '{username}'", login.Username);
|
|
|
|
bool isValid = backDoorOpened || await _dirSearchService.ValidateCredentialsAsync(login.Username, login.Password);
|
|
|
|
if (!isValid)
|
|
return Unauthorized();
|
|
|
|
uRes = await _userService.ReadByUsernameAsync(login.Username);
|
|
if (uRes.IsFailed)
|
|
{
|
|
_logger.LogWarning("{username} is not found. Please import it from Active Directory.", login.Username);
|
|
return NotFound(login.Username + " is not found. Please import it from Active Directory.");
|
|
}
|
|
}
|
|
else if(login.UserId is int userId)
|
|
{
|
|
uRes = await _userService.ReadByIdAsync(userId);
|
|
if (uRes.IsFailed)
|
|
return Unauthorized();
|
|
|
|
bool isValid = await _dirSearchService.ValidateCredentialsAsync(uRes.Data.Username, login.Password);
|
|
|
|
if (!isValid)
|
|
return Unauthorized();
|
|
}
|
|
else
|
|
{
|
|
return BadRequest("User ID or username should be provided.");
|
|
}
|
|
|
|
//find the user
|
|
var consumer = await _consumerService.ReadByNameAsync(consumerName);
|
|
if (consumer is null)
|
|
return Unauthorized();
|
|
|
|
if (!_keyPool.TokenDescriptors.TryGet(_apiParams.Issuer, consumer.Audience, out var descriptor))
|
|
return StatusCode(StatusCodes.Status500InternalServerError);
|
|
|
|
var token = _userSignatureHandler.WriteToken(uRes!.Data, descriptor);
|
|
|
|
//set cookie
|
|
if (cookie)
|
|
{
|
|
var cookieOptions = consumer.CookieOptions ?? _apiParams.DefaultCookieOptions;
|
|
Response.Cookies.Append(_apiParams.DefaultCookieName, token, cookieOptions.Create(lifetime: descriptor.Lifetime));
|
|
return Ok();
|
|
}
|
|
else
|
|
return Ok(token);
|
|
}
|
|
|
|
private async Task<IActionResult> CreateTokenAsync(ConsumerLogin login, bool cookie = true)
|
|
{
|
|
var consumer = await _consumerService.ReadByNameAsync(login.Name);
|
|
if (consumer is null || consumer.Password != login.Password)
|
|
return Unauthorized();
|
|
|
|
if (!_keyPool.TokenDescriptors.TryGet(_apiParams.Issuer, _apiParams.LocalConsumer.Audience, out var descriptor))
|
|
return StatusCode(StatusCodes.Status500InternalServerError);
|
|
|
|
var token = _consumerSignatureHandler.WriteToken(consumer, descriptor);
|
|
|
|
//set cookie
|
|
if (cookie)
|
|
{
|
|
var cookieOptions = _apiParams.LocalConsumer.CookieOptions ?? _apiParams.DefaultCookieOptions;
|
|
Response.Cookies.Append(_apiParams.DefaultCookieName, token, cookieOptions.Create(lifetime: descriptor.Lifetime));
|
|
return Ok();
|
|
}
|
|
else
|
|
return Ok(token);
|
|
}
|
|
|
|
//TODO: Add role depends on group name
|
|
[HttpPost("{consumerName}/login")]
|
|
[AllowAnonymous]
|
|
public async Task<IActionResult> Login([FromForm] UserLogin login, [FromRoute] string consumerName)
|
|
{
|
|
try
|
|
{
|
|
return await CreateTokenAsync(login, consumerName, true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "{Message}", ex.Message);
|
|
return StatusCode(StatusCodes.Status500InternalServerError);
|
|
}
|
|
}
|
|
|
|
[HttpPost("login")]
|
|
[AllowAnonymous]
|
|
public async Task<IActionResult> Login([FromForm] ConsumerLogin login)
|
|
{
|
|
try
|
|
{
|
|
return await CreateTokenAsync(login, true);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "{Message}", ex.Message);
|
|
return StatusCode(StatusCodes.Status500InternalServerError);
|
|
}
|
|
}
|
|
|
|
[HttpPost("logout")]
|
|
public IActionResult Logout()
|
|
{
|
|
try
|
|
{
|
|
Response.Cookies.Delete(_apiParams.DefaultCookieName);
|
|
return Ok();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "{Message}", ex.Message);
|
|
return StatusCode(StatusCodes.Status500InternalServerError);
|
|
}
|
|
}
|
|
|
|
[HttpPost("{consumerName}")]
|
|
public async Task<IActionResult> CreateTokenViaBody([FromBody] UserLogin login, [FromRoute] string consumerName, [FromQuery] bool cookie = false)
|
|
{
|
|
try
|
|
{
|
|
return await CreateTokenAsync(login, consumerName, cookie);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "{Message}", ex.Message);
|
|
return StatusCode(StatusCodes.Status500InternalServerError);
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<IActionResult> CreateTokenViaBody([FromBody] ConsumerLogin login, [FromQuery] bool cookie = false)
|
|
{
|
|
try
|
|
{
|
|
return await CreateTokenAsync(login, cookie);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "{Message}", ex.Message);
|
|
return StatusCode(StatusCodes.Status500InternalServerError);
|
|
}
|
|
}
|
|
|
|
[HttpGet("check")]
|
|
[Authorize]
|
|
public IActionResult Check() => Ok();
|
|
|
|
[HttpPost("envelope-receiver/{key}")]
|
|
public async Task<IActionResult> CreateTokenForEnvelopeReceiver([FromRoute]string key, [FromForm] ReceiverLogin receiverLogin, [FromQuery] bool cookie = true, CancellationToken cancel = default)
|
|
{
|
|
//find the consumer
|
|
var consumer = await _consumerService.ReadByNameAsync("sign-flow");
|
|
if (consumer is null)
|
|
return Unauthorized();
|
|
|
|
if (!_keyPool.TokenDescriptors.TryGet(_apiParams.Issuer, consumer.Audience, out var descriptor))
|
|
return StatusCode(StatusCodes.Status500InternalServerError);
|
|
|
|
// find receiver
|
|
var er = await _mediator.Send(new ReadEnvelopeReceiverSecretQuery()
|
|
{
|
|
Key = key
|
|
}, cancel);
|
|
|
|
if (er is null)
|
|
return NotFound();
|
|
|
|
// check acccess code
|
|
if (er.AccessCode != receiverLogin.AccessCode)
|
|
return Unauthorized();
|
|
|
|
// create token
|
|
var token = _erSignatureHandler.WriteToken(er, descriptor);
|
|
|
|
//set cookie
|
|
if (cookie)
|
|
{
|
|
var cookieOptions = consumer.CookieOptions ?? _apiParams.DefaultCookieOptions;
|
|
Response.Cookies.Append(CookieNames.GetEnvelopeReceiverCookieName(_apiParams.DefaultCookieName, key), token, cookieOptions.Create(lifetime: descriptor.Lifetime));
|
|
return Ok();
|
|
}
|
|
else
|
|
return Ok(token);
|
|
}
|
|
}
|
|
} |