Files
xSaherElmIds/Providers/XIdentityManager/XIdentityManager+Request.cs
T
2026-03-22 14:08:25 +03:30

722 lines
25 KiB
C#

using System.Collections.Generic;
using System.Threading.Tasks;
using xCommons.Extensions;
using xExceptions.Constants;
using xIdentityModels.Constants;
using xIdentityModels.Extensions;
using xIdentityModels.Models;
using xIdentityModels.Navigations;
namespace xIds.Providers
{
public partial class XIdentityManager
{
//
#region Action Handlers ...
/// <summary>
/// Request an Action
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="userSelectByParam">specified user's identifier</param>
/// <param name="step">a member of <see>XAction</see> which represent Action Step</param>
/// <param name="context">an instance of <see>XActionRequestContext</see> which provides required informations for specified step</param>
/// <param name="forceCheckContext">specify checking context, default is false</param>
/// <param name="forceCheckUserExists">specify check user exists, default is true</param>
/// <param name="forceCheckUserDevice">specify check user and device relation, default is false</param>
/// <param name="forceRenewToken">specifies force renew Action Token if it's expired, default is false</param>
/// <returns>an instance of <see>XActionResponse</see></returns>
private async Task<XActionResponse> ActionRequest(
string lang,
XDevice device,
string userSelectByParam,
XAction step,
XActionRequestContext context = null,
bool forceCheckContext = false,
bool forceCheckUserExists = true,
bool forceCheckUserDevice = false,
bool forceRenewToken = false
)
{
//
// Validate Args ...
ValidationProvider.NotEmpty(lang);
ValidationProvider.NotNull(device);
//
// Check Device not Banned ...
await ValidateDeviceForActions(device);
//
// Check User Exists and Device Relation ...
if (forceCheckUserExists)
{
if (forceCheckUserDevice)
{
//
if (context.Device != null &&
!device.IsSameAs(context.Device))
{
XException.InvalidDevice.Throw();
}
//
await ValidateUserAndDeviceRelation(userSelectByParam, device);
}
else
{
await ValidateUserExistsAsync(userSelectByParam);
}
}
//
XToken xToken = null;
XActionResponse result = null;
XActionRequestToken xRequest = null;
//
var isExists = false;
var forceUserSelectByParamNotEmpty = true;
switch (step)
{
case XAction.Registration:
//
forceUserSelectByParamNotEmpty = false;
isExists = await IsTokenExistsByDeviceAndType(device, step);
//
if (isExists)
{
xToken = await ValidateAndRetieveTokenByDeviceAndType(device, step);
}
break;
default:
//
isExists = await IsTokenExistsByUserAndType(userSelectByParam, step);
//
if (isExists)
{
xToken = await ValidateAndRetieveTokenByUserAndType(userSelectByParam, step);
}
break;
}
//
if (isExists)
{
//
var isValidToken = IsValidToken(xToken.Token);
if (!isValidToken && !forceRenewToken)
{
switch (step)
{
case XAction.Registration:
case XAction.Invite:
forceRenewToken = true;
break;
default:
throw XException.InvalidToken.ToException();
}
}
//
if (!isValidToken && forceRenewToken)
{
await ValidateAndHandleToken(xToken.Token);
return await ActionRequest(
lang,
device,
userSelectByParam,
step,
context,
forceCheckContext,
forceCheckUserExists,
forceCheckUserDevice,
forceRenewToken);
}
//
xRequest = ValidateAndParseActionToken(xToken.Token);
//
// Check Exists Context ...
if (forceCheckContext)
{
//
var isSameContext = xRequest.Context.IsSameAs(context);
if (!isSameContext)
{
XException.InvalidData.Throw();
}
}
}
else
{
//
if (context != null)
{
//
var contextUserSelectByParam = GetUserSelectByParam(
context,
false,
new List<XUserSelectBy> {
XUserSelectBy.ID,
XUserSelectBy.Email,
XUserSelectBy.MobileNumber
});
var contextLang = context.Lang;
var contextDevice = context.Device;
//
// prevent from issues on Registration Request ...
if (forceUserSelectByParamNotEmpty)
{
var userSelectByType = GetUserSelectByType(userSelectByParam);
var contextUserSelectByType = GetUserSelectByType(contextUserSelectByParam);
if (contextUserSelectByType != userSelectByType)
{
//
var excludesList = GenerateUserSelectByExcludes(new List<XUserSelectBy> { userSelectByType });
//
contextUserSelectByParam = GetUserSelectByParam(context, false, excludesList);
}
}
//
// Fix Device Not Same on Invitation and Request
if (step == XAction.Registration)
{
//
context.Device = device;
contextDevice = context.Device;
}
//
// Validate Context Data must be Same as Request Data ...
if ((!contextUserSelectByParam.IsNullOrEmpty() &&
contextUserSelectByParam != userSelectByParam) ||
(!contextLang.IsNullOrEmpty() &&
contextLang != lang) ||
(contextDevice != null &&
!contextDevice.IsSameAs(device)))
{
XException.InvalidData.Throw();
}
}
else
{
//
context = new XActionRequestContext
{
Lang = lang,
Device = device
};
//
// Add Content ...
var userSelectByType = GetUserSelectByType(
userSelectByParam,
forceUserSelectByParamNotEmpty);
switch (userSelectByType)
{
case XUserSelectBy.MobileNumber:
context.MobileNumber = userSelectByParam;
break;
case XUserSelectBy.Email:
context.Email = userSelectByParam;
break;
case XUserSelectBy.Username:
context.UserName = userSelectByParam;
break;
case XUserSelectBy.ID:
context.UserId = userSelectByParam;
break;
case XUserSelectBy.NotSpecified:
default:
if (forceCheckUserExists)
{
XException.InvalidArgs.Throw();
}
break;
}
}
//
// Generate Registration Request ...
xRequest = new XActionRequestToken
{
Action = step,
Context = context
};
//
// Add User Id if Exists to Context ...
var isUserExists = await IsUserExistsAsync(userSelectByParam);
if (isUserExists)
{
//
var user = await GetUserAsync(userSelectByParam);
if (user != null)
{
xRequest.Context.UserId = user.Id;
}
}
//
xRequest.Prepare(Configuration.IdentitySecretKey);
//
xToken = await HashRequest(
xRequest,
forceUserSelectByParamNotEmpty);
}
//
// When XToken Exists Passed XToken ...
result = GetActionResultResponse(xToken);
//
if (result == null)
{
XException.ActionFailed.Throw();
}
//
return result;
}
/// <summary>
/// Request a Mobile Verification Code
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="actionToken">a token which approved user action</param>
/// <param name="mobileNumber">the mobile number which is going to request a verification code</param>
/// <param name="checkMobileInUse">check mobile number is in use or not, default is true</param>
/// <returns>an instance of <see>XActionResponse</see></returns>
private async Task<XActionResponse> RequestMobileVerificationCode(
string lang,
XDevice device,
string actionToken,
string mobileNumber,
bool checkMobileInUse = true
)
{
//
// Validate Args ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotEmpty(lang, actionToken, mobileNumber)
.AddMobileNumber(mobileNumber)
.AddNotNull(device)
.ValidateGroupAsync();
//
// Check Device Validation ...
await ValidateDeviceForActions(device);
//
// Check Mobile is Unique ...
if (checkMobileInUse)
{
var isUserExists = await IsUserExistsAsync(mobileNumber);
if (isUserExists)
{
//
var xUser = await GetUserAsync(mobileNumber);
//
if (xUser.PhoneNumberConfirmed)
{
XException.MobileInUsed.Throw();
}
}
}
//
// Get Related Token to Registration Hash and Validate it,
// then Parse XActionRequest Instance from Token ...
var request = await ValidateAndParseActionHash(actionToken);
//
// Validate two Device Must Same ...
ValidateRequestAndGiveDevices(device, request.Context.Device);
//
// Extract UserSelectByParam from XActionRequest ...
var userSelectByParam = GetUserSelectByParam(
request,
forceNotNull: false,
excludes: new List<XUserSelectBy> {
XUserSelectBy.ID,
XUserSelectBy.Email,
XUserSelectBy.MobileNumber
});
//
// if it's null, means user doesn't have Invitation Token
// and there is no User Selector ...
if (userSelectByParam.IsNullOrEmpty())
{
//
// Since Mobile is Unique so it can act as a
// UserSelector ...
userSelectByParam = mobileNumber;
}
//
// Define empty Verification Code
// and Empty Verification Request ...
var verificationCode = "";
XVerificationRequest xVerificationRequest = null;
//
// Check Device Request Verification Code before or Not ...
var isDeviceRequested = await IsDeviceRequestedForVerificationCodeBefor(device);
if (isDeviceRequested)
{
//
// if Requested before, Retrieve XVerification instance by Device from Db
// and Validate it ...
xVerificationRequest = await ValidateAndRetrieveVerificationRequest(device);
//
if (xVerificationRequest.VerificationCode.IsNullOrEmpty())
{
//
verificationCode = GenerateRandom().ToString();
xVerificationRequest.VerificationCode = verificationCode;
//
await RemoveVerificationCodeRequestByDevice(
device,
saveChanges: false
);
await AddVerificationRequest(
xVerificationRequest,
saveChanges: false
);
//
await DbContext.SaveChangesAsync();
}
//
// if XVerificationRequest is Valid, it must Contain an Alive Verification Code
// so retrieve it and assign it ...
verificationCode = xVerificationRequest.VerificationCode;
}
else
{
//
verificationCode = GenerateRandom().ToString();
}
//
// if it's First Verification Request of given Device
// Generate a new Verification Code ...
if (verificationCode.IsNullOrEmpty())
{
verificationCode = GenerateRandom().ToString();
}
//
// Check Context Mobile and given Mobile ...
if (!request.Context.MobileNumber.IsNullOrEmpty() &&
mobileNumber != request.Context.MobileNumber)
{
XException.InvalidMobileNumber.Throw();
}
//
// Check User Added To Db or Not ...
var isAddedUser = !request.Context.UserId.IsNullOrEmpty();
if (isAddedUser)
{
//
// Validate User Exists and Retireve User Object ...
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: false,
checkCanLoginPolicies: false,
checkIsBanned: false);
//
// Check Mobiles Same ...
if (user.PhoneNumber == mobileNumber &&
user.PhoneNumberConfirmed)
{
XException.MobilesSame.Throw();
}
//
// Update User Object's Mobile Number and
// Confirmation Status ...
user.PhoneNumber = mobileNumber;
user.PhoneNumberConfirmed = false;
//
// Save Changes to Db ...
await UpdateUserAsync(user, checkCanLoginPolicies: false, checkIsBanned: false);
}
//
// Prepare Verification Request by Checking Next Tries,
// Device Status, and etc, Update or Add XVerificationRequest to db
// and Retrieve the added Entity ...
xVerificationRequest = await HandleVerificationRequestPreparation(verificationCode, request);
//
// Update Request Context MobileNumber
// and it's Confirmation Status ...
request.Context.MobileNumber = mobileNumber;
request.Context.MobileVerified = false;
//
// Remove Previous Token ...
await RemoveTokenByHash(actionToken);
//
// Prepare New Token Result Resource,
// by Requesting an Action ...
var result = await ActionRequest(
lang,
device,
userSelectByParam,
XAction.RequestMobileVerificationCode,
request.Context,
forceCheckUserExists: isAddedUser
);
//
// Prepare Message ...
var xMessage = GetVerificationCodeMessage(lang, mobileNumber, verificationCode);
//
// Send Verification Code Message to User Using SMS Provider ...
await MessageProvider.SendSmsAsync(xMessage, throwException: false);
//
// Return Result ...
return result;
}
/// <summary>
/// Request a Email Verification Code
/// </summary>
/// <param name="lang">specify destination language</param>
/// <param name="device">an instance of <see>XDevice</see></param>
/// <param name="actionToken">a token which approved user action</param>
/// <param name="emailAddress">the email address which is going to request a verification code</param>
/// <param name="checkEmailInUse">check email address is in use or not, default is true</param>
/// <returns>an instance of <see>XActionResponse</see></returns>
public async Task<XActionResponse> RequestEmailVerificationCode(
string lang,
XDevice device,
string actionToken,
string emailAddress,
bool checkEmailInUse = true
)
{
//
// Validate Args ...
await ValidationProvider
.GroupValidationBuilder()
.AddNotEmpty(lang, actionToken, emailAddress)
.AddEmailAddress(emailAddress)
.AddNotNull(device)
.ValidateGroupAsync();
//
// Check Device Validation ...
await ValidateDeviceForActions(device);
//
// Check Email is Unique ...
if (checkEmailInUse)
{
var isUserExists = await IsUserExistsAsync(emailAddress);
if (isUserExists)
{
//
var xUser = await GetUserAsync(emailAddress);
//
if (xUser.EmailConfirmed)
{
XException.EmailInUsed.Throw();
}
}
}
//
// Get Related Token to Registration Hash and Validate it,
// then Parse XActionRequest Instance from Token ...
var request = await ValidateAndParseActionHash(actionToken);
//
// Validate two Device Must Same ...
ValidateRequestAndGiveDevices(device, request.Context.Device);
//
// Extract UserSelectByParam from XActionRequest ...
var userSelectByParam = GetUserSelectByParam(
request,
forceNotNull: false,
excludes: new List<XUserSelectBy> {
XUserSelectBy.ID,
XUserSelectBy.Email,
XUserSelectBy.MobileNumber
});
//
// if it's null, means user doesn't have Invitation Token
// and there is no User Selector ...
if (userSelectByParam.IsNullOrEmpty())
{
//
// Since Email is Unique so it can act as a
// UserSelector ...
userSelectByParam = emailAddress;
}
//
// Define empty Verification Code
// and Empty Verification Request ...
var verificationCode = "";
XVerificationRequest xVerificationRequest = null;
//
// Check Device Request Verification Code before or Not ...
var isDeviceRequested = await IsDeviceRequestedForVerificationCodeBefor(device);
if (isDeviceRequested)
{
//
// if Requested before, Retrieve XVerification instance by Device from Db
// and Validate it ...
xVerificationRequest = await ValidateAndRetrieveVerificationRequest(device);
//
if (xVerificationRequest.VerificationCode.IsNullOrEmpty())
{
//
verificationCode = GenerateRandom().ToString();
xVerificationRequest.VerificationCode = verificationCode;
//
await RemoveVerificationCodeRequestByDevice(
device,
saveChanges: false
);
await AddVerificationRequest(
xVerificationRequest,
saveChanges: true
);
}
//
// if XVerificationRequest is Valid, it must Contain an Alive Verification Code
// so retrieve it and assign it ...
verificationCode = xVerificationRequest.VerificationCode;
}
else
{
//
verificationCode = GenerateRandom().ToString();
}
//
// Check Context Email and given Email ...
if (!request.Context.Email.IsNullOrEmpty() &&
emailAddress != request.Context.Email)
{
XException.InvalidEmailAddress.Throw();
}
//
// Check User Added To Db or Not ...
var isAddedUser = !request.Context.UserId.IsNullOrEmpty();
if (isAddedUser)
{
//
// Validate User Exists and Retireve User Object ...
var user = await ValidateUserExistsAndRetrieve(
userSelectByParam,
containDetails: false,
checkCanLoginPolicies: false,
checkIsBanned: false);
//
// Check Email Same ...
if (user.Email == emailAddress &&
user.EmailConfirmed)
{
XException.MobilesSame.Throw();
}
//
// Update User Object's Email and
// Confirmation Status ...
user.Email = emailAddress;
user.EmailConfirmed = false;
//
// Save Changes to Db ...
await UpdateUserAsync(
user,
checkCanLoginPolicies: false,
checkIsBanned: false
);
}
//
// Prepare Verification Request by Checking Next Tries,
// Device Status, and etc, Update or Add XVerificationRequest to db
// and Retrieve the added Entity ...
xVerificationRequest = await HandleVerificationRequestPreparation(verificationCode, request);
//
// Update Request Context Email
// and it's Confirmation Status ...
request.Context.Email = emailAddress;
request.Context.EmailVerified = false;
//
// Prepare New Token Result Resource,
// by Requesting an Action ...
var result = await ActionRequest(
lang,
device,
userSelectByParam,
XAction.RequestEmailVerificationCode,
request.Context,
forceCheckUserExists: isAddedUser
);
//
// Remove Hash ...
await RemoveTokenByHash(actionToken);
//
// Prepare Message ...
var xMessage = GetVerificationCodeMessage(lang, emailAddress, verificationCode);
//
// Send Verification Code Message to User Using Mail Provider ...
await MessageProvider.SendMailAsync(xMessage, throwException: false);
//
// Return Result ...
return result;
}
#endregion
}
}