diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Enums.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Enums.cs index dc39a1e..ecdfd70 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Enums.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Enums.cs @@ -718,4 +718,22 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models [Description("Percentage Based Discount")] ZDS2 = 3 } + + public enum UserPlatFormType : short + { + SAP=1, + AppUser =2, + } + + public enum UserRoleTypeEnum + { + [Description("SAP User")] + SapUser = 1, + [Description("Order Collector")] + OrderCollector = 2, + [Description("OrderValidator")] + OrderValidator = 3, + [Description("OrderApprover")] + OrderApprover =4 + } } \ No newline at end of file diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Objects/Systems/User.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Objects/Systems/User.cs index 2155064..0034b57 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Objects/Systems/User.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Objects/Systems/User.cs @@ -26,9 +26,14 @@ namespace OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems public string SchemeName { get; set; } public string MenuLayout { get; set; } public bool IsLocked { get; set; } - public EnumLoginStatus LoginStatus { get; set; } - public DateTime? NextLoginTime { get; set; } + public UserPlatFormType? UserPlatFormType { get; set; } + public UserRoleTypeEnum? UserRoleType { get; set; } + public string? EmployeeNumber { get; set; } + public string? DesignationCode { get; set; } + public string? DesignationName { get; set; } + public EnumLoginStatus LoginStatus { get; set; } + public DateTime? NextLoginTime { get; set; } } public class LoginHistory diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Common/LoginRequest.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Common/LoginRequest.cs new file mode 100644 index 0000000..1694c95 --- /dev/null +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Common/LoginRequest.cs @@ -0,0 +1,14 @@ + +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + +namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Common; + +public class AuthLoginRequest +{ + [Required, NotNull, StringLength(maximumLength: 150, MinimumLength = 3, ErrorMessage = "Login Id must be between 4 and 30 characters.")] + public string LoginId { get; set; } + + [Required, NotNull, StringLength(maximumLength: 150, MinimumLength = 5, ErrorMessage = "Password must be between 1 and 30 characters.")] + public string Password { get; set; } +} diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationAuthRequest.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationAuthRequest.cs index bd373a0..6958975 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationAuthRequest.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Integrations/IntegrationAuthRequest.cs @@ -1,20 +1,14 @@ - -using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems; -using System.ComponentModel.DataAnnotations; -using System.Diagnostics.CodeAnalysis; +using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems; +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Common; + namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations; -public class IntegrationLoginRequest +public class IntegrationLoginRequest : AuthLoginRequest { - [Required, NotNull, StringLength(maximumLength: 150, MinimumLength = 3, ErrorMessage = "Login Id must be between 4 and 30 characters.")] - public string LoginId { get; set; } - - [Required, NotNull, StringLength(maximumLength: 150, MinimumLength = 5, ErrorMessage = "Password must be between 1 and 30 characters.")] - public string Password { get; set; } + } - public class IntegrationRefreshTokenRequest { public string RefreshToken { get; set; } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/MobileApp/MobileAppAuthRequest.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/MobileApp/MobileAppAuthRequest.cs new file mode 100644 index 0000000..2efb893 --- /dev/null +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/MobileApp/MobileAppAuthRequest.cs @@ -0,0 +1,61 @@ + +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Common; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; + +namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp; + +public class MobileAppAuthRequest : AuthLoginRequest +{ + +} + +public class AuthLogoutRequest +{ + [Required, NotNull, StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "Login Id must be between 3 and 10 characters.")] + public string LoginId { get; set; } +} + + +public class AppUserChangePasswordRequest +{ + [Required, NotNull, StringLength(maximumLength: 10, MinimumLength = 3, ErrorMessage = "Login Id must be between 3 and 10 characters.")] + public string LoginId { get; set; } + + [Required, NotNull, StringLength(maximumLength: 20, MinimumLength = 5, ErrorMessage = "Password must be between 5 and 20 characters.")] + public string Password { get; set; } + + [Required, NotNull, StringLength(maximumLength: 20, MinimumLength = 5, ErrorMessage = "Password must be between 5 and 20 characters.")] + public string NewPassword { get; set; } + + [Required, NotNull, StringLength(maximumLength: 20, MinimumLength = 5, ErrorMessage = "Password must be between 5 and 20 characters.")] + [Compare("NewPassword", ErrorMessage = "Password and Confirm Password do not match.")] + public string ConfirmPassword { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (!string.IsNullOrWhiteSpace(NewPassword) && + !string.IsNullOrWhiteSpace(ConfirmPassword) && + Password != ConfirmPassword) + { + yield return new ValidationResult( + "Password and Confirm Password do not match.", + [nameof(NewPassword), nameof(ConfirmPassword)] + ); + } + + if (!string.IsNullOrWhiteSpace(LoginId) && + !string.IsNullOrWhiteSpace(NewPassword) && + Password.Equals(LoginId, StringComparison.OrdinalIgnoreCase)) + { + yield return new ValidationResult( + "Password must not be the same as your Login ID.", + [nameof(NewPassword)] + ); + } + } + +} + diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Systems/UserRequest.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Systems/UserRequest.cs index 3167783..2a0fc26 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Systems/UserRequest.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Requests/Systems/UserRequest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using System.Diagnostics.CodeAnalysis; namespace OnlineSalesAutoCrop.CoreAPI.Models.Requests.Systems; @@ -219,3 +220,43 @@ public class PayslipRequest [Required, NotNull] public DateTime YearMonth { get; set; } } + + +public class AppUserCreateRequest +{ + [Required(ErrorMessage = "Login ID is required.")] + [MaxLength(100, ErrorMessage = "Login ID cannot exceed 100 characters.")] + public string LoginId { get; set; } = string.Empty; + + [Required(ErrorMessage = "Username is required.")] + [MaxLength(150, ErrorMessage = "Username cannot exceed 150 characters.")] + public string UserName { get; set; } = string.Empty; + + [Required] + public int Status { get; set; } + + [Required] + public int AccessStatus { get; set; } + + [Phone(ErrorMessage = "Invalid mobile number format.")] + [MaxLength(20, ErrorMessage = "Mobile number cannot exceed 20 characters.")] + public string? MobileNo { get; set; } + + [Required(ErrorMessage = "Email address is required.")] + [EmailAddress(ErrorMessage = "Invalid email address format.")] + [MaxLength(200, ErrorMessage = "Email address cannot exceed 200 characters.")] + public string EmailAddress { get; set; } = string.Empty; + + public int IsLocked { get; set; } + + [Required(ErrorMessage = "Password is required.")] + [MaxLength(500, ErrorMessage = "Password hash length cannot exceed 500 characters.")] + public string Password { get; set; } = string.Empty; + + public int? UserPlatformId { get; set; } + + public int? UserRoleType { get; set; } + + [MaxLength(10,ErrorMessage = "EmployeeNumber hash length cannot exceed 10 characters")] + public string? EmployeeNumber { get; set; } +} diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Integrations/IntegrationAuthResponse.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Integrations/IntegrationAuthResponse.cs index 656291b..41e1a73 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Integrations/IntegrationAuthResponse.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/Integrations/IntegrationAuthResponse.cs @@ -17,7 +17,6 @@ public class IntegrationRrefreshTokenResponse : ResponseBase public string RefreshToken { get; set; } = string.Empty; } - public class RefreshTokenResponse : ResponseBase { public int UserId { get; set; } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/MobileApp/AppUserResponse.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/MobileApp/AppUserResponse.cs new file mode 100644 index 0000000..e5fe919 --- /dev/null +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Models/Responses/MobileApp/AppUserResponse.cs @@ -0,0 +1,41 @@ + + +using System; + +namespace OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp; + +public class AppAuthUserResponse : ResponseBase +{ + public int UserId { get; set; } + public string LoginId { get; set; } + public string Name { get; set; } + public string? Email { get; set; } + public string? MobileNumber { get; set; } + public string EmployeeNumber { get; set; } + public string DesignationCode { get; set; } + public string DesignationName { get; set; } + public bool IsLocked { get; set; } + public UserRoleTypeEnum UserRole { get; set; } + public string AccessToken { get; set; } + public DateTime AccessTokenExpiryTime { get; set; } + public string RefreshToken { get; set; } + public DateTime RefreshTokenExpiryTime { get; set; } + public EnumLoginStatus LoginStatus { get; set; } + public bool IsAuthorized { get; set; } + public bool IsInitialPassword { get; set; } +} + + +public class AuthLogoutResponse : ResponseBase +{ + public int UserId { get; set; } + public string LoginId { get; set; } + public bool IsSuccess { get; set; } +} + +public class AppChangePasswordResponse : ResponseBase +{ + public int UserId { get; set; } + public string LoginId { get; set; } + public bool IsSuccess { get; set; } +} \ No newline at end of file diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Auth/IRefreshTokenService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Auth/IRefreshTokenService.cs index 9f7730c..be3ff49 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Auth/IRefreshTokenService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Auth/IRefreshTokenService.cs @@ -1,4 +1,5 @@ -using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations; +using Ease.NetCore.DataAccess; +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations; using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations; using System.Threading.Tasks; @@ -9,4 +10,5 @@ public interface IRefreshTokenService Task GenerateRefreshTokenByUserAsync(GenerateRefreshTokenRequest request); Task GenerateRefreshTokenAsync(string refreshToken, string ipAddress); Task GetUserByRefreshTokenAsync(string refreshToken); + Task InvalidRefreshTokenAsync(TransactionContext tc, int userId); } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Systems/IUserService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Systems/IUserService.cs index 5a3aa3e..02ca341 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Systems/IUserService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Contracts/Systems/IUserService.cs @@ -1,7 +1,10 @@ using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Setups; using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems; +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Common; using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations; +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp; using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Systems; +using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp; using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems; using System; using System.Collections.Generic; @@ -13,10 +16,14 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems { Task ValidateAuthValueAsync(string authValue, int userId); Task LoginAsync(LoginRequest request, string ipAddress, bool checkPwd); + Task LoginAsync(MobileAppAuthRequest request, string ipAddress); Task IntegrationLoginAsync(IntegrationLoginRequest request, string ipAddress, bool checkPwd); Task LogoutAsync(string ipAddress, int userId, int logId, bool attendanceLogout, string loginId, string localIp, string macAddress, string hostName, string logoutRemarks); + Task LogoutAsync(AuthLogoutRequest request, string ipAddress); + Task ChangePasswordAsync(AppUserChangePasswordRequest request, string ipAddress); + Task DeleteUserAsync(int userId, int deletedBy); Task ForceLogoutNowAsync(List userIds, string ipAddress); Task UnlockUserAsync(int userId, string loginId, int unlockedBy); diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Auth/RefreshTokenService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Auth/RefreshTokenService.cs index 4453fb3..a262637 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Auth/RefreshTokenService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Auth/RefreshTokenService.cs @@ -3,6 +3,7 @@ using Ease.NetCore.DataAccess.SQL; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Options; using OnlineSalesAutoCrop.CoreAPI.Models.Global; +using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems; using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations; using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations; using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth; @@ -116,6 +117,22 @@ public class RefreshTokenService : IRefreshTokenService return refreshTokenResponse; } + public async Task InvalidRefreshTokenAsync( TransactionContext tc, int userId) + { + bool response = false; + try + { + RevokeRefreshTokenByUserIdAsync(tc, userId); + response = true; + } + catch (Exception e) + { + throw new InvalidOperationException(e.Message, e); + } + + return response; + } + #region Private Methods // ----- private helpers ----- @@ -145,7 +162,7 @@ public class RefreshTokenService : IRefreshTokenService SqlHelperExtension.CreateInParam(pName: "@TokenHash", pType: SqlDbType.VarChar, pValue: refreshToken.TokenHash), SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: refreshToken.IpAddress), SqlHelperExtension.CreateInParam(pName: "@CreatedAt", pType: SqlDbType.DateTime, pValue: DateTime.Now), - SqlHelperExtension.CreateInParam(pName: "@ExpiredAt", pType: SqlDbType.DateTime, pValue: DateTime.Now.AddMinutes(_settings.RefreshTokenDuration)), + SqlHelperExtension.CreateInParam(pName: "@ExpiredAt", pType: SqlDbType.DateTime, pValue: refreshToken.ExpiresAt), ]; _ = tc.ExecuteNonQuerySp(spName: "dbo.InsertRefreshToken", parameterValues: p); @@ -259,7 +276,7 @@ public class RefreshTokenService : IRefreshTokenService return new GenerateRefreshTokenResponse { RefreshToken = tokenHash, - ExpireTime = DateTime.UtcNow.AddMinutes(_settings.RefreshTokenDuration) + ExpireTime = DateTime.UtcNow.AddDays(_settings.RefreshTokenDuration) }; } @@ -277,5 +294,7 @@ public class RefreshTokenService : IRefreshTokenService return Convert.ToBase64String(bytes); } + + #endregion } diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs index d61f6d4..1c3409f 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Integrations/IntegrationService.cs @@ -1,14 +1,20 @@ using Ease.NetCore.DataAccess; using Ease.NetCore.DataAccess.SQL; +using Ease.NetCore.Utility; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Options; +using OnlineSalesAutoCrop.CoreAPI.Models; using OnlineSalesAutoCrop.CoreAPI.Models.Global; +using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems; using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations; +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Systems; using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations; using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations; +using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems; using System; using System.Collections.Generic; using System.Data; +using System.Text; using System.Threading.Tasks; namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Integrations; @@ -16,10 +22,12 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Integrations; public class IntegrationService : IIntegrationService { private readonly AppSettings _settings; + private readonly IUserService _userService; - public IntegrationService(IOptions options) + public IntegrationService(IOptions options, IUserService userService) { _settings = options.Value; + _userService = userService; } @@ -142,6 +150,28 @@ public class IntegrationService : IIntegrationService response.IsUpdated = false; } + bool isUserExist = await IsUserExistByEmployeeNumberAsync(tc, request.EmployeeVendorCode); + + if (!isUserExist) + { + AppUserCreateRequest appUser = new() + { + LoginId = request.EmployeeVendorCode, + UserName = request.EmployeeVendorCode, + Status = (int)EnumStatus.Authorized, + AccessStatus = (int)EnumAccessStatus.FirstTime, + MobileNo = request.MobileNo, + EmailAddress = request.Mail, + IsLocked = 0, + Password ="12345", + UserRoleType = request.Designation == "01"? (int)UserRoleTypeEnum.OrderCollector : null, + UserPlatformId = (int)UserPlatFormType.AppUser, + EmployeeNumber = request.EmployeeVendorCode + }; + + await AddUserAsync(tc, appUser); + } + response.EmployeeVendorCode = request.EmployeeVendorCode; response.CompanyCode = request.CompanyCode; tc.End(); @@ -717,6 +747,93 @@ public class IntegrationService : IIntegrationService return response; } + + public async Task AddUserAsync(TransactionContext tc, AppUserCreateRequest user) + { + bool returnValue; + try + { + int userId = 0; + user.Password = EncryptPassword(password: user.Password); + + if (!string.IsNullOrEmpty(user.EmailAddress)) + user.EmailAddress = user.EmailAddress.Replace(" ", ""); + + + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: user.LoginId, size: 100), + SqlHelperExtension.CreateInParam(pName: "@UserName", pType: SqlDbType.VarChar, pValue: user.UserName, size: 150), + SqlHelperExtension.CreateInParam(pName: "@Status", pType: SqlDbType.Int, pValue: user.Status), + SqlHelperExtension.CreateInParam(pName: "@AccessStatus", pType: SqlDbType.Int, pValue: user.AccessStatus), + SqlHelperExtension.CreateInParam(pName: "@MobileNo", pType: SqlDbType.VarChar, pValue: DataReader.GetNullValue(user.MobileNo), size: 20), + SqlHelperExtension.CreateInParam(pName: "@EmailAddress", pType: SqlDbType.VarChar, pValue: user.EmailAddress, size: 200), + SqlHelperExtension.CreateInParam(pName: "@IsLocked", pType: SqlDbType.Int, pValue: user.IsLocked), + SqlHelperExtension.CreateInParam(pName: "@Password", pType: SqlDbType.NVarChar, pValue: user.Password, size: 500), + SqlHelperExtension.CreateInParam(pName: "@UserPlatformId", pType: SqlDbType.Int, pValue: DataReader.GetNullValue(user.UserPlatformId)), + SqlHelperExtension.CreateInParam(pName: "@UserRoleType", pType: SqlDbType.Int, pValue: DataReader.GetNullValue(user.UserRoleType)), + SqlHelperExtension.CreateInParam(pName: "@EmployeeNumber", pType: SqlDbType.VarChar, pValue: DataReader.GetNullValue(user.EmployeeNumber), size: 10), + SqlHelperExtension.CreateOutParam(pName: "@NewUserId", pType: SqlDbType.Int) // index 11 — output + ]; + + _ = tc.ExecuteNonQuerySp(spName: "dbo.InsertUser", parameterValues: p); + userId = (p[11]?.Value != null && p[11]?.Value != DBNull.Value) + ? Convert.ToInt32(p[11].Value) + : 0; + + returnValue = userId > 0; + } + catch (Exception e) + { + throw new InvalidOperationException(e.Message, e); + } + + return returnValue; + } + + + private string EncryptPassword(string password) + { + try + { + string pwdSecretKey = GlobalFunctions.ConvertFromBase64String(_settings.PwdSecretKey); + return Global.CipherFunctions.EncryptByAES(privateKey: pwdSecretKey, publicKey: pwdSecretKey, data: password); + } + catch (Exception e) + { + throw new InvalidOperationException(e.Message, e); + } + + } + + private async Task IsUserExistByEmployeeNumberAsync(TransactionContext tc, string employeeNumber) + { + bool response = false; + + try + { + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam(pName: "@EmployeeNumber", pType: SqlDbType.NVarChar, pValue: employeeNumber) + ]; + + using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.IsUserExistByEmployeeNumber", parameterValues: p)) + { + if (dr.Read()) + { + response = dr.GetInt32(0) > 0 ? true: false; + } + dr.Close(); + } + } + catch (Exception ex) + { + throw DBCustomError.GenerateCustomError(ex); + } + + return response; + } + private bool InsertEmployee(TransactionContext tc, IntegrationEmployeeRequest request) { bool returnValue = false; @@ -726,40 +843,40 @@ public class IntegrationService : IIntegrationService SqlParameter[] p = [ SqlHelperExtension.CreateInParam(pName: "@EmployeeVendorCode", pType: SqlDbType.NVarChar, pValue: request.EmployeeVendorCode), - SqlHelperExtension.CreateInParam(pName: "@EmployeeVendorName", pType: SqlDbType.NVarChar, pValue: request.EmployeeVendorName), + SqlHelperExtension.CreateInParam(pName: "@EmployeeVendorName", pType: SqlDbType.NVarChar, pValue: request.EmployeeVendorName), - SqlHelperExtension.CreateInParam(pName: "@Designation", pType: SqlDbType.NVarChar, pValue: request.Designation), - SqlHelperExtension.CreateInParam(pName: "@DesignationDescription", pType: SqlDbType.NVarChar, pValue: request.DesignationDescription), + SqlHelperExtension.CreateInParam(pName: "@Designation", pType: SqlDbType.NVarChar, pValue: request.Designation), + SqlHelperExtension.CreateInParam(pName: "@DesignationDescription", pType: SqlDbType.NVarChar, pValue: request.DesignationDescription), - SqlHelperExtension.CreateInParam(pName: "@MobileNo", pType: SqlDbType.NVarChar, pValue: request.MobileNo), - SqlHelperExtension.CreateInParam(pName: "@Mail", pType: SqlDbType.NVarChar, pValue: request.Mail), + SqlHelperExtension.CreateInParam(pName: "@MobileNo", pType: SqlDbType.NVarChar, pValue: request.MobileNo), + SqlHelperExtension.CreateInParam(pName: "@Mail", pType: SqlDbType.NVarChar, pValue: request.Mail), - SqlHelperExtension.CreateInParam(pName: "@CompanyCode", pType: SqlDbType.NVarChar, pValue: request.CompanyCode), - SqlHelperExtension.CreateInParam(pName: "@CompanyName", pType: SqlDbType.NVarChar, pValue: request.CompanyName), + SqlHelperExtension.CreateInParam(pName: "@CompanyCode", pType: SqlDbType.NVarChar, pValue: request.CompanyCode), + SqlHelperExtension.CreateInParam(pName: "@CompanyName", pType: SqlDbType.NVarChar, pValue: request.CompanyName), - SqlHelperExtension.CreateInParam(pName: "@SalesOrg", pType: SqlDbType.NVarChar, pValue: request.SalesOrg), - SqlHelperExtension.CreateInParam(pName: "@SalesOrgDescription", pType: SqlDbType.NVarChar, pValue: request.SalesOrgDescription), + SqlHelperExtension.CreateInParam(pName: "@SalesOrg", pType: SqlDbType.NVarChar, pValue: request.SalesOrg), + SqlHelperExtension.CreateInParam(pName: "@SalesOrgDescription", pType: SqlDbType.NVarChar, pValue: request.SalesOrgDescription), - SqlHelperExtension.CreateInParam(pName: "@DistChannel", pType: SqlDbType.NVarChar, pValue: request.DistChannel), - SqlHelperExtension.CreateInParam(pName: "@DistChannelDescription", pType: SqlDbType.NVarChar, pValue: request.DistChannelDescription), + SqlHelperExtension.CreateInParam(pName: "@DistChannel", pType: SqlDbType.NVarChar, pValue: request.DistChannel), + SqlHelperExtension.CreateInParam(pName: "@DistChannelDescription", pType: SqlDbType.NVarChar, pValue: request.DistChannelDescription), - SqlHelperExtension.CreateInParam(pName: "@Division", pType: SqlDbType.NVarChar, pValue: request.Division), - SqlHelperExtension.CreateInParam(pName: "@DivisionDescription", pType: SqlDbType.NVarChar, pValue: request.DivisionDescription), + SqlHelperExtension.CreateInParam(pName: "@Division", pType: SqlDbType.NVarChar, pValue: request.Division), + SqlHelperExtension.CreateInParam(pName: "@DivisionDescription", pType: SqlDbType.NVarChar, pValue: request.DivisionDescription), - SqlHelperExtension.CreateInParam(pName: "@RegionAreaCode", pType: SqlDbType.NVarChar, pValue: request.RegionAreaCode), - SqlHelperExtension.CreateInParam(pName: "@RegionAreaDescription", pType: SqlDbType.NVarChar, pValue: request.RegionAreaDescription), + SqlHelperExtension.CreateInParam(pName: "@RegionAreaCode", pType: SqlDbType.NVarChar, pValue: request.RegionAreaCode), + SqlHelperExtension.CreateInParam(pName: "@RegionAreaDescription", pType: SqlDbType.NVarChar, pValue: request.RegionAreaDescription), - SqlHelperExtension.CreateInParam(pName: "@AreaAVSalesUnitCode", pType: SqlDbType.NVarChar, pValue: request.AreaAVSalesUnitCode), - SqlHelperExtension.CreateInParam(pName: "@AreaAVSalesUnitDescription", pType: SqlDbType.NVarChar, pValue: request.AreaAVSalesUnitDescription), + SqlHelperExtension.CreateInParam(pName: "@AreaAVSalesUnitCode", pType: SqlDbType.NVarChar, pValue: request.AreaAVSalesUnitCode), + SqlHelperExtension.CreateInParam(pName: "@AreaAVSalesUnitDescription", pType: SqlDbType.NVarChar, pValue: request.AreaAVSalesUnitDescription), - SqlHelperExtension.CreateInParam(pName: "@Territory", pType: SqlDbType.NVarChar, pValue: request.Territory), - SqlHelperExtension.CreateInParam(pName: "@TerritoryDescription", pType: SqlDbType.NVarChar, pValue: request.TerritoryDescription), + SqlHelperExtension.CreateInParam(pName: "@Territory", pType: SqlDbType.NVarChar, pValue: request.Territory), + SqlHelperExtension.CreateInParam(pName: "@TerritoryDescription", pType: SqlDbType.NVarChar, pValue: request.TerritoryDescription), - SqlHelperExtension.CreateInParam(pName: "@Plant", pType: SqlDbType.NVarChar, pValue: request.Plant), - SqlHelperExtension.CreateInParam(pName: "@PlantDescription", pType: SqlDbType.NVarChar, pValue: request.PlantDescription), + SqlHelperExtension.CreateInParam(pName: "@Plant", pType: SqlDbType.NVarChar, pValue: request.Plant), + SqlHelperExtension.CreateInParam(pName: "@PlantDescription", pType: SqlDbType.NVarChar, pValue: request.PlantDescription), - SqlHelperExtension.CreateInParam(pName: "@Status", pType: SqlDbType.NChar, pValue: request.Status), - SqlHelperExtension.CreateInParam(pName: "@StatusDescription", pType: SqlDbType.NVarChar, pValue: request.StatusDescription) + SqlHelperExtension.CreateInParam(pName: "@Status", pType: SqlDbType.NChar, pValue: request.Status), + SqlHelperExtension.CreateInParam(pName: "@StatusDescription", pType: SqlDbType.NVarChar, pValue: request.StatusDescription) ]; _ = tc.ExecuteNonQuerySp(spName: "dbo.InsertEmployee", parameterValues: p); diff --git a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs index 13cb407..e850d91 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI.Services/Services/Systems/UserService.cs @@ -1,28 +1,33 @@ using Ease.NetCore.DataAccess; using Ease.NetCore.DataAccess.SQL; using Ease.NetCore.Utility; +using Microsoft.Data.SqlClient; +using Microsoft.Extensions.Options; using OnlineSalesAutoCrop.CoreAPI.Models; using OnlineSalesAutoCrop.CoreAPI.Models.Global; using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems; +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Common; +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations; +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp; using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Systems; +using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp; using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems; +using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth; using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems; -using Microsoft.Data.SqlClient; -using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; -using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations; namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems { - public class UserService(IOptions settings, IOptions menuSettings) : IUserService + public class UserService(IOptions settings, IOptions menuSettings, IRefreshTokenService refreshTokenService) : IUserService { private readonly AppSettings _settings = settings?.Value; private readonly MenuSettings _menuSettings = menuSettings?.Value; + private readonly IRefreshTokenService _refreshTokenService = refreshTokenService; /// /// @@ -348,6 +353,192 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems } + public async Task LoginAsync(MobileAppAuthRequest request, string ipAddress) + { + AppAuthUserResponse response = null; + try + { + string password = EncryptPassword(password: request.Password); + User user = null; + using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true); + try + { + DateTime sysDate = DateTime.Today.Date; + string appVer = string.Empty, alParams = string.Empty; + int maxTryCount = 5, lockTime = 1; + + bool initialPassword = false; + + #region Read User data using authentication data + + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.NVarChar, pValue: request.LoginId) + ]; + + using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetUserByLogin", parameterValues: p)) + { + if (dr.Read()) + { + user = new User(); + { + user.UserId = dr.GetInt32(0); + user.Password = dr.GetString(1); + user.LoginId = dr.GetString(2); + user.UserName = dr.GetString(3); + user.Status = (EnumStatus)dr.GetInt32(4); + user.MobileNo = dr.GetString(5); + user.EmailAddress = dr.GetString(6); + user.AuthKey = dr.GetString(7); + user.AuthValue = dr.GetString(8); + user.IsLocked = dr.GetBoolean(9); + user.LoginStatus = EnumLoginStatus.Success; + user.UserRoleType = (UserRoleTypeEnum)dr.GetInt32(10); + user.EmployeeNumber = dr.GetString(11); + user.DesignationCode = dr.GetString(12); + user.DesignationName = dr.GetString(13); + }; + + initialPassword = dr.GetInt32(14) > 0; + } + dr.Close(); + } + + if (user is null) + { + tc.End(); + throw new InvalidOperationException($"User not found with login: {request.LoginId}"); + } + + if (!password.Equals(user.Password)) + { + tc.End(); + throw new InvalidOperationException($"Password mismatch for login: {request.LoginId}"); + } + + + #endregion + + #region If the user was locked, try set set unlock if Time expired + + if (!request.LoginId.ToLower().Equals(User.SuperUser_LoginId) && user.IsLocked) + { + int isSuccessful = 0; + p = + [ + SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: request.LoginId, size: 30), + SqlHelperExtension.CreateInParam(pName: "@LockTime", pType: SqlDbType.Int, pValue: lockTime), + SqlHelperExtension.CreateOutParam(pName: "@IsSuccessful", pType: SqlDbType.Int, pValue: isSuccessful), + ]; + _ = tc.ExecuteNonQuerySp(spName: "dbo.DoUnlockUser", parameterValues: p); + if (p[2] != null && p[2].Value != null && p[2].Value != DBNull.Value) + isSuccessful = Convert.ToInt32(p[2].Value); + + if (isSuccessful == 1) + user.IsLocked = false; + } + + #endregion + + #region Keep log for unauthrise access and Set user lock if exceeds max try + + if (!request.LoginId.ToLower().Equals(User.SuperUser_LoginId) && maxTryCount > 0 && user.LoginStatus == EnumLoginStatus.Unsuccessful) + { + int remainsTry = 0; + DateTime? nextLoginTime = null; + string tryLoginInfo = $"{request.LoginId}~{password}~13"; + p = + [ + SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: request.LoginId, size: 30), + SqlHelperExtension.CreateInParam(pName: "@TryLoginInfo", pType: SqlDbType.VarChar, pValue: tryLoginInfo, size: 100), + SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20), + SqlHelperExtension.CreateInParam(pName: "@MaxTryCount", pType: SqlDbType.SmallInt, pValue: maxTryCount), + SqlHelperExtension.CreateInParam(pName: "@LockTime", pType: SqlDbType.Int, pValue: lockTime), + SqlHelperExtension.CreateOutParam(pName: "@RemainingTry", pType: SqlDbType.SmallInt, pValue: remainsTry), + SqlHelperExtension.CreateOutParam(pName: "@NextLoginTime", pType: SqlDbType.DateTime, pValue: nextLoginTime), + ]; + _ = tc.ExecuteNonQuerySp(spName: "dbo.LogUnauthorizeAccess", parameterValues: p); + if (p[5] != null && p[5].Value != null && p[5].Value != DBNull.Value) + remainsTry = Convert.ToInt32(p[5].Value); + + if (p[6] != null && p[6].Value != null && p[6].Value != DBNull.Value) + nextLoginTime = Convert.ToDateTime(p[6].Value); + + if (remainsTry <= 0) + { + user.IsLocked = true; + if (lockTime <= 0) + { + user.NextLoginTime = nextLoginTime; + user.UnsuccessfulMsg = "Please contact with Head office to Unlock"; + } + else + { + user.NextLoginTime = nextLoginTime; + user.UnsuccessfulMsg = $"You can Login after {user.NextLoginTime:dd-MMM-yyyy H:mm:ss}"; + } + } + else + { + user.UnsuccessfulMsg = $"{remainsTry} More attempt{(remainsTry > 1 ? "s" : "")} remaining"; + } + } + + #endregion + + + #region If login successful and user is active read module id for this user + + if (user.LoginStatus == EnumLoginStatus.Success && user.Status == EnumStatus.Authorized && !user.IsLocked) + { + p = + [ + SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: user.UserId), + SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: user.LoginId, size: 30), + SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20), + SqlHelperExtension.CreateInParam(pName: "@LoginTime", pType: SqlDbType.DateTime, pValue: DateTime.Now), + SqlHelperExtension.CreateInParam(pName: "@LogoutTime", pType: SqlDbType.DateTime, pValue: null) + ]; + + _ = tc.ExecuteNonQuerySp(spName: "dbo.SaveAccessLog", parameterValues: p); + } + + #endregion + + response = new() + { + UserId = user.UserId, + Name = user.UserName, + LoginId = user.LoginId, + Email = user.EmailAddress, + MobileNumber = user.MobileNo, + IsLocked = user.IsLocked, + UserRole = user.UserRoleType.Value, + LoginStatus = user.LoginStatus, + IsAuthorized = EnumStatus.Authorized == user.Status, + EmployeeNumber = user.EmployeeNumber, + DesignationCode = user.DesignationCode, + DesignationName = user.DesignationName, + IsInitialPassword = initialPassword + }; + + tc.End(); + } + catch (Exception ie) + { + tc?.HandleError(); + + throw DBCustomError.GenerateCustomError(ie); + } + } + catch (Exception e) + { + throw new InvalidOperationException(e.Message, e); + } + + return response; + } + public async Task IntegrationLoginAsync(IntegrationLoginRequest request, string ipAddress, bool checkPwd) { User user = null; @@ -365,7 +556,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems #region Read User data using authentication data string commandText = SQLParser.MakeSQL("SELECT UserId,Password,LoginId, UserName, Status, ISNULL(MobileNo,'')MobileNo ,ISNULL( EmailAddress,'')EmailAddress," - + " ISNULL(AuthKey,'') AuthKey, ISNULL(AuthValue,'')AuthValue, IsLocked FROM Users WHERE LoginID=%s", request.LoginId); + + " ISNULL(AuthKey,'') AuthKey, ISNULL(AuthValue,'')AuthValue, IsLocked FROM Users WHERE LoginID=%s and UserPlatformId=1 ", request.LoginId); using (IDataReader dr = tc.ExecuteReader(commandText: commandText)) { @@ -505,6 +696,8 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems return user; } + + /// /// /// @@ -2231,6 +2424,206 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems return returnValue; } + public async Task LogoutAsync(AuthLogoutRequest request, string ipAddress) + { + AuthLogoutResponse response = null; + try + { + User user = null; + using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true); + try + { + DateTime sysDate = DateTime.Today.Date; + string appVer = string.Empty, alParams = string.Empty; + + #region Read User data using authentication data + + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.NVarChar, pValue: request.LoginId) + ]; + + using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetUserByLogin", parameterValues: p)) + { + if (dr.Read()) + { + user = new User(); + { + user.UserId = dr.GetInt32(0); + user.Password = dr.GetString(1); + user.LoginId = dr.GetString(2); + user.UserName = dr.GetString(3); + user.Status = (EnumStatus)dr.GetInt32(4); + user.MobileNo = dr.GetString(5); + user.EmailAddress = dr.GetString(6); + user.AuthKey = dr.GetString(7); + user.AuthValue = dr.GetString(8); + user.IsLocked = dr.GetBoolean(9); + user.LoginStatus = EnumLoginStatus.Success; + user.UserRoleType = (UserRoleTypeEnum)dr.GetInt32(10); + user.EmployeeNumber = dr.GetString(11); + user.DesignationCode = dr.GetString(12); + user.DesignationName = dr.GetString(13); + } + ; + } + dr.Close(); + } + + if (user is null) + { + tc.End(); + throw new InvalidOperationException($"User not found with login: {request.LoginId}"); + } + + + #endregion + + + #region If login successful and user is active read module id for this user + + if (user.LoginStatus == EnumLoginStatus.Success && user.Status == EnumStatus.Authorized && !user.IsLocked) + { + p = + [ + SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: user.UserId), + SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.VarChar, pValue: user.LoginId, size: 30), + SqlHelperExtension.CreateInParam(pName: "@IpAddress", pType: SqlDbType.VarChar, pValue: ipAddress, size: 20), + SqlHelperExtension.CreateInParam(pName: "@LoginTime", pType: SqlDbType.DateTime, pValue: null), + SqlHelperExtension.CreateInParam(pName: "@LogoutTime", pType: SqlDbType.DateTime, pValue: DateTime.Now) + ]; + + _ = tc.ExecuteNonQuerySp(spName: "dbo.SaveAccessLog", parameterValues: p); + } + + #endregion + + await _refreshTokenService.InvalidRefreshTokenAsync(tc, user.UserId); + + response = new() + { + UserId = user.UserId, + LoginId = user.LoginId, + IsSuccess = true + }; + + tc.End(); + } + catch (Exception ie) + { + tc?.HandleError(); + + throw DBCustomError.GenerateCustomError(ie); + } + } + catch (Exception e) + { + throw new InvalidOperationException(e.Message, e); + } + + return response; + } + + public async Task ChangePasswordAsync(AppUserChangePasswordRequest request, string ipAddress) + { + AppChangePasswordResponse response = null; + try + { + string password = EncryptPassword(password: request.Password); + User user = null; + using TransactionContext tc = await TransactionContext.BeginAsync(_settings.DefaultConnection.ConnectionNode, true); + try + { + DateTime sysDate = DateTime.Today.Date; + string appVer = string.Empty, alParams = string.Empty; + + #region Read User data using authentication data + + SqlParameter[] p = + [ + SqlHelperExtension.CreateInParam(pName: "@LoginId", pType: SqlDbType.NVarChar, pValue: request.LoginId) + ]; + + using (IDataReader dr = await tc.ExecuteReaderSpAsync("dbo.GetUserByLogin", parameterValues: p)) + { + if (dr.Read()) + { + user = new User(); + { + user.UserId = dr.GetInt32(0); + user.Password = dr.GetString(1); + user.LoginId = dr.GetString(2); + user.UserName = dr.GetString(3); + user.Status = (EnumStatus)dr.GetInt32(4); + user.MobileNo = dr.GetString(5); + user.EmailAddress = dr.GetString(6); + user.AuthKey = dr.GetString(7); + user.AuthValue = dr.GetString(8); + user.IsLocked = dr.GetBoolean(9); + user.LoginStatus = EnumLoginStatus.Success; + user.UserRoleType = (UserRoleTypeEnum)dr.GetInt32(10); + user.EmployeeNumber = dr.GetString(11); + user.DesignationCode = dr.GetString(12); + user.DesignationName = dr.GetString(13); + } + ; + } + dr.Close(); + } + + if (user is null) + { + tc.End(); + throw new InvalidOperationException($"User not found with login: {request.LoginId}"); + } + + if (!password.Equals(user.Password)) + { + tc.End(); + throw new InvalidOperationException($"Password mismatch for login: {request.LoginId}"); + } + + + #endregion + + string newPassword = EncryptPassword(request.ConfirmPassword); + + + p = + [ + SqlHelperExtension.CreateInParam(pName: "@UserId", pType: SqlDbType.Int, pValue: user.UserId), + SqlHelperExtension.CreateInParam(pName: "@Password", pType: SqlDbType.VarChar, pValue: newPassword, size: 30), + SqlHelperExtension.CreateInParam(pName: "@LastPassword", pType: SqlDbType.VarChar, pValue: password, size: 20), + SqlHelperExtension.CreateInParam(pName: "@LastPasswordChnageDate", pType: SqlDbType.DateTime, pValue: DateTime.Now) + ]; + + _ = tc.ExecuteNonQuerySp(spName: "dbo.ChangePassword", parameterValues: p); + + response = new() + { + UserId = user.UserId, + LoginId = user.LoginId, + IsSuccess = true + }; + + tc.End(); + } + catch (Exception ie) + { + tc?.HandleError(); + + throw DBCustomError.GenerateCustomError(ie); + } + } + catch (Exception e) + { + throw new InvalidOperationException(e.Message, e); + } + + return response; + } + + /// /// /// @@ -2635,12 +3028,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Services.Services.Systems throw new NotImplementedException(); } - - + #endregion } - - internal class UserSearchResponse - { - } } \ No newline at end of file diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/IntegretionApi/IntegrationAuthController.cs b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Integretion/IntegrationAuthController.cs similarity index 98% rename from Api/OnlineSalesAutoCrop.CoreAPI/Controllers/IntegretionApi/IntegrationAuthController.cs rename to Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Integretion/IntegrationAuthController.cs index b3c59be..2719f21 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/IntegretionApi/IntegrationAuthController.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Integretion/IntegrationAuthController.cs @@ -230,7 +230,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers loginReponse.AccessToken = userToken; loginReponse.RefreshToken = refreshToken.RefreshToken; loginReponse.AccessTokenExpiry = refreshToken.ExpireTime; - return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, loginReponse); + return StatusCode(StatusCodes.Status200OK, loginReponse); } catch (Exception ex) @@ -295,7 +295,7 @@ namespace OnlineSalesAutoCrop.CoreAPI.Controllers response.AccessToken = userToken; response.RefreshToken = refreshToken; - return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, response); + return StatusCode(StatusCodes.Status200OK, response); } catch (Exception ex) diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/IntegretionApi/IntegrationController.cs b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Integretion/IntegrationController.cs similarity index 99% rename from Api/OnlineSalesAutoCrop.CoreAPI/Controllers/IntegretionApi/IntegrationController.cs rename to Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Integretion/IntegrationController.cs index 1e03b03..c026c16 100644 --- a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/IntegretionApi/IntegrationController.cs +++ b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Integretion/IntegrationController.cs @@ -10,7 +10,7 @@ using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Integrations; using System; using System.Threading.Tasks; -namespace OnlineSalesAutoCrop.CoreAPI.Controllers.IntegretionApi +namespace OnlineSalesAutoCrop.CoreAPI.Controllers { /// /// diff --git a/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Mobile/MobileAuthController.cs b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Mobile/MobileAuthController.cs new file mode 100644 index 0000000..d2a291b --- /dev/null +++ b/Api/OnlineSalesAutoCrop.CoreAPI/Controllers/Mobile/MobileAuthController.cs @@ -0,0 +1,334 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using OnlineSalesAutoCrop.CoreAPI; +using OnlineSalesAutoCrop.CoreAPI.Models; +using OnlineSalesAutoCrop.CoreAPI.Models.Global; +using OnlineSalesAutoCrop.CoreAPI.Models.Objects.Systems; +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Common; +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.Integrations; +using OnlineSalesAutoCrop.CoreAPI.Models.Requests.MobileApp; +using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Integrations; +using OnlineSalesAutoCrop.CoreAPI.Models.Responses.MobileApp; +using OnlineSalesAutoCrop.CoreAPI.Models.Responses.Systems; +using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth; +using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems; +using System; +using System.DirectoryServices; +using System.IdentityModel.Tokens.Jwt; +using System.Runtime.Versioning; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace OnlineSalesAutoCrop.CoreAPI.Controllers +{ + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + [Authorize] + [ApiController] + [ApiVersion("1.0")] + [ValidateAntiForgeryToken] + [Route("api/mobile/v{version:apiVersion}/auth")] + public class MobileAuthController(IUserService service, IOptions appSettings, IEaseCache cache, ILogger logger, + IRefreshTokenService refreshTokenService) : ControllerBase + { + private readonly ILogger _logger = logger; + private readonly IEaseCache _cache = cache; + private readonly IUserService _service = service; + private readonly IRefreshTokenService _refreshTokenService = refreshTokenService; + private readonly AppSettings _appSettings = appSettings?.Value; + private readonly DateTimeOffset _options = Helper.CreateEaseCacheOptions(); + + + /// + /// Login using your credential data retrieve from SqlServer + /// + /// + /// + /// + /// If login successful ValidUser: true + /// If login successful Return ValidUser: true and UserName: not empty + [HttpPost("Login")] + [AllowAnonymous] + [IgnoreAntiforgeryToken] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MobileAppAuthRequest))] + public async Task Login([FromBody] MobileAppAuthRequest request) + { + ArgumentNullException.ThrowIfNull(request); + + AppAuthUserResponse loginReponse = new(); + + if (string.IsNullOrEmpty(request.LoginId)) + { + loginReponse.ReturnStatus = StatusCodes.Status417ExpectationFailed; + loginReponse.ReturnMessage.Add("Login ID is required."); + return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse); + } + + if (string.IsNullOrEmpty(request.Password)) + { + loginReponse.ReturnStatus = StatusCodes.Status417ExpectationFailed; + loginReponse.ReturnMessage.Add("Password is required."); + return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse); + } + + + string ipAddress = string.Empty; + try + { + #region Decrypt LoginID + + string cipherSecretKey = GlobalFunctions.ConvertFromBase64String(_appSettings.CipherSecretKey); + + + #endregion + + bool checkPwd = true; + + ipAddress = Request.HttpContext.GetIpAddress(); + loginReponse = await _service.LoginAsync(request: request, ipAddress: ipAddress); + + if (loginReponse == null || loginReponse.UserId == 0) + { + loginReponse.LoginStatus = EnumLoginStatus.Error; + loginReponse.ReturnStatus = StatusCodes.Status403Forbidden; + loginReponse.ReturnMessage.Add(checkPwd ? "Login ID/Password is invalid." : "You are not Authorized to login into the System"); + return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse); + } + + if (loginReponse.LoginStatus == EnumLoginStatus.Unsuccessful) + { + loginReponse.LoginStatus = loginReponse.LoginStatus; + loginReponse.ReturnStatus = StatusCodes.Status403Forbidden; + loginReponse.ReturnMessage.Add(checkPwd ? $"Login ID/Password is invalid for {request.LoginId}." : "You are not Authorized to login into the System"); + return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse); + } + + + if (loginReponse.IsLocked) + { + loginReponse.LoginStatus = loginReponse.LoginStatus; + loginReponse.ReturnStatus = StatusCodes.Status403Forbidden; + loginReponse.ReturnMessage.Add($"Your Account is locked . Please try again after few minutes"); + return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse); + } + + if (!loginReponse.IsAuthorized ) + { + loginReponse.LoginStatus = loginReponse.LoginStatus; + loginReponse.ReturnStatus = StatusCodes.Status403Forbidden; + loginReponse.ReturnMessage.Add("You are not Authorized to Login into the System, Please contact with System Administrator."); + return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse); + } + + loginReponse.ReturnStatus = StatusCodes.Status200OK; + string pwdSecretKey = GlobalFunctions.ConvertFromBase64String(_appSettings.PwdSecretKey); + string userPwd = Ease.NetCore.Utility.Global.CipherFunctions.EncryptByAES(privateKey: pwdSecretKey, publicKey: pwdSecretKey, data: request.Password); + byte[] key = Encoding.ASCII.GetBytes(_appSettings.JwtCryptoKey); + JwtSecurityTokenHandler tokenHandler = new(); + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity( + [ + Helper.CreateClaim("LoginId", loginReponse.LoginId), + Helper.CreateClaim("Email", loginReponse.Email), + Helper.CreateClaim("Mobile", $"{loginReponse.MobileNumber}"), + Helper.CreateClaim("HashKey", Guid.NewGuid().ToString()) + ]), + Expires = DateTime.UtcNow.AddHours(12), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature) + }; + + SecurityToken token = tokenHandler.CreateToken(tokenDescriptor); + string userToken = tokenHandler.WriteToken(token); + GenerateRefreshTokenRequest refreshTokenRequest = new GenerateRefreshTokenRequest() + { + UserId = loginReponse.UserId, + IpAddress = ipAddress, + RawRefreshToken = string.Empty + }; + + var refreshToken = await _refreshTokenService.GenerateRefreshTokenByUserAsync(refreshTokenRequest); + + //If token length is greater than or equal to 4096 (4KB) then return error + //because cookie can not store more than 4KB data and we are storing this token in cookie for authentication + if (userToken.Length >= 4096) //4Kb + { + loginReponse.LoginStatus = loginReponse.LoginStatus; + loginReponse.ReturnStatus = StatusCodes.Status431RequestHeaderFieldsTooLarge; + loginReponse.ReturnMessage.Add("Authentication Token is too large for cookie."); + return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, loginReponse); + } + + loginReponse.ReturnStatus = loginReponse.ReturnStatus; + loginReponse.ReturnMessage = loginReponse.ReturnMessage; + loginReponse.LoginId = loginReponse.LoginId; + loginReponse.AccessToken = userToken; + loginReponse.RefreshToken = refreshToken.RefreshToken; + loginReponse.AccessTokenExpiryTime = DateTime.UtcNow.AddHours(12); + loginReponse.RefreshTokenExpiryTime = refreshToken.ExpireTime; + return StatusCode(StatusCodes.Status200OK, loginReponse); + + } + catch (Exception ex) + { + string msg = $"{request?.LoginId}~{ipAddress}"; + _logger.LogError(exception: ex, message: msg); + loginReponse.ReturnStatus = StatusCodes.Status500InternalServerError; + loginReponse.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message); + return StatusCode(StatusCodes.Status500InternalServerError, loginReponse); + } + } + + + /// + /// Login using your credential data retrieve from SqlServer + /// + /// + /// + /// + /// If login successful ValidUser: true + /// If login successful Return ValidUser: true and UserName: not empty + [HttpPost("RefreshToken")] + [AllowAnonymous] + [IgnoreAntiforgeryToken] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IntegrationLoginResponse))] + public async Task GetRefreshToken([FromBody] IntegrationRefreshTokenRequest request) + { + IntegrationRrefreshTokenResponse response = new(); + try + { + var userRefreshToken = await _refreshTokenService.GetUserByRefreshTokenAsync(request.RefreshToken); + if (!userRefreshToken.IsActive) + { + string msg = $"Refresh Token is not active: {request?.RefreshToken}"; + _logger.LogError(message: msg); + response.ReturnStatus = StatusCodes.Status401Unauthorized; + response.ReturnMessage.Add(msg); + return StatusCode(StatusCodes.Status401Unauthorized, response); + } + + byte[] key = Encoding.ASCII.GetBytes(_appSettings.JwtCryptoKey); + JwtSecurityTokenHandler tokenHandler = new(); + var tokenDescriptor = new SecurityTokenDescriptor + { + Subject = new ClaimsIdentity( + [ + Helper.CreateClaim("LoginId", userRefreshToken.LoginId), + Helper.CreateClaim("Email", userRefreshToken.EmailAddress), + Helper.CreateClaim("AuthKey", $"{userRefreshToken.AuthKey}"), + Helper.CreateClaim("HashKey", Guid.NewGuid().ToString()) + ]), + Expires = DateTime.UtcNow.AddHours(12), + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha512Signature) + }; + + SecurityToken token = tokenHandler.CreateToken(tokenDescriptor); + string userToken = tokenHandler.WriteToken(token); + + string ipAddress = Request.HttpContext.GetIpAddress(); + var refreshToken = await _refreshTokenService.GenerateRefreshTokenAsync(request.RefreshToken, ipAddress); + + response.AccessToken = userToken; + response.RefreshToken = refreshToken; + + return StatusCode(StatusCodes.Status200OK, response); + + } + catch (Exception ex) + { + string msg = $"{request?.RefreshToken}"; + _logger.LogError(exception: ex, message: msg); + response.ReturnStatus = StatusCodes.Status500InternalServerError; + response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message); + return StatusCode(StatusCodes.Status500InternalServerError, response); + } + } + + + /// + /// Login using your credential data retrieve from SqlServer + /// + /// + /// + /// + /// If login successful ValidUser: true + /// If login successful Return ValidUser: true and UserName: not empty + [HttpPost("Logout")] + [Authorize] + [IgnoreAntiforgeryToken] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AuthLogoutResponse))] + public async Task Logout([FromBody] AuthLogoutRequest request) + { + AuthLogoutResponse response = new(); + try + { + string ipAddress = Request.HttpContext.GetIpAddress(); + response = await _service.LogoutAsync(request, ipAddress); + + response.ReturnStatus = StatusCodes.Status200OK; + response.ReturnMessage.Add("Successfully Logout"); + return StatusCode(StatusCodes.Status200OK, response); + + } + catch (Exception ex) + { + string msg = $"Exception occur on logout {request?.LoginId}"; + _logger.LogError(exception: ex, message: msg); + response.ReturnStatus = StatusCodes.Status500InternalServerError; + response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message); + return StatusCode(StatusCodes.Status500InternalServerError, response); + } + } + + + /// + /// Login using your credential data retrieve from SqlServer + /// + /// + /// + /// + /// If login successful ValidUser: true + /// If login successful Return ValidUser: true and UserName: not empty + [HttpPost("ChangePassword")] + [Authorize] + [IgnoreAntiforgeryToken] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AppChangePasswordResponse))] + public async Task ChangePassword([FromBody] AppUserChangePasswordRequest request) + { + AppChangePasswordResponse response = new(); + try + { + string ipAddress = Request.HttpContext.GetIpAddress(); + response = await _service.ChangePasswordAsync(request, ipAddress); + + response.ReturnStatus = StatusCodes.Status200OK; + response.ReturnMessage.Add("Successfully Changed the Password"); + return StatusCode(StatusCodes.Status200OK, response); + + } + catch (Exception ex) + { + string msg = $"Exception occur on logout {request?.LoginId}"; + _logger.LogError(exception: ex, message: msg); + response.ReturnStatus = StatusCodes.Status500InternalServerError; + response.ReturnMessage.Add(ex.InnerException != null ? ex.InnerException.Message : ex.Message); + return StatusCode(StatusCodes.Status500InternalServerError, response); + } + } + } +} \ No newline at end of file