|
@@ -1,22 +1,22 @@
|
|
|
|
|
+using System;
|
|
|
|
|
+using System.Linq;
|
|
|
|
|
+using System.Net.Http;
|
|
|
|
|
+using System.Net.Http.Headers;
|
|
|
|
|
+using System.Threading.Tasks;
|
|
|
|
|
+using System.Xml.Serialization;
|
|
|
using Common;
|
|
using Common;
|
|
|
using Common.Constant;
|
|
using Common.Constant;
|
|
|
using Common.Http;
|
|
using Common.Http;
|
|
|
using Common.Logic;
|
|
using Common.Logic;
|
|
|
-//using Esim.Apis.Logic;
|
|
|
|
|
-using Esim.Apis.Singleton;
|
|
|
|
|
using Database;
|
|
using Database;
|
|
|
using Database.Database;
|
|
using Database.Database;
|
|
|
|
|
+//using Esim.Apis.Logic;
|
|
|
|
|
+using Esim.Apis.Singleton;
|
|
|
using log4net;
|
|
using log4net;
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
using Microsoft.VisualBasic;
|
|
using Microsoft.VisualBasic;
|
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json;
|
|
|
-using System;
|
|
|
|
|
-using System.Threading.Tasks;
|
|
|
|
|
-using System.Xml.Serialization;
|
|
|
|
|
-using System.Net.Http;
|
|
|
|
|
-using System.Net.Http.Headers;
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
using Newtonsoft.Json.Linq;
|
|
|
-using System.Linq;
|
|
|
|
|
|
|
|
|
|
namespace Esim.Apis.Business
|
|
namespace Esim.Apis.Business
|
|
|
{
|
|
{
|
|
@@ -40,7 +40,6 @@ namespace Esim.Apis.Business
|
|
|
return configuration.GetSection(key).Value ?? "";
|
|
return configuration.GetSection(key).Value ?? "";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
|
/// Request OTP to be sent to email
|
|
/// Request OTP to be sent to email
|
|
|
/// </summary>
|
|
/// </summary>
|
|
@@ -68,8 +67,8 @@ namespace Esim.Apis.Business
|
|
|
string otpCode = isTestAccount ? "111111" : GenerateOtp();
|
|
string otpCode = isTestAccount ? "111111" : GenerateOtp();
|
|
|
|
|
|
|
|
// Check if customer exists, if not create new
|
|
// Check if customer exists, if not create new
|
|
|
- var customer = dbContext.CustomerInfos
|
|
|
|
|
- .Where(c => c.Email == request.email)
|
|
|
|
|
|
|
+ var customer = dbContext
|
|
|
|
|
+ .CustomerInfos.Where(c => c.Email == request.email)
|
|
|
.FirstOrDefault();
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
|
decimal? customerId = customer?.Id;
|
|
decimal? customerId = customer?.Id;
|
|
@@ -77,11 +76,14 @@ namespace Esim.Apis.Business
|
|
|
if (customer == null)
|
|
if (customer == null)
|
|
|
{
|
|
{
|
|
|
// Create new customer record - manually get ID from Oracle sequence
|
|
// Create new customer record - manually get ID from Oracle sequence
|
|
|
- var newCustomerId = await Database.DbLogic.GenIdAsync(dbContext, "CUSTOMER_INFO_SEQ");
|
|
|
|
|
-
|
|
|
|
|
|
|
+ var newCustomerId = await Database.DbLogic.GenIdAsync(
|
|
|
|
|
+ dbContext,
|
|
|
|
|
+ "CUSTOMER_INFO_SEQ"
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
// Extract name from email (part before @)
|
|
// Extract name from email (part before @)
|
|
|
string emailUsername = request.email.Split('@')[0];
|
|
string emailUsername = request.email.Split('@')[0];
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
var newCustomer = new CustomerInfo
|
|
var newCustomer = new CustomerInfo
|
|
|
{
|
|
{
|
|
|
Id = newCustomerId,
|
|
Id = newCustomerId,
|
|
@@ -101,48 +103,60 @@ namespace Esim.Apis.Business
|
|
|
// Check if there's an existing OTP record for this email (ANY record, used or unused)
|
|
// Check if there's an existing OTP record for this email (ANY record, used or unused)
|
|
|
int otpExpireMinutes = 5;
|
|
int otpExpireMinutes = 5;
|
|
|
int minSecondsBetweenRequests = 60; // Anti-spam: minimum 60 seconds between requests
|
|
int minSecondsBetweenRequests = 60; // Anti-spam: minimum 60 seconds between requests
|
|
|
-
|
|
|
|
|
- var existingOtp = dbContext.OtpVerifications
|
|
|
|
|
- .Where(o => o.UserEmail == request.email)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ var existingOtp = dbContext
|
|
|
|
|
+ .OtpVerifications.Where(o => o.UserEmail == request.email)
|
|
|
.OrderByDescending(o => o.CreatedDate)
|
|
.OrderByDescending(o => o.CreatedDate)
|
|
|
.FirstOrDefault();
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
|
if (existingOtp != null)
|
|
if (existingOtp != null)
|
|
|
{
|
|
{
|
|
|
// Anti-spam check: prevent rapid OTP requests
|
|
// Anti-spam check: prevent rapid OTP requests
|
|
|
- var secondsSinceLastRequest = (DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)).TotalSeconds;
|
|
|
|
|
|
|
+ var secondsSinceLastRequest = (
|
|
|
|
|
+ DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)
|
|
|
|
|
+ ).TotalSeconds;
|
|
|
if (secondsSinceLastRequest < minSecondsBetweenRequests)
|
|
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.");
|
|
|
|
|
-
|
|
|
|
|
|
|
+ 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(
|
|
return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
log,
|
|
log,
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
|
CommonErrorCode.OtpTooManyRequests,
|
|
CommonErrorCode.OtpTooManyRequests,
|
|
|
$"Please wait {waitSeconds} seconds before requesting a new OTP.",
|
|
$"Please wait {waitSeconds} seconds before requesting a new OTP.",
|
|
|
- new {
|
|
|
|
|
|
|
+ new
|
|
|
|
|
+ {
|
|
|
waitSeconds = waitSeconds,
|
|
waitSeconds = waitSeconds,
|
|
|
- canRequestAt = existingOtp.CreatedDate?.AddSeconds(minSecondsBetweenRequests)
|
|
|
|
|
|
|
+ canRequestAt = existingOtp.CreatedDate?.AddSeconds(
|
|
|
|
|
+ minSecondsBetweenRequests
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// UPDATE existing record - reuse for this email to prevent table bloat
|
|
// UPDATE existing record - reuse for this email to prevent table bloat
|
|
|
existingOtp.OtpCode = otpCode;
|
|
existingOtp.OtpCode = otpCode;
|
|
|
existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
|
|
existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
|
|
|
existingOtp.AttemptCount = 0; // Reset attempt count
|
|
existingOtp.AttemptCount = 0; // Reset attempt count
|
|
|
- existingOtp.IsUsed = false; // Reset to unused (allow reuse)
|
|
|
|
|
|
|
+ existingOtp.IsUsed = false; // Reset to unused (allow reuse)
|
|
|
existingOtp.CustomerId = customerId; // Update customer ID if changed
|
|
existingOtp.CustomerId = customerId; // Update customer ID if changed
|
|
|
existingOtp.CreatedDate = DateTime.Now; // Update to current time
|
|
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")})");
|
|
|
|
|
|
|
+
|
|
|
|
|
+ log.Info(
|
|
|
|
|
+ $"Updated existing OTP record ID={existingOtp.Id} for {request.email} (was {(existingOtp.IsUsed == true ? "used" : "unused")})"
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
// INSERT new record only for first-time email
|
|
// INSERT new record only for first-time email
|
|
|
- var otpId = (int)await Database.DbLogic.GenIdAsync(dbContext, "OTP_VERIFICATION_SEQ");
|
|
|
|
|
|
|
+ var otpId = (int)
|
|
|
|
|
+ await Database.DbLogic.GenIdAsync(dbContext, "OTP_VERIFICATION_SEQ");
|
|
|
var otpVerification = new OtpVerification
|
|
var otpVerification = new OtpVerification
|
|
|
{
|
|
{
|
|
|
Id = otpId,
|
|
Id = otpId,
|
|
@@ -171,8 +185,9 @@ namespace Esim.Apis.Business
|
|
|
string templateCode = "OTP_LOGIN";
|
|
string templateCode = "OTP_LOGIN";
|
|
|
|
|
|
|
|
// Query template and get language-specific content
|
|
// Query template and get language-specific content
|
|
|
- var template = dbContext.MessageTemplates
|
|
|
|
|
- .FirstOrDefault(t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value);
|
|
|
|
|
|
|
+ var template = dbContext.MessageTemplates.FirstOrDefault(
|
|
|
|
|
+ t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
if (template == null)
|
|
if (template == null)
|
|
|
{
|
|
{
|
|
@@ -181,14 +196,20 @@ namespace Esim.Apis.Business
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Get subject based on language: vi=default, en=_EN, lo=_LO (default)
|
|
// 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 ?? "");
|
|
|
|
|
|
|
+ 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)
|
|
// 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 ?? "");
|
|
|
|
|
|
|
+ string emailContent =
|
|
|
|
|
+ lang == "en"
|
|
|
|
|
+ ? (template.ContentEn ?? template.Content ?? "")
|
|
|
|
|
+ : lang == "vi"
|
|
|
|
|
+ ? (template.Content ?? "")
|
|
|
|
|
+ : (template.ContentLo ?? template.Content ?? "");
|
|
|
|
|
|
|
|
// Replace placeholders in content
|
|
// Replace placeholders in content
|
|
|
emailContent = emailContent
|
|
emailContent = emailContent
|
|
@@ -199,14 +220,15 @@ namespace Esim.Apis.Business
|
|
|
emailSubject = emailSubject
|
|
emailSubject = emailSubject
|
|
|
.Replace("{{OTP_CODE}}", otpCode)
|
|
.Replace("{{OTP_CODE}}", otpCode)
|
|
|
.Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
|
|
.Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
|
|
|
- var emailMessageID = (int)await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
|
|
|
|
|
|
|
+ var emailMessageID = (int)
|
|
|
|
|
+ await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
|
|
|
var emailMessage = new MessageQueue
|
|
var emailMessage = new MessageQueue
|
|
|
{
|
|
{
|
|
|
Id = emailMessageID,
|
|
Id = emailMessageID,
|
|
|
MessageType = 1, // Email
|
|
MessageType = 1, // Email
|
|
|
Recipient = request.email,
|
|
Recipient = request.email,
|
|
|
- Subject = emailSubject, // Pre-resolved subject
|
|
|
|
|
- Content = emailContent, // Pre-resolved content
|
|
|
|
|
|
|
+ Subject = emailSubject, // Pre-resolved subject
|
|
|
|
|
+ Content = emailContent, // Pre-resolved content
|
|
|
Priority = true, // High priority
|
|
Priority = true, // High priority
|
|
|
Status = 0, // Pending
|
|
Status = 0, // Pending
|
|
|
ScheduledAt = DateTime.Now,
|
|
ScheduledAt = DateTime.Now,
|
|
@@ -220,7 +242,9 @@ namespace Esim.Apis.Business
|
|
|
await dbContext.SaveChangesAsync();
|
|
await dbContext.SaveChangesAsync();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- log.Info($"OTP generated for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}");
|
|
|
|
|
|
|
+ log.Info(
|
|
|
|
|
+ $"OTP generated for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}"
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
//return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
//return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
// log,
|
|
// log,
|
|
@@ -241,11 +265,7 @@ namespace Esim.Apis.Business
|
|
|
json,
|
|
json,
|
|
|
CommonErrorCode.Success,
|
|
CommonErrorCode.Success,
|
|
|
ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS"),
|
|
ConfigManager.Instance.GetConfigWebValue("OTP_SENT_SUCCESS"),
|
|
|
- new
|
|
|
|
|
- {
|
|
|
|
|
- email = request.email,
|
|
|
|
|
- expireInSeconds = otpExpireMinutes * 60
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ new { email = request.email, expireInSeconds = otpExpireMinutes * 60 }
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
catch (Exception exception)
|
|
catch (Exception exception)
|
|
@@ -271,11 +291,11 @@ namespace Esim.Apis.Business
|
|
|
var url = httpRequest.Path;
|
|
var url = httpRequest.Path;
|
|
|
var json = JsonConvert.SerializeObject(request);
|
|
var json = JsonConvert.SerializeObject(request);
|
|
|
log.Debug("URL: " + url + " => ResendOtp Request: " + json);
|
|
log.Debug("URL: " + url + " => ResendOtp Request: " + json);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
try
|
|
try
|
|
|
{
|
|
{
|
|
|
string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
|
|
string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// Validate email is required
|
|
// Validate email is required
|
|
|
if (string.IsNullOrEmpty(request.email))
|
|
if (string.IsNullOrEmpty(request.email))
|
|
|
{
|
|
{
|
|
@@ -290,8 +310,8 @@ namespace Esim.Apis.Business
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Check if there's an existing OTP record for this email
|
|
// Check if there's an existing OTP record for this email
|
|
|
- var existingOtp = dbContext.OtpVerifications
|
|
|
|
|
- .Where(o => o.UserEmail == request.email)
|
|
|
|
|
|
|
+ var existingOtp = dbContext
|
|
|
|
|
+ .OtpVerifications.Where(o => o.UserEmail == request.email)
|
|
|
.OrderByDescending(o => o.CreatedDate)
|
|
.OrderByDescending(o => o.CreatedDate)
|
|
|
.FirstOrDefault();
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
@@ -311,22 +331,29 @@ namespace Esim.Apis.Business
|
|
|
|
|
|
|
|
// RESEND has reduced cooldown: 60 seconds (vs 60 seconds for RequestOtp)
|
|
// RESEND has reduced cooldown: 60 seconds (vs 60 seconds for RequestOtp)
|
|
|
int minSecondsBetweenResend = 60;
|
|
int minSecondsBetweenResend = 60;
|
|
|
- var secondsSinceLastRequest = (DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)).TotalSeconds;
|
|
|
|
|
-
|
|
|
|
|
|
|
+ var secondsSinceLastRequest = (
|
|
|
|
|
+ DateTime.Now - (existingOtp.CreatedDate ?? DateTime.Now)
|
|
|
|
|
+ ).TotalSeconds;
|
|
|
|
|
+
|
|
|
if (secondsSinceLastRequest < minSecondsBetweenResend)
|
|
if (secondsSinceLastRequest < minSecondsBetweenResend)
|
|
|
{
|
|
{
|
|
|
var waitSeconds = (int)(minSecondsBetweenResend - secondsSinceLastRequest);
|
|
var waitSeconds = (int)(minSecondsBetweenResend - secondsSinceLastRequest);
|
|
|
- log.Warn($"ResendOtp: Too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago.");
|
|
|
|
|
-
|
|
|
|
|
|
|
+ log.Warn(
|
|
|
|
|
+ $"ResendOtp: Too soon for {request.email}. Last request {secondsSinceLastRequest:F0}s ago."
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
log,
|
|
log,
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
|
CommonErrorCode.OtpTooManyRequests,
|
|
CommonErrorCode.OtpTooManyRequests,
|
|
|
$"Please wait {waitSeconds} seconds before resending OTP.",
|
|
$"Please wait {waitSeconds} seconds before resending OTP.",
|
|
|
- new {
|
|
|
|
|
|
|
+ new
|
|
|
|
|
+ {
|
|
|
waitSeconds = waitSeconds,
|
|
waitSeconds = waitSeconds,
|
|
|
- canResendAt = existingOtp.CreatedDate?.AddSeconds(minSecondsBetweenResend)
|
|
|
|
|
|
|
+ canResendAt = existingOtp.CreatedDate?.AddSeconds(
|
|
|
|
|
+ minSecondsBetweenResend
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
@@ -334,13 +361,13 @@ namespace Esim.Apis.Business
|
|
|
// Generate new 6-digit OTP (fixed 111111 for test account abc@gmail.com)
|
|
// Generate new 6-digit OTP (fixed 111111 for test account abc@gmail.com)
|
|
|
bool isTestAccount = request.email.ToLower() == "abc@gmail.com";
|
|
bool isTestAccount = request.email.ToLower() == "abc@gmail.com";
|
|
|
string otpCode = isTestAccount ? "111111" : GenerateOtp();
|
|
string otpCode = isTestAccount ? "111111" : GenerateOtp();
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// OTP expires in 5 minutes
|
|
// OTP expires in 5 minutes
|
|
|
int otpExpireMinutes = 5;
|
|
int otpExpireMinutes = 5;
|
|
|
|
|
|
|
|
// Get customer ID (should exist since OTP record exists)
|
|
// Get customer ID (should exist since OTP record exists)
|
|
|
- var customer = dbContext.CustomerInfos
|
|
|
|
|
- .Where(c => c.Email == request.email)
|
|
|
|
|
|
|
+ var customer = dbContext
|
|
|
|
|
+ .CustomerInfos.Where(c => c.Email == request.email)
|
|
|
.FirstOrDefault();
|
|
.FirstOrDefault();
|
|
|
decimal? customerId = customer?.Id ?? existingOtp.CustomerId;
|
|
decimal? customerId = customer?.Id ?? existingOtp.CustomerId;
|
|
|
|
|
|
|
@@ -348,12 +375,12 @@ namespace Esim.Apis.Business
|
|
|
existingOtp.OtpCode = otpCode;
|
|
existingOtp.OtpCode = otpCode;
|
|
|
existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
|
|
existingOtp.ExpiredAt = DateTime.Now.AddMinutes(otpExpireMinutes);
|
|
|
existingOtp.AttemptCount = 0; // Reset attempt count
|
|
existingOtp.AttemptCount = 0; // Reset attempt count
|
|
|
- existingOtp.IsUsed = false; // Reset to unused
|
|
|
|
|
|
|
+ existingOtp.IsUsed = false; // Reset to unused
|
|
|
existingOtp.CustomerId = customerId;
|
|
existingOtp.CustomerId = customerId;
|
|
|
existingOtp.CreatedDate = DateTime.Now; // Update to track resend time
|
|
existingOtp.CreatedDate = DateTime.Now; // Update to track resend time
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
log.Info($"ResendOtp: Updated OTP record ID={existingOtp.Id} for {request.email}");
|
|
log.Info($"ResendOtp: Updated OTP record ID={existingOtp.Id} for {request.email}");
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
await dbContext.SaveChangesAsync();
|
|
await dbContext.SaveChangesAsync();
|
|
|
|
|
|
|
|
// Skip email sending for test account
|
|
// Skip email sending for test account
|
|
@@ -361,9 +388,10 @@ namespace Esim.Apis.Business
|
|
|
{
|
|
{
|
|
|
// Add to MESSAGE_QUEUE for background email sending
|
|
// Add to MESSAGE_QUEUE for background email sending
|
|
|
string templateCode = "OTP_LOGIN";
|
|
string templateCode = "OTP_LOGIN";
|
|
|
-
|
|
|
|
|
- var template = dbContext.MessageTemplates
|
|
|
|
|
- .FirstOrDefault(t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ var template = dbContext.MessageTemplates.FirstOrDefault(
|
|
|
|
|
+ t => t.TemplateCode == templateCode && t.Status.HasValue && t.Status.Value
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
if (template == null)
|
|
if (template == null)
|
|
|
{
|
|
{
|
|
@@ -372,14 +400,20 @@ namespace Esim.Apis.Business
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Get subject based on language
|
|
// Get subject based on language
|
|
|
- string emailSubject = lang == "en" ? (template.SubjectEn ?? template.Subject ?? "")
|
|
|
|
|
- : lang == "vi" ? (template.Subject ?? "")
|
|
|
|
|
- : (template.SubjectLo ?? template.Subject ?? "");
|
|
|
|
|
|
|
+ string emailSubject =
|
|
|
|
|
+ lang == "en"
|
|
|
|
|
+ ? (template.SubjectEn ?? template.Subject ?? "")
|
|
|
|
|
+ : lang == "vi"
|
|
|
|
|
+ ? (template.Subject ?? "")
|
|
|
|
|
+ : (template.SubjectLo ?? template.Subject ?? "");
|
|
|
|
|
|
|
|
// Get content based on language
|
|
// Get content based on language
|
|
|
- string emailContent = lang == "en" ? (template.ContentEn ?? template.Content ?? "")
|
|
|
|
|
- : lang == "vi" ? (template.Content ?? "")
|
|
|
|
|
- : (template.ContentLo ?? template.Content ?? "");
|
|
|
|
|
|
|
+ string emailContent =
|
|
|
|
|
+ lang == "en"
|
|
|
|
|
+ ? (template.ContentEn ?? template.Content ?? "")
|
|
|
|
|
+ : lang == "vi"
|
|
|
|
|
+ ? (template.Content ?? "")
|
|
|
|
|
+ : (template.ContentLo ?? template.Content ?? "");
|
|
|
|
|
|
|
|
// Replace placeholders
|
|
// Replace placeholders
|
|
|
emailContent = emailContent
|
|
emailContent = emailContent
|
|
@@ -390,7 +424,8 @@ namespace Esim.Apis.Business
|
|
|
.Replace("{{OTP_CODE}}", otpCode)
|
|
.Replace("{{OTP_CODE}}", otpCode)
|
|
|
.Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
|
|
.Replace("{{EXPIRE_MINUTES}}", otpExpireMinutes.ToString());
|
|
|
|
|
|
|
|
- var emailMessageID = (int)await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
|
|
|
|
|
|
|
+ var emailMessageID = (int)
|
|
|
|
|
+ await Database.DbLogic.GenIdAsync(dbContext, "MESSAGE_QUEUE_SEQ");
|
|
|
var emailMessage = new MessageQueue
|
|
var emailMessage = new MessageQueue
|
|
|
{
|
|
{
|
|
|
Id = emailMessageID,
|
|
Id = emailMessageID,
|
|
@@ -411,7 +446,9 @@ namespace Esim.Apis.Business
|
|
|
await dbContext.SaveChangesAsync();
|
|
await dbContext.SaveChangesAsync();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- log.Info($"ResendOtp: OTP resent for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}");
|
|
|
|
|
|
|
+ log.Info(
|
|
|
|
|
+ $"ResendOtp: OTP resent for {request.email}: {otpCode} - {(isTestAccount ? "Test account, no email sent" : "Email queued")}"
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
log,
|
|
log,
|
|
@@ -419,11 +456,7 @@ namespace Esim.Apis.Business
|
|
|
json,
|
|
json,
|
|
|
CommonErrorCode.Success,
|
|
CommonErrorCode.Success,
|
|
|
ConfigManager.Instance.GetConfigWebValue("OTP_RESENT_SUCCESS", lang),
|
|
ConfigManager.Instance.GetConfigWebValue("OTP_RESENT_SUCCESS", lang),
|
|
|
- new
|
|
|
|
|
- {
|
|
|
|
|
- email = request.email,
|
|
|
|
|
- expireInSeconds = otpExpireMinutes * 60
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ new { email = request.email, expireInSeconds = otpExpireMinutes * 60 }
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
catch (Exception exception)
|
|
catch (Exception exception)
|
|
@@ -467,19 +500,24 @@ namespace Esim.Apis.Business
|
|
|
string responseLang = CommonLogic.GetLanguage(httpRequest, request.lang);
|
|
string responseLang = CommonLogic.GetLanguage(httpRequest, request.lang);
|
|
|
|
|
|
|
|
// Find valid OTP
|
|
// Find valid OTP
|
|
|
- var otpRecord = dbContext.OtpVerifications
|
|
|
|
|
- .Where(o => o.UserEmail == request.email
|
|
|
|
|
- && o.OtpCode == request.otpCode
|
|
|
|
|
- && o.IsUsed == false
|
|
|
|
|
- && o.ExpiredAt > DateTime.Now)
|
|
|
|
|
|
|
+ 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)
|
|
.OrderByDescending(o => o.CreatedDate)
|
|
|
.FirstOrDefault();
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
|
if (otpRecord == null)
|
|
if (otpRecord == null)
|
|
|
{
|
|
{
|
|
|
// Check if OTP exists but expired or used
|
|
// Check if OTP exists but expired or used
|
|
|
- var anyOtp = dbContext.OtpVerifications
|
|
|
|
|
- .Where(o => o.UserEmail == request.email && o.OtpCode == request.otpCode)
|
|
|
|
|
|
|
+ var anyOtp = dbContext
|
|
|
|
|
+ .OtpVerifications.Where(
|
|
|
|
|
+ o => o.UserEmail == request.email && o.OtpCode == request.otpCode
|
|
|
|
|
+ )
|
|
|
.FirstOrDefault();
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
|
if (anyOtp != null)
|
|
if (anyOtp != null)
|
|
@@ -491,7 +529,10 @@ namespace Esim.Apis.Business
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
|
CommonErrorCode.OtpAlreadyUsed,
|
|
CommonErrorCode.OtpAlreadyUsed,
|
|
|
- ConfigManager.Instance.GetConfigWebValue("OTP_ALREADY_USED", responseLang),
|
|
|
|
|
|
|
+ ConfigManager.Instance.GetConfigWebValue(
|
|
|
|
|
+ "OTP_ALREADY_USED",
|
|
|
|
|
+ responseLang
|
|
|
|
|
+ ),
|
|
|
new { }
|
|
new { }
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
@@ -502,7 +543,10 @@ namespace Esim.Apis.Business
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
|
CommonErrorCode.OtpExpired,
|
|
CommonErrorCode.OtpExpired,
|
|
|
- ConfigManager.Instance.GetConfigWebValue("OTP_EXPIRED", responseLang),
|
|
|
|
|
|
|
+ ConfigManager.Instance.GetConfigWebValue(
|
|
|
|
|
+ "OTP_EXPIRED",
|
|
|
|
|
+ responseLang
|
|
|
|
|
+ ),
|
|
|
new { }
|
|
new { }
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
@@ -522,8 +566,8 @@ namespace Esim.Apis.Business
|
|
|
otpRecord.IsUsed = true;
|
|
otpRecord.IsUsed = true;
|
|
|
|
|
|
|
|
// Get customer info
|
|
// Get customer info
|
|
|
- var customer = dbContext.CustomerInfos
|
|
|
|
|
- .Where(c => c.Email == request.email)
|
|
|
|
|
|
|
+ var customer = dbContext
|
|
|
|
|
+ .CustomerInfos.Where(c => c.Email == request.email)
|
|
|
.FirstOrDefault();
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
|
if (customer == null)
|
|
if (customer == null)
|
|
@@ -547,14 +591,21 @@ namespace Esim.Apis.Business
|
|
|
int tokenExpireHours = 24;
|
|
int tokenExpireHours = 24;
|
|
|
int refreshTokenExpireDays = 30;
|
|
int refreshTokenExpireDays = 30;
|
|
|
|
|
|
|
|
- string accessToken = CommonLogic.GenToken(configuration, customer.Email ?? "", customer.Id.ToString() ?? "");
|
|
|
|
|
- string refreshToken = CommonLogic.GenRefreshToken(configuration, customer.Email ?? "");
|
|
|
|
|
|
|
+ string accessToken = CommonLogic.GenToken(
|
|
|
|
|
+ configuration,
|
|
|
|
|
+ customer.Email ?? "",
|
|
|
|
|
+ customer.Id.ToString() ?? ""
|
|
|
|
|
+ );
|
|
|
|
|
+ string refreshToken = CommonLogic.GenRefreshToken(
|
|
|
|
|
+ configuration,
|
|
|
|
|
+ customer.Email ?? ""
|
|
|
|
|
+ );
|
|
|
var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
|
|
var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
|
|
|
var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
|
|
var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
|
|
|
|
|
|
|
|
// Revoke old tokens
|
|
// Revoke old tokens
|
|
|
- var oldTokens = dbContext.UserTokens
|
|
|
|
|
- .Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
|
|
|
|
|
|
|
+ var oldTokens = dbContext
|
|
|
|
|
+ .UserTokens.Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
|
|
|
.ToList();
|
|
.ToList();
|
|
|
|
|
|
|
|
foreach (var oldToken in oldTokens)
|
|
foreach (var oldToken in oldTokens)
|
|
@@ -642,23 +693,26 @@ namespace Esim.Apis.Business
|
|
|
return ipAddress ?? "Unknown";
|
|
return ipAddress ?? "Unknown";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public async Task<IActionResult> GoogleLogin(HttpRequest httpRequest, GoogleLoginReq request)
|
|
|
|
|
|
|
+ public async Task<IActionResult> GoogleLogin(
|
|
|
|
|
+ HttpRequest httpRequest,
|
|
|
|
|
+ GoogleLoginReq request
|
|
|
|
|
+ )
|
|
|
{
|
|
{
|
|
|
var url = httpRequest.Path;
|
|
var url = httpRequest.Path;
|
|
|
var json = JsonConvert.SerializeObject(request);
|
|
var json = JsonConvert.SerializeObject(request);
|
|
|
log.Debug("URL: " + url + " => GoogleLogin Request: " + json);
|
|
log.Debug("URL: " + url + " => GoogleLogin Request: " + json);
|
|
|
-
|
|
|
|
|
- try
|
|
|
|
|
|
|
+
|
|
|
|
|
+ try
|
|
|
{
|
|
{
|
|
|
string lang = CommonLogic.GetLanguage(httpRequest, request?.lang);
|
|
string lang = CommonLogic.GetLanguage(httpRequest, request?.lang);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
var clientId = configuration["Google:ClientId"];
|
|
var clientId = configuration["Google:ClientId"];
|
|
|
var redirectUri = configuration["Google:RedirectUri"];
|
|
var redirectUri = configuration["Google:RedirectUri"];
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(redirectUri))
|
|
if (string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(redirectUri))
|
|
|
{
|
|
{
|
|
|
log.Error("Google Auth configuration missing");
|
|
log.Error("Google Auth configuration missing");
|
|
|
- return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
|
|
|
|
+ return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
log,
|
|
log,
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
@@ -668,21 +722,22 @@ namespace Esim.Apis.Business
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- var googleUrl = $"https://accounts.google.com/o/oauth2/v2/auth?client_id={clientId}&redirect_uri={redirectUri}&response_type=code&scope=email%20profile";
|
|
|
|
|
-
|
|
|
|
|
|
|
+ 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(
|
|
return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
- log,
|
|
|
|
|
- url,
|
|
|
|
|
- json,
|
|
|
|
|
- CommonErrorCode.Success,
|
|
|
|
|
- ConfigManager.Instance.GetConfigWebValue("SUCCESS", lang),
|
|
|
|
|
- new { url = googleUrl }
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ log,
|
|
|
|
|
+ url,
|
|
|
|
|
+ json,
|
|
|
|
|
+ CommonErrorCode.Success,
|
|
|
|
|
+ ConfigManager.Instance.GetConfigWebValue("SUCCESS", lang),
|
|
|
|
|
+ new { url = googleUrl }
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
catch (Exception ex)
|
|
catch (Exception ex)
|
|
|
{
|
|
{
|
|
|
- log.Error("GoogleLogin Exception: ", ex);
|
|
|
|
|
- return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
|
|
|
|
+ log.Error("GoogleLogin Exception: ", ex);
|
|
|
|
|
+ return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
log,
|
|
log,
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
@@ -693,20 +748,23 @@ namespace Esim.Apis.Business
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- public async Task<IActionResult> GoogleCallback(HttpRequest httpRequest, GoogleCallbackReq request)
|
|
|
|
|
|
|
+ public async Task<IActionResult> GoogleCallback(
|
|
|
|
|
+ HttpRequest httpRequest,
|
|
|
|
|
+ GoogleCallbackReq request
|
|
|
|
|
+ )
|
|
|
{
|
|
{
|
|
|
var url = httpRequest.Path;
|
|
var url = httpRequest.Path;
|
|
|
var json = JsonConvert.SerializeObject(request);
|
|
var json = JsonConvert.SerializeObject(request);
|
|
|
log.Debug("URL: " + url + " => GoogleCallback Request: " + json);
|
|
log.Debug("URL: " + url + " => GoogleCallback Request: " + json);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
try
|
|
try
|
|
|
{
|
|
{
|
|
|
// Get language for response messages
|
|
// Get language for response messages
|
|
|
string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
|
|
string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (string.IsNullOrEmpty(request.code))
|
|
if (string.IsNullOrEmpty(request.code))
|
|
|
{
|
|
{
|
|
|
- return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
|
|
|
|
+ return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
log,
|
|
log,
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
@@ -718,32 +776,42 @@ namespace Esim.Apis.Business
|
|
|
|
|
|
|
|
var clientId = configuration["Google:ClientId"];
|
|
var clientId = configuration["Google:ClientId"];
|
|
|
var clientSecret = configuration["Google:ClientSecret"];
|
|
var clientSecret = configuration["Google:ClientSecret"];
|
|
|
- var redirectUri = !string.IsNullOrEmpty(request.redirectUri) ? request.redirectUri : configuration["Google:RedirectUri"];
|
|
|
|
|
|
|
+ var redirectUri = !string.IsNullOrEmpty(request.redirectUri)
|
|
|
|
|
+ ? request.redirectUri
|
|
|
|
|
+ : configuration["Google:RedirectUri"];
|
|
|
|
|
|
|
|
using (var httpClient = new HttpClient())
|
|
using (var httpClient = new HttpClient())
|
|
|
{
|
|
{
|
|
|
// 1. Exchange code for token
|
|
// 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 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();
|
|
var tokenResponseString = await tokenResponse.Content.ReadAsStringAsync();
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (!tokenResponse.IsSuccessStatusCode)
|
|
if (!tokenResponse.IsSuccessStatusCode)
|
|
|
{
|
|
{
|
|
|
- log.Error($"Google Token Exchange Failed: {tokenResponseString}");
|
|
|
|
|
- return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
|
|
|
|
+ log.Error($"Google Token Exchange Failed: {tokenResponseString}");
|
|
|
|
|
+ return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
log,
|
|
log,
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
|
CommonErrorCode.ExternalServiceError,
|
|
CommonErrorCode.ExternalServiceError,
|
|
|
- ConfigManager.Instance.GetConfigWebValue("GOOGLE_TOKEN_EXCHANGE_FAILED", lang),
|
|
|
|
|
|
|
+ ConfigManager.Instance.GetConfigWebValue(
|
|
|
|
|
+ "GOOGLE_TOKEN_EXCHANGE_FAILED",
|
|
|
|
|
+ lang
|
|
|
|
|
+ ),
|
|
|
new { error = tokenResponseString }
|
|
new { error = tokenResponseString }
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
@@ -753,30 +821,41 @@ namespace Esim.Apis.Business
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(accessToken))
|
|
if (string.IsNullOrEmpty(accessToken))
|
|
|
{
|
|
{
|
|
|
- return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
|
|
|
|
+ return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
log,
|
|
log,
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
|
CommonErrorCode.ExternalServiceError,
|
|
CommonErrorCode.ExternalServiceError,
|
|
|
- ConfigManager.Instance.GetConfigWebValue("GOOGLE_NO_ACCESS_TOKEN", lang),
|
|
|
|
|
|
|
+ ConfigManager.Instance.GetConfigWebValue(
|
|
|
|
|
+ "GOOGLE_NO_ACCESS_TOKEN",
|
|
|
|
|
+ lang
|
|
|
|
|
+ ),
|
|
|
new { }
|
|
new { }
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 2. Get User Info
|
|
// 2. Get User Info
|
|
|
- httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
|
|
|
|
|
- var userInfoResponse = await httpClient.GetAsync("https://www.googleapis.com/oauth2/v2/userinfo");
|
|
|
|
|
|
|
+ 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();
|
|
var userInfoString = await userInfoResponse.Content.ReadAsStringAsync();
|
|
|
|
|
|
|
|
if (!userInfoResponse.IsSuccessStatusCode)
|
|
if (!userInfoResponse.IsSuccessStatusCode)
|
|
|
{
|
|
{
|
|
|
- log.Error($"Google User Info Failed: {userInfoString}");
|
|
|
|
|
- return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
|
|
|
|
+ log.Error($"Google User Info Failed: {userInfoString}");
|
|
|
|
|
+ return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
log,
|
|
log,
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
|
CommonErrorCode.ExternalServiceError,
|
|
CommonErrorCode.ExternalServiceError,
|
|
|
- ConfigManager.Instance.GetConfigWebValue("GOOGLE_USERINFO_FAILED", lang),
|
|
|
|
|
|
|
+ ConfigManager.Instance.GetConfigWebValue(
|
|
|
|
|
+ "GOOGLE_USERINFO_FAILED",
|
|
|
|
|
+ lang
|
|
|
|
|
+ ),
|
|
|
new { error = userInfoString }
|
|
new { error = userInfoString }
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
@@ -785,10 +864,10 @@ namespace Esim.Apis.Business
|
|
|
var email = userInfo["email"]?.ToString();
|
|
var email = userInfo["email"]?.ToString();
|
|
|
var name = userInfo["name"]?.ToString(); // Full name
|
|
var name = userInfo["name"]?.ToString(); // Full name
|
|
|
var picture = userInfo["picture"]?.ToString();
|
|
var picture = userInfo["picture"]?.ToString();
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
if (string.IsNullOrEmpty(email))
|
|
if (string.IsNullOrEmpty(email))
|
|
|
{
|
|
{
|
|
|
- return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
|
|
|
|
+ return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
log,
|
|
log,
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
@@ -799,14 +878,16 @@ namespace Esim.Apis.Business
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 3. Login or Register logic
|
|
// 3. Login or Register logic
|
|
|
- var customer = dbContext.CustomerInfos
|
|
|
|
|
- .FirstOrDefault(c => c.Email == email);
|
|
|
|
|
|
|
+ var customer = dbContext.CustomerInfos.FirstOrDefault(c => c.Email == email);
|
|
|
|
|
|
|
|
if (customer == null)
|
|
if (customer == null)
|
|
|
{
|
|
{
|
|
|
// Register new user
|
|
// Register new user
|
|
|
- var newCustomerId = await Database.DbLogic.GenIdAsync(dbContext, "CUSTOMER_INFO_SEQ");
|
|
|
|
|
-
|
|
|
|
|
|
|
+ var newCustomerId = await Database.DbLogic.GenIdAsync(
|
|
|
|
|
+ dbContext,
|
|
|
|
|
+ "CUSTOMER_INFO_SEQ"
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
// Try to split name
|
|
// Try to split name
|
|
|
string surname = name;
|
|
string surname = name;
|
|
|
string lastname = "";
|
|
string lastname = "";
|
|
@@ -819,9 +900,9 @@ namespace Esim.Apis.Business
|
|
|
surname = string.Join(" ", parts.Take(parts.Length - 1));
|
|
surname = string.Join(" ", parts.Take(parts.Length - 1));
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- else
|
|
|
|
|
|
|
+ else
|
|
|
{
|
|
{
|
|
|
- surname = email.Split('@')[0];
|
|
|
|
|
|
|
+ surname = email.Split('@')[0];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
customer = new CustomerInfo
|
|
customer = new CustomerInfo
|
|
@@ -830,7 +911,7 @@ namespace Esim.Apis.Business
|
|
|
Email = email,
|
|
Email = email,
|
|
|
SurName = surname,
|
|
SurName = surname,
|
|
|
LastName = lastname,
|
|
LastName = lastname,
|
|
|
- AvatarUrl = picture,
|
|
|
|
|
|
|
+ AvatarUrl = picture,
|
|
|
Status = true,
|
|
Status = true,
|
|
|
IsVerified = true, // Verified by Google
|
|
IsVerified = true, // Verified by Google
|
|
|
CreatedDate = DateTime.Now,
|
|
CreatedDate = DateTime.Now,
|
|
@@ -838,34 +919,48 @@ namespace Esim.Apis.Business
|
|
|
};
|
|
};
|
|
|
dbContext.CustomerInfos.Add(customer);
|
|
dbContext.CustomerInfos.Add(customer);
|
|
|
await dbContext.SaveChangesAsync();
|
|
await dbContext.SaveChangesAsync();
|
|
|
- log.Info($"Created new customer via Google Login: ID={newCustomerId}, Email={email}");
|
|
|
|
|
|
|
+ log.Info(
|
|
|
|
|
+ $"Created new customer via Google Login: ID={newCustomerId}, Email={email}"
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
- else
|
|
|
|
|
|
|
+ else
|
|
|
{
|
|
{
|
|
|
// Update existing user info if needed or just log them in
|
|
// Update existing user info if needed or just log them in
|
|
|
- customer.IsVerified = true;
|
|
|
|
|
- if (string.IsNullOrEmpty(customer.AvatarUrl) && !string.IsNullOrEmpty(picture))
|
|
|
|
|
|
|
+ customer.IsVerified = true;
|
|
|
|
|
+ if (
|
|
|
|
|
+ string.IsNullOrEmpty(customer.AvatarUrl)
|
|
|
|
|
+ && !string.IsNullOrEmpty(picture)
|
|
|
|
|
+ )
|
|
|
{
|
|
{
|
|
|
customer.AvatarUrl = picture;
|
|
customer.AvatarUrl = picture;
|
|
|
}
|
|
}
|
|
|
customer.LastLoginDate = DateTime.Now;
|
|
customer.LastLoginDate = DateTime.Now;
|
|
|
customer.LastUpdate = DateTime.Now;
|
|
customer.LastUpdate = DateTime.Now;
|
|
|
await dbContext.SaveChangesAsync();
|
|
await dbContext.SaveChangesAsync();
|
|
|
- log.Info($"Existing customer logged in via Google: ID={customer.Id}, Email={email}");
|
|
|
|
|
|
|
+ log.Info(
|
|
|
|
|
+ $"Existing customer logged in via Google: ID={customer.Id}, Email={email}"
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 4. Generate JWT
|
|
// 4. Generate JWT
|
|
|
int tokenExpireHours = 24;
|
|
int tokenExpireHours = 24;
|
|
|
int refreshTokenExpireDays = 30;
|
|
int refreshTokenExpireDays = 30;
|
|
|
|
|
|
|
|
- string jwtAccessToken = CommonLogic.GenToken(configuration, customer.Email ?? "", customer.Id.ToString() ?? "");
|
|
|
|
|
- string jwtRefreshToken = CommonLogic.GenRefreshToken(configuration, customer.Email ?? "");
|
|
|
|
|
|
|
+ string jwtAccessToken = CommonLogic.GenToken(
|
|
|
|
|
+ configuration,
|
|
|
|
|
+ customer.Email ?? "",
|
|
|
|
|
+ customer.Id.ToString() ?? ""
|
|
|
|
|
+ );
|
|
|
|
|
+ string jwtRefreshToken = CommonLogic.GenRefreshToken(
|
|
|
|
|
+ configuration,
|
|
|
|
|
+ customer.Email ?? ""
|
|
|
|
|
+ );
|
|
|
var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
|
|
var expiresAt = DateTime.Now.AddHours(tokenExpireHours);
|
|
|
var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
|
|
var refreshExpiresAt = DateTime.Now.AddDays(refreshTokenExpireDays);
|
|
|
|
|
|
|
|
// Revoke old tokens
|
|
// Revoke old tokens
|
|
|
- var oldTokens = dbContext.UserTokens
|
|
|
|
|
- .Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
|
|
|
|
|
|
|
+ var oldTokens = dbContext
|
|
|
|
|
+ .UserTokens.Where(t => t.CustomerId == customer.Id && t.IsRevoked == false)
|
|
|
.ToList();
|
|
.ToList();
|
|
|
|
|
|
|
|
foreach (var oldToken in oldTokens)
|
|
foreach (var oldToken in oldTokens)
|
|
@@ -874,7 +969,8 @@ namespace Esim.Apis.Business
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Save new token
|
|
// Save new token
|
|
|
- var tokenId = (int)await Database.DbLogic.GenIdAsync(dbContext, "USER_TOKEN_SEQ");
|
|
|
|
|
|
|
+ var tokenId = (int)
|
|
|
|
|
+ await Database.DbLogic.GenIdAsync(dbContext, "USER_TOKEN_SEQ");
|
|
|
var userToken = new UserToken
|
|
var userToken = new UserToken
|
|
|
{
|
|
{
|
|
|
Id = tokenId,
|
|
Id = tokenId,
|
|
@@ -916,7 +1012,7 @@ namespace Esim.Apis.Business
|
|
|
catch (Exception exception)
|
|
catch (Exception exception)
|
|
|
{
|
|
{
|
|
|
log.Error("GoogleCallback Exception: ", exception);
|
|
log.Error("GoogleCallback Exception: ", exception);
|
|
|
- return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
|
|
|
|
+ return DotnetLib.Http.HttpResponse.BuildResponse(
|
|
|
log,
|
|
log,
|
|
|
url,
|
|
url,
|
|
|
json,
|
|
json,
|
|
@@ -926,6 +1022,5 @@ namespace Esim.Apis.Business
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|