UserBusinessImpl.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. using Common;
  2. using Common.Constant;
  3. using Common.Http;
  4. using Common.Logic;
  5. //using Esim.Apis.Logic;
  6. using Esim.Apis.Singleton;
  7. using Database;
  8. using Database.Database;
  9. using log4net;
  10. using Microsoft.AspNetCore.Mvc;
  11. using Microsoft.VisualBasic;
  12. using Newtonsoft.Json;
  13. using System;
  14. using System.Threading.Tasks;
  15. using System.Xml.Serialization;
  16. namespace Esim.Apis.Business
  17. {
  18. public class UserBusinessImpl : IUserBusiness
  19. {
  20. private static readonly log4net.ILog log = log4net.LogManager.GetLogger(
  21. typeof(UserBusinessImpl)
  22. );
  23. private ModelContext dbContext;
  24. IConfiguration configuration;
  25. public UserBusinessImpl(ModelContext _dbContext, IConfiguration _configuration)
  26. {
  27. dbContext = _dbContext;
  28. configuration = _configuration;
  29. }
  30. private string GetParameter(string key)
  31. {
  32. return configuration.GetSection(key).Value ?? "";
  33. }
  34. /// <summary>
  35. /// Request OTP to be sent to email
  36. /// </summary>
  37. public async Task<IActionResult> RequestOtp(HttpRequest httpRequest, RequestOtpReq request)
  38. {
  39. var url = httpRequest.Path;
  40. var json = JsonConvert.SerializeObject(request);
  41. log.Debug("URL: " + url + " => Request: " + json);
  42. try
  43. {
  44. if (string.IsNullOrEmpty(request.email))
  45. {
  46. return DotnetLib.Http.HttpResponse.BuildResponse(
  47. log,
  48. url,
  49. json,
  50. CommonErrorCode.RequiredFieldMissing,
  51. ConfigManager.Instance.GetConfigWebValue("EMAIL_REQUIRED"),
  52. new { }
  53. );
  54. }
  55. // Generate 6-digit OTP (fixed 111111 for test account abc@gmail.com)
  56. bool isTestAccount = request.email.ToLower() == "abc@gmail.com";
  57. string otpCode = isTestAccount ? "111111" : GenerateOtp();
  58. // Check if customer exists, if not create new
  59. var customer = dbContext.CustomerInfos
  60. .Where(c => c.Email == request.email)
  61. .FirstOrDefault();
  62. decimal? customerId = customer?.Id;
  63. if (customer == null)
  64. {
  65. // Create new customer record - manually get ID from Oracle sequence
  66. var newCustomerId = await Database.DbLogic.GenIdAsync(dbContext, "CUSTOMER_INFO_SEQ");
  67. // Extract name from email (part before @)
  68. string emailUsername = request.email.Split('@')[0];
  69. var newCustomer = new CustomerInfo
  70. {
  71. Id = newCustomerId,
  72. Email = request.email,
  73. SurName = emailUsername,
  74. LastName = emailUsername,
  75. Status = true,
  76. IsVerified = false,
  77. CreatedDate = DateTime.Now,
  78. LastUpdate = DateTime.Now
  79. };
  80. dbContext.CustomerInfos.Add(newCustomer);
  81. await dbContext.SaveChangesAsync();
  82. customerId = newCustomerId;
  83. }
  84. // Check if there's an existing OTP record for this email (ANY record, used or unused)
  85. int otpExpireMinutes = 5;
  86. int minSecondsBetweenRequests = 60; // Anti-spam: minimum 60 seconds between requests
  87. var existingOtp = dbContext.OtpVerifications
  88. .Where(o => o.UserEmail == request.email)
  89. .OrderByDescending(o => o.CreatedDate)
  90. .FirstOrDefault();
  91. if (existingOtp != null)
  92. {
  93. // Anti-spam check: prevent rapid OTP requests
  94. var secondsSinceLastRequest = (DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)).TotalSeconds;
  95. if (secondsSinceLastRequest < minSecondsBetweenRequests)
  96. {
  97. var waitSeconds = (int)(minSecondsBetweenRequests - secondsSinceLastRequest);
  98. log.Warn($"Spam prevention: OTP request too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago.");
  99. return DotnetLib.Http.HttpResponse.BuildResponse(
  100. log,
  101. url,
  102. json,
  103. CommonErrorCode.OtpTooManyRequests,
  104. $"Please wait {waitSeconds} seconds before requesting a new OTP.",
  105. new {
  106. waitSeconds = waitSeconds,
  107. canRequestAt = existingOtp.CreatedDate?.AddSeconds(minSecondsBetweenRequests)
  108. }
  109. );
  110. }
  111. // UPDATE existing record - reuse for this email to prevent table bloat
  112. existingOtp.OtpCode = otpCode;
  113. existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
  114. existingOtp.AttemptCount = 0; // Reset attempt count
  115. existingOtp.IsUsed = false; // Reset to unused (allow reuse)
  116. existingOtp.CustomerId = customerId; // Update customer ID if changed
  117. existingOtp.CreatedDate = DateTime.Now; // Update to current time
  118. log.Info($"Updated existing OTP record ID={existingOtp.Id} for {request.email} (was {(existingOtp.IsUsed == true ? "used" : "unused")})");
  119. }
  120. else
  121. {
  122. // INSERT new record only for first-time email
  123. var otpId = (int)await Database.DbLogic.GenIdAsync(dbContext, "OTP_VERIFICATION_SEQ");
  124. var otpVerification = new OtpVerification
  125. {
  126. Id = otpId,
  127. CustomerId = customerId,
  128. UserEmail = request.email,
  129. OtpCode = otpCode,
  130. OtpType = 1, // Login OTP
  131. ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes),
  132. IsUsed = false,
  133. AttemptCount = 0,
  134. CreatedDate = DateTime.Now
  135. };
  136. dbContext.OtpVerifications.Add(otpVerification);
  137. log.Info($"Created new OTP record for {request.email} (first time)");
  138. }
  139. await dbContext.SaveChangesAsync();
  140. // Skip email sending for test account
  141. if (!isTestAccount)
  142. {
  143. // Add to MESSAGE_QUEUE for background email sending
  144. // Resolve template content now so Worker only needs to send email
  145. string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
  146. string templateCode = "OTP_LOGIN";
  147. // Query template and get language-specific content
  148. var template = dbContext.MessageTemplates
  149. .FirstOrDefault(t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value);
  150. if (template == null)
  151. {
  152. log.Error($"Template '{templateCode}' not found in MESSAGE_TEMPLATE");
  153. throw new Exception($"Email template '{templateCode}' not found");
  154. }
  155. // Get subject based on language: vi=default, en=_EN, lo=_LO (default)
  156. string emailSubject = lang == "en" ? (template.SubjectEn ?? template.Subject ?? "")
  157. : lang == "vi" ? (template.Subject ?? "")
  158. : (template.SubjectLo ?? template.Subject ?? "");
  159. // Get content based on language: vi=default, en=_EN, lo=_LO (default)
  160. string emailContent = lang == "en" ? (template.ContentEn ?? template.Content ?? "")
  161. : lang == "vi" ? (template.Content ?? "")
  162. : (template.ContentLo ?? template.Content ?? "");
  163. // Replace placeholders in content
  164. emailContent = emailContent
  165. .Replace("{{OTP_CODE}}", otpCode)
  166. .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
  167. // Replace placeholders in subject (if any)
  168. emailSubject = emailSubject
  169. .Replace("{{OTP_CODE}}", otpCode)
  170. .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
  171. var emailMessageID = (int)await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
  172. var emailMessage = new MessageQueue
  173. {
  174. Id = emailMessageID,
  175. MessageType = 1, // Email
  176. Recipient = request.email,
  177. Subject = emailSubject, // Pre-resolved subject
  178. Content = emailContent, // Pre-resolved content
  179. Priority = true, // High priority
  180. Status = 0, // Pending
  181. ScheduledAt = DateTime.Now,
  182. RetryCount = 0,
  183. MaxRetry = 3,
  184. CreatedBy = customerId,
  185. CreatedDate = DateTime.Now
  186. };
  187. dbContext.MessageQueues.Add(emailMessage);
  188. await dbContext.SaveChangesAsync();
  189. }
  190. log.Info($"OTP generated for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}");
  191. //return DotnetLib.Http.HttpResponse.BuildResponse(
  192. // log,
  193. // url,
  194. // json,
  195. // CommonErrorCode.Success,
  196. // ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS"),
  197. // new
  198. // {
  199. // email = request.email,
  200. // expireInSeconds = otpExpireMinutes * 60
  201. // }
  202. //);
  203. return DotnetLib.Http.HttpResponse.BuildResponse(
  204. log,
  205. url,
  206. json,
  207. CommonErrorCode.Success,
  208. ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS"),
  209. new
  210. {
  211. email = request.email,
  212. expireInSeconds = otpExpireMinutes * 60
  213. }
  214. );
  215. }
  216. catch (Exception exception)
  217. {
  218. log.Error("Exception: ", exception);
  219. }
  220. return DotnetLib.Http.HttpResponse.BuildResponse(
  221. log,
  222. url,
  223. json,
  224. CommonErrorCode.SystemError,
  225. ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
  226. new { }
  227. );
  228. }
  229. /// <summary>
  230. /// Resend OTP - Only works if user has already requested OTP before
  231. /// Has reduced cooldown (30 seconds vs 60 seconds for RequestOtp)
  232. /// </summary>
  233. public async Task<IActionResult> ResendOtp(HttpRequest httpRequest, RequestOtpReq request)
  234. {
  235. var url = httpRequest.Path;
  236. var json = JsonConvert.SerializeObject(request);
  237. log.Debug("URL: " + url + " => ResendOtp Request: " + json);
  238. try
  239. {
  240. string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
  241. // Validate email is required
  242. if (string.IsNullOrEmpty(request.email))
  243. {
  244. return DotnetLib.Http.HttpResponse.BuildResponse(
  245. log,
  246. url,
  247. json,
  248. CommonErrorCode.RequiredFieldMissing,
  249. ConfigManager.Instance.GetConfigWebValue("EMAIL_REQUIRED", lang),
  250. new { }
  251. );
  252. }
  253. // Check if there's an existing OTP record for this email
  254. var existingOtp = dbContext.OtpVerifications
  255. .Where(o => o.UserEmail == request.email)
  256. .OrderByDescending(o => o.CreatedDate)
  257. .FirstOrDefault();
  258. // RESEND requires existing OTP - must have requested OTP before
  259. if (existingOtp == null)
  260. {
  261. log.Warn($"ResendOtp failed: No existing OTP record for {request.email}");
  262. return DotnetLib.Http.HttpResponse.BuildResponse(
  263. log,
  264. url,
  265. json,
  266. CommonErrorCode.OtpNotRequested,
  267. ConfigManager.Instance.GetConfigWebValue("OTP_NOT_REQUESTED", lang),
  268. new { }
  269. );
  270. }
  271. // RESEND has reduced cooldown: 60 seconds (vs 60 seconds for RequestOtp)
  272. int minSecondsBetweenResend = 60;
  273. var secondsSinceLastRequest = (DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)).TotalSeconds;
  274. if (secondsSinceLastRequest < minSecondsBetweenResend)
  275. {
  276. var waitSeconds = (int)(minSecondsBetweenResend - secondsSinceLastRequest);
  277. log.Warn($"ResendOtp: Too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago.");
  278. return DotnetLib.Http.HttpResponse.BuildResponse(
  279. log,
  280. url,
  281. json,
  282. CommonErrorCode.OtpTooManyRequests,
  283. $"Please wait {waitSeconds} seconds before resending OTP.",
  284. new {
  285. waitSeconds = waitSeconds,
  286. canResendAt = existingOtp.CreatedDate?.AddSeconds(minSecondsBetweenResend)
  287. }
  288. );
  289. }
  290. // Generate new 6-digit OTP (fixed 111111 for test account abc@gmail.com)
  291. bool isTestAccount = request.email.ToLower() == "abc@gmail.com";
  292. string otpCode = isTestAccount ? "111111" : GenerateOtp();
  293. // OTP expires in 5 minutes
  294. int otpExpireMinutes = 5;
  295. // Get customer ID (should exist since OTP record exists)
  296. var customer = dbContext.CustomerInfos
  297. .Where(c => c.Email == request.email)
  298. .FirstOrDefault();
  299. decimal? customerId = customer?.Id ?? existingOtp.CustomerId;
  300. // UPDATE existing OTP record with new code and expiry
  301. existingOtp.OtpCode = otpCode;
  302. existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
  303. existingOtp.AttemptCount = 0; // Reset attempt count
  304. existingOtp.IsUsed = false; // Reset to unused
  305. existingOtp.CustomerId = customerId;
  306. existingOtp.CreatedDate = DateTime.Now; // Update to track resend time
  307. log.Info($"ResendOtp: Updated OTP record ID={existingOtp.Id} for {request.email}");
  308. await dbContext.SaveChangesAsync();
  309. // Skip email sending for test account
  310. if (!isTestAccount)
  311. {
  312. // Add to MESSAGE_QUEUE for background email sending
  313. string templateCode = "OTP_LOGIN";
  314. var template = dbContext.MessageTemplates
  315. .FirstOrDefault(t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value);
  316. if (template == null)
  317. {
  318. log.Error($"Template '{templateCode}' not found in MESSAGE_TEMPLATE");
  319. throw new Exception($"Email template '{templateCode}' not found");
  320. }
  321. // Get subject based on language
  322. string emailSubject = lang == "en" ? (template.SubjectEn ?? template.Subject ?? "")
  323. : lang == "vi" ? (template.Subject ?? "")
  324. : (template.SubjectLo ?? template.Subject ?? "");
  325. // Get content based on language
  326. string emailContent = lang == "en" ? (template.ContentEn ?? template.Content ?? "")
  327. : lang == "vi" ? (template.Content ?? "")
  328. : (template.ContentLo ?? template.Content ?? "");
  329. // Replace placeholders
  330. emailContent = emailContent
  331. .Replace("{{OTP_CODE}}", otpCode)
  332. .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
  333. emailSubject = emailSubject
  334. .Replace("{{OTP_CODE}}", otpCode)
  335. .Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
  336. var emailMessageID = (int)await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
  337. var emailMessage = new MessageQueue
  338. {
  339. Id = emailMessageID,
  340. MessageType = 1, // Email
  341. Recipient = request.email,
  342. Subject = emailSubject,
  343. Content = emailContent,
  344. Priority = true, // High priority
  345. Status = 0, // Pending
  346. ScheduledAt = DateTime.Now,
  347. RetryCount = 0,
  348. MaxRetry = 3,
  349. CreatedBy = customerId,
  350. CreatedDate = DateTime.Now
  351. };
  352. dbContext.MessageQueues.Add(emailMessage);
  353. await dbContext.SaveChangesAsync();
  354. }
  355. log.Info($"ResendOtp: OTP resent for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}");
  356. return DotnetLib.Http.HttpResponse.BuildResponse(
  357. log,
  358. url,
  359. json,
  360. CommonErrorCode.Success,
  361. ConfigManager.Instance.GetConfigWebValue("OTP_RESENT_SUCCESS", lang),
  362. new
  363. {
  364. email = request.email,
  365. expireInSeconds = otpExpireMinutes * 60
  366. }
  367. );
  368. }
  369. catch (Exception exception)
  370. {
  371. log.Error("ResendOtp Exception: ", exception);
  372. }
  373. return DotnetLib.Http.HttpResponse.BuildResponse(
  374. log,
  375. url,
  376. json,
  377. CommonErrorCode.SystemError,
  378. ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
  379. new { }
  380. );
  381. }
  382. /// <summary>
  383. /// Verify OTP and complete login - return JWT token
  384. /// </summary>
  385. public async Task<IActionResult> VerifyOtp(HttpRequest httpRequest, VerifyOtpReq request)
  386. {
  387. var url = httpRequest.Path;
  388. var json = JsonConvert.SerializeObject(request);
  389. log.Debug("URL: " + url + " => Request: " + json);
  390. try
  391. {
  392. if (string.IsNullOrEmpty(request.email) || string.IsNullOrEmpty(request.otpCode))
  393. {
  394. string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
  395. return DotnetLib.Http.HttpResponse.BuildResponse(
  396. log,
  397. url,
  398. json,
  399. CommonErrorCode.RequiredFieldMissing,
  400. ConfigManager.Instance.GetConfigWebValue("EMAIL_OTP_REQUIRED", lang),
  401. new { }
  402. );
  403. }
  404. // Get language for response messages
  405. string responseLang = CommonLogic.GetLanguage(httpRequest, request.lang);
  406. // Find valid OTP
  407. var otpRecord = dbContext.OtpVerifications
  408. .Where(o => o.UserEmail == request.email
  409. && o.OtpCode == request.otpCode
  410. && o.IsUsed == false
  411. && o.ExpiredAt > DateTime.Now)
  412. .OrderByDescending(o => o.CreatedDate)
  413. .FirstOrDefault();
  414. if (otpRecord == null)
  415. {
  416. // Check if OTP exists but expired or used
  417. var anyOtp = dbContext.OtpVerifications
  418. .Where(o => o.UserEmail == request.email && o.OtpCode == request.otpCode)
  419. .FirstOrDefault();
  420. if (anyOtp != null)
  421. {
  422. if (anyOtp.IsUsed == true)
  423. {
  424. return DotnetLib.Http.HttpResponse.BuildResponse(
  425. log,
  426. url,
  427. json,
  428. CommonErrorCode.OtpAlreadyUsed,
  429. ConfigManager.Instance.GetConfigWebValue("OTP_ALREADY_USED", responseLang),
  430. new { }
  431. );
  432. }
  433. if (anyOtp.ExpiredAt <= DateTime.Now)
  434. {
  435. return DotnetLib.Http.HttpResponse.BuildResponse(
  436. log,
  437. url,
  438. json,
  439. CommonErrorCode.OtpExpired,
  440. ConfigManager.Instance.GetConfigWebValue("OTP_EXPIRED", responseLang),
  441. new { }
  442. );
  443. }
  444. }
  445. return DotnetLib.Http.HttpResponse.BuildResponse(
  446. log,
  447. url,
  448. json,
  449. CommonErrorCode.OtpInvalid,
  450. ConfigManager.Instance.GetConfigWebValue("OTP_INVALID", responseLang),
  451. new { }
  452. );
  453. }
  454. // Mark OTP as used
  455. otpRecord.IsUsed = true;
  456. // Get customer info
  457. var customer = dbContext.CustomerInfos
  458. .Where(c => c.Email == request.email)
  459. .FirstOrDefault();
  460. if (customer == null)
  461. {
  462. return DotnetLib.Http.HttpResponse.BuildResponse(
  463. log,
  464. url,
  465. json,
  466. CommonErrorCode.UserNotFound,
  467. ConfigManager.Instance.GetConfigWebValue("USER_NOT_FOUND", responseLang),
  468. new { }
  469. );
  470. }
  471. // Update customer verification status
  472. customer.IsVerified = true;
  473. customer.LastLoginDate = DateTime.Now;
  474. customer.LastUpdate = DateTime.Now;
  475. // Generate JWT tokens
  476. int tokenExpireHours = 24;
  477. int refreshTokenExpireDays = 30;
  478. string accessToken = CommonLogic.GenToken(configuration, customer.Email ?? "", customer.Id.ToString() ?? "");
  479. string refreshToken = CommonLogic.GenRefreshToken(configuration, customer.Email ?? "");
  480. var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
  481. var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
  482. // Revoke old tokens
  483. var oldTokens = dbContext.UserTokens
  484. .Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
  485. .ToList();
  486. foreach (var oldToken in oldTokens)
  487. {
  488. oldToken.IsRevoked = true;
  489. }
  490. // Save new token
  491. var tokenId = (int)await Database.DbLogic.GenIdAsync(dbContext, "USER_TOKEN_SEQ");
  492. var userToken = new UserToken
  493. {
  494. Id = tokenId,
  495. CustomerId = customer.Id,
  496. AccessToken = accessToken,
  497. RefreshToken = refreshToken,
  498. TokenType = "Bearer",
  499. DeviceInfo = httpRequest.Headers["User-Agent"].ToString(),
  500. IpAddress = GetClientIpAddress(httpRequest),
  501. ExpiredAt = expiresAt,
  502. RefreshExpiredAt = refreshExpiresAt,
  503. IsRevoked = false,
  504. CreatedDate = DateTime.Now,
  505. LastUsed = DateTime.Now
  506. };
  507. dbContext.UserTokens.Add(userToken);
  508. await dbContext.SaveChangesAsync();
  509. return DotnetLib.Http.HttpResponse.BuildResponse(
  510. log,
  511. url,
  512. json,
  513. CommonErrorCode.Success,
  514. ConfigManager.Instance.GetConfigWebValue("LOGIN_SUCCESS", responseLang),
  515. new
  516. {
  517. userId = customer.Id,
  518. email = customer.Email ?? "",
  519. fullName = $"{customer.SurName} {customer.LastName}".Trim(),
  520. avatarUrl = customer.AvatarUrl,
  521. accessToken,
  522. refreshToken,
  523. expiresAt
  524. }
  525. );
  526. }
  527. catch (Exception exception)
  528. {
  529. log.Error("Exception: ", exception);
  530. }
  531. return DotnetLib.Http.HttpResponse.BuildResponse(
  532. log,
  533. url,
  534. json,
  535. CommonErrorCode.SystemError,
  536. ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
  537. new { }
  538. );
  539. }
  540. /// <summary>
  541. /// Generate 6-digit OTP code
  542. /// </summary>
  543. private string GenerateOtp()
  544. {
  545. using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create())
  546. {
  547. var bytes = new byte[4];
  548. rng.GetBytes(bytes);
  549. var number = Math.Abs(BitConverter.ToInt32(bytes, 0)) % 1000000;
  550. return number.ToString("D6");
  551. }
  552. }
  553. /// <summary>
  554. /// Get client IP address
  555. /// </summary>
  556. private string GetClientIpAddress(HttpRequest httpRequest)
  557. {
  558. var ipAddress = httpRequest.Headers["X-Forwarded-For"].FirstOrDefault();
  559. if (string.IsNullOrEmpty(ipAddress))
  560. {
  561. ipAddress = httpRequest.HttpContext.Connection.RemoteIpAddress?.ToString();
  562. }
  563. return ipAddress ?? "Unknown";
  564. }
  565. }
  566. }