288 lines
14 KiB
C#
288 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Security.Claims;
|
|
using Asp.Versioning;
|
|
using AutoMapper;
|
|
using gehGassi.Core.Interfaces;
|
|
using gehGassi.Domain.Users;
|
|
using gehGassi.Dto;
|
|
using gehGassi.Web.Helper;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Extensions.Options;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace gehGassi.Web.Controllers.WebsiteApi
|
|
{
|
|
/// <summary>
|
|
/// Controller für den Zugriff auf gehGassi Authentifizierung und Tokens für Website-API
|
|
/// </summary>
|
|
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
|
|
[ApiController]
|
|
[ApiVersion(1)]
|
|
[Route("api/website/account")]
|
|
[Route("api/website/v{v:apiVersion}/account")]
|
|
public class WebSiteApiAccountController : WebSiteApiBaseController
|
|
{
|
|
private readonly IUserService _userService;
|
|
private readonly UserManager<ApplicationUser> _userManager;
|
|
private readonly IOptions<JwtTokenOptions> _jwtTokenOptions;
|
|
private readonly ITokenService _tokenService;
|
|
private readonly IRefreshTokenService _refreshTokenService;
|
|
|
|
public WebSiteApiAccountController(IMapper mapper, IOptions<LocalizationOptions> localizationOptions, IUserService userService, UserManager<ApplicationUser> userManager,
|
|
IOptions<JwtTokenOptions> jwtTokenOptions, ITokenService tokenService, IRefreshTokenService refreshTokenService) : base(mapper, localizationOptions, userService)
|
|
{
|
|
_userService = userService;
|
|
_userManager = userManager;
|
|
_jwtTokenOptions = jwtTokenOptions;
|
|
_tokenService = tokenService;
|
|
_refreshTokenService = refreshTokenService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Anmelden via API. Erstellt ein Access-Token und ein Refresh-Token wenn erfolgreich
|
|
/// </summary>
|
|
/// <param name="model">Model mit Benutzername & Passwort</param>
|
|
/// <returns>Token und Refreshtoken oder BadRequest wenn nicht erfolgreich</returns>
|
|
[AllowAnonymous]
|
|
[HttpPost]
|
|
[Route("Login")]
|
|
public async Task<IActionResult> Login(LoginDto model)
|
|
{
|
|
var clientOffset = GetClientDateOffset();
|
|
|
|
if (ModelState.IsValid)
|
|
{
|
|
var user = await _userManager.FindByNameAsync(model.UserName);// _userService.GetByUsernameAsync(model.UserName);
|
|
|
|
if (user != null)
|
|
{
|
|
if (await _userManager.CheckPasswordAsync(user, model.Password))
|
|
{
|
|
if (await _userManager.IsLockedOutAsync(user) == false)
|
|
{
|
|
if (await _userManager.IsEmailConfirmedAsync(user))
|
|
{
|
|
var usersClaims = new List<Claim>()
|
|
{
|
|
new Claim(ClaimTypes.Name, user.UserName),
|
|
new Claim(ClaimTypes.GivenName, user.FirstName),
|
|
new Claim(ClaimTypes.Surname, user.LastName),
|
|
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
|
new Claim(ClaimTypes.UserData, user.Photo ?? ""),
|
|
};
|
|
|
|
var roles = await _userManager.GetRolesAsync(user);
|
|
foreach (var role in roles)
|
|
{
|
|
usersClaims.Add(new Claim(ClaimTypes.Role, role));
|
|
}
|
|
|
|
var token = _tokenService.GenerateAccessToken(usersClaims);
|
|
await _refreshTokenService.RemoveExpiredAsync(user.Id, user.UserName);
|
|
var refreshToken = _refreshTokenService.Create();
|
|
refreshToken.UserId = user.Id;
|
|
_refreshTokenService.Add(refreshToken);
|
|
await _refreshTokenService.CommitAsync(user.UserName);
|
|
|
|
var userDto = Mapper.Map<WebsiteApiUserDto>(user);
|
|
userDto.AccessToken = token;
|
|
userDto.AccessTokenExpires = DateTimeOffset.UtcNow.AddMinutes(_jwtTokenOptions.Value.TokenMinutes);
|
|
userDto.RefreshToken = refreshToken.Token;
|
|
userDto.RefreshTokenExpires = refreshToken.Expires;
|
|
|
|
user.LastLoginDate = DateTimeOffset.UtcNow;
|
|
await _userManager.UpdateAsync(user);
|
|
//await _userService.CommitAsync(user.UserName);
|
|
|
|
return Ok(userDto);
|
|
}
|
|
|
|
return BadRequest(CommunicationErrors.Login_Email_NotConfirmed);
|
|
}
|
|
|
|
return Unauthorized(CommunicationErrors.Login_LockedOut);
|
|
}
|
|
|
|
return Unauthorized(CommunicationErrors.Login_Invalid_Credentials);
|
|
}
|
|
|
|
return NotFound(CommunicationErrors.Common_NotFound);
|
|
}
|
|
|
|
return BadRequest(CommunicationErrors.Common_Model_Invalid);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Abmelden eines Benutzers
|
|
/// </summary>
|
|
/// <returns>HTTP 200 wenn erfolgreich</returns>
|
|
[HttpGet]
|
|
[Route("Logout")]
|
|
public async Task<IActionResult> Logout()
|
|
{
|
|
var clientOffset = GetClientDateOffset();
|
|
|
|
if (User?.Identity != null && User.Identity.IsAuthenticated)
|
|
{
|
|
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
|
|
if (user != null)
|
|
{
|
|
//Löschen aller Refresh-tokens des Benutzers da eine Abmeldung erfolgt ist
|
|
await _refreshTokenService.RemoveAllAsync(user.Id, user.UserName);
|
|
await _refreshTokenService.CommitAsync(user.UserName);
|
|
return Ok();
|
|
}
|
|
}
|
|
return NotFound(CommunicationErrors.Common_NotFound);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Mit einem abgelaufenen Token und einem RefreshToken ein neues AccessToken holen
|
|
/// </summary>
|
|
/// <param name="model">Model</param>
|
|
/// <returns>Token und Refreshtoken oder BadRequest wenn nicht erfolgreich</returns>
|
|
[AllowAnonymous]
|
|
[HttpPost]
|
|
[Route("Refresh")]
|
|
public async Task<IActionResult> Refresh(RefreshTokenDto model)
|
|
{
|
|
var clientOffset = GetClientDateOffset();
|
|
|
|
if (ModelState.IsValid)
|
|
{
|
|
var principal = _tokenService.GetPrincipalFromExpiredToken(model.AccessToken);
|
|
if (principal != null)
|
|
{
|
|
var username = principal.Identity.Name; //this is mapped to the Name claim by default
|
|
|
|
var user = await _userService.GetByUsernameAsync(username);
|
|
if (user != null)
|
|
{
|
|
//Wenn der Benutzer gesperrt ist, dann alle RefreshTokens löschen und Fehler zurückgeben
|
|
if (user.LockoutEnd.HasValue && user.LockoutEnd > DateTimeOffset.UtcNow)
|
|
{
|
|
await _refreshTokenService.RemoveAllAsync(user.Id, user.UserName);
|
|
await _refreshTokenService.CommitAsync(user.UserName);
|
|
return BadRequest(CommunicationErrors.RefreshToken_Expired);
|
|
}
|
|
|
|
var existingRefreshToken = await _refreshTokenService.GetAsync(user.Id, model.RefreshToken);
|
|
if (existingRefreshToken != null)
|
|
{
|
|
if (existingRefreshToken.Expires > DateTimeOffset.UtcNow)
|
|
{
|
|
var token = _tokenService.GenerateAccessToken(principal.Claims);
|
|
|
|
await _refreshTokenService.RemoveExpiredAsync(user.Id, user.UserName);
|
|
await _refreshTokenService.CommitAsync(user.UserName);
|
|
|
|
RefreshToken refreshToken = null;
|
|
if (existingRefreshToken.Expires >= DateTimeOffset.UtcNow.AddDays(1))
|
|
{
|
|
refreshToken = existingRefreshToken;
|
|
}
|
|
else
|
|
{
|
|
refreshToken = _refreshTokenService.Create();
|
|
refreshToken.UserId = user.Id;
|
|
_refreshTokenService.Add(refreshToken);
|
|
_refreshTokenService.Remove(existingRefreshToken);
|
|
await _refreshTokenService.CommitAsync(user.UserName);
|
|
}
|
|
|
|
var dto = new RefreshTokenResponseDto()
|
|
{
|
|
AccessToken = token,
|
|
AccessTokenExpires = DateTimeOffset.UtcNow.AddMinutes(_jwtTokenOptions.Value.TokenMinutes),
|
|
RefreshToken = refreshToken.Token,
|
|
RefreshTokenExpires = refreshToken.Expires
|
|
};
|
|
|
|
return Ok(dto);
|
|
}
|
|
return BadRequest(CommunicationErrors.RefreshToken_Expired);
|
|
}
|
|
return BadRequest(CommunicationErrors.RefreshToken_NotFound);
|
|
}
|
|
}
|
|
//Principal oder user null, nun anhand des Tokens alleine versuchen
|
|
if (!string.IsNullOrWhiteSpace(model.AccessToken))
|
|
{
|
|
var existingRefreshToken = await _refreshTokenService.GetAsync(model.RefreshToken);
|
|
if (existingRefreshToken != null)
|
|
{
|
|
if (existingRefreshToken.Expires > DateTimeOffset.UtcNow)
|
|
{
|
|
var user = await _userService.GetAsync(existingRefreshToken.UserId);
|
|
if (user != null)
|
|
{
|
|
//Wenn der Benutzer gesperrt ist, dann alle RefreshTokens löschen und Fehler zurückgeben
|
|
if (user.LockoutEnd.HasValue && user.LockoutEnd > DateTimeOffset.UtcNow)
|
|
{
|
|
await _refreshTokenService.RemoveAllAsync(user.Id, user.UserName);
|
|
await _refreshTokenService.CommitAsync(user.UserName);
|
|
return BadRequest(CommunicationErrors.RefreshToken_Expired);
|
|
}
|
|
|
|
var usersClaims = new List<Claim>()
|
|
{
|
|
new Claim(ClaimTypes.Name, user.UserName),
|
|
new Claim(ClaimTypes.GivenName, user.FirstName),
|
|
new Claim(ClaimTypes.Surname, user.LastName),
|
|
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
|
new Claim(ClaimTypes.UserData, user.Photo ?? ""),
|
|
};
|
|
|
|
var roles = await _userManager.GetRolesAsync(user);
|
|
foreach (var role in roles)
|
|
{
|
|
usersClaims.Add(new Claim(ClaimTypes.Role, role));
|
|
}
|
|
|
|
var token = _tokenService.GenerateAccessToken(usersClaims);
|
|
|
|
await _refreshTokenService.RemoveExpiredAsync(user.Id, user.UserName);
|
|
await _refreshTokenService.CommitAsync(user.UserName);
|
|
|
|
RefreshToken refreshToken = null;
|
|
if (existingRefreshToken.Expires >= DateTimeOffset.UtcNow.AddDays(1))
|
|
{
|
|
refreshToken = existingRefreshToken;
|
|
}
|
|
else
|
|
{
|
|
refreshToken = _refreshTokenService.Create();
|
|
refreshToken.UserId = user.Id;
|
|
_refreshTokenService.Add(refreshToken);
|
|
_refreshTokenService.Remove(existingRefreshToken);
|
|
await _refreshTokenService.CommitAsync(user.UserName);
|
|
}
|
|
|
|
var dto = new RefreshTokenResponseDto()
|
|
{
|
|
AccessToken = token,
|
|
AccessTokenExpires = DateTimeOffset.UtcNow.AddMinutes(_jwtTokenOptions.Value.TokenMinutes),
|
|
RefreshToken = refreshToken.Token,
|
|
RefreshTokenExpires = refreshToken.Expires
|
|
};
|
|
|
|
return Ok(dto);
|
|
}
|
|
return NotFound(CommunicationErrors.RefreshToken_UserNotFound);
|
|
}
|
|
return BadRequest(CommunicationErrors.RefreshToken_Expired);
|
|
}
|
|
return BadRequest(CommunicationErrors.RefreshToken_NotFound);
|
|
}
|
|
return NotFound(CommunicationErrors.AccessToken_Empty);
|
|
}
|
|
|
|
return BadRequest(CommunicationErrors.Common_Model_Invalid);
|
|
}
|
|
}
|
|
}
|