| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180 |
- using Common;
- using Common.Constant;
- using Common.Http;
- using Common.Logic;
- using Database;
- using Database.Database;
- //using Esim.Apis.Logic;
- using Esim.Apis.Singleton;
- using log4net;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.VisualBasic;
- using Newtonsoft.Json;
- using Newtonsoft.Json.Linq;
- using System;
- using System.Linq;
- using System.Net.Http;
- using System.Net.Http.Headers;
- using System.Text;
- 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 DotnetLib.Http.HttpResponse.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;
- }
- // Check if there's an existing OTP record for this email (ANY record, used or unused)
- int otpExpireMinutes = 5;
- int minSecondsBetweenRequests = 60; // Anti-spam: minimum 60 seconds between requests
- var existingOtp = dbContext
- .OtpVerifications.Where(o => o.UserEmail == request.email)
- .OrderByDescending(o => o.CreatedDate)
- .FirstOrDefault();
- if (existingOtp != null)
- {
- // Anti-spam check: prevent rapid OTP requests
- var secondsSinceLastRequest = (
- DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)
- ).TotalSeconds;
- if (secondsSinceLastRequest < minSecondsBetweenRequests)
- {
- var waitSeconds = (int)(
- minSecondsBetweenRequests - secondsSinceLastRequest
- );
- log.Warn(
- $"Spam prevention: OTP request too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago."
- );
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.OtpTooManyRequests,
- $"Please wait {waitSeconds} seconds before requesting a new OTP.",
- new
- {
- waitSeconds = waitSeconds,
- canRequestAt = existingOtp.CreatedDate?.AddSeconds(
- minSecondsBetweenRequests
- )
- }
- );
- }
- // UPDATE existing record - reuse for this email to prevent table bloat
- existingOtp.OtpCode = otpCode;
- existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
- existingOtp.AttemptCount = 0; // Reset attempt count
- existingOtp.IsUsed = false; // Reset to unused (allow reuse)
- existingOtp.CustomerId = customerId; // Update customer ID if changed
- existingOtp.CreatedDate = DateTime.Now; // Update to current time
- log.Info(
- $"Updated existing OTP record ID={existingOtp.Id} for {request.email} (was {(existingOtp.IsUsed == true ? "used" : "unused")})"
- );
- }
- else
- {
- // INSERT new record only for first-time email
- 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);
- log.Info($"Created new OTP record for {request.email} (first time)");
- }
- 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.HasValue && t.Status.Value
- );
- 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: vi=default, en=_EN, lo=_LO (default)
- string emailSubject =
- lang == "en"
- ? (template.SubjectEn ?? template.Subject ?? "")
- : lang == "vi"
- ? (template.Subject ?? "")
- : (template.SubjectLo ?? template.Subject ?? "");
- // Get content based on language: vi=default, en=_EN, lo=_LO (default)
- string emailContent =
- lang == "en"
- ? (template.ContentEn ?? template.Content ?? "")
- : lang == "vi"
- ? (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 DotnetLib.Http.HttpResponse.BuildResponse(
- // log,
- // url,
- // json,
- // CommonErrorCode.Success,
- // ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS"),
- // new
- // {
- // email = request.email,
- // expireInSeconds = otpExpireMinutes * 60
- // }
- //);
- return DotnetLib.Http.HttpResponse.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 DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.SystemError,
- ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
- new { }
- );
- }
- /// <summary>
- /// Resend OTP - Only works if user has already requested OTP before
- /// Has reduced cooldown (30 seconds vs 60 seconds for RequestOtp)
- /// </summary>
- public async Task<IActionResult> ResendOtp(HttpRequest httpRequest, RequestOtpReq request)
- {
- var url = httpRequest.Path;
- var json = JsonConvert.SerializeObject(request);
- log.Debug("URL: " + url + " => ResendOtp Request: " + json);
- try
- {
- string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
- // Validate email is required
- if (string.IsNullOrEmpty(request.email))
- {
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.RequiredFieldMissing,
- ConfigManager.Instance.GetConfigWebValue("EMAIL_REQUIRED", lang),
- new { }
- );
- }
- // Check if there's an existing OTP record for this email
- var existingOtp = dbContext
- .OtpVerifications.Where(o => o.UserEmail == request.email)
- .OrderByDescending(o => o.CreatedDate)
- .FirstOrDefault();
- // RESEND requires existing OTP - must have requested OTP before
- if (existingOtp == null)
- {
- log.Warn($"ResendOtp failed: No existing OTP record for {request.email}");
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.OtpNotRequested,
- ConfigManager.Instance.GetConfigWebValue("OTP_NOT_REQUESTED", lang),
- new { }
- );
- }
- // RESEND has reduced cooldown: 60 seconds (vs 60 seconds for RequestOtp)
- int minSecondsBetweenResend = 60;
- var secondsSinceLastRequest = (
- DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)
- ).TotalSeconds;
- if (secondsSinceLastRequest < minSecondsBetweenResend)
- {
- var waitSeconds = (int)(minSecondsBetweenResend - secondsSinceLastRequest);
- log.Warn(
- $"ResendOtp: Too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago."
- );
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.OtpTooManyRequests,
- $"Please wait {waitSeconds} seconds before resending OTP.",
- new
- {
- waitSeconds = waitSeconds,
- canResendAt = existingOtp.CreatedDate?.AddSeconds(
- minSecondsBetweenResend
- )
- }
- );
- }
- // Generate new 6-digit OTP (fixed 111111 for test account abc@gmail.com)
- bool isTestAccount = request.email.ToLower() == "abc@gmail.com";
- string otpCode = isTestAccount ? "111111" : GenerateOtp();
- // OTP expires in 5 minutes
- int otpExpireMinutes = 5;
- // Get customer ID (should exist since OTP record exists)
- var customer = dbContext
- .CustomerInfos.Where(c => c.Email == request.email)
- .FirstOrDefault();
- decimal? customerId = customer?.Id ?? existingOtp.CustomerId;
- // UPDATE existing OTP record with new code and expiry
- existingOtp.OtpCode = otpCode;
- existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
- existingOtp.AttemptCount = 0; // Reset attempt count
- existingOtp.IsUsed = false; // Reset to unused
- existingOtp.CustomerId = customerId;
- existingOtp.CreatedDate = DateTime.Now; // Update to track resend time
- log.Info($"ResendOtp: Updated OTP record ID={existingOtp.Id} for {request.email}");
- await dbContext.SaveChangesAsync();
- // Skip email sending for test account
- if (!isTestAccount)
- {
- // Add to MESSAGE_QUEUE for background email sending
- string templateCode = "OTP_LOGIN";
- var template = dbContext.MessageTemplates.FirstOrDefault(
- t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value
- );
- 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
- string emailSubject =
- lang == "en"
- ? (template.SubjectEn ?? template.Subject ?? "")
- : lang == "vi"
- ? (template.Subject ?? "")
- : (template.SubjectLo ?? template.Subject ?? "");
- // Get content based on language
- string emailContent =
- lang == "en"
- ? (template.ContentEn ?? template.Content ?? "")
- : lang == "vi"
- ? (template.Content ?? "")
- : (template.ContentLo ?? template.Content ?? "");
- // Replace placeholders
- emailContent = emailContent
- .Replace("{{OTP_CODE}}", otpCode)
- .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
- 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,
- Content = emailContent,
- 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(
- $"ResendOtp: OTP resent for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}"
- );
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.Success,
- ConfigManager.Instance.GetConfigWebValue("OTP_RESENT_SUCCESS", lang),
- new { email = request.email, expireInSeconds = otpExpireMinutes * 60 }
- );
- }
- catch (Exception exception)
- {
- log.Error("ResendOtp Exception: ", exception);
- }
- return DotnetLib.Http.HttpResponse.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 DotnetLib.Http.HttpResponse.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 DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.OtpAlreadyUsed,
- ConfigManager.Instance.GetConfigWebValue(
- "OTP_ALREADY_USED",
- responseLang
- ),
- new { }
- );
- }
- if (anyOtp.ExpiredAt <= DateTime.Now)
- {
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.OtpExpired,
- ConfigManager.Instance.GetConfigWebValue(
- "OTP_EXPIRED",
- responseLang
- ),
- new { }
- );
- }
- }
- return DotnetLib.Http.HttpResponse.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 DotnetLib.Http.HttpResponse.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 DotnetLib.Http.HttpResponse.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,
- msisdn = customer.PhoneNumber,
- accessToken,
- refreshToken,
- expiresAt
- }
- );
- }
- catch (Exception exception)
- {
- log.Error("Exception: ", exception);
- }
- return DotnetLib.Http.HttpResponse.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";
- }
- public async Task<IActionResult> GoogleLogin(
- HttpRequest httpRequest,
- GoogleLoginReq request
- )
- {
- var url = httpRequest.Path;
- var json = JsonConvert.SerializeObject(request);
- log.Debug("URL: " + url + " => GoogleLogin Request: " + json);
- try
- {
- string lang = CommonLogic.GetLanguage(httpRequest, request?.lang);
- var clientId = configuration["Google:ClientId"];
- var redirectUri = configuration["Google:RedirectUri"];
- if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(redirectUri))
- {
- log.Error("Google Auth configuration missing");
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.SystemError,
- ConfigManager.Instance.GetConfigWebValue("GOOGLE_CONFIG_MISSING", lang),
- new { }
- );
- }
- var googleUrl =
- $"https://accounts.google.com/o/oauth2/v2/auth?client_id={clientId}&redirect_uri={redirectUri}&response_type=code&scope=email%20profile&prompt=select_account";
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.Success,
- ConfigManager.Instance.GetConfigWebValue("SUCCESS", lang),
- new { url = googleUrl }
- );
- }
- catch (Exception ex)
- {
- log.Error("GoogleLogin Exception: ", ex);
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.SystemError,
- ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
- new { }
- );
- }
- }
- public async Task<IActionResult> GoogleCallback(
- HttpRequest httpRequest,
- GoogleCallbackReq request
- )
- {
- var url = httpRequest.Path;
- var json = JsonConvert.SerializeObject(request);
- log.Debug("URL: " + url + " => GoogleCallback Request: " + json);
- try
- {
- // Get language for response messages
- string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
- if (string.IsNullOrEmpty(request.code))
- {
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.RequiredFieldMissing,
- ConfigManager.Instance.GetConfigWebValue("GOOGLE_CODE_REQUIRED", lang),
- new { }
- );
- }
- var clientId = configuration["Google:ClientId"];
- var clientSecret = configuration["Google:ClientSecret"];
- var redirectUri = !string.IsNullOrEmpty(request.redirectUri)
- ? request.redirectUri
- : configuration["Google:RedirectUri"];
- using (var httpClient = new HttpClient())
- {
- // 1. Exchange code for token
- var tokenRequestContent = new FormUrlEncodedContent(
- new[]
- {
- new KeyValuePair<string, string>("code", request.code),
- new KeyValuePair<string, string>("client_id", clientId),
- new KeyValuePair<string, string>("client_secret", clientSecret),
- new KeyValuePair<string, string>("redirect_uri", redirectUri),
- new KeyValuePair<string, string>("grant_type", "authorization_code")
- }
- );
- var tokenResponse = await httpClient.PostAsync(
- "https://oauth2.googleapis.com/token",
- tokenRequestContent
- );
- var tokenResponseString = await tokenResponse.Content.ReadAsStringAsync();
- if (!tokenResponse.IsSuccessStatusCode)
- {
- log.Error($"Google Token Exchange Failed: {tokenResponseString}");
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.ExternalServiceError,
- ConfigManager.Instance.GetConfigWebValue(
- "GOOGLE_TOKEN_EXCHANGE_FAILED",
- lang
- ),
- new { error = tokenResponseString }
- );
- }
- var tokenData = JsonConvert.DeserializeObject<JObject>(tokenResponseString);
- var accessToken = tokenData["access_token"]?.ToString();
- if (string.IsNullOrEmpty(accessToken))
- {
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.ExternalServiceError,
- ConfigManager.Instance.GetConfigWebValue(
- "GOOGLE_NO_ACCESS_TOKEN",
- lang
- ),
- new { }
- );
- }
- // 2. Get User Info
- httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
- "Bearer",
- accessToken
- );
- var userInfoResponse = await httpClient.GetAsync(
- "https://www.googleapis.com/oauth2/v2/userinfo"
- );
- var userInfoString = await userInfoResponse.Content.ReadAsStringAsync();
- if (!userInfoResponse.IsSuccessStatusCode)
- {
- log.Error($"Google User Info Failed: {userInfoString}");
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.ExternalServiceError,
- ConfigManager.Instance.GetConfigWebValue(
- "GOOGLE_USERINFO_FAILED",
- lang
- ),
- new { error = userInfoString }
- );
- }
- var userInfo = JsonConvert.DeserializeObject<JObject>(userInfoString);
- var email = userInfo["email"]?.ToString();
- var name = userInfo["name"]?.ToString(); // Full name
- var picture = userInfo["picture"]?.ToString();
- if (string.IsNullOrEmpty(email))
- {
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.ExternalServiceError,
- ConfigManager.Instance.GetConfigWebValue("GOOGLE_NO_EMAIL", lang),
- new { }
- );
- }
- // 3. Login or Register logic
- var customer = dbContext.CustomerInfos.FirstOrDefault(c => c.Email == email);
- if (customer == null)
- {
- // Register new user
- var newCustomerId = await Database.DbLogic.GenIdAsync(
- dbContext,
- "CUSTOMER_INFO_SEQ"
- );
- // Try to split name
- string surname = name;
- string lastname = "";
- if (!string.IsNullOrEmpty(name))
- {
- var parts = name.Split(' ');
- if (parts.Length > 1)
- {
- lastname = parts[parts.Length - 1];
- surname = string.Join(" ", parts.Take(parts.Length - 1));
- }
- }
- else
- {
- surname = email.Split('@')[0];
- }
- customer = new CustomerInfo
- {
- Id = newCustomerId,
- Email = email,
- SurName = surname,
- LastName = lastname,
- AvatarUrl = picture,
- Status = true,
- IsVerified = true, // Verified by Google
- CreatedDate = DateTime.Now,
- LastUpdate = DateTime.Now
- };
- dbContext.CustomerInfos.Add(customer);
- await dbContext.SaveChangesAsync();
- log.Info(
- $"Created new customer via Google Login: ID={newCustomerId}, Email={email}"
- );
- }
- else
- {
- // Update existing user info if needed or just log them in
- customer.IsVerified = true;
- if (
- string.IsNullOrEmpty(customer.AvatarUrl)
- && !string.IsNullOrEmpty(picture)
- )
- {
- customer.AvatarUrl = picture;
- }
- customer.LastLoginDate = DateTime.Now;
- customer.LastUpdate = DateTime.Now;
- await dbContext.SaveChangesAsync();
- log.Info(
- $"Existing customer logged in via Google: ID={customer.Id}, Email={email}"
- );
- }
- // 4. Generate JWT
- int tokenExpireHours = 24;
- int refreshTokenExpireDays = 30;
- string jwtAccessToken = CommonLogic.GenToken(
- configuration,
- customer.Email ?? "",
- customer.Id.ToString() ?? ""
- );
- string jwtRefreshToken = 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 = jwtAccessToken,
- RefreshToken = jwtRefreshToken,
- 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 DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.Success,
- ConfigManager.Instance.GetConfigWebValue("GOOGLE_LOGIN_SUCCESS", lang),
- new
- {
- userId = customer.Id,
- email = customer.Email ?? "",
- fullName = $"{customer.SurName} {customer.LastName}".Trim(),
- avatarUrl = customer.AvatarUrl,
- msisdn = customer.PhoneNumber,
- accessToken = jwtAccessToken,
- refreshToken = jwtRefreshToken,
- expiresAt
- }
- );
- }
- }
- catch (Exception exception)
- {
- log.Error("GoogleCallback Exception: ", exception);
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.SystemError,
- ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
- new { }
- );
- }
- }
- public async Task<IActionResult> PartnerLogin(HttpRequest httpRequest, PartnerLoginReq request)
- {
- var url = httpRequest.Path;
- var json = JsonConvert.SerializeObject(request);
- log.Debug("URL: " + url + " => GoogleCallback Request: " + json);
- try
- {
- byte[] keyBytes = Encoding.UTF8.GetBytes(configuration["Partner:EncryptionKey"] ?? "");
- var decrypt = DotnetLib.Secure.SecureLogic.DecryptAes(
- request.data.Replace(" ", "+"),
- configuration["Partner:EncryptionKey"] ?? ""
- );
- if (decrypt == null)
- {
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.Error,
- ConfigManager.Instance.GetConfigWebValue("INVALID_PARTNER_DATA"),
- new { }
- );
- }
- var requestData = JsonConvert.DeserializeObject<JObject>(decrypt);
- log.Debug("Decrypted PartnerLogin data: " + JsonConvert.SerializeObject(requestData));
- string email = requestData["email"]?.ToString() ?? "";
- if (string.IsNullOrEmpty(email))
- {
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.RequiredFieldMissing,
- ConfigManager.Instance.GetConfigWebValue("EMAIL_REQUIRED"),
- new { }
- );
- }
- // Get customer info
- var customer = dbContext
- .CustomerInfos.Where(c => c.Email == email)
- .FirstOrDefault();
- if (customer == null)
- {
- // create a new customer
- customer = new CustomerInfo
- {
- Id = await Database.DbLogic.GenIdAsync(dbContext, "CUSTOMER_INFO_SEQ"),
- Email = email,
- SurName = requestData["fullName"]?.ToString() ?? "",
- LastName = requestData["fullName"]?.ToString() ?? "",
- AvatarUrl = requestData["avatarUrl"]?.ToString() ?? "",
- PhoneNumber = requestData["phoneNumber"]?.ToString() ?? "",
- Status = true,
- IsVerified = true,
- CreatedDate = DateTime.Now,
- LastUpdate = DateTime.Now
- };
- dbContext.CustomerInfos.Add(customer);
- await dbContext.SaveChangesAsync();
- }
- // 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 DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.Success,
- ConfigManager.Instance.GetConfigWebValue("LOGIN_SUCCESS", "en"),
- new
- {
- userId = customer.Id,
- email = customer.Email ?? "",
- fullName = $"{customer.SurName} {customer.LastName}".Trim(),
- avatarUrl = customer.AvatarUrl,
- msisdn = customer.PhoneNumber,
- accessToken,
- refreshToken,
- expiresAt
- }
- );
- }
- catch (Exception exception)
- {
- log.Error("GoogleCallback Exception: ", exception);
- }
- return DotnetLib.Http.HttpResponse.BuildResponse(
- log,
- url,
- json,
- CommonErrorCode.SystemError,
- ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
- new { }
- );
- }
- }
- }
|