using System.Diagnostics; using Asp.Versioning; using Microsoft.AspNetCore.Mvc; using OliverBooth.Common.Data.Web.Users; using OliverBooth.Common.Services; using ISession = OliverBooth.Common.Data.Web.Users.ISession; namespace OliverBooth.Controllers.Api.v1; /// /// Represents a controller which handles user authentication. /// [Controller] [Route("api/v{version:apiVersion}/auth")] [ApiVersion(1)] public sealed class AuthenticationController : ControllerBase { private readonly ILogger _logger; private readonly ISessionService _sessionService; private readonly IUserService _userService; /// /// Initializes a new instance of the class. /// /// The logger. /// The session service. /// The user service. public AuthenticationController(ILogger logger, ISessionService sessionService, IUserService userService) { _logger = logger; _sessionService = sessionService; _userService = userService; } /// /// Authorizes a multi-factor login request using the specified token and TOTP. /// /// The token. /// The time-based one-time password. /// The result of the authentication process. public IActionResult DoMultiFactor([FromForm(Name = "token")] string token, [FromForm(Name = "totp")] string totp) { string epName = nameof(DoMultiFactor); if (Request.HttpContext.Connection.RemoteIpAddress is not { } ip) { _logger.LogWarning("Endpoint {Name} reached with no remote IP!", epName); return BadRequest(); } MfaRequestResult result = _userService.VerifyMfaRequest(token, totp, out IUser? user); switch (result) { case MfaRequestResult.InvalidTotp: return RedirectToPage("/admin/multifactorstep", new { token, result = (int)result }); case MfaRequestResult.TokenExpired: return RedirectToPage("/admin/login", new { result = (int)result }); case MfaRequestResult.TooManyAttempts: return RedirectToPage("/admin/login", new { result = (int)result }); } Debug.Assert(user is not null); _userService.DeleteToken(token); ISession session = _sessionService.CreateSession(Request, user); _sessionService.SaveSessionCookie(Response, session); _logger.LogInformation("MFA request from {Host} with login {Login} succeeded", ip, user.EmailAddress); return RedirectToPage("/admin/index"); } /// /// Authorizes a login request using the specified credentials. /// /// The login email address. /// The login password. /// The result of the authentication process. [HttpPost("signin")] public IActionResult DoSignIn([FromForm(Name = "login-email")] string emailAddress, [FromForm(Name = "login-password")] string password) { string epName = nameof(DoSignIn); if (Request.HttpContext.Connection.RemoteIpAddress is not { } ip) { _logger.LogWarning("Endpoint {Name} reached with no remote IP!", epName); return BadRequest(); } IActionResult redirectResult = RedirectToPage("/admin/login"); if (Request.Headers.Referer is var referer && !string.IsNullOrWhiteSpace(referer.ToString())) { _logger.LogInformation("Endpoint {Name} reached by {Host} with referer {Referer}", epName, ip, referer); redirectResult = Redirect(referer!); } if (string.IsNullOrWhiteSpace(emailAddress)) { _logger.LogInformation("Login attempt from {Host} has empty login", ip); return redirectResult; } if (string.IsNullOrWhiteSpace(emailAddress)) { _logger.LogInformation("Login attempt from {Host} with login {Login} has empty password", ip, emailAddress); return redirectResult; } if (!_userService.VerifyLogin(emailAddress, password, out IUser? user)) { _logger.LogInformation("Login attempt from {Host} with login {Login} failed", ip, emailAddress); return redirectResult; } if (!string.IsNullOrWhiteSpace(user.Totp)) { // mfa required _logger.LogInformation("Login attempt from {Host} with login {Login} requires MFA", ip, emailAddress); IMfaToken token = _userService.CreateMfaToken(user); return RedirectToPage("/admin/multifactorstep", new { token = token.Token }); } ISession session = _sessionService.CreateSession(Request, user); _sessionService.SaveSessionCookie(Response, session); _logger.LogInformation("Login attempt from {Host} with login {Login} succeeded", ip, emailAddress); return redirectResult; } /// /// Signs the client out of its current session. /// /// The result of the sign-out process. [HttpGet("signout")] public IActionResult DoSignOut() { string epName = nameof(DoSignOut); if (Request.HttpContext.Connection.RemoteIpAddress is not { } ip) { _logger.LogWarning("Endpoint {Name} reached with no remote IP!", epName); return BadRequest(); } IActionResult redirectResult = RedirectToPage("/admin/login"); if (Request.Headers.Referer is var referer && !string.IsNullOrWhiteSpace(referer.ToString())) { _logger.LogInformation("Endpoint {Name} reached by {Host} with referer {Referer}", epName, ip, referer); redirectResult = Redirect(referer!); } if (!_sessionService.TryGetSession(HttpContext.Request, out ISession? session)) { _logger.LogInformation("Session sign-out from {Host} requested with no valid session", ip); return redirectResult; } _sessionService.DeleteSession(session); _sessionService.DeleteSessionCookie(HttpContext.Response); _logger.LogInformation("Session sign-out from {Host} completed successfully", ip); return redirectResult; } }