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
{
///
/// Controller für den Zugriff auf gehGassi Authentifizierung und Tokens für Website-API
///
[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 _userManager;
private readonly IOptions _jwtTokenOptions;
private readonly ITokenService _tokenService;
private readonly IRefreshTokenService _refreshTokenService;
public WebSiteApiAccountController(IMapper mapper, IOptions localizationOptions, IUserService userService, UserManager userManager,
IOptions jwtTokenOptions, ITokenService tokenService, IRefreshTokenService refreshTokenService) : base(mapper, localizationOptions, userService)
{
_userService = userService;
_userManager = userManager;
_jwtTokenOptions = jwtTokenOptions;
_tokenService = tokenService;
_refreshTokenService = refreshTokenService;
}
///
/// Anmelden via API. Erstellt ein Access-Token und ein Refresh-Token wenn erfolgreich
///
/// Model mit Benutzername & Passwort
/// Token und Refreshtoken oder BadRequest wenn nicht erfolgreich
[AllowAnonymous]
[HttpPost]
[Route("Login")]
public async Task 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()
{
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(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);
}
///
/// Abmelden eines Benutzers
///
/// HTTP 200 wenn erfolgreich
[HttpGet]
[Route("Logout")]
public async Task 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);
}
///
/// Mit einem abgelaufenen Token und einem RefreshToken ein neues AccessToken holen
///
/// Model
/// Token und Refreshtoken oder BadRequest wenn nicht erfolgreich
[AllowAnonymous]
[HttpPost]
[Route("Refresh")]
public async Task 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()
{
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);
}
}
}