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 ... /// /// Request an Action /// /// specify destination language /// an instance of XDevice /// specified user's identifier /// a member of XAction which represent Action Step /// an instance of XActionRequestContext which provides required informations for specified step /// specify checking context, default is false /// specify check user exists, default is true /// specify check user and device relation, default is false /// specifies force renew Action Token if it's expired, default is false /// an instance of XActionResponse private async Task 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.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 { 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; } /// /// Request a Mobile Verification Code /// /// specify destination language /// an instance of XDevice /// a token which approved user action /// the mobile number which is going to request a verification code /// check mobile number is in use or not, default is true /// an instance of XActionResponse private async Task 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.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; } /// /// Request a Email Verification Code /// /// specify destination language /// an instance of XDevice /// a token which approved user action /// the email address which is going to request a verification code /// check email address is in use or not, default is true /// an instance of XActionResponse public async Task 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.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 } }