Example #1
0
        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 }));
        }