using DigitalData.Auth.API.Config; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using DigitalData.UserManager.Application.Contracts; using DigitalData.UserManager.Application.DTOs.User; using DigitalData.Auth.API.Models; using DigitalData.Auth.API.Services.Contracts; using DigitalData.Auth.API.Entities; using DigitalData.Core.Abstractions.Security.Services; using DigitalData.Core.Abstractions.Security.Extensions; using DigitalData.Core.Abstraction.Application; using DigitalData.Core.Abstraction.Application.DTO; namespace DigitalData.Auth.API.Controllers { [Route("api/[controller]")] [ApiController] public class AuthController : ControllerBase { private readonly IJwtSignatureHandler _userSignatureHandler; private readonly IJwtSignatureHandler _consumerSignatureHandler; private readonly AuthApiParams _apiParams; private readonly IAsymmetricKeyPool _keyPool; private readonly ILogger _logger; private readonly IUserService _userService; private readonly IDirectorySearchService _dirSearchService; private readonly IConsumerService _consumerService; private readonly IOptionsMonitor _backdoorMonitor; public AuthController(IJwtSignatureHandler userSignatureHandler, IOptions cookieParamsOptions, IAsymmetricKeyPool keyPool, ILogger logger, IUserService userService, IDirectorySearchService dirSearchService, IConsumerService consumerService, IJwtSignatureHandler apiSignatureHandler, IOptionsMonitor backdoorMonitor) { _apiParams = cookieParamsOptions.Value; _userSignatureHandler = userSignatureHandler; _keyPool = keyPool; _logger = logger; _userService = userService; _dirSearchService = dirSearchService; _consumerService = consumerService; _consumerSignatureHandler = apiSignatureHandler; _backdoorMonitor = backdoorMonitor; } private async Task CreateTokenAsync(UserLogin login, string consumerName, bool cookie = true) { DataResult? 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 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 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 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 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 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(); } }