using System.IdentityModel.Tokens.Jwt; using AyCode.Core.Helpers; using AyCode.Interfaces.Addresses; using AyCode.Interfaces.Logins; using AyCode.Interfaces.Profiles; using AyCode.Interfaces.Server.Logins; using AyCode.Interfaces.ServiceProviders; using AyCode.Interfaces.Users; using AyCode.Services.Logins; using System.Runtime.InteropServices; using System.Security; using System.Security.Claims; using System.Security.Cryptography; using System.Text; using Microsoft.IdentityModel.Tokens; using AyCode.Database.DataLayers; using Microsoft.EntityFrameworkCore; using AyCode.Database.DbContexts; using AyCode.Core.Consts; using AyCode.Core.Extensions; using AyCode.Core.Loggers; using AyCode.Core.Server.Loggers; using AyCode.Database.DataLayers.Users; using AyCode.Database.DbContexts.Users; using AyCode.Interfaces.Messages; using AyCode.Models.Server.Logins; using AyCode.Utils.Extensions; using Microsoft.Extensions.Configuration; namespace AyCode.Services.Server.Logins; public class AcLoginServiceServer(TDal userDal, IConfiguration configuration) : AcLoginServiceBase, IAcLoginServiceServer where TResultLoggedInModel : class, IAcLoggedInModelBase where TDal : AcUserDalBase where TDbContext : AcDbContextBase, IAcUserDbContextBase where TUser : class, IAcUser where TUserToken : class, IAcUserTokenBase where TProfile : class, IAcProfile where TCompany : class, IAcCompanyBase where TUserToServiceProvider : class, IAcUserToCompanyBase where TProfileAddress : class, IAcAddress where TEmailMessage : class, IAcEmailMessageBase { public TResultLoggedInModel? LoggedInModel { get; private set; } public override TUser? LoggedInUser => LoggedInModel?.LoggedInUser; public override bool IsLoggedIn => LoggedInModel?.IsLoggedIn ?? false; public virtual TResultLoggedInModel Login(string? email, string? password) { if (LoggedInModel != null) Logout(); LoggedInModel = Activator.CreateInstance(); if (AcValidate.IsValidEmailAndPasswordFormat(email, password, out var errorCode)) { var user = userDal.AuthenticateUser(email, password, GenerateRefreshToken(), out errorCode); if (user != null) LoggedInModel.AddLoggedInUser(user, GenerateAccessToken(user)); } LoggedInModel.LoginErrorCode = errorCode; return LoggedInModel; } public virtual Task LoginAsync(string? email, string? password) { return TaskHelper.ToThreadPoolTask(() => Login(email, password)); } public virtual bool Logout() { LoggedInModel?.Logout(); LoggedInModel = null; return true; } public virtual Task LogoutAsync() { return TaskHelper.ToThreadPoolTask(Logout); } public virtual AcErrorCode Registration(string email, string password, string? phoneNumber = null, Guid? referralId = null) => Registration(Guid.NewGuid(), email, password, phoneNumber, referralId); public virtual AcErrorCode Registration(Guid userId, string email, string password, string? phoneNumber = null, Guid? referralId = null) { email = email.ToLower(); if ((phoneNumber != null && !AcValidate.IsValidPhoneNumberFormat(phoneNumber, out var errorCode)) || !AcValidate.IsValidEmailAndPasswordFormat(email, password, out errorCode)) return errorCode; var user = Activator.CreateInstance(); user.Id = userId; user.EmailAddress = email; user.EmailConfirmed = true; user.Password = PasswordHasher.HashPassword(password, PasswordHasher.GenerateDynamicSalt(userId)); user.RefferalId = referralId; var address = Activator.CreateInstance(); address.Id = Guid.NewGuid(); var userName = email.Split('@')[0]; //TODO: generálni egy nevet... - J. if (!userDal.AddUser(user, userName, address)) errorCode = AcErrorCode.DatabaseError; return errorCode; } public virtual Task RegistrationAsync(string email, string password, string? phoneNumber = null, Guid? referralId = null) => RegistrationAsync(Guid.NewGuid(), email, password, phoneNumber, referralId); public virtual Task RegistrationAsync(Guid userId, string email, string password, string? phoneNumber = null, Guid? referralId = null) => TaskHelper.ToThreadPoolTask(() => Registration(userId, email, password, phoneNumber, referralId)); public virtual Task ChangePasswordAsync(Guid userId, string oldPassword, string newPassword) => TaskHelper.ToThreadPoolTask(() => ChangePassword(userId, oldPassword, newPassword)); public virtual AcErrorCode ChangePassword(Guid userId, string oldPassword, string newPassword) { try { if (userId.IsNullOrEmpty()) return AcErrorCode.IdIsNullOrEmpty; if (!AcValidate.IsValidPasswordFormat(newPassword, out var errorCode)) return errorCode; var user = userDal.GetUserById(userId, false); //TODO: csak az EmailConfirmed user password-öket lehessen change-elni! - J. if (user == null) return AcErrorCode.EntityIsNull; return PasswordHasher.VerifyPassword(oldPassword, user.Password, PasswordHasher.GenerateDynamicSalt(user.Id)) ? UpdatePassword(user, newPassword) : AcErrorCode.WrongLoginData; } catch (Exception) { // ignored } return AcErrorCode.UnknownError; } public Task ForgotPasswordAsync(string email, string newPassword) => TaskHelper.ToThreadPoolTask(() => ForgotPassword(email, newPassword)); public AcErrorCode ForgotPassword(string email, string newPassword) { try { if (email.IsNullOrEmpty()) return AcErrorCode.EmailIsNullOrEmpty; if (!AcValidate.IsValidPasswordFormat(newPassword, out var errorCode)) return errorCode; var user = userDal.GetUserByEmail(email, false); return user == null ? AcErrorCode.EntityIsNull : UpdatePassword(user, newPassword); } catch (Exception) { // ignored } return AcErrorCode.UnknownError; } public AcErrorCode UpdatePassword(TUser user, string password) { user.Password = PasswordHasher.HashPassword(password, PasswordHasher.GenerateDynamicSalt(user.Id)); return userDal.UpdateUser(user) == null ? AcErrorCode.DatabaseError : AcErrorCode.Unset; } public virtual bool SendConfirmationToken(string? email, string confirmationToken) { //var sendGrid = SendGrid.SendGridClient(); return true; } public virtual Task SendConfirmationTokenAsync(string email, string confirmationToken) => TaskHelper.ToThreadPoolTask(() => SendConfirmationToken(email, confirmationToken)); public string GenerateAccessToken(TUser user) { var tokenHandler = new JwtSecurityTokenHandler(); GlobalLogger.Info("----------------------------------------------------------"); if (configuration["JWT:Key"] == null) throw new SecurityTokenException("Token is null"); var keyDetail = Encoding.UTF8.GetBytes(configuration["JWT:Key"] ?? string.Empty); GlobalLogger.Info(configuration["JWT:Key"]); var claims = new List { new(ClaimTypes.NameIdentifier, user.Id.ToString()), new(ClaimTypes.Email, user.EmailAddress) }; var tokenDescriptor = new SecurityTokenDescriptor { Audience = configuration["JWT:Audience"], Issuer = configuration["JWT:Issuer"], Expires = DateTime.UtcNow.AddMinutes(30), Subject = new ClaimsIdentity(claims), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(keyDetail), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor) as JwtSecurityToken; var writtenToken = tokenHandler.WriteToken(token); GlobalLogger.Info(writtenToken); return writtenToken; } public ClaimsPrincipal GetPrincipalFromExpiredToken(string token) { var tokenHandler = new JwtSecurityTokenHandler(); if (configuration["JWT:Key"] == null) throw new SecurityTokenException("Token is null"); var keyDetail = Encoding.UTF8.GetBytes(configuration["JWT:Key"] ?? string.Empty); var tokenValidationParameter = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, ValidIssuer = configuration["JWT:Issuer"], ValidAudience = configuration["JWT:Audience"], IssuerSigningKey = new SymmetricSecurityKey(keyDetail), }; var principal = tokenHandler.ValidateToken(token, tokenValidationParameter, out var securityToken); if (securityToken is not JwtSecurityToken jwtSecurityToken || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase)) throw new SecurityTokenException("Invalid token"); return principal; } public string GenerateRefreshToken() { var randomNumber = new byte[32]; using var rng = RandomNumberGenerator.Create(); rng.GetBytes(randomNumber); return Convert.ToBase64String(randomNumber); } }