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.Text.Json;
using JdeScoping.Api.Extensions;
using JdeScoping.Api.Models;
using JdeScoping.Core.Interfaces;
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Auth;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
@@ -20,37 +21,66 @@ namespace JdeScoping.Api.Controllers;
public class AuthController : ApiControllerBase
{
private readonly IAuthService _authService;
private readonly IRsaKeyService _rsaKeyService;
private readonly ILogger<AuthController> _logger;
public AuthController(
IAuthService authService,
IRsaKeyService rsaKeyService,
ILogger<AuthController> logger)
{
_authService = authService;
_rsaKeyService = rsaKeyService;
_logger = logger;
}
/// <summary>
/// Authenticates a user and creates a session cookie
/// Gets the server's RSA public key for encrypting login credentials.
/// </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>
/// <returns>User info on success, 401 on failure</returns>
/// <returns>Login result with user info on success</returns>
[HttpPost("login")]
[AllowAnonymous]
[ProducesResponseType(typeof(UserInfo), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<UserInfo>> Login(
[FromBody] LoginRequest request,
[ProducesResponseType(typeof(LoginResultModel), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(LoginResultModel), StatusCodes.Status401Unauthorized)]
public async Task<ActionResult<LoginResultModel>> Login(
[FromBody] EncryptedLoginRequest request,
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(
request.Username, request.Password, ct);
loginModel.Username, loginModel.Password, ct);
if (!result.Success)
{
_logger.LogWarning("Failed login attempt for user {Username}", request.Username);
return Unauthorized(new { message = result.ErrorMessage });
_logger.LogWarning("Failed login attempt for user {Username}", loginModel.Username);
return Unauthorized(new LoginResultModel(false, result.ErrorMessage, null));
}
// Sign out existing session
@@ -66,8 +96,8 @@ public class AuthController : ApiControllerBase
principal,
new AuthenticationProperties { IsPersistent = false });
_logger.LogInformation("User {Username} logged in successfully", request.Username);
return Ok(result.User);
_logger.LogInformation("User {Username} logged in successfully", loginModel.Username);
return Ok(new LoginResultModel(true, null, result.User));
}
/// <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;
}