From 28067d12b2c7a9f645d2182bd9a940cf63e44487 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Thu, 14 Nov 2019 14:09:06 +0000 Subject: [PATCH] hoping that fixes the tzo factor option ... It needs unitary testing --- .../ViewModels/Account/VerifyCodeViewModel.cs | 3 - .../Accounting/AccountController.cs | 67 ++++++++++++++---- src/Yavsc/Helpers/Tags/MarkDownTagHelper.cs | 3 +- .../Resources/Yavsc.YavscLocalisation.resx | 14 +++- src/Yavsc/Startup/Startup.OAuth.cs | 13 ++-- ...firmEmail.cshtml => EmailConfirmed.cshtml} | 0 src/Yavsc/Views/Account/VerifyCode.cshtml | 4 +- src/Yavsc/wwwroot/favicon-yavsc.ico | Bin 0 -> 16958 bytes 8 files changed, 77 insertions(+), 27 deletions(-) rename src/Yavsc/Views/Account/{ConfirmEmail.cshtml => EmailConfirmed.cshtml} (100%) create mode 100644 src/Yavsc/wwwroot/favicon-yavsc.ico diff --git a/src/Yavsc.Server/ViewModels/Account/VerifyCodeViewModel.cs b/src/Yavsc.Server/ViewModels/Account/VerifyCodeViewModel.cs index 223f6e8f..a113ac14 100644 --- a/src/Yavsc.Server/ViewModels/Account/VerifyCodeViewModel.cs +++ b/src/Yavsc.Server/ViewModels/Account/VerifyCodeViewModel.cs @@ -14,9 +14,6 @@ namespace Yavsc.ViewModels.Account public string ReturnUrl { get; set; } [Display(Name = "Se souvenir de ce navigateur?")] - public bool RememberBrowser { get; set; } - - [Display(Name = "Se souvenir de moi?")] public bool RememberMe { get; set; } } } diff --git a/src/Yavsc/Controllers/Accounting/AccountController.cs b/src/Yavsc/Controllers/Accounting/AccountController.cs index 332a36a8..7411f24c 100644 --- a/src/Yavsc/Controllers/Accounting/AccountController.cs +++ b/src/Yavsc/Controllers/Accounting/AccountController.cs @@ -26,10 +26,10 @@ namespace Yavsc.Controllers public class AccountController : Controller { - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; const string nextPageTokenKey = "nextPageTokenKey"; const int defaultLen = 10; + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; private readonly IEmailSender _emailSender; // private readonly ISmsSender _smsSender; private readonly ILogger _logger; @@ -323,6 +323,19 @@ namespace Yavsc.Controllers _siteSettings.Audience)); return res; } + + private async Task SendEMailFactorAsync(ApplicationUser user, string provider) + { + var code = await _userManager.GenerateTwoFactorTokenAsync(user, provider); + var callbackUrl = Url.Action("VerifyCode", "Account", + new { userId = user.Id, code, provider }, protocol: "https", host: Startup.Authority); + var res = await _emailSender.SendEmailAsync(user.UserName, user.Email, + this._localizer["AccountEmailFactorTitle"], + string.Format(this._localizer["AccountEmailFactorBody"], + _siteSettings.Title, callbackUrl, _siteSettings.Slogan, + _siteSettings.Audience, code)); + return res; + } // // POST: /Account/LogOff [HttpPost(Constants.LogoutPath)] @@ -366,7 +379,7 @@ namespace Yavsc.Controllers } if (result.RequiresTwoFactor) { - return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl }); + return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe= true }); } if (result.IsLockedOut) { @@ -465,13 +478,41 @@ namespace Yavsc.Controllers IdentityResult result=null; try { result = await _userManager.ConfirmEmailAsync(user, code); + _dbContext.SaveChanges(userId); } catch (Exception ex) { _logger.LogError(ex.StackTrace); _logger.LogError(ex.Message); } - return View(result.Succeeded ? "ConfirmEmail" : "Error"); + return View(result.Succeeded ? "EmailConfirmed" : "Error"); + } + + // GET: /Account/ConfirmTwoFactorToken + [HttpGet] + [AllowAnonymous] + public async Task ConfirmTwoFactorToken(string userId, string code) + { + if (userId == null || code == null) + { + return View("Error"); + } + var user = await _userManager.FindByIdAsync(userId); + if (user == null) + { + return View("Error"); + } + bool result=false; + try { + result = await _userManager.VerifyTwoFactorTokenAsync(user, Constants.DefaultFactor, code); + _dbContext.SaveChanges(userId); + } + catch (Exception ex) + { + _logger.LogError(ex.StackTrace); + _logger.LogError(ex.Message); + } + return View(result ? "EmailConfirmed" : "Error"); } // @@ -607,7 +648,7 @@ namespace Yavsc.Controllers // // GET: /Account/SendCode [HttpGet, AllowAnonymous] - public async Task SendCode(string returnUrl = null, bool rememberMe = false) + public async Task SendCode(string returnUrl = null, bool rememberMe = true) { var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); if (user == null) @@ -615,11 +656,8 @@ namespace Yavsc.Controllers return View("Error", new Exception("No Two factor authentication user")); } - - var userFactors = await _userManager.GetValidTwoFactorProvidersAsync(user); - var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe }); } @@ -645,7 +683,7 @@ namespace Yavsc.Controllers { return View("Error", new Exception("No mobile app service was activated")); } - else // if (model.SelectedProvider == Constants.EMailFactor || model.SelectedProvider == "Default" ) + else if (model.SelectedProvider == Constants.SMSFactor) { return View("Error", new Exception("No SMS service was activated")); @@ -653,7 +691,7 @@ namespace Yavsc.Controllers } else // if (model.SelectedProvider == Constants.EMailFactor || model.SelectedProvider == "Default" ) { - var sent = await this.SendEMailForConfirmAsync(user); + var sent = await this.SendEMailFactorAsync(user, model.SelectedProvider); } return RedirectToAction(nameof(VerifyCode), new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe }); } @@ -662,7 +700,7 @@ namespace Yavsc.Controllers // GET: /Account/VerifyCode [HttpGet] [AllowAnonymous] - public async Task VerifyCode(string provider, bool rememberMe, string returnUrl = null) + public async Task VerifyCode(string code, string provider, bool rememberMe=true, string returnUrl = null) { // Require that the user has already logged in via username/password or external login var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); @@ -670,7 +708,8 @@ namespace Yavsc.Controllers { return View("Error", new Exception("user is null")); } - return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + // it may be a GET response from some email url, or the web response to second fqctor requirement + return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe, Code = code }); } // @@ -690,12 +729,14 @@ namespace Yavsc.Controllers // will be locked out for a specified amount of time. _logger.LogWarning("Signin with code: {0} {1}", model.Provider, model.Code); - var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberBrowser); + var result = await _signInManager.TwoFactorSignInAsync(model.Provider, model.Code, model.RememberMe, model.RememberMe); if (result.Succeeded) { ViewData["StatusMessage"] = "Your code was verified"; _logger.LogInformation($"Signed in. returning to {model.ReturnUrl}"); + if (model.ReturnUrl!=null) return Redirect(model.ReturnUrl); + else RedirectToAction("Index","Home"); } if (result.IsLockedOut) { diff --git a/src/Yavsc/Helpers/Tags/MarkDownTagHelper.cs b/src/Yavsc/Helpers/Tags/MarkDownTagHelper.cs index e15ca4f5..cb00a72e 100644 --- a/src/Yavsc/Helpers/Tags/MarkDownTagHelper.cs +++ b/src/Yavsc/Helpers/Tags/MarkDownTagHelper.cs @@ -47,7 +47,8 @@ namespace Yavsc.TagHelpers string actual; var settings = CommonMarkSettings.Default.Clone(); settings.OutputFormat = OutputFormat.Html; - // settings.PrologueLineHandler = null; + settings.AdditionalFeatures |= CommonMarkAdditionalFeatures.StrikethroughTilde; + Block document; // Act diff --git a/src/Yavsc/Resources/Yavsc.YavscLocalisation.resx b/src/Yavsc/Resources/Yavsc.YavscLocalisation.resx index f6fd25b3..6782d8e1 100644 --- a/src/Yavsc/Resources/Yavsc.YavscLocalisation.resx +++ b/src/Yavsc/Resources/Yavsc.YavscLocalisation.resx @@ -463,10 +463,22 @@ Confirmation du mot de passe L'envoi de de courrier pour confirmation de l'adresse e-mail a échoué. Un courrier a été envoyé pour confirmation de l'adresse e-mail . + + Le second facteur de votre identification + Votre compte est endurci d'une requisition d'un second facteur d'identification. + Une nouvelle connection depuis un navigateur web nécéssite votre authorisation. +Vous pouvez l'accorder en suivant le lien suivant : <{1}>. + +Votre code dáctivation est : {4} + +Vour pourrez cochez la case "Se souvenir de ce navigateur" pour conserver cette authorisation pour ce navigateur. +{0} - {2} <{3}> S'il vous plait, confirmez votre addresse e-mail Vous avez créé avec succès votre compte {0}, mais votre adresse e-mail reste à confirmer. -Pour ce faire, suivez le lien suivant : <{1}>. + +Pour ce faire, veuillez, s'il vous plait, suivre le lien suivant +dans votre navigateur favori : <{1}>. -- {0} - {2} <{3}> diff --git a/src/Yavsc/Startup/Startup.OAuth.cs b/src/Yavsc/Startup/Startup.OAuth.cs index cb2380e9..1961ef3b 100644 --- a/src/Yavsc/Startup/Startup.OAuth.cs +++ b/src/Yavsc/Startup/Startup.OAuth.cs @@ -65,31 +65,30 @@ namespace Yavsc IdentityAppOptions = option; option.User.AllowedUserNameCharacters += " "; option.User.RequireUniqueEmail = true; - // option.Cookies.ApplicationCookieAuthenticationScheme = Constants.ApplicationAuthenticationSheme; option.Cookies.ApplicationCookie.LoginPath = "/signin"; + // option.Cookies.TwoFactorRememberMeCookie.ExpireTimeSpan = TimeSpan.FromDays(30); + // option.Cookies.TwoFactorRememberMeCookie.DataProtectionProvider = ProtectionProvider; + // option.Cookies.ApplicationCookie.DataProtectionProvider = ProtectionProvider; + // option.Cookies.ExternalCookie.DataProtectionProvider = ProtectionProvider; // option.Cookies.ApplicationCookie.AuthenticationScheme = Constants.ApplicationAuthenticationSheme; /* - option.Cookies.ApplicationCookie.DataProtectionProvider = protector; option.Cookies.ApplicationCookie.LoginPath = new PathString(Constants.LoginPath.Substring(1)); option.Cookies.ApplicationCookie.AccessDeniedPath = new PathString(Constants.AccessDeniedPath.Substring(1)); option.Cookies.ApplicationCookie.AutomaticAuthenticate = true; option.Cookies.ApplicationCookie.AuthenticationScheme = Constants.ApplicationAuthenticationSheme; option.Cookies.ApplicationCookieAuthenticationScheme = Constants.ApplicationAuthenticationSheme; - option.Cookies.TwoFactorRememberMeCookie.ExpireTimeSpan = TimeSpan.FromDays(30); - option.Cookies.TwoFactorRememberMeCookie.DataProtectionProvider = protector; option.Cookies.ExternalCookieAuthenticationScheme = Constants.ExternalAuthenticationSheme; option.Cookies.ExternalCookie.AutomaticAuthenticate = true; option.Cookies.ExternalCookie.AuthenticationScheme = Constants.ExternalAuthenticationSheme; - option.Cookies.ExternalCookie.DataProtectionProvider = protector; */ } ).AddEntityFrameworkStores() .AddTokenProvider>(Constants.DefaultFactor) // .AddTokenProvider(Constants.DefaultFactor) // .AddTokenProvider(Constants.SMSFactor) - // .AddTokenProvider(Constants.EMailFactor) + .AddTokenProvider(Constants.EMailFactor) // .AddTokenProvider(Constants.AppFactor) - // .AddDefaultTokenProviders() + // ; } private void ConfigureOAuthApp(IApplicationBuilder app, diff --git a/src/Yavsc/Views/Account/ConfirmEmail.cshtml b/src/Yavsc/Views/Account/EmailConfirmed.cshtml similarity index 100% rename from src/Yavsc/Views/Account/ConfirmEmail.cshtml rename to src/Yavsc/Views/Account/EmailConfirmed.cshtml diff --git a/src/Yavsc/Views/Account/VerifyCode.cshtml b/src/Yavsc/Views/Account/VerifyCode.cshtml index c1fd1560..1cce17ae 100755 --- a/src/Yavsc/Views/Account/VerifyCode.cshtml +++ b/src/Yavsc/Views/Account/VerifyCode.cshtml @@ -22,8 +22,8 @@
- - + +
diff --git a/src/Yavsc/wwwroot/favicon-yavsc.ico b/src/Yavsc/wwwroot/favicon-yavsc.ico new file mode 100644 index 0000000000000000000000000000000000000000..0b8d8e63f6f6be02490c69fb823bf48d11d4d908 GIT binary patch literal 16958 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 literal 0 HcmV?d00001