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); } } }