395 lines
15 KiB
C#
395 lines
15 KiB
C#
using System.Text.Json;
|
|
using e_suite.API.Common;
|
|
using e_suite.API.Common.exceptions;
|
|
using e_suite.API.Common.models;
|
|
using e_suite.Database.Audit;
|
|
using e_suite.Database.Core.Extensions;
|
|
using e_suite.Service.Sentinel;
|
|
using eSuite.API.Extensions;
|
|
using eSuite.API.security;
|
|
using eSuite.API.SingleSignOn;
|
|
using eSuite.API.Translation;
|
|
using eSuite.API.Utilities;
|
|
using eSuite.Core.Security;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace eSuite.API.Controllers;
|
|
|
|
public class ProfileViewModel
|
|
{
|
|
public Models.UserProfile Profile { get; set; }
|
|
public ITranslatorFactory TranslatorFactory { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// This MVC controller is used to support all user interactions when logging in and out of the system, including profile updates.
|
|
/// </summary>
|
|
[Route("/[controller]")]
|
|
[ApiExplorerSettings(IgnoreApi = true)]
|
|
public class AccountController : ESuiteController
|
|
{
|
|
private readonly IUserManager _userManager;
|
|
private readonly ISentinel _sentinel;
|
|
private readonly ISingleSignOn _singleSignOn;
|
|
private readonly ICookieManager _cookieManager;
|
|
private readonly ITranslatorFactory _translatorFactory;
|
|
/// <summary>
|
|
/// Default constructor used to inject dependencies
|
|
/// </summary>
|
|
/// <param name="userManager"></param>
|
|
/// <param name="sentinel"></param>
|
|
/// <param name="singleSignOn"></param>
|
|
/// <param name="cookieManager"></param>
|
|
public AccountController(IUserManager userManager, ISentinel sentinel, ISingleSignOn singleSignOn, ICookieManager cookieManager, ITranslatorFactory translatorFactory)
|
|
{
|
|
_userManager = userManager;
|
|
_sentinel = sentinel;
|
|
_singleSignOn = singleSignOn;
|
|
_cookieManager = cookieManager;
|
|
_translatorFactory = translatorFactory;
|
|
}
|
|
|
|
private const string AccountProfileUrl = "~/account/profile";
|
|
private const string RootUrl = "/";
|
|
private const string ProfileUrl = "~/profile";
|
|
|
|
/// <summary>
|
|
/// Returns the login page for the system
|
|
/// </summary>
|
|
/// <param name="login"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[Route("login")]
|
|
[AllowAnonymous]
|
|
[AccessKey(SecurityAccess.Everyone)]
|
|
[HttpGet]
|
|
public async Task<IActionResult> LoginGet(Login? login, CancellationToken cancellationToken)
|
|
{
|
|
var ssoId = await _cookieManager.GetSsoIdFromSsoIdCookie(Request);
|
|
if (ssoId.HasValue)
|
|
{
|
|
await _cookieManager.DeleteSsoIdCookie(Response);
|
|
var url = await _singleSignOn.StartSingleSignOn(ssoId.Value, cancellationToken);
|
|
return Redirect(url);
|
|
}
|
|
|
|
login ??= new Login();
|
|
|
|
return LoginView(login);
|
|
}
|
|
|
|
private ActionResult LoginView(Login? login)
|
|
{
|
|
return PartialView("Login", login);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used for processing the login attempts
|
|
/// </summary>
|
|
/// <param name="login"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[Route("login")]
|
|
[AllowAnonymous]
|
|
[AccessKey(SecurityAccess.Everyone)]
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> LoginPost(Login login, CancellationToken cancellationToken)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(login.Password))
|
|
{
|
|
var url = await _singleSignOn.StartSingleSignOn(login.Email, cancellationToken);
|
|
if (!string.IsNullOrWhiteSpace(url))
|
|
return Redirect(url);
|
|
|
|
if (login.ForgotPassword)
|
|
{
|
|
await _userManager.ForgotPassword(login.Email, cancellationToken);
|
|
}
|
|
|
|
return LoginView(login);
|
|
}
|
|
|
|
if (login.ForgotPassword)
|
|
{
|
|
await _userManager.ForgotPassword(login.Email, cancellationToken);
|
|
return LoginView(login);
|
|
}
|
|
|
|
var loginResponse = await _userManager.Login(login, cancellationToken);
|
|
ViewBag.loginResponse = loginResponse.Result;
|
|
switch (loginResponse.Result)
|
|
{
|
|
case LoginResult.Success:
|
|
await _cookieManager.CreateSessionCookie(Response, loginResponse);
|
|
|
|
return Redirect("/");
|
|
case LoginResult.EmailNotConfirmed:
|
|
case LoginResult.TwoFactorAuthenticationRemovalRequested:
|
|
case LoginResult.TwoFactorAuthenticationCodeRequired:
|
|
case LoginResult.TwoFactorAuthenticationCodeIncorrect:
|
|
return LoginView(login);
|
|
case LoginResult.Failed:
|
|
default:
|
|
await _sentinel.LogBadRequest(this, cancellationToken);
|
|
return LoginView(login);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Logs a user out of the system and tidies up any cookies.
|
|
/// </summary>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[Route("logout")]
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
#pragma warning disable IDE0060 // Remove unused parameter cancellationToken - used here as I want this parameter on all controller methods.
|
|
public async Task<IActionResult> Logout(CancellationToken cancellationToken)
|
|
#pragma warning restore IDE0060 // Remove unused parameter
|
|
{
|
|
await _cookieManager.DeleteSessionCookie(Response);
|
|
await _cookieManager.DeleteSsoIdCookie(Response);
|
|
return Redirect("~/");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by an identity provider when processing an OAuth2 identity request.
|
|
/// </summary>
|
|
/// <param name="ssoId"></param>
|
|
/// <param name="code"></param>
|
|
/// <param name="scope"></param>
|
|
/// <param name="authuser"></param>
|
|
/// <param name="prompt"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[Route("auth/{ssoId}")]
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
public async Task<IActionResult> Auth([FromRoute] long ssoId, [FromQuery] string code, [FromQuery] string scope, [FromQuery] string authuser, [FromQuery] string prompt, CancellationToken cancellationToken)
|
|
{
|
|
var ssoUserId = await _singleSignOn.ExchangeAuthorisationToken(ssoId, code, cancellationToken);
|
|
|
|
var cookieLink = await _cookieManager.GetUserIdFromLinkCookie(Request, cancellationToken);
|
|
|
|
if (cookieLink?.User != null)
|
|
{
|
|
await _cookieManager.DeleteLinkCookie(Response);
|
|
|
|
//Creating my own Audit details as the JWT isn't present when being called from an outside source
|
|
var auditUserDetails = new AuditUserDetails
|
|
{
|
|
UserId = cookieLink.User.Id,
|
|
UserDisplayName = cookieLink.User.DisplayName
|
|
};
|
|
|
|
await _userManager.LinkSsoProfileToUser(auditUserDetails, cookieLink.User, ssoId, ssoUserId, cookieLink.LinkType == LinkType.NewUser , cancellationToken);
|
|
|
|
if (cookieLink.LinkType == LinkType.Profile)
|
|
return Redirect(ProfileUrl);
|
|
}
|
|
|
|
var loginResponse = await _userManager.LoginSso(ssoId, ssoUserId, cancellationToken);
|
|
|
|
switch (loginResponse.Result)
|
|
{
|
|
case LoginResult.Success:
|
|
await _cookieManager.CreateSessionCookie(Response, loginResponse);
|
|
await _cookieManager.CreateSsoIdCookie(Response, ssoId);
|
|
|
|
return Redirect("~/");
|
|
case LoginResult.EmailNotConfirmed:
|
|
case LoginResult.TwoFactorAuthenticationRemovalRequested:
|
|
case LoginResult.TwoFactorAuthenticationCodeRequired:
|
|
case LoginResult.TwoFactorAuthenticationCodeIncorrect:
|
|
return Redirect("~/");
|
|
case LoginResult.Failed:
|
|
default:
|
|
await _sentinel.LogBadRequest(this, cancellationToken);
|
|
return Redirect("~/");
|
|
}
|
|
}
|
|
|
|
/// <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 id = User.GeneralIdRef();
|
|
var loginResponse = await _userManager.RefreshToken(id, cancellationToken);
|
|
|
|
await _cookieManager.CreateSessionCookie(Response, loginResponse);
|
|
|
|
return Ok();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Page used for making alterations to a users profile.
|
|
/// </summary>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[Route("profile")]
|
|
[HttpGet]
|
|
[AccessKey(SecurityAccess.Everyone)]
|
|
public async Task<IActionResult> ProfileGet(CancellationToken cancellationToken)
|
|
{
|
|
var profile = await _userManager.GetProfile(User.Email(), cancellationToken);
|
|
Models.UserProfile userProfile = profile.ToUserProfile();
|
|
|
|
_translatorFactory.Locale = profile.PreferredLocale;
|
|
var profileViewModel = new ProfileViewModel
|
|
{
|
|
Profile = userProfile,
|
|
TranslatorFactory = _translatorFactory
|
|
};
|
|
|
|
|
|
return PartialView("Profile", profileViewModel);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used to make updates to a users profile.
|
|
/// </summary>
|
|
/// <param name="userProfile"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[Route("profile")]
|
|
[AccessKey(SecurityAccess.Everyone)]
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ProfilePost(Models.UserProfile userProfile, CancellationToken cancellationToken)
|
|
{
|
|
var updatedUserProfile = new UpdatedUserProfile
|
|
{
|
|
Email = User.Email(),
|
|
Password = userProfile.Password ?? string.Empty,
|
|
FirstName = userProfile.FirstName ?? string.Empty,
|
|
MiddleNames = userProfile.MiddleNames ?? string.Empty,
|
|
LastName = userProfile.LastName ?? string.Empty,
|
|
SecurityCode = userProfile.SecurityCode ?? string.Empty,
|
|
UsingTwoFactorAuthentication = userProfile.UsingTwoFactorAuthentication
|
|
};
|
|
|
|
await _userManager.UpdateProfile(AuditUserDetails, User.Email(), updatedUserProfile, cancellationToken);
|
|
|
|
var profile = await _userManager.GetProfile(User.Email(), cancellationToken);
|
|
|
|
var id = User.GeneralIdRef();
|
|
|
|
if (profile.DomainSsoProviderId == null)
|
|
{
|
|
if (profile.SsoProviderId != userProfile.SsoProviderId)
|
|
{
|
|
if (userProfile.SsoProviderId != -1)
|
|
{
|
|
var url = await _singleSignOn.StartSingleSignOn(userProfile.SsoProviderId, cancellationToken);
|
|
if (!string.IsNullOrWhiteSpace(url))
|
|
{
|
|
await _cookieManager.CreateProfileLinkCookie(Response, AuditUserDetails, id, cancellationToken);
|
|
|
|
return Redirect(url);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await _userManager.TurnOfSsoForUser(AuditUserDetails, id, cancellationToken);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Redirect(AccountProfileUrl);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Page used for making alterations to a users profile.
|
|
/// </summary>
|
|
/// <param name="token"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[Route("confirmaccount/{token}")]
|
|
[HttpGet]
|
|
[AllowAnonymous]
|
|
[AccessKey(SecurityAccess.Everyone)]
|
|
public async Task<IActionResult> ConfirmAccountGet([FromRoute] string token, CancellationToken cancellationToken)
|
|
{
|
|
var emailActionToken = DecodeToken(token);
|
|
|
|
var profile = await _userManager.GetProfile(emailActionToken?.Email!, cancellationToken);
|
|
var userProfile = profile.ToConfirmEmailAccount();
|
|
|
|
return View("ConfirmAccount", userProfile);
|
|
}
|
|
|
|
private static EmailActionToken? DecodeToken(string token)
|
|
{
|
|
var jsonString = Convert.FromBase64String(token);
|
|
var emailActionToken = JsonSerializer.Deserialize<EmailActionToken>(jsonString);
|
|
return emailActionToken;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used to make updates to a users profile.
|
|
/// </summary>
|
|
/// <param name="userProfile"></param>
|
|
/// <param name="token"></param>
|
|
/// <param name="cancellationToken"></param>
|
|
/// <returns></returns>
|
|
[Route("confirmaccount/{token}")]
|
|
[AllowAnonymous]
|
|
[AccessKey(SecurityAccess.Everyone)]
|
|
[HttpPost]
|
|
[ValidateAntiForgeryToken]
|
|
public async Task<IActionResult> ConfirmAccountPost(
|
|
Models.ConfirmEmailAccount userProfile,
|
|
[FromRoute] string token,
|
|
CancellationToken cancellationToken
|
|
)
|
|
{
|
|
var emailActionToken = DecodeToken(token);
|
|
var user = await _userManager.GetUserByEmailAsync(emailActionToken?.Email!, cancellationToken) ??
|
|
throw new NotFoundException("User not found");
|
|
|
|
var auditUserDetails = new AuditUserDetails
|
|
{
|
|
UserId = user.Id,
|
|
UserDisplayName = user.DisplayName
|
|
};
|
|
|
|
|
|
if (user.Domain.SsoProviderId is not null || userProfile.SsoProviderId != -1)
|
|
{
|
|
var url = await _singleSignOn.StartSingleSignOn(user.Domain.SsoProviderId ?? userProfile.SsoProviderId, cancellationToken);
|
|
if (!string.IsNullOrWhiteSpace(url))
|
|
{
|
|
await _cookieManager.CreateNewUserLinkCookie(Response, auditUserDetails, user.ToGeneralIdRef()!,
|
|
cancellationToken);
|
|
|
|
return Redirect(url);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await _userManager.TurnOfSsoForUser(auditUserDetails, user.ToGeneralIdRef()!, cancellationToken);
|
|
|
|
var userAuthenticationDetails = new UserAuthenticationDetails
|
|
{
|
|
Id = user.ToGeneralIdRef()!,
|
|
Password = userProfile.Password ?? string.Empty,
|
|
SecurityCode = userProfile.SecurityCode ?? string.Empty,
|
|
UsingTwoFactorAuthentication = userProfile.UsingTwoFactorAuthentication
|
|
};
|
|
|
|
await _userManager.SetAuthentication(auditUserDetails, userAuthenticationDetails, true, cancellationToken);
|
|
}
|
|
|
|
return Redirect(RootUrl);
|
|
}
|
|
} |