From 647a5b60311908abc34ddda611597a65421bdf0c Mon Sep 17 00:00:00 2001 From: Praburaj Date: Wed, 24 Sep 2014 15:13:28 -0700 Subject: [PATCH] Enabling account validation, 2FA, Forgot password in the sample Some identity helpers are still missing. Work arounds are applied to enable them working. Will clean it up as soon as identity helpers are available. --- .../Controllers/AccountController.cs | 251 +++++++++++------- .../Controllers/ManageController.cs | 76 ++++-- src/MusicStore/Startup.cs | 16 +- .../Views/Account/ConfirmEmail.cshtml | 2 +- .../Views/Account/VerifyCode.cshtml | 47 ++++ .../Views/Manage/VerifyPhoneNumber.cshtml | 6 +- .../Views/Shared/DemoCodeDisplay.cshtml | 15 -- .../Views/Shared/DemoLinkDisplay.cshtml | 21 ++ src/MusicStore/project.json | 2 +- test/E2ETests/Scenarios.cs | 16 +- test/E2ETests/SmokeTests.cs | 2 + 11 files changed, 304 insertions(+), 150 deletions(-) create mode 100644 src/MusicStore/Views/Account/VerifyCode.cshtml delete mode 100644 src/MusicStore/Views/Shared/DemoCodeDisplay.cshtml create mode 100644 src/MusicStore/Views/Shared/DemoLinkDisplay.cshtml diff --git a/src/MusicStore/Controllers/AccountController.cs b/src/MusicStore/Controllers/AccountController.cs index 9e3443a3f3..ce4e8ead61 100644 --- a/src/MusicStore/Controllers/AccountController.cs +++ b/src/MusicStore/Controllers/AccountController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNet.Identity; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Rendering; using MusicStore.Models; +using System.Threading; namespace MusicStore.Controllers { @@ -16,6 +17,11 @@ namespace MusicStore.Controllers { UserManager = userManager; SignInManager = signInManager; + + //TODO: Work around - Identity helpers will be available to do this + UserManager.UserTokenProvider = new StaticTokenProvider(); + UserManager.RegisterTwoFactorProvider("Phone Code", UserManager.UserTokenProvider); + UserManager.RegisterTwoFactorProvider("Email Code", UserManager.UserTokenProvider); } public UserManager UserManager { get; private set; } @@ -64,21 +70,29 @@ namespace MusicStore.Controllers // // GET: /Account/VerifyCode //TODO : Some of the identity helpers not implemented - //[AllowAnonymous] - //public async Task VerifyCode(string provider, string returnUrl, bool rememberMe) - //{ - // // Require that the user has already logged in via username/password or external login - // if (!await SignInManager.HasBeenVerifiedAsync()) - // { - // return View("Error"); - // } - // var user = await UserManager.FindByIdAsync(await SignInManager.GetVerifiedUserIdAsync()); - // if (user != null) - // { - // var code = await UserManager.GenerateTwoFactorTokenAsync(user.Id, provider); - // } - // return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); - //} + [AllowAnonymous] + public async Task VerifyCode(string provider, bool rememberMe, string returnUrl = null) + { + //https://github.com/aspnet/Identity/issues/209 + // Require that the user has already logged in via username/password or external login + //if (!await SignInManager.HasBeenVerifiedAsync()) + if ((await Context.AuthenticateAsync("Microsoft.AspNet.Identity.TwoFactor.UserId")).Identity.GetUserName() == null) + { + return View("Error"); + } + + //https://github.com/aspnet/Identity/issues/207 + //var user = await UserManager.FindByIdAsync(await SignInManager.GetVerifiedUserIdAsync()); + var user = await UserManager.FindByIdAsync((await Context.AuthenticateAsync("Microsoft.AspNet.Identity.TwoFactor.UserId")).Identity.GetUserName()); +#if DEMO + if (user != null) + { + var code = await UserManager.GenerateTwoFactorTokenAsync(user, provider); + ViewBag.Code = code; + } +#endif + return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl, RememberMe = rememberMe }); + } // // POST: /Account/VerifyCode @@ -134,19 +148,21 @@ namespace MusicStore.Controllers if (result.Succeeded) { //Bug: Remember browser option missing? - await SignInManager.SignInAsync(user, isPersistent: false); + //Uncomment this and comment the later part if account verification is not needed. + //await SignInManager.SignInAsync(user, isPersistent: false); // For more information on how to enable account confirmation and password reset please visit http://go.microsoft.com/fwlink/?LinkID=320771 // Send an email with this link - //string code = await UserManager.GenerateEmailConfirmationTokenAsync(user); - //var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Context.Request.Scheme); - // await UserManager.SendEmailAsync(user.Id, "Confirm your account", "Please confirm your account by clicking here"); - // return RedirectToAction("Index", "Home"); - - // TODO: Email libraries not available on Coreclr yet - Checkout a demo implementation below which displays the code in the browser for testing. - //ViewBag.Status = "Navigate to this URL to confirm your account " + callbackUrl; - //return View("DemoCodeDisplay"); + string code = await UserManager.GenerateEmailConfirmationTokenAsync(user); + var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Context.Request.Scheme); + await UserManager.SendEmailAsync(user, "Confirm your account", "Please confirm your account by clicking here"); +#if !DEMO return RedirectToAction("Index", "Home"); +#else + //To display the email link in a friendly page instead of sending email + ViewBag.Link = callbackUrl; + return View("DemoLinkDisplay"); +#endif } AddErrors(result); } @@ -157,7 +173,6 @@ namespace MusicStore.Controllers // // GET: /Account/ConfirmEmail - //TODO: This does not work yet due to some missing identity features [AllowAnonymous] public async Task ConfirmEmail(string userId, string code) { @@ -165,8 +180,8 @@ namespace MusicStore.Controllers { return View("Error"); } - var user = new ApplicationUser { Id = userId }; - //Bug: Throws NullRefException + + var user = await SignInManager.UserManager.FindByIdAsync(userId); var result = await UserManager.ConfirmEmailAsync(user, code); return View(result.Succeeded ? "ConfirmEmail" : "Error"); } @@ -199,13 +214,14 @@ namespace MusicStore.Controllers // Send an email with this link string code = await UserManager.GeneratePasswordResetTokenAsync(user); var callbackUrl = Url.Action("ResetPassword", "Account", new { code = code }, protocol: Context.Request.Scheme); - // No libraries to send email on CoreCLR yet - // await UserManager.SendEmailAsync(user.Id, "Reset Password", "Please reset your password by clicking here"); - // return RedirectToAction("ForgotPasswordConfirmation", "Account"); - - // TODO: Email libraries not available on Coreclr yet - Checkout a demo implementation below which displays the code in the browser for testing. - ViewBag.Status = "Navigate to the URL to reset password " + callbackUrl; - return View("DemoCodeDisplay"); + await UserManager.SendEmailAsync(user, "Reset Password", "Please reset your password by clicking here"); +#if !DEMO + return RedirectToAction("ForgotPasswordConfirmation", "Account"); +#else + //To display the email link in a friendly page instead of sending email + ViewBag.Link = callbackUrl; + return View("DemoLinkDisplay"); +#endif } ModelState.AddModelError("", string.Format("We could not locate an account with email : {0}", model.Email)); @@ -282,13 +298,14 @@ namespace MusicStore.Controllers [AllowAnonymous] public async Task SendCode(bool rememberMe, string returnUrl = null) { - // TODO: This currently throws - var userId = await GetCurrentUserAsync(); + //https://github.com/aspnet/Identity/issues/207 + //var userId = await SignInManager.GetVerifiedUserIdAsync(); + var userId = (await Context.AuthenticateAsync("Microsoft.AspNet.Identity.TwoFactor.UserId")).Identity.GetUserName(); if (userId == null) { return View("Error"); } - var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId); + var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(new ApplicationUser() { Id = userId }); var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList(); return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe }); } @@ -315,74 +332,79 @@ namespace MusicStore.Controllers // // GET: /Account/ExternalLoginCallback - // TODO: Some identity helpers on external login does not exist - //[AllowAnonymous] - //public async Task ExternalLoginCallback(string returnUrl) - //{ - // var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(); - // if (loginInfo == null) - // { - // return RedirectToAction("Login"); - // } + [AllowAnonymous] + public async Task ExternalLoginCallback(string returnUrl = null) + { + //TODO: Helper not available + //var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(); + var loginInfo = await Context.AuthenticateAsync("External"); + if (loginInfo == null) + { + return RedirectToAction("Login"); + } - // // Sign in the user with this external login provider if the user already has a login - // var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false); - // switch (result) - // { - // case SignInStatus.Success: - // return RedirectToLocal(returnUrl); - // case SignInStatus.LockedOut: - // return View("Lockout"); - // case SignInStatus.RequiresVerification: - // return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false }); - // case SignInStatus.Failure: - // default: - // // If the user does not have an account, then prompt the user to create an account - // ViewBag.ReturnUrl = returnUrl; - // ViewBag.LoginProvider = loginInfo.Login.LoginProvider; - // return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Email }); - // } - //} + //TODO: Helper not available + // Sign in the user with this external login provider if the user already has a login + //var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false); + var result = SignInStatus.Failure; //Always redirect to a login confirmation page for now. + switch (result) + { + case SignInStatus.Success: + return RedirectToLocal(returnUrl); + case SignInStatus.LockedOut: + return View("Lockout"); + case SignInStatus.RequiresVerification: + return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = false }); + case SignInStatus.Failure: + default: + // If the user does not have an account, then prompt the user to create an account + ViewBag.ReturnUrl = returnUrl; + ViewBag.LoginProvider = loginInfo.Identity.AuthenticationType; + return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = loginInfo.Identity.Claims.Single(c => c.Type == System.Security.Claims.ClaimTypes.Email).Value }); + } + } // // POST: /Account/ExternalLoginConfirmation // TODO: Some of the identity helpers not available - //[HttpPost] - //[AllowAnonymous] - //[ValidateAntiForgeryToken] - //public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl) - //{ - // if (User.Identity.IsAuthenticated) - // { - // return RedirectToAction("Index", "Manage"); - // } + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl = null) + { + if (User.Identity.IsAuthenticated) + { + return RedirectToAction("Index", "Manage"); + } - // if (ModelState.IsValid) - // { - // // Get the information about the user from the external login provider - // var info = await AuthenticationManager.GetExternalLoginInfoAsync(); - // if (info == null) - // { - // return View("ExternalLoginFailure"); - // } - // var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; - // var result = await UserManager.CreateAsync(user); - // if (result.Succeeded) - // { - // result = await UserManager.AddLoginAsync(user.Id, info.Login); - // if (result.Succeeded) - // { - // // TODO: rememberBrowser option not being taken in SignInAsync - // await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); - // return RedirectToLocal(returnUrl); - // } - // } - // AddErrors(result); - // } + if (ModelState.IsValid) + { + // Get the information about the user from the external login provider + //var info = await AuthenticationManager.GetExternalLoginInfoAsync(); + var info = await Context.AuthenticateAsync("External"); + if (info == null) + { + return View("ExternalLoginFailure"); + } + var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; + var result = await UserManager.CreateAsync(user); + if (result.Succeeded) + { + var userloginInfo = new UserLoginInfo(info.Description.AuthenticationType, info.Description.AuthenticationType, info.Description.Caption); + result = await UserManager.AddLoginAsync(user, userloginInfo); + if (result.Succeeded) + { + // TODO: rememberBrowser option not being taken in SignInAsync + await SignInManager.SignInAsync(user, isPersistent: false); + return RedirectToLocal(returnUrl); + } + } + AddErrors(result); + } - // ViewBag.ReturnUrl = returnUrl; - // return View(model); - //} + ViewBag.ReturnUrl = returnUrl; + return View(model); + } // // POST: /Account/LogOff @@ -431,4 +453,37 @@ namespace MusicStore.Controllers #endregion } + + /// + /// TODO: Work around until there is a token provider + /// + internal class StaticTokenProvider : IUserTokenProvider + { + public Task GenerateAsync(string purpose, UserManager manager, + ApplicationUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(MakeToken(purpose, user)); + } + + public Task ValidateAsync(string purpose, string token, UserManager manager, + ApplicationUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(token == MakeToken(purpose, user)); + } + + public Task NotifyAsync(string token, UserManager manager, ApplicationUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(0); + } + + public Task IsValidProviderForUserAsync(UserManager manager, ApplicationUser user, CancellationToken cancellationToken = default(CancellationToken)) + { + return Task.FromResult(true); + } + + private static string MakeToken(string purpose, ApplicationUser user) + { + return string.Join(":", user.Id, purpose, "ImmaToken"); + } + } } \ No newline at end of file diff --git a/src/MusicStore/Controllers/ManageController.cs b/src/MusicStore/Controllers/ManageController.cs index 74a054ae02..94d283dc4b 100644 --- a/src/MusicStore/Controllers/ManageController.cs +++ b/src/MusicStore/Controllers/ManageController.cs @@ -4,6 +4,8 @@ using System.Security.Principal; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Mvc; using MusicStore.Models; +using System.Linq; +using Microsoft.AspNet.Http.Security; namespace MusicStore.Controllers { @@ -138,7 +140,7 @@ namespace MusicStore.Controllers // This code allows you exercise the flow without actually sending codes // For production use please register a SMS provider in IdentityConfig and generate a code here. var code = await UserManager.GenerateChangePhoneNumberTokenAsync(await GetCurrentUserAsync(), phoneNumber); - ViewBag.Status = "For DEMO purposes only, the current code is " + code; + ViewBag.Code = code; return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber }); } @@ -276,28 +278,60 @@ namespace MusicStore.Controllers // // GET: /Account/Manage - //public async Task ManageLogins(ManageMessageId? message) - //{ - // ViewBag.StatusMessage = - // message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed." - // : message == ManageMessageId.Error ? "An error has occurred." - // : ""; - // var user = await GetCurrentUserAsync(); - // if (user == null) - // { - // return View("Error"); - // } - // var userLogins = await UserManager.GetLoginsAsync(user); - //var otherLogins = AuthenticationManager.GetExternalAuthenticationTypes().Where(auth => userLogins.All(ul => auth.AuthenticationType != ul.LoginProvider)).ToList(); - //ViewBag.ShowRemoveButton = user.PasswordHash != null || userLogins.Count > 1; - //return View(new ManageLoginsViewModel - //{ - // CurrentLogins = userLogins, - // OtherLogins = otherLogins - //}); - //} + public async Task ManageLogins(ManageMessageId? message = null) + { + ViewBag.StatusMessage = + message == ManageMessageId.RemoveLoginSuccess ? "The external login was removed." + : message == ManageMessageId.Error ? "An error has occurred." + : ""; + var user = await GetCurrentUserAsync(); + if (user == null) + { + return View("Error"); + } + var userLogins = await UserManager.GetLoginsAsync(user); + var otherLogins = Context.GetAuthenticationTypes().Where(auth => auth.Caption != null && userLogins.All(ul => auth.AuthenticationType != ul.LoginProvider)).ToList(); + //var otherLogins = AuthenticationManager.GetExternalAuthenticationTypes().Where(auth => userLogins.All(ul => auth.AuthenticationType != ul.LoginProvider)).ToList(); + ViewBag.ShowRemoveButton = user.PasswordHash != null || userLogins.Count > 1; + return View(new ManageLoginsViewModel + { + CurrentLogins = userLogins, + OtherLogins = otherLogins + }); + } + + // + // POST: /Manage/LinkLogin + [HttpPost] + [ValidateAntiForgeryToken] + public ActionResult LinkLogin(string provider) + { + // Request a redirect to the external login provider to link a login for the current user + var authenticationProperties = new AuthenticationProperties() { RedirectUri = Url.Action("LinkLoginCallback", "Manage") }; + authenticationProperties.Dictionary[XsrfKey] = User.Identity.GetUserId(); + return new ChallengeResult(provider, authenticationProperties); + } + + // + // GET: /Manage/LinkLoginCallback + public async Task LinkLoginCallback() + { + //var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId()); + var loginInfo = await Context.AuthenticateAsync("External"); + if (loginInfo == null || loginInfo.Properties.Dictionary[XsrfKey] != User.Identity.GetUserId()) + { + return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error }); + } + var user = new ApplicationUser() { Id = User.Identity.GetUserId() }; + var userloginInfo = new UserLoginInfo(loginInfo.Description.AuthenticationType, loginInfo.Description.AuthenticationType, loginInfo.Description.Caption); + var result = await UserManager.AddLoginAsync(user, userloginInfo); + return result.Succeeded ? RedirectToAction("ManageLogins") : RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error }); + } #region Helpers + // Used for XSRF protection when adding external logins + private const string XsrfKey = "XsrfId"; + private void AddErrors(IdentityResult result) { foreach (var error in result.Errors) diff --git a/src/MusicStore/Startup.cs b/src/MusicStore/Startup.cs index 1b9dcd69e0..6d1767f5d5 100644 --- a/src/MusicStore/Startup.cs +++ b/src/MusicStore/Startup.cs @@ -108,27 +108,27 @@ namespace MusicStore app.UseFacebookAuthentication(new FacebookAuthenticationOptions() { - AppId = "[AppId]", - AppSecret = "[AppSecret]", + AppId = "550624398330273", + AppSecret = "10e56a291d6b618da61b1e0dae3a8954", }); app.UseGoogleAuthentication(new GoogleAuthenticationOptions() { - ClientId = "[ClientId]", - ClientSecret = "[ClientSecret]", + ClientId = "977382855444.apps.googleusercontent.com", + ClientSecret = "NafT482F70Vjj_9q1PU4B0pN", }); app.UseTwitterAuthentication(new TwitterAuthenticationOptions() { - ConsumerKey = "[ConsumerKey]", - ConsumerSecret = "[ConsumerSecret]", + ConsumerKey = "9J3j3pSwgbWkgPFH7nAf0Spam", + ConsumerSecret = "jUBYkQuBFyqp7G3CUB9SW3AfflFr9z3oQBiNvumYy87Al0W4h8", }); app.UseMicrosoftAccountAuthentication(new MicrosoftAccountAuthenticationOptions() { Caption = "MicrosoftAccount - Requires project changes", - ClientId = "[ClientId]", - ClientSecret = "[ClientSecret]", + ClientId = "000000004012C08A", + ClientSecret = "GaMQ2hCnqAC6EcDLnXsAeBVIJOLmeutL", }); // Add MVC to the request pipeline diff --git a/src/MusicStore/Views/Account/ConfirmEmail.cshtml b/src/MusicStore/Views/Account/ConfirmEmail.cshtml index 98c66ede3a..5608c5460a 100644 --- a/src/MusicStore/Views/Account/ConfirmEmail.cshtml +++ b/src/MusicStore/Views/Account/ConfirmEmail.cshtml @@ -1,4 +1,4 @@ -{ +@{ ViewBag.Title = "Confirm Email"; } diff --git a/src/MusicStore/Views/Account/VerifyCode.cshtml b/src/MusicStore/Views/Account/VerifyCode.cshtml new file mode 100644 index 0000000000..36871d3dfd --- /dev/null +++ b/src/MusicStore/Views/Account/VerifyCode.cshtml @@ -0,0 +1,47 @@ +@model MusicStore.Models.VerifyCodeViewModel +@{ + ViewBag.Title = "Verify"; +} + +

@ViewBag.Title.

+ +@using (Html.BeginForm("VerifyCode", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) +{ + @Html.AntiForgeryToken() + @Html.Hidden("provider", @Model.Provider) + @Html.Hidden("rememberMe", @Model.RememberMe) +

Enter verification code

+

+ For DEMO only: You can type in this code in the below text box to proceed: [ @ViewBag.Code ] +
+ Please change this code to register an SMS/Email service in IdentityConfig to send a message. +

+
+ @Html.ValidationSummary("", new { @class = "text-danger" }) +
+ @Html.LabelFor(m => m.Code, new { @class = "col-md-2 control-label" }) +
+ @Html.TextBoxFor(m => m.Code, new { @class = "form-control" }) +
+
+
+
+
+ @Html.CheckBoxFor(m => m.RememberBrowser) + @Html.LabelFor(m => m.RememberBrowser) +
+
+
+
+
+ +
+
+} + +@section Scripts { + @*TODO : Until script helpers are available, adding script references manually*@ + @*@Scripts.Render("~/bundles/jqueryval")*@ + + +} diff --git a/src/MusicStore/Views/Manage/VerifyPhoneNumber.cshtml b/src/MusicStore/Views/Manage/VerifyPhoneNumber.cshtml index 6ab6abe5cc..d7d1c6508f 100644 --- a/src/MusicStore/Views/Manage/VerifyPhoneNumber.cshtml +++ b/src/MusicStore/Views/Manage/VerifyPhoneNumber.cshtml @@ -10,7 +10,11 @@ @Html.AntiForgeryToken() @Html.Hidden("phoneNumber", @Model.PhoneNumber)

Enter verification code

-
@ViewBag.Status
+

+ For DEMO only: You can type in this code in the below text box to proceed: @ViewBag.Code +
+ Please change this code to register an SMS service in IdentityConfig to send a text message. +


@Html.ValidationSummary("", new { @class = "text-danger" })
diff --git a/src/MusicStore/Views/Shared/DemoCodeDisplay.cshtml b/src/MusicStore/Views/Shared/DemoCodeDisplay.cshtml deleted file mode 100644 index 1b188da8c5..0000000000 --- a/src/MusicStore/Views/Shared/DemoCodeDisplay.cshtml +++ /dev/null @@ -1,15 +0,0 @@ -@{ - ViewBag.Title = "Demo code display page - Not for production use"; -} - -
-

@ViewBag.Title.

-
-
-

- Demo code display page - Not for production use. -

-

- [Demo]: @ViewBag.Status -

-
\ No newline at end of file diff --git a/src/MusicStore/Views/Shared/DemoLinkDisplay.cshtml b/src/MusicStore/Views/Shared/DemoLinkDisplay.cshtml new file mode 100644 index 0000000000..d4ff51bb29 --- /dev/null +++ b/src/MusicStore/Views/Shared/DemoLinkDisplay.cshtml @@ -0,0 +1,21 @@ +@{ + ViewBag.Title = "Demo link display page - Not for production use"; +} + +
+

@ViewBag.Title.

+
+
+

+ Demo link display page - Not for production use. +

+ + @if (ViewBag.Link != null) + { +

+ For DEMO only: You can click this link to confirm the email: [[link]] +
+ Please change this code to register an email service in IdentityConfig to send an email. +

+ } +
\ No newline at end of file diff --git a/src/MusicStore/project.json b/src/MusicStore/project.json index b83b639fc8..a1c3d8c75e 100644 --- a/src/MusicStore/project.json +++ b/src/MusicStore/project.json @@ -4,7 +4,7 @@ ], "description": "Music store application on K", "version": "1.0.0-*", - "compilationOptions": { "warningsAsErrors": true }, + "compilationOptions": { "warningsAsErrors": true, "define": [ "DEMO" ] }, "webroot": "wwwroot", "dependencies": { "Kestrel": "1.0.0-*", diff --git a/test/E2ETests/Scenarios.cs b/test/E2ETests/Scenarios.cs index 4eb5c11cad..aa111b377c 100644 --- a/test/E2ETests/Scenarios.cs +++ b/test/E2ETests/Scenarios.cs @@ -136,11 +136,17 @@ namespace E2ETests var content = new FormUrlEncodedContent(formParameters.ToArray()); response = httpClient.PostAsync("Account/Register", content).Result; responseContent = response.Content.ReadAsStringAsync().Result; - Assert.Contains(string.Format("Hello {0}!", generatedEmail), responseContent, StringComparison.OrdinalIgnoreCase); - Assert.Contains("Log off", responseContent, StringComparison.OrdinalIgnoreCase); - //Verify cookie sent - Assert.NotNull(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application")); - Console.WriteLine("Successfully registered user '{0}' and signed in", generatedEmail); + + //Account verification + Assert.Equal(ApplicationBaseUrl + "Account/Register", response.RequestMessage.RequestUri.AbsoluteUri); + Assert.Contains("For DEMO only: You can click this link to confirm the email:", responseContent, StringComparison.OrdinalIgnoreCase); + var startIndex = responseContent.IndexOf("[[link]]", startIndex); + var confirmUrl = responseContent.Substring(startIndex, endIndex - startIndex); + response = httpClient.GetAsync(confirmUrl).Result; + ThrowIfResponseStatusNotOk(response); + responseContent = response.Content.ReadAsStringAsync().Result; + Assert.Contains("Thank you for confirming your email.", responseContent, StringComparison.OrdinalIgnoreCase); return generatedEmail; } diff --git a/test/E2ETests/SmokeTests.cs b/test/E2ETests/SmokeTests.cs index 7767598398..07c9bea34b 100644 --- a/test/E2ETests/SmokeTests.cs +++ b/test/E2ETests/SmokeTests.cs @@ -84,6 +84,8 @@ namespace E2ETests //Register a valid user var generatedEmail = RegisterValidUser(); + SignInWithUser(generatedEmail, "Password~1"); + //Register a user - Negative scenario : Trying to register a user name that's already registered. RegisterExistingUser(generatedEmail);