feat(api): update AuthController for encrypted login

This commit is contained in:
Joseph Doherty
2026-01-03 08:24:10 -05:00
parent 5ae634888f
commit 6debbe2740
2 changed files with 43 additions and 34 deletions
@@ -1,8 +1,9 @@
using System.Security.Claims; using System.Security.Claims;
using System.Text.Json;
using JdeScoping.Api.Extensions; using JdeScoping.Api.Extensions;
using JdeScoping.Api.Models;
using JdeScoping.Core.Interfaces; using JdeScoping.Core.Interfaces;
using JdeScoping.Core.Models; using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Auth;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -20,37 +21,66 @@ namespace JdeScoping.Api.Controllers;
public class AuthController : ApiControllerBase public class AuthController : ApiControllerBase
{ {
private readonly IAuthService _authService; private readonly IAuthService _authService;
private readonly IRsaKeyService _rsaKeyService;
private readonly ILogger<AuthController> _logger; private readonly ILogger<AuthController> _logger;
public AuthController( public AuthController(
IAuthService authService, IAuthService authService,
IRsaKeyService rsaKeyService,
ILogger<AuthController> logger) ILogger<AuthController> logger)
{ {
_authService = authService; _authService = authService;
_rsaKeyService = rsaKeyService;
_logger = logger; _logger = logger;
} }
/// <summary> /// <summary>
/// Authenticates a user and creates a session cookie /// Gets the server's RSA public key for encrypting login credentials.
/// </summary> /// </summary>
/// <param name="request">Login credentials</param> [HttpGet("public-key")]
[AllowAnonymous]
[ProducesResponseType(typeof(PublicKeyResponse), StatusCodes.Status200OK)]
public ActionResult<PublicKeyResponse> GetPublicKey()
{
var publicKeyPem = _rsaKeyService.GetPublicKeyPem();
return Ok(new PublicKeyResponse(publicKeyPem));
}
/// <summary>
/// Authenticates a user with encrypted credentials and creates a session cookie.
/// </summary>
/// <param name="request">Encrypted login credentials</param>
/// <param name="ct">Cancellation token</param> /// <param name="ct">Cancellation token</param>
/// <returns>User info on success, 401 on failure</returns> /// <returns>Login result with user info on success</returns>
[HttpPost("login")] [HttpPost("login")]
[AllowAnonymous] [AllowAnonymous]
[ProducesResponseType(typeof(UserInfo), StatusCodes.Status200OK)] [ProducesResponseType(typeof(LoginResultModel), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(LoginResultModel), StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<UserInfo>> Login( public async Task<ActionResult<LoginResultModel>> Login(
[FromBody] LoginRequest request, [FromBody] EncryptedLoginRequest request,
CancellationToken ct) CancellationToken ct)
{ {
LoginModel loginModel;
try
{
var ciphertext = Convert.FromBase64String(request.EncryptedData);
var plaintext = _rsaKeyService.Decrypt(ciphertext);
loginModel = JsonSerializer.Deserialize<LoginModel>(plaintext)
?? throw new InvalidOperationException("Deserialization returned null");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to decrypt login request");
return BadRequest(new LoginResultModel(false, "Invalid encrypted payload", null));
}
var result = await _authService.AuthenticateAsync( var result = await _authService.AuthenticateAsync(
request.Username, request.Password, ct); loginModel.Username, loginModel.Password, ct);
if (!result.Success) if (!result.Success)
{ {
_logger.LogWarning("Failed login attempt for user {Username}", request.Username); _logger.LogWarning("Failed login attempt for user {Username}", loginModel.Username);
return Unauthorized(new { message = result.ErrorMessage }); return Unauthorized(new LoginResultModel(false, result.ErrorMessage, null));
} }
// Sign out existing session // Sign out existing session
@@ -66,8 +96,8 @@ public class AuthController : ApiControllerBase
principal, principal,
new AuthenticationProperties { IsPersistent = false }); new AuthenticationProperties { IsPersistent = false });
_logger.LogInformation("User {Username} logged in successfully", request.Username); _logger.LogInformation("User {Username} logged in successfully", loginModel.Username);
return Ok(result.User); return Ok(new LoginResultModel(true, null, result.User));
} }
/// <summary> /// <summary>
@@ -1,21 +0,0 @@
using System.ComponentModel.DataAnnotations;
namespace JdeScoping.Api.Models;
/// <summary>
/// Login request payload
/// </summary>
public class LoginRequest
{
/// <summary>
/// Username for authentication
/// </summary>
[Required(ErrorMessage = "Username is required")]
public string Username { get; set; } = string.Empty;
/// <summary>
/// Password for authentication
/// </summary>
[Required(ErrorMessage = "Password is required")]
public string Password { get; set; } = string.Empty;
}