| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- using Common;
- using Common.Constant;
- using Common.Http;
- using Common.Logic;
- //using Esim.Apis.Logic;
- using Esim.Apis.Singleton;
- using Database;
- using Database.Database;
- using log4net;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.VisualBasic;
- using Newtonsoft.Json;
- using System;
- using System.Threading.Tasks;
- using System.Xml.Serialization;
- namespace Esim.Apis.Business
- {
- public class UserBusinessImpl : IUserBusiness
- {
- private static readonly log4net.ILog log = log4net.LogManager.GetLogger(
- typeof(UserBusinessImpl)
- );
- private ModelContext dbContext;
- IConfiguration configuration;
- public UserBusinessImpl(ModelContext _dbContext, IConfiguration _configuration)
- {
- dbContext = _dbContext;
- configuration = _configuration;
- }
- private string GetParameter(string key)
- {
- return configuration.GetSection(key).Value ?? "";
- }
- /// <summary>
- /// Request OTP to be sent to email
- /// </summary>
- public async Task<IActionResult> RequestOtp(HttpRequest httpRequest, RequestOtpReq request)
- {
- var url = httpRequest.Path;
- var json = JsonConvert.SerializeObject(request);
- log.Debug("URL: " + url + " => Request: " + json);
- try
- {
- if (string.IsNullOrEmpty(request.email))
- {
- return ApiResponseHelper.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.RequiredFieldMissing,
- ConfigManager.Instance.GetConfigWebValue("EMAIL_REQUIRED"),
- new { }
- );
- }
- // Generate 6-digit OTP (fixed 111111 for test account abc@gmail.com)
- bool isTestAccount = request.email.ToLower() == "abc@gmail.com";
- string otpCode = isTestAccount ? "111111" : GenerateOtp();
- // Check if customer exists, if not create new
- var customer = dbContext.CustomerInfos
- .Where(c => c.Email == request.email)
- .FirstOrDefault();
- decimal? customerId = customer?.Id;
- if (customer == null)
- {
- // Create new customer record - manually get ID from Oracle sequence
- var newCustomerId = await Database.DbLogic.GenIdAsync(dbContext, "CUSTOMER_INFO_SEQ");
-
- // Extract name from email (part before @)
- string emailUsername = request.email.Split('@')[0];
-
- var newCustomer = new CustomerInfo
- {
- Id = newCustomerId,
- Email = request.email,
- SurName = emailUsername,
- LastName = emailUsername,
- Status = true,
- IsVerified = false,
- CreatedDate = DateTime.Now,
- LastUpdate = DateTime.Now
- };
- dbContext.CustomerInfos.Add(newCustomer);
- await dbContext.SaveChangesAsync();
- customerId = newCustomerId;
- }
- // Invalidate previous unused OTPs for this email
- var oldOtps = dbContext.OtpVerifications
- .Where(o => o.UserEmail == request.email && o.IsUsed == false)
- .ToList();
- foreach (var oldOtp in oldOtps)
- {
- oldOtp.IsUsed = true;
- }
- // Create new OTP record
- int otpExpireMinutes = 5;
- var otpId = (int)await Database.DbLogic.GenIdAsync(dbContext, "OTP_VERIFICATION_SEQ");
- var otpVerification = new OtpVerification
- {
- Id = otpId,
- CustomerId = customerId,
- UserEmail = request.email,
- OtpCode = otpCode,
- OtpType = 1, // Login OTP
- ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes),
- IsUsed = false,
- AttemptCount = 0,
- CreatedDate = DateTime.Now
- };
- dbContext.OtpVerifications.Add(otpVerification);
- await dbContext.SaveChangesAsync();
- // Skip email sending for test account
- if (!isTestAccount)
- {
- // Add to MESSAGE_QUEUE for background email sending
- // Resolve template content now so Worker only needs to send email
- string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
- string templateCode = "OTP_LOGIN";
- // Query template and get language-specific content
- var template = dbContext.MessageTemplates
- .FirstOrDefault(t => t.TemplateCode == templateCode && t.Status == true);
- if (template == null)
- {
- log.Error($"Template '{templateCode}' not found in MESSAGE_TEMPLATE");
- throw new Exception($"Email template '{templateCode}' not found");
- }
- // Get subject based on language (fallback to default column if _LO/_EN is null)
- string emailSubject = lang == "en"
- ? (template.SubjectEn ?? template.Subject ?? "")
- : (template.SubjectLo ?? template.Subject ?? "");
- // Get content based on language (fallback to default column if _LO/_EN is null)
- string emailContent = lang == "en"
- ? (template.ContentEn ?? template.Content ?? "")
- : (template.ContentLo ?? template.Content ?? "");
- // Replace placeholders in content
- emailContent = emailContent
- .Replace("{{OTP_CODE}}", otpCode)
- .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
- // Replace placeholders in subject (if any)
- emailSubject = emailSubject
- .Replace("{{OTP_CODE}}", otpCode)
- .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
- var emailMessageID = (int)await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
- var emailMessage = new MessageQueue
- {
- Id = emailMessageID,
- MessageType = 1, // Email
- Recipient = request.email,
- Subject = emailSubject, // Pre-resolved subject
- Content = emailContent, // Pre-resolved content
- Priority = true, // High priority
- Status = 0, // Pending
- ScheduledAt = DateTime.Now,
- RetryCount = 0,
- MaxRetry = 3,
- CreatedBy = customerId,
- CreatedDate = DateTime.Now
- };
- dbContext.MessageQueues.Add(emailMessage);
- await dbContext.SaveChangesAsync();
- }
- log.Info($"OTP generated for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}");
- return ApiResponseHelper.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.Success,
- ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS"),
- new
- {
- email = request.email,
- expireInSeconds = otpExpireMinutes * 60
- }
- );
- }
- catch (Exception exception)
- {
- log.Error("Exception: ", exception);
- }
- return ApiResponseHelper.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.SystemError,
- ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
- new { }
- );
- }
- /// <summary>
- /// Verify OTP and complete login - return JWT token
- /// </summary>
- public async Task<IActionResult> VerifyOtp(HttpRequest httpRequest, VerifyOtpReq request)
- {
- var url = httpRequest.Path;
- var json = JsonConvert.SerializeObject(request);
- log.Debug("URL: " + url + " => Request: " + json);
- try
- {
- if (string.IsNullOrEmpty(request.email) || string.IsNullOrEmpty(request.otpCode))
- {
- string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
- return ApiResponseHelper.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.RequiredFieldMissing,
- ConfigManager.Instance.GetConfigWebValue("EMAIL_OTP_REQUIRED", lang),
- new { }
- );
- }
- // Get language for response messages
- string responseLang = CommonLogic.GetLanguage(httpRequest, request.lang);
- // Find valid OTP
- var otpRecord = dbContext.OtpVerifications
- .Where(o => o.UserEmail == request.email
- && o.OtpCode == request.otpCode
- && o.IsUsed == false
- && o.ExpiredAt > DateTime.Now)
- .OrderByDescending(o => o.CreatedDate)
- .FirstOrDefault();
- if (otpRecord == null)
- {
- // Check if OTP exists but expired or used
- var anyOtp = dbContext.OtpVerifications
- .Where(o => o.UserEmail == request.email && o.OtpCode == request.otpCode)
- .FirstOrDefault();
- if (anyOtp != null)
- {
- if (anyOtp.IsUsed == true)
- {
- return ApiResponseHelper.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.OtpAlreadyUsed,
- ConfigManager.Instance.GetConfigWebValue("OTP_ALREADY_USED", responseLang),
- new { }
- );
- }
- if (anyOtp.ExpiredAt <= DateTime.Now)
- {
- return ApiResponseHelper.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.OtpExpired,
- ConfigManager.Instance.GetConfigWebValue("OTP_EXPIRED", responseLang),
- new { }
- );
- }
- }
- return ApiResponseHelper.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.OtpInvalid,
- ConfigManager.Instance.GetConfigWebValue("OTP_INVALID", responseLang),
- new { }
- );
- }
- // Mark OTP as used
- otpRecord.IsUsed = true;
- // Get customer info
- var customer = dbContext.CustomerInfos
- .Where(c => c.Email == request.email)
- .FirstOrDefault();
- if (customer == null)
- {
- return ApiResponseHelper.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.UserNotFound,
- ConfigManager.Instance.GetConfigWebValue("USER_NOT_FOUND", responseLang),
- new { }
- );
- }
- // Update customer verification status
- customer.IsVerified = true;
- customer.LastLoginDate = DateTime.Now;
- customer.LastUpdate = DateTime.Now;
- // Generate JWT tokens
- int tokenExpireHours = 24;
- int refreshTokenExpireDays = 30;
- string accessToken = CommonLogic.GenToken(configuration, customer.Email ?? "", customer.Id.ToString() ?? "");
- string refreshToken = CommonLogic.GenRefreshToken(configuration, customer.Email ?? "");
- var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
- var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
- // Revoke old tokens
- var oldTokens = dbContext.UserTokens
- .Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
- .ToList();
- foreach (var oldToken in oldTokens)
- {
- oldToken.IsRevoked = true;
- }
- // Save new token
- var tokenId = (int)await Database.DbLogic.GenIdAsync(dbContext, "USER_TOKEN_SEQ");
- var userToken = new UserToken
- {
- Id = tokenId,
- CustomerId = customer.Id,
- AccessToken = accessToken,
- RefreshToken = refreshToken,
- TokenType = "Bearer",
- DeviceInfo = httpRequest.Headers["User-Agent"].ToString(),
- IpAddress = GetClientIpAddress(httpRequest),
- ExpiredAt = expiresAt,
- RefreshExpiredAt = refreshExpiresAt,
- IsRevoked = false,
- CreatedDate = DateTime.Now,
- LastUsed = DateTime.Now
- };
- dbContext.UserTokens.Add(userToken);
- await dbContext.SaveChangesAsync();
- return ApiResponseHelper.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.Success,
- ConfigManager.Instance.GetConfigWebValue("LOGIN_SUCCESS", responseLang),
- new
- {
- userId = customer.Id,
- email = customer.Email ?? "",
- fullName = $"{customer.SurName} {customer.LastName}".Trim(),
- avatarUrl = customer.AvatarUrl,
- accessToken,
- refreshToken,
- expiresAt
- }
- );
- }
- catch (Exception exception)
- {
- log.Error("Exception: ", exception);
- }
- return ApiResponseHelper.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.SystemError,
- ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
- new { }
- );
- }
- /// <summary>
- /// Generate 6-digit OTP code
- /// </summary>
- private string GenerateOtp()
- {
- using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create())
- {
- var bytes = new byte[4];
- rng.GetBytes(bytes);
- var number = Math.Abs(BitConverter.ToInt32(bytes, 0)) % 1000000;
- return number.ToString("D6");
- }
- }
- /// <summary>
- /// Get client IP address
- /// </summary>
- private string GetClientIpAddress(HttpRequest httpRequest)
- {
- var ipAddress = httpRequest.Headers["X-Forwarded-For"].FirstOrDefault();
- if (string.IsNullOrEmpty(ipAddress))
- {
- ipAddress = httpRequest.HttpContext.Connection.RemoteIpAddress?.ToString();
- }
- return ipAddress ?? "Unknown";
- }
- }
- }
|