213 lines
8.6 KiB
C#
213 lines
8.6 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// This part of the API is responsible for creating and maintaining user sessions within e-suite
|
|
/// </summary>
|
|
[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";
|
|
|
|
/// <summary>
|
|
/// Default constructor used for dependency injection
|
|
/// </summary>
|
|
/// <param name="userManager"></param>
|
|
/// <param name="sentinel"></param>
|
|
public AuthenticationController(IUserManager userManager, ISentinel sentinel)
|
|
{
|
|
_userManager = userManager;
|
|
_sentinel = sentinel;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Request an password recovery email is sent
|
|
/// </summary>
|
|
/// <remarks>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.</remarks>
|
|
/// <returns></returns>
|
|
[Route("forgotPassword")]
|
|
[HttpPost]
|
|
[AccessKey(SecurityAccess.Everyone)]
|
|
[AllowAnonymous]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
|
public async Task<IActionResult> 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"
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Login with username, password and optionally Two Factor Authentication code
|
|
/// </summary>
|
|
/// <remarks>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.</remarks>
|
|
/// <example>This is my example</example>
|
|
/// <returns></returns>
|
|
/// <response code="200">Authentication successful. The "instance" property will contain the token to include to access secure api calls</response>
|
|
/// <response code="202">Username and password accepted. Two factor (Tfa) security code required, or request to disable Tfa processed. See the detail property.</response>
|
|
/// <response code="401">Authentication is unsuccessful</response>
|
|
[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<IActionResult> 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"
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Call this to complete the action for an email with an action link.
|
|
/// </summary>
|
|
/// <remarks>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</remarks>
|
|
/// <param name="token"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[Route("completeEmailAction")]
|
|
[HttpPost]
|
|
[AccessKey(SecurityAccess.Everyone)]
|
|
[AllowAnonymous]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ProblemDetails))]
|
|
public async Task<IActionResult> 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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a new token to replace your current token.
|
|
/// </summary>
|
|
/// <remarks>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.</remarks>
|
|
/// <returns></returns>
|
|
[Route("refreshToken")]
|
|
[HttpGet]
|
|
[AccessKey(SecurityAccess.Everyone)]
|
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
|
public async Task<IActionResult> 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 });
|
|
}
|
|
} |