public async Task <IActionResult> TwoFactorValidation(string userName) { ViewData["ButtonID"] = ButtonID.Login; if (string.IsNullOrWhiteSpace(userName)) { TempData[TempDataKeys.Redirect] = RedirectPurpose.TwoFactorSmsResendFailure; return(RedirectToActionPermanent(nameof(Login))); } ApplicationUser user = await _accountRepository.GetUserByNameAsync(userName); if (user == null) { TempData[TempDataKeys.Redirect] = RedirectPurpose.TwoFactorSmsResendFailure; return(RedirectToActionPermanent(nameof(Login))); } // Invalid Authorize Cookie if (!await _twoFactorTokenRepository.TokenValidAsync(user.Id)) { _logger.LogInformation($"USER {user.Id} was rejected two-factor sign in due to invalid authorize cookie."); TempData[TempDataKeys.Redirect] = RedirectPurpose.TwoFactorSmsResendFailure; return(RedirectToActionPermanent(nameof(Login))); } if (user.TwoFactorMethod == (int)TwoFactorMethod.SmsVerify && TempData[TempDataKeys.Redirect] == null && await _timeoutsRepository.CanRequestInitalTwoFactorSmsAsync(user.Id)) { await _accountRepository.SendTwoFactorCodeSms(user); await _timeoutsRepository.UpdateRequestAsync(user.Id, UserRequest.InitalTwoFactorRequestSms); } return(View(nameof(TwoFactorValidation), new TwoFactorAuthenticateModel() { InputUsername = userName, UserMethod = user.TwoFactorMethod == (int)TwoFactorMethod.SmsVerify ? TwoFactorMethod.SmsVerify : TwoFactorMethod.MobileAppVerify, })); }
public async Task<IActionResult> Profile(ProfileSettingsModel model) { if (!ModelState.IsValid) { return View(model); } if (!await _captcha.IsCaptchaValidAsync()) { ModelState.AddModelError(_captcha.CaptchaValidationError().Key, _captcha.CaptchaValidationError().Value); return View(); } ApplicationUser goliathUser = await _accountRepository.GetUserFromContextAsync(User); if (!await _timeouts.CanRequestProfileSettingsUpdateAsync(goliathUser.Id)) { ModelState.AddModelError(string.Empty, "Please wait before updating your profile again."); return View(); } #region Simple value updates // ** Values which do not need database checking goliathUser.BackgroundColor = model.BackgroundColor; goliathUser.DarkTheme = model.DarkThemeEnabled; goliathUser.LogoutThreshold = int.Parse(model.LogoutThreshold); // ** If settings which require a two-factor code are updated. bool requiresTwoFactor = false; #endregion Simple value updates #region Validating email if (!string.IsNullOrWhiteSpace(model.NewEmail)) { if (await _accountRepository.DoesEmailExistAsync(model.NewEmail)) { ModelState.AddModelError(string.Empty, $"The email {model.NewEmail} is currently in use."); return View(); } else { goliathUser.UnverifiedNewEmail = model.NewEmail; requiresTwoFactor = true; } } #endregion Validating email #region Validating phone number if (!string.IsNullOrWhiteSpace(model.NewPhoneNumber)) { if (await _accountRepository.DoesPhoneNumberExistAsync(model.NewPhoneNumber)) { ModelState.AddModelError(string.Empty, $"The phone number {model.NewPhoneNumber} is currently in use."); return View(); } else { goliathUser.UnverifiedNewPhone = model.NewPhoneNumber; requiresTwoFactor = true; } } #endregion Validating phone number #region Validating password if (!string.IsNullOrWhiteSpace(model.NewPassword)) { if (await _accountRepository.IsPasswordValidAsync(goliathUser, model.NewPassword)) { ModelState.AddModelError(string.Empty, "Your new password must be different then your previous password."); return View(); } IdentityResult result = await _accountRepository.UpdatePasswordAsync(goliathUser, model.CurrentPassword, model.NewPassword); if (!result.Succeeded) { _captcha.DeleteCaptchaCookie(); ModelState.AddModelError(string.Empty, "Your entry in \"Current Password\" does not match your current password."); return View(); } goliathUser.LastPasswordUpdate = DateTime.UtcNow.ToString(); requiresTwoFactor = true; } #endregion Validating password #region Check two-factor codes if (goliathUser.TwoFactorEnabled && requiresTwoFactor) { if (string.IsNullOrWhiteSpace(model.TwoFactorCode)) { _captcha.DeleteCaptchaCookie(); ModelState.AddModelError(string.Empty, "Your two-factor code is invalid."); return View(); } if (!await _accountRepository.TwoFactorCodeValidAsync(goliathUser, model.TwoFactorCode)) { _captcha.DeleteCaptchaCookie(); ModelState.AddModelError(string.Empty, "Your two-factor code is invalid."); return View(); } } #endregion Check two-factor codes #region Sending potential verification emails // If all of the user settings are correct then check if we need to send messages to // update phone/email. if (!string.IsNullOrWhiteSpace(model.NewEmail)) { await _accountRepository.GenerateNewEmailConfirmationTokenAsync(goliathUser, new DeviceParser(GetClientUserAgent(), GetRemoteClientIPv4())); } if (!string.IsNullOrWhiteSpace(model.NewPhoneNumber)) { await _accountRepository.GenerateNewPhoneConfirmationTokenAsync(goliathUser, new DeviceParser(GetClientUserAgent(), GetRemoteClientIPv4())); } #endregion Sending potential verification emails #region Updating/Caching // Update all of the changed values. await _accountRepository.UpdateUserAsync(goliathUser); await _captcha.CacheNewCaptchaValidateAsync(); await _timeouts.UpdateRequestAsync(goliathUser.Id, UserRequest.UpdateProfileSettings); #endregion Updating/Caching ModelState.Clear(); _logger.LogInformation($"{goliathUser.Id} ({goliathUser.UserName}) - Updated profile settings successfully."); TempData[TempDataKeys.Redirect] = RedirectPurpose.SettingsUpdatedSuccess; return View(); }
public async Task <IActionResult> SendSmsCode(string m) { ResendTwoFactorSmsCodeModel model; // Deserialize the resend two factor sms model. #region Parse & deserialize the parameter. try { model = JsonConvert.DeserializeObject <ResendTwoFactorSmsCodeModel>(Encoding.UTF8.GetString(Convert.FromBase64String(m))); } catch (Exception) { _logger.LogInformation($"[CHECK-1] Received invalid serialization for SendSmsCode(m). Model Data = [{m}]"); TempData[TempDataKeys.Redirect] = RedirectPurpose.TwoFactorSmsResendFailure; return(RedirectToActionPermanent(nameof(AuthController.Index), GoliathControllers.AuthController)); } #endregion Parse & deserialize the parameter. #region Validate object data (Non-Null + Valid Two-Factor method) // Check if the model exists and the username is not null. if (model == null || string.IsNullOrWhiteSpace(model.Username) || string.IsNullOrWhiteSpace(model.Controller) || string.IsNullOrWhiteSpace(model.Action)) { _logger.LogInformation($"[CHECK-2] Received invalid serialization for SendSmsCode(m). Model Data = [{m}]"); TempData[TempDataKeys.Redirect] = RedirectPurpose.TwoFactorSmsResendFailure; if (model.UtilizeHtmlMessage ?? true) { TempData[TempDataKeys.HtmlMessage] = "Internal Error. Try again later."; } return(RedirectToActionPermanent(nameof(AuthController.Index), GoliathControllers.AuthController)); } // Query for the user. ApplicationUser user = await _repository.GetUserByNameAsync(model.Username); // Check if the user exists and there two-factor method is SMS. if (user == null || user.TwoFactorMethod != (int)TwoFactorMethod.SmsVerify) { _logger.LogInformation($"[CHECK-3] Received invalid serialization for SendSmsCode(m). Model Data = [{m}]"); TempData[TempDataKeys.Redirect] = RedirectPurpose.TwoFactorSmsResendFailure; if (model.UtilizeHtmlMessage ?? true) { TempData[TempDataKeys.HtmlMessage] = "Internal Error. Try again later."; } if (model.IsUrnRedirect) { return(RedirectToActionPermanent(model.Action, model.Controller)); } return(RedirectToActionPermanent(model.Action, model.Controller, new { userName = model.Username })); } #endregion Validate object data (Non-Null + Valid Two-Factor method) #region Ensure a unauthorized user can request token // Check for Invalid Two-Factor Authorize Cookie + User not logged in if (!await _authorizeTokenRepository.TokenValidAsync(user.Id) && !User.Identity.IsAuthenticated) { _logger.LogInformation($"[CHECK-4A] Unauthorized request for user {user.Id} ({user.UserName} was rejected (Invalid Authorization Token). Model Data = [{m}]"); TempData[TempDataKeys.Redirect] = RedirectPurpose.TwoFactorSmsResendFailure; if (model.UtilizeHtmlMessage ?? true) { TempData[TempDataKeys.HtmlMessage] = "Invalid account session."; } if (model.IsUrnRedirect) { return(RedirectToAction(model.Action, model.Controller)); } return(RedirectToAction(model.Action, model.Controller, new { userName = model.Username })); } #endregion Ensure a unauthorized user can request token #region Ensure that a authorized user can request a token. // If the user is logged in and they are requesting a username that is not theirs. if (User.Identity.IsAuthenticated && (User.Identity.Name != model.Username)) { _logger.LogInformation($"[CHECK-4B] Authorized request for user {user.Id} ({user.UserName} was rejected (Invalid Session). Model Data = [{m}]"); TempData[TempDataKeys.Redirect] = RedirectPurpose.TwoFactorSmsResendFailure; if (model.UtilizeHtmlMessage ?? true) { TempData[TempDataKeys.HtmlMessage] = "Invalid account session."; } if (model.IsUrnRedirect) { return(RedirectToAction(model.Action, model.Controller)); } return(RedirectToAction(model.Action, model.Controller, new { userName = model.Username })); } #endregion Ensure that a authorized user can request a token. #region Check if the user is on timeout // If the user has waited the timeout and is allowed to request another token. if (!User.Identity.IsAuthenticated && !await _timeoutsRepository.CanRequestResendTwoFactorSmsAsync(user.Id)) { _logger.LogInformation($"[CHECK-5A] Unauthorized request for user {user.Id} ({user.UserName} was rejected (Bad Timeout). Model Data = [{m}]"); TempData[TempDataKeys.Redirect] = RedirectPurpose.TwoFactorSmsResendFailureTimeout; if (model.IsUrnRedirect) { return(RedirectToAction(model.Action, model.Controller)); } return(RedirectToAction(model.Action, model.Controller, new { userName = model.Username })); } if (User.Identity.IsAuthenticated && !await _timeoutsRepository.CanRequestAuthorizedTwoFactorSmsAsync(user.Id)) { _logger.LogInformation($"[CHECK-5B] Authorized request for user {user.Id} ({user.UserName} was rejected (Bad Timeout). Model Data = [{m}]"); TempData[TempDataKeys.Redirect] = RedirectPurpose.TwoFactorSmsResendFailureTimeout; if (model.UtilizeHtmlMessage ?? true) { TempData[TempDataKeys.HtmlMessage] = "Please wait before requesting another code."; } if (model.IsUrnRedirect) { return(RedirectToAction(model.Action, model.Controller)); } return(RedirectToAction(model.Action, model.Controller, new { userName = model.Username })); } #endregion Check if the user is on timeout // Send the user their code. await _repository.SendTwoFactorCodeSms(user); // Register the fact that they requested a two-factor code. if (User.Identity.IsAuthenticated) { await _timeoutsRepository.UpdateRequestAsync(user.Id, UserRequest.RequestTwoFactorSmsAuthorized); } else { await _timeoutsRepository.UpdateRequestAsync(user.Id, UserRequest.RequestTwoFactorResendSms); } // Send the success information back. TempData[TempDataKeys.Redirect] = RedirectPurpose.TwoFactorSmsResendSuccess; // If the redirect is a url with query parameters. _logger.LogInformation($"USER {user.Id} ({user.UserName}) - Received two-factor resend successfully. Model Data = [{m}]"); if (model.IsUrnRedirect) { return(LocalRedirect(model.ReturnPath)); } return(RedirectToAction(model.Action, model.Controller, new { userName = model.Username })); }