gehgassi_backend/gehGassi.Web/Controllers/Api/ApiAccountController.cs

2572 lines
129 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using AutoMapper;
using gehGassi.Core.Interfaces;
using gehGassi.Core.Services;
using gehGassi.Domain.Common;
using gehGassi.Domain.Users;
using gehGassi.Dto;
using gehGassi.Dto.Common;
using gehGassi.Permissions;
using gehGassi.Web.Auth;
using gehGassi.Web.Auth.Attributes;
using gehGassi.Web.Helper;
using gehGassi.Web.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NetTopologySuite.Geometries;
using NetTopologySuite;
using SixLabors.ImageSharp;
using LocalizationOptions = gehGassi.Web.Helper.LocalizationOptions;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Advanced;
using System.Net;
using System.Net.Http;
using gehGassi.Dto.DogWalkers;
using System.Net.Http.Headers;
using System.Text.Json;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.Hosting;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Threading;
using Microsoft.Extensions.Caching.Distributed;
using gehGassi.Dto.Reporting;
using Asp.Versioning;
using Microsoft.AspNetCore.Authentication.Cookies;
namespace gehGassi.Web.Controllers.Api
{
/// <summary>
/// Controller für den API-Zugriff auf gehGassi Authentifizierung und Tokens
/// </summary>
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
[HasHeaderAuthorize(ClaimConstants.HasHeader, ClaimConstants.HasHeaderValue)]
[ApiController]
[ApiVersion(1)]
[ApiVersion(2)]
[Route("api/account")]
[Route("api/v{v:apiVersion}/account")]
public class ApiAccountController : ApiBaseController
{
private readonly ILogger<AccountController> _logger;
private readonly ITokenService _tokenService;
private readonly IRefreshTokenService _refreshTokenService;
private readonly IUserService _userService;
private readonly UserManager<ApplicationUser> _userManager;
private readonly IOptions<JwtTokenOptions> _jwtTokenOptions;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly IEmailSender _emailSender;
private readonly IStringLocalizer<ApiAccountController> _localizer;
private readonly IOptions<LicenseOptions> _licenseOptions;
private readonly IOptions<AuthOptions> _authOptions;
private readonly IGeoLocationService _geoLocationService;
private readonly IMessageService _messageService;
private readonly IWebHostEnvironment _environment;
private readonly IDistributedCache _memoryCache;
private readonly IOptions<AppleOptions> _appleOptions;
private readonly IMangoPayService _mangoPayService;
private readonly IOptions<EmailSenderOptions> _emailSenderOptions;
private readonly IWalkService _walkService;
private readonly IPublicWalkRequestService _publicWalkRequestService;
private readonly IPublicWalkResponseService _publicWalkResponseService;
private readonly IAppUserReportService _appUserReportService;
/// <summary>
/// Erstellt eine Instanz
/// </summary>
/// <param name="mapper">Instanz eines IMapper</param>
/// <param name="localizationOptions">Instanz von LocalizationOptions</param>
/// <param name="logger">Instanz eines ILogger</param>
/// <param name="tokenService">Instanz eines ITokenService</param>
/// <param name="refreshTokenService">Instanz eines IRefreshTokenService</param>
/// <param name="userService">Instanz eines IUserService</param>
/// <param name="userManager">Instanz eines UserManager</param>
/// <param name="jwtTokenOptions">Instanz eines IOptions JwtTokenOptions</param>
/// <param name="signInManager">Instanz eines SignInManager</param>
/// <param name="emailSender">Instanz eines IEmailSender</param>
/// <param name="localizer">Instanz eines IStringLocalizer</param>
/// <param name="licenseOptions">Instanz von LicenseOptions</param>
/// <param name="appUserService">Instanz eines IAppUserService</param>
/// <param name="authOptions">Instanz von AuthOptions</param>
/// <param name="geoLocationService">Instanz eines IGeoLocationService</param>
/// <param name="messageService">Instanz eines IMessageService</param>
/// <param name="environment">Instanz eines IWebHostEnvironment</param>
/// <param name="memoryCache">Instanz eines IDistributedCache</param>
/// <param name="appleOptions">Instanz von AppleOptions</param>
/// <param name="mangoPayService">Instanz eines IMangoPayService</param>
/// <param name="emailSenderOptions">Instanz eines EmailSenderOptions</param>
/// <param name="walkService">Instanz eines IWalkService</param>
/// <param name="publicWalkRequestService">Instanz eines IPublicWalkRequestService</param>
/// <param name="publicWalkResponseService">Instanz eines IPublicWalkResponseService</param>
/// <param name="appUserReportService">Instanz eines IAppUserReportService</param>
public ApiAccountController(IMapper mapper, IOptions<LocalizationOptions> localizationOptions, ILogger<AccountController> logger, ITokenService tokenService, IRefreshTokenService refreshTokenService, IUserService userService,
UserManager<ApplicationUser> userManager, IOptions<JwtTokenOptions> jwtTokenOptions, SignInManager<ApplicationUser> signInManager,
IEmailSender emailSender, IStringLocalizer<ApiAccountController> localizer, IOptions<LicenseOptions> licenseOptions, IAppUserService appUserService,
IOptions<AuthOptions> authOptions, IGeoLocationService geoLocationService, IMessageService messageService, IWebHostEnvironment environment, IDistributedCache memoryCache,
IOptions<AppleOptions> appleOptions, IMangoPayService mangoPayService, IOptions<EmailSenderOptions> emailSenderOptions, IWalkService walkService,
IPublicWalkRequestService publicWalkRequestService, IPublicWalkResponseService publicWalkResponseService, IAppUserReportService appUserReportService) : base(mapper, localizationOptions, appUserService)
{
_logger = logger;
_tokenService = tokenService;
_refreshTokenService = refreshTokenService;
_userService = userService;
_userManager = userManager;
_jwtTokenOptions = jwtTokenOptions;
_signInManager = signInManager;
_emailSender = emailSender;
_localizer = localizer;
_licenseOptions = licenseOptions;
_authOptions = authOptions;
_geoLocationService = geoLocationService;
_messageService = messageService;
_environment = environment;
_memoryCache = memoryCache;
_appleOptions = appleOptions;
_mangoPayService = mangoPayService;
_emailSenderOptions = emailSenderOptions;
_walkService = walkService;
_publicWalkRequestService = publicWalkRequestService;
_publicWalkResponseService = publicWalkResponseService;
_appUserReportService = appUserReportService;
}
/// <summary>
/// Prüfen ob eine E-Mail Adresse noch verfügbar ist
/// </summary>
/// <param name="model">Model mit Benutzername & Passwort</param>
/// <returns>HTTP 200 OK wenn erfolgreich</returns>
[AllowAnonymous]
[HttpPost]
[Route("IsAvailable")]
public async Task<IActionResult> IsAvailable(LoginDto model)
{
var clientOffset = GetClientDateOffset();
if (ModelState.IsValid)
{
var user = await _userService.GetByUsernameAsync(model.UserName);
if (user == null)
{
//Der Benutzername ist noch frei.
//Prüfen ob das Passwort ausreicht
var helpUser = new ApplicationUser();
foreach (var passwordValidator in _userManager.PasswordValidators)
{
var result = await passwordValidator.ValidateAsync(_userManager, helpUser, model.Password);
if (!result.Succeeded)
{
if (result.Errors.FirstOrDefault(c => c.Code == "PwnedPassword") != null)
{
return BadRequest(CommunicationErrors.Register_Password_Pwned);
}
return BadRequest(CommunicationErrors.Register_Password_Rules);
}
}
return Ok();
}
return BadRequest(CommunicationErrors.Register_Email_Exists);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
/// <summary>
/// Prüfen ob ein Passwort gültig ist
/// </summary>
/// <param name="model">Model mit Passwort</param>
/// <returns>HTTP 200 OK wenn erfolgreich</returns>
[AllowAnonymous]
[HttpPost]
[Route("CheckPassword")]
public async Task<IActionResult> CheckPassword(CheckPasswordDto model)
{
var clientOffset = GetClientDateOffset();
if (ModelState.IsValid)
{
var helpUser = new ApplicationUser();
foreach (var passwordValidator in _userManager.PasswordValidators)
{
var result = await passwordValidator.ValidateAsync(_userManager, helpUser, model.Password);
if (!result.Succeeded)
{
if (result.Errors.FirstOrDefault(c => c.Code == "PwnedPassword") != null)
{
return BadRequest(CommunicationErrors.Register_Password_Pwned);
}
return BadRequest(CommunicationErrors.Register_Password_Rules);
}
}
return Ok();
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
/// <summary>
/// Registrieren eines App-Users
/// </summary>
/// <param name="model">Model</param>
/// <returns>HTTP 200 OK wenn erfolgreich</returns>
[AllowAnonymous]
[HttpPost]
[Route("Register")]
public async Task<IActionResult> Register(RegisterDto model)
{
var clientOffset = GetClientDateOffset();
if (ModelState.IsValid)
{
var user = await _userService.GetByUsernameAsync(model.UserName);
if (user == null)
{
user = _userService.Create();
user.Id = Guid.NewGuid().ToString();
user.UserName = model.UserName;
user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.FullName = $"{model.FirstName} {model.LastName}";
user.RegistrationDate = DateTimeOffset.UtcNow;
user.Photo = string.Empty;
user.Email = model.UserName;
user.EmailConfirmed = !_authOptions.Value.MustConfirmEmail;
user.Permissions = PermissionHelper.GetForRole("AppUser").PackPermissionsIntoString();
var createResult = await _userManager.CreateAsync(user, model.Password);
if (createResult.Succeeded)
{
await _userManager.AddToRoleAsync(user, "AppUser");
user = await _userService.GetAsync(user.Id);
//Nun den AppUser anlegen und dann mit dem Benutzer verknüpfen
var appUser = AppUserService.Create();
appUser.Type = (AppUserType)model.AppUserType;
appUser.Number = AppUserService.GetNextNumber();
appUser.FirstName = model.FirstName;
appUser.LastName = model.LastName;
appUser.Sex = Sex.Undefined;
appUser.BirthDate = model.BirthDate;
appUser.Photo = string.Empty;
appUser.Contact.Email = model.UserName;
appUser.Address.CountryCode = model.CountryCode;
appUser.Address.State = model.State;
appUser.TermsAccepted = true;
appUser.TermsAcceptedDate = DateTimeOffset.UtcNow;
appUser.PrivacyAccepted = true;
appUser.PrivacyAcceptedDate = DateTimeOffset.UtcNow;
appUser.Verified = false;
appUser.VerifiedDate = null;
appUser.PaymentTermsAccepted = false;
appUser.PaymentTermsAcceptedDate = null;
appUser.NationalityCode = string.Empty;
appUser.MainResidenceCode = string.Empty;
if (appUser.Type == AppUserType.DogWalker)
{
appUser.PaymentTermsAccepted = true;
appUser.PaymentTermsAcceptedDate = DateTimeOffset.UtcNow;
appUser.NationalityCode = model.NationalityCode;
appUser.MainResidenceCode = model.MainResidenceCountryCode;
}
var location = await _geoLocationService.GetLocationAsync($"{model.CountryCode},{model.State}", CultureInfo.CurrentCulture.TwoLetterISOLanguageName);
if (location.Success)
{
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var geoLocation = geometryFactory.CreatePoint(new Coordinate(location.Longitude, location.Latitude));
appUser.Location = geoLocation;
}
AppUserService.Add(appUser);
await AppUserService.CommitAsync("System");
user.AppUserId = appUser.Id;
await _userService.CommitAsync("System");
if (appUser.Type != AppUserType.DogOwner)
{
await AppUserService.CreateWalkerProfileIfNotExistsAsync(appUser.Id);
await AppUserService.CommitAsync("System");
//Nun für den Walker Mangopay User anlegen
var mangoPayResult = await _mangoPayService.CreateOwnerAsync(appUser.Id, false);
if (mangoPayResult.Success)
{
//Wallets anlegen
var walletCreditsResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Credits);
var walletFeesResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Fees);
}
}
if (_authOptions.Value.MustConfirmEmail)
{
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action("ConfirmEmailApp", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(user.UserName, callbackUrl, _localizer, _licenseOptions);
}
return Ok();
}
else
{
if (createResult.Errors.FirstOrDefault(c => c.Code == "PwnedPassword") != null)
{
return BadRequest(CommunicationErrors.Register_Password_Pwned);
}
if (createResult.Errors.FirstOrDefault(c => c.Code.StartsWith("IdentityError_Password")) != null)
{
return BadRequest(CommunicationErrors.Register_Password_Rules);
}
}
return BadRequest(CommunicationErrors.Register_Failed);
}
return BadRequest(CommunicationErrors.Register_Email_Exists);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
/// <summary>
/// Registrieren eines App-Users
/// </summary>
/// <param name="model">Model</param>
/// <returns>HTTP 200 OK wenn erfolgreich</returns>
[AllowAnonymous]
[HttpPost]
[MapToApiVersion(2)]
[Route("Register")]
public async Task<IActionResult> RegisterV2(RegisterV2Dto model)
{
var clientOffset = GetClientDateOffset();
if (ModelState.IsValid)
{
var user = await _userService.GetByUsernameAsync(model.UserName);
if (user == null)
{
user = _userService.Create();
user.Id = Guid.NewGuid().ToString();
user.UserName = model.UserName;
user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.FullName = $"{model.FirstName} {model.LastName}";
user.RegistrationDate = DateTimeOffset.UtcNow;
user.Photo = string.Empty;
user.Email = model.UserName;
user.EmailConfirmed = !_authOptions.Value.MustConfirmEmail;
user.Permissions = PermissionHelper.GetForRole("AppUser").PackPermissionsIntoString();
var createResult = await _userManager.CreateAsync(user, model.Password);
if (createResult.Succeeded)
{
await _userManager.AddToRoleAsync(user, "AppUser");
user = await _userService.GetAsync(user.Id);
//Nun den AppUser anlegen und dann mit dem Benutzer verknüpfen
var appUser = AppUserService.Create();
appUser.Type = (AppUserType)model.AppUserType;
appUser.Number = AppUserService.GetNextNumber();
appUser.FirstName = model.FirstName;
appUser.LastName = model.LastName;
appUser.Sex = Sex.Undefined;
appUser.BirthDate = model.BirthDate;
appUser.Photo = string.Empty;
appUser.Contact.Email = model.UserName;
appUser.Address.City = model.City;
appUser.Address.Zip = model.Zip;
appUser.Address.CountryCode = model.CountryCode;
appUser.Address.State = model.State;
appUser.TermsAccepted = true;
appUser.TermsAcceptedDate = DateTimeOffset.UtcNow;
appUser.PrivacyAccepted = true;
appUser.PrivacyAcceptedDate = DateTimeOffset.UtcNow;
appUser.Verified = false;
appUser.VerifiedDate = null;
appUser.PaymentTermsAccepted = false;
appUser.PaymentTermsAcceptedDate = null;
appUser.NationalityCode = string.Empty;
appUser.MainResidenceCode = string.Empty;
if (appUser.Type == AppUserType.DogWalker)
{
appUser.PaymentTermsAccepted = true;
appUser.PaymentTermsAcceptedDate = DateTimeOffset.UtcNow;
appUser.NationalityCode = model.NationalityCode;
appUser.MainResidenceCode = model.MainResidenceCountryCode;
}
var location = await _geoLocationService.GetLocationAsync($"{model.Zip},{model.City},{model.CountryCode},{model.State}", CultureInfo.CurrentCulture.TwoLetterISOLanguageName);
if (location.Success)
{
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var geoLocation = geometryFactory.CreatePoint(new Coordinate(location.Longitude, location.Latitude));
appUser.Location = geoLocation;
}
AppUserService.Add(appUser);
await AppUserService.CommitAsync("System");
user.AppUserId = appUser.Id;
await _userService.CommitAsync("System");
if (appUser.Type != AppUserType.DogOwner)
{
await AppUserService.CreateWalkerProfileIfNotExistsAsync(appUser.Id);
await AppUserService.CommitAsync("System");
//Nun für den Walker Mangopay User anlegen
var mangoPayResult = await _mangoPayService.CreateOwnerAsync(appUser.Id, false);
if (mangoPayResult.Success)
{
//Wallets anlegen
var walletCreditsResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Credits);
var walletFeesResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Fees);
}
}
if (_authOptions.Value.MustConfirmEmail)
{
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action("ConfirmEmailApp", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(user.UserName, callbackUrl, _localizer, _licenseOptions);
}
return Ok();
}
else
{
if (createResult.Errors.FirstOrDefault(c => c.Code == "PwnedPassword") != null)
{
return BadRequest(CommunicationErrors.Register_Password_Pwned);
}
if (createResult.Errors.FirstOrDefault(c => c.Code.StartsWith("IdentityError_Password")) != null)
{
return BadRequest(CommunicationErrors.Register_Password_Rules);
}
}
return BadRequest(CommunicationErrors.Register_Failed);
}
return BadRequest(CommunicationErrors.Register_Email_Exists);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
/// <summary>
/// Registrieren eines App-Users mittels external Provider
/// </summary>
/// <param name="model">Model</param>
/// <returns>HTTP 200 OK wenn erfolgreich</returns>
[AllowAnonymous]
[HttpPost]
[Route("RegisterExternal")]
public async Task<IActionResult> RegisterExternal(RegisterExternalDto model)
{
var clientOffset = GetClientDateOffset();
if (ModelState.IsValid)
{
//Zuerst holen wir nochmal die Infos für den User. Token prüfen... //TODO: Je nach Provider vorgehen. Derzeit nur google. Apple einführen...
var tokenValid = false;
ExternalLoginGoogleResponse userInfo = null;
AppleResponseDto appleResponse = null;
if (model.LoginProvider == "Google")
{
HttpStatusCode tokenResponseCode = HttpStatusCode.OK;
var httpClient = new HttpClient();
try
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", model.AccessToken);
var tokenResult = await httpClient.GetAsync($"https://www.googleapis.com/oauth2/v3/userinfo");
if (tokenResult.IsSuccessStatusCode)
{
string jsonString = tokenResult.Content.ReadAsStringAsync().Result;
userInfo = JsonSerializer.Deserialize<ExternalLoginGoogleResponse>(jsonString);
tokenValid = true;
}
else
{
tokenResponseCode = tokenResult.StatusCode;
}
}
catch (Exception ex)
{
tokenValid = false;
}
}
else if (model.LoginProvider == "Apple")
{
var clientSecret = await GenerateAppleClientSecretAsync();
var parameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", _appleOptions.Value.ClientId),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("refresh_token", model.AccessToken),
new KeyValuePair<string, string>("grant_type", "refresh_token")
};
var httpClient = new HttpClient();
try
{
var tokenResult = await httpClient.PostAsync($"https://appleid.apple.com/auth/token", new FormUrlEncodedContent(parameters));
if (tokenResult.IsSuccessStatusCode)
{
var successResponse = await tokenResult.Content.ReadAsStringAsync();
appleResponse = JsonSerializer.Deserialize<AppleResponseDto>(successResponse);
tokenValid = true;
}
else
{
var errorResponse = await tokenResult.Content.ReadAsStringAsync();
_logger.LogError(errorResponse);
}
}
catch (Exception ex)
{
tokenValid = false;
}
}
if (tokenValid)
{
var user = await _userService.GetByUsernameAsync(model.UserName);
if (user == null)
{
user = _userService.Create();
user.Id = Guid.NewGuid().ToString();
user.UserName = model.UserName;
user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.FullName = $"{model.FirstName} {model.LastName}";
user.RegistrationDate = DateTimeOffset.UtcNow;
user.Photo = string.Empty;
user.Email = model.UserName;
user.EmailConfirmed = true; //Weil über externen Provider
user.Permissions = PermissionHelper.GetForRole("AppUser").PackPermissionsIntoString();
IdentityResult createResult;
if (!string.IsNullOrEmpty(model.Password))
createResult = await _userManager.CreateAsync(user, model.Password);
else
createResult = await _userManager.CreateAsync(user);
if (createResult.Succeeded)
{
await _userManager.AddToRoleAsync(user, "AppUser");
user = await _userManager.FindByEmailAsync(user.UserName);
//Nun den AppUser anlegen und dann mit dem Benutzer verknüpfen
var appUser = AppUserService.Create();
appUser.Type = (AppUserType)model.AppUserType;
appUser.Number = AppUserService.GetNextNumber();
appUser.FirstName = model.FirstName;
appUser.LastName = model.LastName;
appUser.Sex = Sex.Undefined;
appUser.BirthDate = model.BirthDate;
appUser.Photo = string.Empty;
appUser.Contact.Email = model.UserName;
appUser.Address.CountryCode = model.CountryCode;
appUser.Address.State = model.State;
appUser.TermsAccepted = true;
appUser.TermsAcceptedDate = DateTimeOffset.UtcNow;
appUser.PrivacyAccepted = true;
appUser.PrivacyAcceptedDate = DateTimeOffset.UtcNow;
appUser.Verified = false;
appUser.VerifiedDate = null;
appUser.PaymentTermsAccepted = false;
appUser.PaymentTermsAcceptedDate = null;
appUser.NationalityCode = string.Empty;
appUser.MainResidenceCode = string.Empty;
if (appUser.Type == AppUserType.DogWalker)
{
appUser.PaymentTermsAccepted = true;
appUser.PaymentTermsAcceptedDate = DateTimeOffset.UtcNow;
appUser.NationalityCode = model.NationalityCode;
appUser.MainResidenceCode = model.MainResidenceCountryCode;
}
var location = await _geoLocationService.GetLocationAsync($"{model.CountryCode},{model.State}", CultureInfo.CurrentCulture.TwoLetterISOLanguageName);
if (location.Success)
{
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var geoLocation = geometryFactory.CreatePoint(new Coordinate(location.Longitude, location.Latitude));
appUser.Location = geoLocation;
}
AppUserService.Add(appUser);
await AppUserService.CommitAsync("System");
user.AppUserId = appUser.Id;
await _userManager.UpdateAsync(user);
if (appUser.Type != AppUserType.DogOwner)
{
await AppUserService.CreateWalkerProfileIfNotExistsAsync(appUser.Id);
await AppUserService.CommitAsync("System");
//Nun für den Walker Mangopay User anlegen
var mangoPayResult = await _mangoPayService.CreateOwnerAsync(appUser.Id, false);
if (mangoPayResult.Success)
{
//Wallets anlegen
var walletCreditsResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Credits);
var walletFeesResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Fees);
}
}
//Da wir den User gleich einloggen möchten, geben wir alles nötige zurück
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 ?? ""),
new Claim(ClaimConstants.AppUserIdClaimType, user.AppUserId ?? ""),
};
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<UserDto>(user);
userDto.AccessToken = token;
userDto.AccessTokenExpires = DateTimeOffset.UtcNow.AddMinutes(_jwtTokenOptions.Value.TokenMinutes);
userDto.RefreshToken = refreshToken.Token;
userDto.RefreshTokenExpires = refreshToken.Expires;
userDto.AppUserType = (AppUserTypeDto)appUser.Type;
var roleList = roles.ToList();
userDto.Roles = string.Join(";", roleList);
user.LastLoginDate = DateTimeOffset.UtcNow;
await _userManager.UpdateAsync(user);
if (model.LoginProvider == "Google" && userInfo != null)
{
var createdUser = await _userManager.FindByEmailAsync(user.UserName);
if (createdUser != null)
{
await _userManager.AddLoginAsync(createdUser, new UserLoginInfo("Google", userInfo.Sub, user.FullName));
}
}
else if (model.LoginProvider == "Apple" && appleResponse != null)
{
var createdUser = await _userManager.FindByEmailAsync(user.UserName);
if (createdUser != null)
{
var appleToken = new JwtSecurityToken(appleResponse.IdToken);
if (appleToken != null)
{
var appleUserId = appleToken.Claims.First(c => c.Type == "sub").Value;
await _userManager.AddLoginAsync(createdUser, new UserLoginInfo("Apple", appleUserId, user.FullName));
}
}
}
return Ok(userDto);
}
else
{
if (createResult.Errors.FirstOrDefault(c => c.Code == "PwnedPassword") != null)
{
return BadRequest(CommunicationErrors.Register_Password_Pwned);
}
if (createResult.Errors.FirstOrDefault(c => c.Code.StartsWith("Password")) != null)
{
return BadRequest(CommunicationErrors.Register_Password_Rules);
}
}
return BadRequest(CommunicationErrors.Register_Failed);
}
return BadRequest(CommunicationErrors.Register_Email_Exists);
}
return BadRequest(CommunicationErrors.Register_TokenInvalid);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
/// <summary>
/// Registrieren eines App-Users mittels external Provider
/// </summary>
/// <param name="model">Model</param>
/// <returns>HTTP 200 OK wenn erfolgreich</returns>
[AllowAnonymous]
[HttpPost]
[MapToApiVersion(2)]
[Route("RegisterExternal")]
public async Task<IActionResult> RegisterExternalV2(RegisterExternalV2Dto model)
{
var clientOffset = GetClientDateOffset();
if (ModelState.IsValid)
{
//Zuerst holen wir nochmal die Infos für den User. Token prüfen... //TODO: Je nach Provider vorgehen. Derzeit nur google. Apple einführen...
var tokenValid = false;
ExternalLoginGoogleResponse userInfo = null;
AppleResponseDto appleResponse = null;
if (model.LoginProvider == "Google")
{
HttpStatusCode tokenResponseCode = HttpStatusCode.OK;
var httpClient = new HttpClient();
try
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", model.AccessToken);
var tokenResult = await httpClient.GetAsync($"https://www.googleapis.com/oauth2/v3/userinfo");
if (tokenResult.IsSuccessStatusCode)
{
string jsonString = tokenResult.Content.ReadAsStringAsync().Result;
userInfo = JsonSerializer.Deserialize<ExternalLoginGoogleResponse>(jsonString);
tokenValid = true;
}
else
{
tokenResponseCode = tokenResult.StatusCode;
}
}
catch (Exception ex)
{
tokenValid = false;
}
}
else if (model.LoginProvider == "Apple")
{
var clientSecret = await GenerateAppleClientSecretAsync();
var parameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", _appleOptions.Value.ClientId),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("refresh_token", model.AccessToken),
new KeyValuePair<string, string>("grant_type", "refresh_token")
};
var httpClient = new HttpClient();
try
{
var tokenResult = await httpClient.PostAsync($"https://appleid.apple.com/auth/token", new FormUrlEncodedContent(parameters));
if (tokenResult.IsSuccessStatusCode)
{
var successResponse = await tokenResult.Content.ReadAsStringAsync();
appleResponse = JsonSerializer.Deserialize<AppleResponseDto>(successResponse);
tokenValid = true;
}
else
{
var errorResponse = await tokenResult.Content.ReadAsStringAsync();
_logger.LogError(errorResponse);
}
}
catch (Exception ex)
{
tokenValid = false;
}
}
if (tokenValid)
{
var user = await _userService.GetByUsernameAsync(model.UserName);
if (user == null)
{
user = _userService.Create();
user.Id = Guid.NewGuid().ToString();
user.UserName = model.UserName;
user.FirstName = model.FirstName;
user.LastName = model.LastName;
user.FullName = $"{model.FirstName} {model.LastName}";
user.RegistrationDate = DateTimeOffset.UtcNow;
user.Photo = string.Empty;
user.Email = model.UserName;
user.EmailConfirmed = true; //Weil über externen Provider
user.Permissions = PermissionHelper.GetForRole("AppUser").PackPermissionsIntoString();
IdentityResult createResult;
if (!string.IsNullOrEmpty(model.Password))
createResult = await _userManager.CreateAsync(user, model.Password);
else
createResult = await _userManager.CreateAsync(user);
if (createResult.Succeeded)
{
await _userManager.AddToRoleAsync(user, "AppUser");
user = await _userManager.FindByEmailAsync(user.UserName);
//Nun den AppUser anlegen und dann mit dem Benutzer verknüpfen
var appUser = AppUserService.Create();
appUser.Type = (AppUserType)model.AppUserType;
appUser.Number = AppUserService.GetNextNumber();
appUser.FirstName = model.FirstName;
appUser.LastName = model.LastName;
appUser.Sex = Sex.Undefined;
appUser.BirthDate = model.BirthDate;
appUser.Photo = string.Empty;
appUser.Contact.Email = model.UserName;
appUser.Address.City = model.City;
appUser.Address.Zip = model.Zip;
appUser.Address.CountryCode = model.CountryCode;
appUser.Address.State = model.State;
appUser.TermsAccepted = true;
appUser.TermsAcceptedDate = DateTimeOffset.UtcNow;
appUser.PrivacyAccepted = true;
appUser.PrivacyAcceptedDate = DateTimeOffset.UtcNow;
appUser.Verified = false;
appUser.VerifiedDate = null;
appUser.PaymentTermsAccepted = false;
appUser.PaymentTermsAcceptedDate = null;
appUser.NationalityCode = string.Empty;
appUser.MainResidenceCode = string.Empty;
if (appUser.Type == AppUserType.DogWalker)
{
appUser.PaymentTermsAccepted = true;
appUser.PaymentTermsAcceptedDate = DateTimeOffset.UtcNow;
appUser.NationalityCode = model.NationalityCode;
appUser.MainResidenceCode = model.MainResidenceCountryCode;
}
var location = await _geoLocationService.GetLocationAsync($"{model.Zip},{model.City},{model.CountryCode},{model.State}", CultureInfo.CurrentCulture.TwoLetterISOLanguageName);
if (location.Success)
{
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var geoLocation = geometryFactory.CreatePoint(new Coordinate(location.Longitude, location.Latitude));
appUser.Location = geoLocation;
}
AppUserService.Add(appUser);
await AppUserService.CommitAsync("System");
user.AppUserId = appUser.Id;
await _userManager.UpdateAsync(user);
if (appUser.Type != AppUserType.DogOwner)
{
await AppUserService.CreateWalkerProfileIfNotExistsAsync(appUser.Id);
await AppUserService.CommitAsync("System");
//Nun für den Walker Mangopay User anlegen
var mangoPayResult = await _mangoPayService.CreateOwnerAsync(appUser.Id, false);
if (mangoPayResult.Success)
{
//Wallets anlegen
var walletCreditsResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Credits);
var walletFeesResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Fees);
}
}
//Da wir den User gleich einloggen möchten, geben wir alles nötige zurück
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 ?? ""),
new Claim(ClaimConstants.AppUserIdClaimType, user.AppUserId ?? ""),
};
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<UserDto>(user);
userDto.AccessToken = token;
userDto.AccessTokenExpires = DateTimeOffset.UtcNow.AddMinutes(_jwtTokenOptions.Value.TokenMinutes);
userDto.RefreshToken = refreshToken.Token;
userDto.RefreshTokenExpires = refreshToken.Expires;
userDto.AppUserType = (AppUserTypeDto)appUser.Type;
var roleList = roles.ToList();
userDto.Roles = string.Join(";", roleList);
user.LastLoginDate = DateTimeOffset.UtcNow;
await _userManager.UpdateAsync(user);
if (model.LoginProvider == "Google" && userInfo != null)
{
var createdUser = await _userManager.FindByEmailAsync(user.UserName);
if (createdUser != null)
{
await _userManager.AddLoginAsync(createdUser, new UserLoginInfo("Google", userInfo.Sub, user.FullName));
}
}
else if (model.LoginProvider == "Apple" && appleResponse != null)
{
var createdUser = await _userManager.FindByEmailAsync(user.UserName);
if (createdUser != null)
{
var appleToken = new JwtSecurityToken(appleResponse.IdToken);
if (appleToken != null)
{
var appleUserId = appleToken.Claims.First(c => c.Type == "sub").Value;
await _userManager.AddLoginAsync(createdUser, new UserLoginInfo("Apple", appleUserId, user.FullName));
}
}
}
return Ok(userDto);
}
else
{
if (createResult.Errors.FirstOrDefault(c => c.Code == "PwnedPassword") != null)
{
return BadRequest(CommunicationErrors.Register_Password_Pwned);
}
if (createResult.Errors.FirstOrDefault(c => c.Code.StartsWith("Password")) != null)
{
return BadRequest(CommunicationErrors.Register_Password_Rules);
}
}
return BadRequest(CommunicationErrors.Register_Failed);
}
return BadRequest(CommunicationErrors.Register_Email_Exists);
}
return BadRequest(CommunicationErrors.Register_TokenInvalid);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
/// <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))
{
if (!string.IsNullOrWhiteSpace(user.AppUserId))
{
var appUser = await AppUserService.GetAsync(user.AppUserId);
if (appUser != null)
{
if (appUser.Locked && appUser.LockedUntil < DateTimeOffset.UtcNow)
{
appUser.Locked = false;
appUser.LockedUntil = null;
await AppUserService.CommitAsync("System");
}
if (!appUser.Locked)
{
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 ?? ""),
new Claim(ClaimConstants.AppUserIdClaimType, user.AppUserId ?? ""),
};
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<UserDto>(user);
userDto.AccessToken = token;
userDto.AccessTokenExpires = DateTimeOffset.UtcNow.AddMinutes(_jwtTokenOptions.Value.TokenMinutes);
userDto.RefreshToken = refreshToken.Token;
userDto.RefreshTokenExpires = refreshToken.Expires;
userDto.AppUserType = (AppUserTypeDto)appUser.Type;
var roleList = roles.ToList();
userDto.Roles = string.Join(";", roleList);
if (!string.IsNullOrWhiteSpace(user.Photo))
{
var photo = await FileService.GetAsync(FileServiceHelper.DocumentContainer, user.Photo);
//if (photo != null)
// userDto.PhotoBase64 = Convert.ToBase64String(photo);
}
user.LastLoginDate = DateTimeOffset.UtcNow;
await _userManager.UpdateAsync(user);
//await _userService.CommitAsync(user.UserName);
return Ok(userDto);
}
else
{
return Unauthorized(CommunicationErrors.Login_LockedOut);
}
}
}
else
{
return BadRequest(CommunicationErrors.Login_Assignment_Missing);
}
}
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>
/// Anmelden mit external Provider von der App.
/// Die App hat erfolgreich ein AccessToken erhalten, daher wissen wir wer der Benutzer ist.
/// Nun einen Login durchführen ohne Prüfung des Passwortes
/// </summary>
/// <returns>Token und Refreshtoken oder BadRequest wenn nicht erfolgreich</returns>
[HttpGet]
[Route("LoginExternal")]
public async Task<IActionResult> LoginExternal()
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userManager.FindByNameAsync(User.Identity.Name);
if (user != null)
{
if (await _userManager.IsLockedOutAsync(user) == false)
{
if (await _userManager.IsEmailConfirmedAsync(user))
{
if (!string.IsNullOrWhiteSpace(user.AppUserId))
{
var appUser = await AppUserService.GetAsync(user.AppUserId);
if (appUser != null)
{
if (appUser.Locked && appUser.LockedUntil < DateTimeOffset.UtcNow)
{
appUser.Locked = false;
appUser.LockedUntil = null;
await AppUserService.CommitAsync("System");
}
if (!appUser.Locked)
{
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 ?? ""),
new Claim(ClaimConstants.AppUserIdClaimType, user.AppUserId ?? ""),
};
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<UserDto>(user);
userDto.AccessToken = token;
userDto.AccessTokenExpires = DateTimeOffset.UtcNow.AddMinutes(_jwtTokenOptions.Value.TokenMinutes);
userDto.RefreshToken = refreshToken.Token;
userDto.RefreshTokenExpires = refreshToken.Expires;
userDto.AppUserType = (AppUserTypeDto)appUser.Type;
var roleList = roles.ToList();
userDto.Roles = string.Join(";", roleList);
if (!string.IsNullOrWhiteSpace(user.Photo))
{
var photo = await FileService.GetAsync(FileServiceHelper.DocumentContainer, user.Photo);
//if (photo != null)
// userDto.PhotoBase64 = Convert.ToBase64String(photo);
}
user.LastLoginDate = DateTimeOffset.UtcNow;
await _userManager.UpdateAsync(user);
//await _userService.CommitAsync(user.UserName);
return Ok(userDto);
}
else
{
return Unauthorized(CommunicationErrors.Login_LockedOut);
}
}
}
else
{
return BadRequest(CommunicationErrors.Login_Assignment_Missing);
}
}
return BadRequest(CommunicationErrors.Login_Email_NotConfirmed);
}
return Unauthorized(CommunicationErrors.Login_LockedOut);
}
return NotFound(CommunicationErrors.Common_NotFound);
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Anmelden mit Apple
/// </summary>
/// <param name="model">Model mit notwendigen Daten von Apple</param>
/// <returns>Token und Refreshtoken oder BadRequest wenn nicht erfolgreich</returns>
[AllowAnonymous]
[HttpPost]
[Route("LoginApple")]
public async Task<IActionResult> LoginApple(LoginAppleDto model)
{
var clientOffset = GetClientDateOffset();
if (ModelState.IsValid)
{
//Daten gegen Apple validieren...
var tokenValid = false;
var emailToUse = model.Email;
AppleResponseDto tokenResponse = null;
var clientSecret = await GenerateAppleClientSecretAsync();
var parameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("client_id", _appleOptions.Value.ClientId),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("code", model.AuthCode),
new KeyValuePair<string, string>("grant_type", "authorization_code")
};
var httpClient = new HttpClient();
try
{
var tokenResult = await httpClient.PostAsync($"https://appleid.apple.com/auth/token", new FormUrlEncodedContent(parameters));
if (tokenResult.IsSuccessStatusCode)
{
var successResponse = await tokenResult.Content.ReadAsStringAsync();
tokenResponse = JsonSerializer.Deserialize<AppleResponseDto>(successResponse);
tokenValid = true;
}
else
{
var errorResponse = await tokenResult.Content.ReadAsStringAsync();
_logger.LogError(errorResponse);
}
}
catch (Exception ex)
{
tokenValid = false;
}
if (tokenValid)
{
var user = await _userManager.FindByLoginAsync("Apple", model.UserId);
if (user == null && !string.IsNullOrWhiteSpace(emailToUse))
{
user = await _userManager.FindByEmailAsync(emailToUse);
}
if (user != null)
{
if (await _userManager.IsLockedOutAsync(user) == false)
{
if (await _userManager.IsEmailConfirmedAsync(user))
{
if (!string.IsNullOrWhiteSpace(user.AppUserId))
{
var appUser = await AppUserService.GetAsync(user.AppUserId);
if (appUser != null)
{
if (appUser.Locked && appUser.LockedUntil < DateTimeOffset.UtcNow)
{
appUser.Locked = false;
appUser.LockedUntil = null;
await AppUserService.CommitAsync("System");
}
if (!appUser.Locked)
{
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 ?? ""),
new Claim(ClaimConstants.AppUserIdClaimType, user.AppUserId ?? ""),
};
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<UserDto>(user);
userDto.AccessToken = token;
userDto.AccessTokenExpires = DateTimeOffset.UtcNow.AddMinutes(_jwtTokenOptions.Value.TokenMinutes);
userDto.RefreshToken = refreshToken.Token;
userDto.RefreshTokenExpires = refreshToken.Expires;
userDto.AppUserType = (AppUserTypeDto)appUser.Type;
var roleList = roles.ToList();
userDto.Roles = string.Join(";", roleList);
if (!string.IsNullOrWhiteSpace(user.Photo))
{
var photo = await FileService.GetAsync(FileServiceHelper.DocumentContainer, user.Photo);
//if (photo != null)
// userDto.PhotoBase64 = Convert.ToBase64String(photo);
}
user.LastLoginDate = DateTimeOffset.UtcNow;
await _userManager.UpdateAsync(user);
//await _userService.CommitAsync(user.UserName);
var externalLogins = await _userManager.GetLoginsAsync(user);
if (externalLogins?.FirstOrDefault(c => c.LoginProvider == "Apple") == null)
{
await _userManager.AddLoginAsync(user, new UserLoginInfo("Apple", model.UserId, user.FullName));
}
return Ok(userDto);
}
else
{
return Unauthorized(CommunicationErrors.Login_LockedOut);
}
}
else
{
return BadRequest(CommunicationErrors.Login_Assignment_Missing);
}
}
else
{
return BadRequest(CommunicationErrors.Login_Assignment_Missing);
}
}
return BadRequest(CommunicationErrors.Login_Email_NotConfirmed);
}
return Unauthorized(CommunicationErrors.Login_LockedOut);
}
return NotFound(JsonSerializer.Serialize(tokenResponse)); //Wir müssen hier tricksen, damit das Refreshtoken zur App übertragen wird
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
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>
/// Zurücksetzen des Passworts für einen App-Benutzer
/// </summary>
/// <param name="email">Benutzername / E-Mail Adresse des Benutzers</param>
/// <returns>HTTP 200 - immer!</returns>
[AllowAnonymous]
[HttpPost]
[Route("ResetPassword")]
public async Task<IActionResult> ResetPassword([FromBody] string email)
{
var clientOffset = GetClientDateOffset();
try
{
var user = await _signInManager.UserManager.FindByNameAsync(email);
if (user != null && (await _signInManager.UserManager.IsEmailConfirmedAsync(user)))
{
var roles = await _userManager.GetRolesAsync(user);
if (roles.Contains("AppUser"))
{
var token = await _signInManager.UserManager.GeneratePasswordResetTokenAsync(user);
var callbackUrl = Url.ResetPasswordCallbackLinkApp(user.Id, token, Request.Scheme);
await _emailSender.SendPasswordResetAppAsync(email, callbackUrl, _localizer, _licenseOptions);
}
}
}
catch { }
return Ok();
}
/// <summary>
/// Senden der E-Mail Bestätigung für einen App-Benutzer
/// </summary>
/// <param name="email">Benutzername / E-Mail Adresse des Benutzers</param>
/// <returns>HTTP 200 - immer!</returns>
[AllowAnonymous]
[HttpPost]
[Route("SendConfirmation")]
public async Task<IActionResult> SendConfirmation([FromBody] string email)
{
var clientOffset = GetClientDateOffset();
try
{
var user = await _signInManager.UserManager.FindByNameAsync(email);
if (user != null && (await _signInManager.UserManager.IsEmailConfirmedAsync(user)) == false)
{
var roles = await _userManager.GetRolesAsync(user);
if (roles.Contains("AppUser"))
{
var code = await _signInManager.UserManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.Action("ConfirmEmailApp", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme);
await _emailSender.SendEmailConfirmationAppAsync(email, callbackUrl, _localizer, _licenseOptions);
}
}
}
catch { }
return Ok();
}
/// <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 ?? ""),
new Claim(ClaimConstants.AppUserIdClaimType, user.AppUserId ?? ""),
};
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);
}
/// <summary>
/// Prüfen ob der angemeldete Benutzer ein Passwort hat oder nicht.
/// Das kann bei externen Providern vorkommen
/// </summary>
/// <returns>HTTP 200 OK</returns>
[HttpGet]
[Route("HasPassword")]
public async Task<IActionResult> HasPassword()
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
if (user != null)
{
var hasPassword = await _userManager.HasPasswordAsync(user);
return Ok(hasPassword);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Hinzufügen eines PAsswortes zu einem Benutzer wenn dieser keines har
/// </summary>
/// <param name="model">Model mit Passwort</param>
/// <returns>HTTP 200 OK</returns>
[HttpPost]
[Route("AddPassword")]
public async Task<IActionResult> AddPassword(AddPasswordDto model)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
if (user != null && user.UserName.ToLower() == model.UserName.ToLower())
{
var hasPassword = await _userManager.HasPasswordAsync(user);
if (!hasPassword)
{
var addPasswordResult = await _userManager.AddPasswordAsync(user, model.Password);
if (addPasswordResult.Succeeded)
{
return Ok(true);
}
else
{
if (addPasswordResult.Errors.FirstOrDefault(c => c.Code == "PwnedPassword") != null)
{
return BadRequest(CommunicationErrors.Register_Password_Pwned);
}
if (addPasswordResult.Errors.FirstOrDefault(c => c.Code.StartsWith("IdentityError_Password")) != null)
{
return BadRequest(CommunicationErrors.Register_Password_Rules);
}
}
}
return Ok(false);
}
return NotFound(CommunicationErrors.Common_NotFound);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Ändern des Passwortes eines Benutzers
/// </summary>
/// <param name="model">Model mit Passwort</param>
/// <returns>HTTP 200 OK</returns>
[HttpPost]
[Route("ChangePassword")]
public async Task<IActionResult> ChangePassword(ChangePasswordDto model)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
if (user != null && user.UserName.ToLower() == model.UserName.ToLower())
{
var changeResult = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.Password);
if (changeResult.Succeeded)
{
return Ok(true);
}
else
{
if (changeResult.Errors.FirstOrDefault(c => c.Code == "PwnedPassword") != null)
{
return BadRequest(CommunicationErrors.Register_Password_Pwned);
}
if (changeResult.Errors.FirstOrDefault(c => c.Code.StartsWith("IdentityError_PasswordMismatch")) != null)
{
return BadRequest(CommunicationErrors.Register_Password_Mismatch);
}
if (changeResult.Errors.FirstOrDefault(c => c.Code.StartsWith("IdentityError_Password")) != null)
{
return BadRequest(CommunicationErrors.Register_Password_Rules);
}
}
return Ok(false);
}
return NotFound(CommunicationErrors.Common_NotFound);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Löschen eines Accounts, Sperren des Benutzers und Ausloggen
/// </summary>
/// <returns>HTTP 200 wenn erfolgreich</returns>
[HttpPost]
[Route("DeleteAccount")]
public async Task<IActionResult> DeleteAccount(DeleteAccountDto model)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
if (ModelState.IsValid)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (user.Id == model.UserId && user.Email == model.UserName)
{
//Sperren des Benutzers
user.LockoutEnd = DateTimeOffset.UtcNow.AddYears(5);
await _userService.CommitAsync(user.UserName);
//Löschen aller Refresh-tokens des Benutzers da eine Abmeldung erfolgt ist
await _refreshTokenService.RemoveAllAsync(user.Id, user.UserName);
await _refreshTokenService.CommitAsync(user.UserName);
var appUser = await AppUserService.GetAsync(user.AppUserId);
if (appUser != null)
{
appUser.Locked = true;
appUser.LockedReason = "Delete account requested";
appUser.LockedUntil = DateTimeOffset.UtcNow.AddYears(5);
await AppUserService.CommitAsync(user.UserName);
}
//Nun Email an gehgassi senden, dass der Benutzer gelöscht werden soll
await _emailSender.SendDeleteAccountEmailAsync(user.Email, user.FirstName, user.LastName, _localizer, _emailSenderOptions);
return Ok(true);
}
return NotFound(CommunicationErrors.Common_NotFound);
}
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
return NotFound(CommunicationErrors.Common_NotFound);
}
#region AppUser
/// <summary>
/// Gibt den App-User zurück
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("GetAppUser")]
public async Task<IActionResult> GetAppUser()
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (!string.IsNullOrWhiteSpace(user.AppUserId))
{
var appUser = await AppUserService.GetAsync(user.AppUserId);
if (appUser != null)
{
var appUserDto = Mapper.Map<AppUserDto>(appUser);
var baseAddress = GetBaseAddress();
if (!string.IsNullOrWhiteSpace(appUserDto.Photo))
{
appUserDto.Photo = $"{baseAddress}/file/documents/thumbnails/{200}/{appUserDto.Photo}";
}
return Ok(appUserDto);
}
}
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Gibt den App-User für die Background Sync zurück
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("GetAppUserSync")]
public async Task<IActionResult> GetAppUserSync(DateTimeOffset? lastUpdate)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (!string.IsNullOrWhiteSpace(user.AppUserId))
{
var appUser = await AppUserService.GetAsync(user.AppUserId);
if (appUser != null && appUser.UpdatedAt > lastUpdate)
{
var appUserDto = Mapper.Map<AppUserDto>(appUser);
var baseAddress = GetBaseAddress();
if (!string.IsNullOrWhiteSpace(appUserDto.Photo))
{
appUserDto.Photo = $"{baseAddress}/file/documents/thumbnails/{200}/{appUserDto.Photo}";
}
return Ok(appUserDto);
}
return NoContent();
}
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Aktualisieren eines App-Users
/// </summary>
/// <param name="model">AppUserDto</param>
/// <param name="photoUpdateFile">Optional Foto</param>
/// <returns></returns>
[HttpPost]
[Route("Update")]
public async Task<IActionResult> Update([ModelBinder(BinderType = typeof(JsonModelBinder))] AppUserDto model, IFormFile photoUpdateFile)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (ModelState.IsValid)
{
var appUser = await AppUserService.GetAsync(model.Id);
if (appUser != null)
{
if (model.UpdatedAt > appUser.UpdatedAt)
{
var photoChanged = false;
var oldPhoto = appUser.Photo;
Mapper.Map(model, appUser);
if (string.IsNullOrWhiteSpace(model.Photo) || model.Photo.StartsWithHttp())
{
//Altes Foto behalten wenn eines als Link kommt
appUser.Photo = oldPhoto;
}
try
{
if (photoUpdateFile != null && !string.IsNullOrEmpty(photoUpdateFile.FileName))
{
//Wenn es ein altes Foto gibt, dieses löschen
if (!string.IsNullOrWhiteSpace(oldPhoto))
{
await FileService.DeleteAsync(FileServiceHelper.DocumentContainer, oldPhoto);
await RemoveThumbnail(FileServiceHelper.DocumentContainer, oldPhoto, 400);
await RemoveThumbnail(FileServiceHelper.DocumentContainer, oldPhoto, 200);
await RemoveThumbnail(FileServiceHelper.DocumentContainer, oldPhoto, 100);
}
var extension = Path.GetExtension(photoUpdateFile.FileName);
var filenameToUse = FileServiceHelper.GetAppUserPath(appUser.Id) + $"photo-{Guid.NewGuid():N}{extension}";
using var memoryStream = new MemoryStream();
await photoUpdateFile.CopyToAsync(memoryStream);
memoryStream.Position = 0;
using var img = await Image.LoadAsync(memoryStream);
img.Mutate(x => x.Resize(new ResizeOptions() { Mode = ResizeMode.Crop, Size = new Size(800) }));
var format = img.DetectEncoder(photoUpdateFile.FileName);
await using var memStream = new MemoryStream();
await img.SaveAsync(memStream, format);
memStream.Position = 0;
await FileService.StoreAsync(FileServiceHelper.DocumentContainer, filenameToUse, memStream);
appUser.Photo = filenameToUse;
//Thumbnails erstellten
await GenerateThumbnailByWidth(FileServiceHelper.DocumentContainer, filenameToUse, 400);
await GenerateThumbnailByWidth(FileServiceHelper.DocumentContainer, filenameToUse, 200);
await GenerateThumbnailByWidth(FileServiceHelper.DocumentContainer, filenameToUse, 100);
photoChanged = true;
}
}
catch (Exception ex)
{
}
//Wenn beim Update keine Koordinaten übergeben werden, dann basierend auf der Adresse versuchen
if (model.Lat == 0 && model.Lng == 0)
{
var location = await _geoLocationService.GetLocationAsync(appUser.Address, CultureInfo.CurrentCulture.TwoLetterISOLanguageName);
if (location.Success)
{
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
var geoLocation = geometryFactory.CreatePoint(new Coordinate(location.Longitude, location.Latitude));
appUser.Location = geoLocation;
}
}
await AppUserService.CommitAsync(User.Identity.Name);
user.FirstName = appUser.FirstName;
user.LastName = appUser.LastName;
user.FullName = $"{appUser.FirstName} {appUser.LastName}";
user.Photo = appUser.Photo;
await _userService.CommitAsync(User.Identity.Name);
if (photoChanged)
{
//Update-Datum der Konversationen des Benutzers neu setzen, damit das Foto neu geholt wird...
await _messageService.SetUpdatedAtAsync(appUser.Id, DateTimeOffset.UtcNow, User.Identity.Name);
}
if (!string.IsNullOrWhiteSpace(appUser.PaymentId) && appUser.PaymentTermsAccepted)
{
//Wenn schon eine MangopayId vorhanden ist, dann auch das Mangopay-Profil aktualisieren
var mangoUpdateResult = await _mangoPayService.UpdateOwnerAsync(appUser.Id);
}
}
return Ok();
}
return NotFound(CommunicationErrors.Common_NotFound);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Setzt den Modus eines App-Users auf "Beides"
/// </summary>
/// <returns>200 OK</returns>
[HttpPost]
[Route("SetAppUserType")]
public async Task<IActionResult> SetAppUserType(SetAppUserTypeDto model)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (ModelState.IsValid)
{
var appUser = await AppUserService.GetAsync(model.Id);
if (appUser != null)
{
if (model.UpdatedAt > appUser.UpdatedAt)
{
appUser.Type = (AppUserType)model.Type;
appUser.UpdatedAt = model.UpdatedAt;
await AppUserService.CommitAsync(User.Identity.Name);
if (appUser.Type != AppUserType.DogOwner)
{
await AppUserService.CreateWalkerProfileIfNotExistsAsync(appUser.Id);
await AppUserService.CommitAsync(User.Identity.Name);
}
return Ok();
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Setzt den Modus eines App-Users auf "Beides" - von Owner aufgerufen.
/// Erstellt ebenso einen MangoPay User und legt die Wallets an
/// </summary>
/// <returns>200 OK</returns>
[HttpPost]
[Route("SetAppUserTypeEx")]
public async Task<IActionResult> SetAppUserTypeEx(SetAppUserTypeExDto model)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (ModelState.IsValid)
{
var appUser = await AppUserService.GetAsync(model.Id);
if (appUser != null)
{
if (model.UpdatedAt > appUser.UpdatedAt)
{
appUser.Type = (AppUserType)model.Type;
appUser.NationalityCode = model.NationalityCode;
appUser.MainResidenceCode = model.MainResidenceCode;
appUser.PaymentTermsAccepted = model.PaymentTermsAccepted;
appUser.PaymentTermsAcceptedDate = model.PaymentTermsAcceptedDate;
appUser.UpdatedAt = model.UpdatedAt;
await AppUserService.CommitAsync(User.Identity.Name);
if (appUser.Type != AppUserType.DogOwner)
{
await AppUserService.CreateWalkerProfileIfNotExistsAsync(appUser.Id);
await AppUserService.CommitAsync(User.Identity.Name);
//Nun für den Walker Mangopay User anlegen
var mangoPayResult = await _mangoPayService.CreateOwnerAsync(appUser.Id, false);
if (mangoPayResult.Success)
{
appUser.PaymentId = mangoPayResult.Value;
//Wallets anlegen
var walletCreditsResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Credits);
var walletFeesResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Fees);
}
}
var appUserDto = Mapper.Map<AppUserDto>(appUser);
var baseAddress = GetBaseAddress();
if (!string.IsNullOrWhiteSpace(appUserDto.Photo))
{
appUserDto.Photo = $"{baseAddress}/file/documents/thumbnails/{200}/{appUserDto.Photo}";
}
return Ok(appUserDto);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Hinzufügen eines PaymentUsers zu einem AppUser
/// </summary>
/// <param name="model">Model</param>
/// <returns>200 OK</returns>
[HttpPost]
[Route("AddPaymentUser")]
public async Task<IActionResult> AddPaymentUser(AddPaymentUserDto model)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (ModelState.IsValid)
{
var appUser = await AppUserService.GetAsync(model.Id);
if (appUser != null)
{
appUser.NationalityCode = model.NationalityCode;
appUser.MainResidenceCode = model.MainResidenceCode;
appUser.PaymentTermsAccepted = model.PaymentTermsAccepted;
appUser.PaymentTermsAcceptedDate = model.PaymentTermsAcceptedDate;
appUser.UpdatedAt = DateTimeOffset.UtcNow;
await AppUserService.CommitAsync(User.Identity.Name);
//Nun für den Benutzer einen Mangopay User anlegen
var mangoPayResult = await _mangoPayService.CreateOwnerAsync(appUser.Id, false);
if (mangoPayResult.Success)
{
appUser.PaymentId = mangoPayResult.Value;
//Wallets anlegen
var walletCreditsResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Credits);
var walletFeesResult = await _mangoPayService.CreateWalletAsync(appUser.Id, WalletType.Fees);
}
var appUserDto = Mapper.Map<AppUserDto>(appUser);
var baseAddress = GetBaseAddress();
if (!string.IsNullOrWhiteSpace(appUserDto.Photo))
{
appUserDto.Photo = $"{baseAddress}/file/documents/thumbnails/{200}/{appUserDto.Photo}";
}
return Ok(appUserDto);
}
return NotFound(CommunicationErrors.Common_NotFound);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
#endregion
#region DogWalker Profil
/// <summary>
/// Gibt ein DogWalker Profil zurück
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("GetWalkerProfile")]
public async Task<IActionResult> GetWalkerProfile()
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (!string.IsNullOrWhiteSpace(user.AppUserId))
{
var walkerProfile = await AppUserService.GetWalkerProfileAsync(user.AppUserId);
if (walkerProfile != null)
{
var walkerProfileDto = Mapper.Map<DogWalkerProfileDto>(walkerProfile);
return Ok(walkerProfileDto);
}
}
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Gibt ein DogWalker Profil für die Background Sync zurück
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("GetWalkerProfileSync")]
public async Task<IActionResult> GetWalkerProfileSync(DateTimeOffset? lastUpdate)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (!string.IsNullOrWhiteSpace(user.AppUserId))
{
var walkerProfile = await AppUserService.GetWalkerProfileAsync(user.AppUserId);
if (walkerProfile != null && walkerProfile.UpdatedAt > lastUpdate)
{
var walkerProfileDto = Mapper.Map<DogWalkerProfileDto>(walkerProfile);
return Ok(walkerProfileDto);
}
return NoContent();
}
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Aktualisieren eines Dogwalker Profils
/// </summary>
/// <param name="model">AppUserDto</param>
/// <returns></returns>
[HttpPost]
[Route("UpdateWalkerProfile")]
public async Task<IActionResult> UpdateWalkerProfile(DogWalkerProfileDto model)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (ModelState.IsValid)
{
var walkerProfile = await AppUserService.GetWalkerProfileAsync(model.Id);
if (walkerProfile != null)
{
if (model.UpdatedAt > walkerProfile.UpdatedAt)
{
Mapper.Map(model, walkerProfile);
await AppUserService.CommitAsync(User.Identity.Name);
}
return Ok();
}
return NotFound(CommunicationErrors.Common_NotFound);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
#endregion
#region Blocks
/// <summary>
/// Blockieren eines AppUsers durch einen anderen AppUser
/// </summary>
/// <param name="model">Model</param>
/// <returns>200 OK</returns>
[HttpPost]
[Route("BlockAppUser")]
public async Task<IActionResult> BlockAppUser(BlockCreateDto model)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (ModelState.IsValid)
{
var appUser = await AppUserService.GetAsync(model.BlockingAppUserId);
if (appUser != null)
{
//Nur dann zulassen, wenn der Benutzer der angemeldet ist auch der Benutzer ist der die Blockierung vornimmt
if (appUser.Id == user.AppUserId)
{
var block = await AppUserService.BlockAppUserAsync(model.BlockingAppUserId, model.BlockedAppUserId, User.Identity.Name);
await AppUserService.CommitAsync(User.Identity.Name);
//Stornieren der offenen Walks des blockierten Users für den Benutzer der die Blockierung vorgenommen hat
var canceled = await _walkService.CancelWalksForBlockAsync(model.BlockingAppUserId, model.BlockedAppUserId);
//Sehen ob der blockierte Benutzer noch offene Angebote für den blockierenden Benutzer hat und diese Ablehnen
var openRequests = await _publicWalkRequestService.GetByStatusAsync(model.BlockingAppUserId, PublicWalkRequestStatus.Open);
foreach (var publicWalkRequest in openRequests)
{
var respone = await _publicWalkResponseService.GetForWalkerAsync(model.BlockedAppUserId, publicWalkRequest.Id);
if (respone != null)
{
respone.Status = PublicWalkResponseStatus.Declined;
respone.UpdatedAt = DateTimeOffset.UtcNow;
await _publicWalkResponseService.CommitAsync(User.Identity.Name);
}
}
var dto = Mapper.Map<AppUserBlockDto>(block);
return Ok(dto);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Blockieren eines AppUsers durch einen anderen AppUser aufheben
/// </summary>
/// <param name="model">Model</param>
/// <returns>200 OK</returns>
[HttpPost]
[Route("UnblockAppUser")]
public async Task<IActionResult> UnblockAppUser(BlockRemoveDto model)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (ModelState.IsValid)
{
var appUser = await AppUserService.GetAsync(model.BlockingAppUserId);
if (appUser != null)
{
//Nur dann zulassen, wenn der Benutzer der angemeldet ist auch der Benutzer ist der die Blockierung vorgenommen hat
if (appUser.Id == user.AppUserId)
{
var success = await AppUserService.UnblockAppUserAsync(model.BlockingAppUserId, model.BlockedAppUserId);
if(success)
await AppUserService.CommitAsync(User.Identity.Name);
return Ok(success);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
/// <summary>
/// Abfragen gesperrten Benutzern für einen App-User
/// </summary>
/// <param name="model">Abfragemodel</param>
/// <returns>Liste von gesperrten Benutzern - App-Users</returns>
[HttpPost]
[Route("GetBlockedAppUsers")]
public async Task<IActionResult> GetBlockedAppUsers(BlockedQueryDto model)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var baseAddress = GetBaseAddress();
var response = new ListResponseDto<AppUserBlockWithNamesDto>();
var queryResult = await AppUserService.GetBlockedAppUsersAsync(model);
response.Total = queryResult.total;
var resultList = queryResult.list;
var dtoList = Mapper.Map<List<AppUserBlockWithNamesDto>>(resultList);
foreach (var dtoItem in dtoList)
{
if (!string.IsNullOrWhiteSpace(dtoItem.Photo))
dtoItem.Photo = $"{baseAddress}/file/documents/thumbnails/{400}/{dtoItem.Photo}";
}
response.List = dtoList;
response.Take = model.Take;
response.Skip = model.Skip;
return Ok(response);
}
return NotFound(CommunicationErrors.Common_NotFound);
}
#endregion
#region AppUserReports - Meldungen
/// <summary>
/// Melden eines AppUsers durch einen anderen AppUser
/// </summary>
/// <param name="model">Model</param>
/// <returns>200 OK</returns>
[HttpPost]
[Route("ReportAppUser")]
public async Task<IActionResult> ReportAppUser(AppUserReportDto model)
{
var clientOffset = GetClientDateOffset();
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var user = await _userService.GetByUsernameAsync(User.Identity.Name);
if (user != null)
{
if (ModelState.IsValid)
{
var reportingAppUser = await AppUserService.GetAsync(model.ReportingAppUserId);
if (reportingAppUser != null)
{
//Nur dann zulassen, wenn der Benutzer der angemeldet ist auch der Benutzer ist der die Blockierung vornimmt
if (reportingAppUser.Id == user.AppUserId)
{
var reportedAppUser = await AppUserService.GetAsync(model.ReportedAppUserId);
if (reportedAppUser != null)
{
var report = _appUserReportService.Create();
Mapper.Map(model, report);
report.Status = AppUserReportStatus.Open;
_appUserReportService.Add(report);
await _appUserReportService.CommitAsync(User.Identity.Name);
//Jetzt Block behandeln...
if (model.BlockType != AppUserReportBlockTypeDto.NoBlock)
{
DateTimeOffset? blockUntil = null;
if (model.BlockType == AppUserReportBlockTypeDto.BlockForTime)
blockUntil = DateTimeOffset.UtcNow.AddHours(24);
var block = await AppUserService.BlockAppUserAsync(model.ReportingAppUserId, model.ReportedAppUserId, User.Identity.Name, blockUntil, report.Id);
await AppUserService.CommitAsync(User.Identity.Name);
if (model.BlockType == AppUserReportBlockTypeDto.BlockForever)
{
//Stornieren der offenen Walks des blockierten Users für den Benutzer der die Blockierung vorgenommen hat
var canceled = await _walkService.CancelWalksForBlockAsync(model.ReportingAppUserId, model.ReportedAppUserId);
//Sehen ob der blockierte Benutzer noch offene Angebote für den blockierenden Benutzer hat und diese Ablehnen
var openRequests = await _publicWalkRequestService.GetByStatusAsync(model.ReportingAppUserId, PublicWalkRequestStatus.Open);
foreach (var publicWalkRequest in openRequests)
{
var respone = await _publicWalkResponseService.GetForWalkerAsync(model.ReportedAppUserId, publicWalkRequest.Id);
if (respone != null)
{
respone.Status = PublicWalkResponseStatus.Declined;
respone.UpdatedAt = DateTimeOffset.UtcNow;
await _publicWalkResponseService.CommitAsync(User.Identity.Name);
}
}
}
}
return Ok(true);
}
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
return BadRequest(CommunicationErrors.Common_Model_Invalid);
}
}
return NotFound(CommunicationErrors.Common_NotFound);
}
#endregion
#region Private
/// <summary>
/// Generieren des Client-Secrets das Apple für die Prüfung des Benutzers benötigt
/// </summary>
/// <returns></returns>
private async Task<string> GenerateAppleClientSecretAsync()
{
var clientSecret = await _memoryCache.GetStringAsync("AppleClientSecret", CancellationToken.None);
if (string.IsNullOrEmpty(clientSecret))
{
var rootPath = _environment.WebRootPath;
var path = Path.Combine(rootPath, $"app_files\\apple\\AuthKey_{_appleOptions.Value.KeyId}.p8");
var lines = System.IO.File.ReadLines(path);
var keyStart = String.Join(String.Empty, lines.Where(x => !x.Contains("PRIVATE KEY")));
var bytes = Convert.FromBase64String(keyStart);
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
//Import the key using a Pkcs8PrivateBlob.
var cngKey = CngKey.Import(bytes, CngKeyBlobFormat.Pkcs8PrivateBlob);
//Create new ECDsaCng object with the imported key.
var ecDsaCng = new ECDsaCng(cngKey);
ecDsaCng.HashAlgorithm = CngAlgorithm.ECDsaP256;
//Create new SigningCredentials instance which will be used for signing the token.
var signingCredentials = new SigningCredentials(new ECDsaSecurityKey(ecDsaCng), SecurityAlgorithms.EcdsaSha256);
var now = DateTime.UtcNow.AddHours(12);
//Create new list with the required claims.
var claims = new List<Claim>
{
new Claim("iss", _appleOptions.Value.TeamId),
new Claim("iat", EpochTime.GetIntDate(now).ToString(), ClaimValueTypes.Integer64),
new Claim("exp", EpochTime.GetIntDate(now.AddMinutes(5)).ToString(), ClaimValueTypes.Integer64),
new Claim("aud", "https://appleid.apple.com"),
new Claim("sub", _appleOptions.Value.ClientId)
};
//Create the JSON Web Token object.
var token = new JwtSecurityToken(
issuer: _appleOptions.Value.TeamId,
claims: claims,
expires: now.AddMinutes(5),
signingCredentials: signingCredentials);
token.Header.Add("kid", _appleOptions.Value.KeyId);
clientSecret = tokenHandler.WriteToken(token);
//var audience = "https://appleid.apple.com";
//var expiresAt = DateTimeOffset.UtcNow.AddSeconds(15777000).AddMinutes(-15).UtcDateTime; //apple max, but let's not get messed up with clock sync issues
//var subject = new System.Security.Claims.ClaimsIdentity(new[] { new System.Security.Claims.Claim("sub", _appleOptions.Value.ClientId) });
//var lines = System.IO.File.ReadLines(path);
//var keyStart = String.Join(String.Empty, lines.Where(x => !x.Contains("PRIVATE KEY")));
//var bytes = Convert.FromBase64String(keyStart);
//var ecd = ECDsa.Create();
//ecd.ImportPkcs8PrivateKey(bytes, out var _);
//var ecpStart = ecd.ExportParameters(true);
////var param = new { ecpStart.D, ecpStart.Q };
////var json = JsonSerializer.Serialize(param);
//var ecp = new ECParameters
//{
// D = ecpStart.D,
// Q = ecpStart.Q,
// Curve = ECCurve.NamedCurves.nistP256
//};
//var alg = ECDsa.Create(ecp);
//var key = new ECDsaSecurityKey(alg) { KeyId = _appleOptions.Value.KeyId };
//var sig = new SigningCredentials(key, SecurityAlgorithms.EcdsaSha256Signature);
//var tokenHandler = new JwtSecurityTokenHandler();
//var tokenDescriptor = new SecurityTokenDescriptor()
//{
// Audience = audience,
// Expires = expiresAt,
// Issuer = _appleOptions.Value.TeamId,
// Subject = subject,
// SigningCredentials = sig
//};
//clientSecret = tokenHandler.CreateEncodedJwt(tokenDescriptor);
var options = new DistributedCacheEntryOptions();
options.SetAbsoluteExpiration(now.AddMinutes(-15));
await _memoryCache.SetStringAsync("AppleClientSecret", clientSecret, options);
// Tokengültigkeit von 6 Monaten (das ist der Max-Wert laut Apple)
//var expiresAt = DateTime.UtcNow.Add(TimeSpan.FromSeconds(15777000));
//var tokenDescriptor = new SecurityTokenDescriptor()
//{
// Audience = "https://appleid.apple.com",
// Expires = expiresAt,
// Issuer = _appleOptions.Value.TeamId,
// Subject = new ClaimsIdentity(new[] { new Claim("sub", _appleOptions.Value.ClientId) }),
//};
//// Load the .p8 file from disk, removing the
//// `-----BEGIN PRIVATE KEY-----` and `-----END PRIVATE KEY-----`
//// prefix and suffix, and joining `\n` characters between lines.
//var content = await System.IO.File.ReadAllTextAsync(path);
//string[] keyLines = content.Split('\n');
//content = string.Join(string.Empty, keyLines.Skip(1).Take(keyLines.Length - 2));
//byte[] keyBlob = Convert.FromBase64String(content);
//// Create an ECDSA 256 algorithm to sign the token
//using var privateKey = CngKey.Import(keyBlob, CngKeyBlobFormat.Pkcs8PrivateBlob);
//using var algorithm = new ECDsaCng(privateKey);
//{
// algorithm.HashAlgorithm = CngAlgorithm.Sha256;
// var key = new ECDsaSecurityKey(algorithm) { KeyId = _appleOptions.Value.KeyId };
// // Set the signing key for the token
// tokenDescriptor.SigningCredentials = new SigningCredentials(
// key,
// SecurityAlgorithms.EcdsaSha256Signature);
// // Create the token, which acts as the Client Secret
// var tokenHandler = new JwtSecurityTokenHandler();
// clientSecret = tokenHandler.CreateEncodedJwt(tokenDescriptor);
// var options = new DistributedCacheEntryOptions();
// options.SetAbsoluteExpiration(expiresAt.AddMinutes(-15));
// await _memoryCache.SetStringAsync("AppleClientSecret", clientSecret, options);
//}
}
return clientSecret;
}
#endregion
}
}