using e_suite.API.Common;
using e_suite.API.Common.exceptions;
using e_suite.API.Common.models;
using e_suite.Service.Sentinel;
using eSuite.API.Models;
using eSuite.API.security;
using eSuite.API.Utilities;
using eSuite.Core.Security;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace eSuite.API.Controllers;
///
/// This part of the API is responsible for creating and maintaining user sessions within e-suite
///
[Route("api/[controller]")]
[ApiController]
public class AuthenticationController : ESuiteControllerBase
{
private readonly IUserManager _userManager;
private readonly ISentinel _sentinel;
private const string AccessDeniedText = "Access denied";
private const string LoginAcceptedText = "Login accepted";
///
/// Default constructor used for dependency injection
///
///
///
public AuthenticationController(IUserManager userManager, ISentinel sentinel)
{
_userManager = userManager;
_sentinel = sentinel;
}
///
/// Request an password recovery email is sent
///
/// A password recovery email allows someone to gain access to the system after they have lost their password. It will allow them to reset their password.
///
[Route("forgotPassword")]
[HttpPost]
[AccessKey(SecurityAccess.Everyone)]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task ForgotPassword(
[FromBody] EmailAddress forgotPasswordRequest,
CancellationToken cancellationToken = default!
)
{
try
{
var securityResult = await _sentinel.CheckSecurity(this, cancellationToken);
if (securityResult != null)
return securityResult;
await _userManager.ForgotPassword(forgotPasswordRequest.Email, cancellationToken);
return Ok();
}
catch (NotFoundException)
{
await _sentinel.LogBadRequest(this, cancellationToken);
throw;
}
catch (EmailNotConfirmedException)
{
await _sentinel.LogBadRequest(this, cancellationToken);
return Unauthorized(new ProblemDetails
{
Title = AccessDeniedText,
Detail = "Email not confirmed"
});
}
}
///
/// Login with username, password and optionally Two Factor Authentication code
///
/// Log into e-suite using username, password and optionally Two factor Authentication. This call can also be used to attempt to request two factor authentication is disabled.
/// This is my example
///
/// Authentication successful. The "instance" property will contain the token to include to access secure api calls
/// Username and password accepted. Two factor (Tfa) security code required, or request to disable Tfa processed. See the detail property.
/// Authentication is unsuccessful
[Route("login")]
[HttpPost]
[AccessKey(SecurityAccess.Everyone)]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(SuccessfulLogin))]
[ProducesResponseType(StatusCodes.Status202Accepted)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[Produces("application/json")]
[Obsolete("Login is dealt with via SSO now. Only username and password auth will work with this call. Will be replaced with user created API Keys.")]
public async Task Login([FromBody] Login login, CancellationToken cancellationToken = default!)
{
var securityResult = await _sentinel.CheckSecurity(this, cancellationToken);
if (securityResult != null)
return securityResult;
var loginResponse = await _userManager.Login(login, cancellationToken);
switch (loginResponse.Result)
{
case LoginResult.EmailNotConfirmed:
return Unauthorized(new ProblemDetails
{
Title = AccessDeniedText,
Detail = "Email not confirmed"
});
case LoginResult.TwoFactorAuthenticationRemovalRequested:
return Accepted(new ProblemDetails
{
Title = LoginAcceptedText,
Detail = "Request to remove two factor authentication accepted"
});
case LoginResult.TwoFactorAuthenticationCodeRequired:
return Accepted(new ProblemDetails
{
Title = LoginAcceptedText,
Detail = "Please supply Two Factor Authentication code"
});
case LoginResult.TwoFactorAuthenticationCodeIncorrect:
await _sentinel.LogBadRequest(this, cancellationToken);
return Unauthorized(new ProblemDetails
{
Title = AccessDeniedText,
Detail = "Incorrect authorisation code"
});
case LoginResult.Success:
return Ok(new SuccessfulLogin
{
Title = LoginAcceptedText,
Token = loginResponse.Token
});
case LoginResult.Failed:
default:
await _sentinel.LogBadRequest(this, cancellationToken);
return Unauthorized(new ProblemDetails
{
Title = AccessDeniedText, Detail = "Email and/or Password has been entered incorrectly"
});
}
}
///
/// Call this to complete the action for an email with an action link.
///
/// There are several functions in the system that will result in an email being sent to the user with a link so they can confirm. Currently these are to for a forgotten password, to disable the two factor authentication and for a new user to confirm their e-mail
///
///
///
[Route("completeEmailAction")]
[HttpPost]
[AccessKey(SecurityAccess.Everyone)]
[AllowAnonymous]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))]
public async Task CompleteEmailAction(
[FromBody] EmailActionToken token,
CancellationToken cancellationToken = default!
)
{
var securityResult = await _sentinel.CheckSecurity(this, cancellationToken);
if (securityResult != null)
return securityResult;
try
{
await _userManager.CompleteEmailAction(token, cancellationToken);
return Ok();
}
catch (TokenInvalidException)
{
await _sentinel.LogBadRequest(this, cancellationToken);
throw;
}
catch (InvalidEmailException)
{
await _sentinel.LogBadRequest(this, cancellationToken);
throw;
}
}
///
/// Get a new token to replace your current token.
///
/// e-suite authentication tokens are valid for a limited amount of time. To gain more time on your user session you will need to exchange your token for a new one.
///
[Route("refreshToken")]
[HttpGet]
[AccessKey(SecurityAccess.Everyone)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task RefreshToken(CancellationToken cancellationToken = default!)
{
var userId = User.GeneralIdRef();
var loginResponse = await _userManager.RefreshToken(userId, cancellationToken);
if (loginResponse.Result != LoginResult.Success)
return Unauthorized(new ProblemDetails
{
Title = AccessDeniedText,
Detail = "Username or Password incorrect"
});
return Ok(new SuccessfulLogin { Title = "Access Granted", Token = loginResponse.Token });
}
}