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; 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.Models; 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.Models.Responses.Systems; using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Auth; using OnlineSalesAutoCrop.CoreAPI.Services.Contracts.Systems; namespace OnlineSalesAutoCrop.CoreAPI.Controllers { /// /// /// /// /// /// /// /// /// /// [Authorize] [ApiController] [ApiVersion("1.0")] [ValidateAntiForgeryToken] [Route("api/v{version:apiVersion}/IntegrationAuth")] public class IntegrationAuthController(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(); [HttpGet] [Route("GetServerDateTime")] [AllowAnonymous] [IgnoreAntiforgeryToken] public async Task GetServerDateTime() { return Ok(DateTime.Now.ToString()); } /// /// 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(IntegrationLoginResponse))] public async Task Login([FromBody] IntegrationLoginRequest request) { ArgumentNullException.ThrowIfNull(request); IntegrationLoginResponse loginReponse = new(); LoginResponse response = 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; #region If AD (Active Directory) authentication is enabled do validate if (_appSettings.ADConfig.Enabled) { checkPwd = false; #pragma warning disable CA1416 // Validate platform compatibility int adLoginStatus = GetADLoginStatus(loginId: request.LoginId, password: request.Password); #pragma warning restore CA1416 // Validate platform compatibility if (adLoginStatus != 1) { response.LoginStatus = EnumLoginStatus.Unsuccessful; response.ReturnStatus = StatusCodes.Status403Forbidden; if (adLoginStatus == 2) response.ReturnMessage.Add("Active Directory User is DISABLED."); else if (adLoginStatus == 3) response.ReturnMessage.Add("Active Directory User's Password has EXPIRED."); else response.ReturnMessage.Add("Active Directory User/Password is INVALID."); return StatusCode(StatusCodes.Status417ExpectationFailed, response); } } #endregion ipAddress = Request.HttpContext.GetIpAddress(); User user = await _service.IntegrationLoginAsync(request: request, ipAddress: ipAddress, checkPwd: checkPwd); if (user == null || user.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 (user.LoginStatus == EnumLoginStatus.Unsuccessful) { string usm = string.Empty; if (user != null && !string.IsNullOrEmpty(user.UnsuccessfulMsg)) usm = $" ({user.UnsuccessfulMsg})"; loginReponse.LoginStatus = user.LoginStatus; loginReponse.ReturnStatus = StatusCodes.Status403Forbidden; loginReponse.ReturnMessage.Add(checkPwd ? $"Login ID/Password is invalid{usm}." : "You are not Authorized to login into the System"); return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse); } if (user.IsLocked) { loginReponse.LoginStatus = user.LoginStatus; loginReponse.ReturnStatus = StatusCodes.Status403Forbidden; if (!user.NextLoginTime.HasValue) loginReponse.ReturnMessage.Add("You are locked, please contact Head office."); else loginReponse.ReturnMessage.Add($"You can Login after {user.NextLoginTime:dd-MMM-yyyy H:mm:ss}"); return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse); } if (user.Status != EnumStatus.Authorized) { loginReponse.LoginStatus = user.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); } response.ValidUser = user.LoginStatus == EnumLoginStatus.Success; if (!response.ValidUser) { loginReponse.LoginStatus = user.LoginStatus; loginReponse.ReturnStatus = StatusCodes.Status403Forbidden; loginReponse.ReturnMessage.Add("Unknown error."); return StatusCode(StatusCodes.Status417ExpectationFailed, loginReponse); } response.Map(user); response.ValidUser = true; response.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", user.LoginId), Helper.CreateClaim("Email", user.EmailAddress), Helper.CreateClaim("AuthKey", $"{user.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); GenerateRefreshTokenRequest refreshTokenRequest = new GenerateRefreshTokenRequest() { UserId = user.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 = user.LoginStatus; loginReponse.ReturnStatus = StatusCodes.Status431RequestHeaderFieldsTooLarge; loginReponse.ReturnMessage.Add("Authentication Token is too large for cookie."); return StatusCode(StatusCodes.Status431RequestHeaderFieldsTooLarge, loginReponse); } loginReponse.ReturnStatus = response.ReturnStatus; loginReponse.ReturnMessage = response.ReturnMessage; loginReponse.LoginId = response.LoginId; loginReponse.AccessToken = userToken; loginReponse.RefreshToken = refreshToken.RefreshToken; loginReponse.AccessTokenExpiry = 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); } return Ok(); } /// /// /// /// /// /// [SupportedOSPlatform("windows")] private int GetADLoginStatus(string loginId, string password) { try { const string displayNameAttribute = "DisplayName"; const string samAccountNameAttribute = "SAMAccountName"; const string userAccountControlAttribute = "useraccountcontrol"; string username = (string.IsNullOrEmpty(_appSettings.ADConfig.Domain) || string.IsNullOrWhiteSpace(_appSettings.ADConfig.Domain)) ? loginId : $"{loginId}@{_appSettings.ADConfig.Domain}"; using DirectoryEntry entry = new(path: _appSettings.ADConfig.Path, username: username, password: password); using DirectorySearcher searcher = new(searchRoot: entry); searcher.Filter = $"({samAccountNameAttribute}={loginId})"; searcher.PropertiesToLoad.Add(value: displayNameAttribute); searcher.PropertiesToLoad.Add(value: samAccountNameAttribute); searcher.PropertiesToLoad.Add(value: userAccountControlAttribute); var result = searcher.FindOne(); if (result == null) return 0; ResultPropertyValueCollection displayName = result.Properties[name: displayNameAttribute]; ResultPropertyValueCollection samAccountName = result.Properties[name: samAccountNameAttribute]; ResultPropertyValueCollection userAccountControl = result.Properties[name: userAccountControlAttribute]; int uacFlag = (userAccountControl != null && userAccountControl.Count > 0) ? Convert.ToInt32(userAccountControl[0]) : 0; if ((uacFlag & 0x000002) == 0x000002) //Disabled return 2; else if ((uacFlag & 0x800000) == 0x800000) //Password expired return 3; if (displayName != null && displayName.Count > 0 && samAccountName != null && samAccountName.Count > 0) return 1; else return 0; } catch (Exception ex) { _logger.LogError(ex); return 0; } } } }