diff --git a/src/Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml b/src/Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml
new file mode 100644
index 0000000..401bf32
--- /dev/null
+++ b/src/Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml
@@ -0,0 +1,12 @@
+@page
+@model ConfirmEmailModel
+@{
+ ViewData["Title"] = "Confirm email";
+}
+
+
@ViewData["Title"]
+
+
+ Thank you for confirming your email.
+
+
diff --git a/src/Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs b/src/Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs
new file mode 100644
index 0000000..b113768
--- /dev/null
+++ b/src/Web/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.eShopWeb.Infrastructure.Identity;
+
+namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
+{
+ [AllowAnonymous]
+ public class ConfirmEmailModel : PageModel
+ {
+ private readonly UserManager _userManager;
+
+ public ConfirmEmailModel(UserManager userManager)
+ {
+ _userManager = userManager;
+ }
+
+ public async Task OnGetAsync(string userId, string code)
+ {
+ if (userId == null || code == null)
+ {
+ return RedirectToPage("/Index");
+ }
+
+ var user = await _userManager.FindByIdAsync(userId);
+ if (user == null)
+ {
+ return NotFound($"Unable to load user with ID '{userId}'.");
+ }
+
+ var result = await _userManager.ConfirmEmailAsync(user, code);
+ if (!result.Succeeded)
+ {
+ throw new InvalidOperationException($"Error confirming email for user with ID '{userId}':");
+ }
+
+ return Page();
+ }
+ }
+}
diff --git a/src/Web/Controllers/AccountController.cs b/src/Web/Controllers/AccountController.cs
deleted file mode 100644
index 281497b..0000000
--- a/src/Web/Controllers/AccountController.cs
+++ /dev/null
@@ -1,232 +0,0 @@
-using Microsoft.AspNetCore.Authentication;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Identity;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.eShopWeb.ApplicationCore.Interfaces;
-using Microsoft.eShopWeb.Infrastructure.Identity;
-using Microsoft.eShopWeb.Web.ViewModels.Account;
-using System;
-using System.Threading.Tasks;
-
-namespace Microsoft.eShopWeb.Web.Controllers
-{
- [ApiExplorerSettings(IgnoreApi = true)]
- [Route("[controller]/[action]")]
- [Authorize] // Controllers that mainly require Authorization still use Controller/View; other pages use Pages
- public class AccountController : Controller
- {
- private readonly UserManager _userManager;
- private readonly SignInManager _signInManager;
- private readonly IBasketService _basketService;
- private readonly IAppLogger _logger;
-
- public AccountController(
- UserManager userManager,
- SignInManager signInManager,
- IBasketService basketService,
- IAppLogger logger)
- {
- _userManager = userManager;
- _signInManager = signInManager;
- _basketService = basketService;
- _logger = logger;
- }
-
- // GET: /Account/SignIn
- [HttpGet]
- [AllowAnonymous]
- public async Task SignIn(string returnUrl = null)
- {
- await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
-
- ViewData["ReturnUrl"] = returnUrl;
- if (!String.IsNullOrEmpty(returnUrl) &&
- returnUrl.IndexOf("checkout", StringComparison.OrdinalIgnoreCase) >= 0)
- {
- ViewData["ReturnUrl"] = "/Basket/Index";
- }
-
- return View();
- }
-
- // POST: /Account/SignIn
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public async Task SignIn(LoginViewModel model, string returnUrl = null)
- {
- if (!ModelState.IsValid)
- {
- return View(model);
- }
- ViewData["ReturnUrl"] = returnUrl;
-
- var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
- if (result.RequiresTwoFactor)
- {
- return RedirectToAction(nameof(LoginWith2fa), new { returnUrl, model.RememberMe });
- }
- if (result.Succeeded)
- {
- string anonymousBasketId = Request.Cookies[Constants.BASKET_COOKIENAME];
- if (!String.IsNullOrEmpty(anonymousBasketId))
- {
- await _basketService.TransferBasketAsync(anonymousBasketId, model.Email);
- Response.Cookies.Delete(Constants.BASKET_COOKIENAME);
- }
- return RedirectToLocal(returnUrl);
- }
- ModelState.AddModelError(string.Empty, "Invalid login attempt.");
- return View(model);
- }
-
- [HttpGet]
- [AllowAnonymous]
- public async Task LoginWith2fa(bool rememberMe, string returnUrl = null)
- {
- // Ensure the user has gone through the username & password screen first
- var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
-
- if (user == null)
- {
- throw new ApplicationException($"Unable to load two-factor authentication user.");
- }
-
- var model = new LoginWith2faViewModel { RememberMe = rememberMe };
- ViewData["ReturnUrl"] = returnUrl;
-
- return View(model);
- }
-
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public async Task LoginWith2fa(LoginWith2faViewModel model, bool rememberMe, string returnUrl = null)
- {
- if (!ModelState.IsValid)
- {
- return View(model);
- }
-
- var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
- }
-
- var authenticatorCode = model.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
-
- var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, model.RememberMachine);
-
- if (result.Succeeded)
- {
- _logger.LogInformation("User with ID {UserId} logged in with 2fa.", user.Id);
- return RedirectToLocal(returnUrl);
- }
- else if (result.IsLockedOut)
- {
- _logger.LogWarning("User with ID {UserId} account locked out.", user.Id);
- return RedirectToAction(nameof(Lockout));
- }
- else
- {
- _logger.LogWarning("Invalid authenticator code entered for user with ID {UserId}.", user.Id);
- ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
- return View();
- }
- }
-
- [HttpGet]
- [AllowAnonymous]
- public IActionResult Lockout()
- {
- return View();
- }
-
-
- [HttpPost]
- [ValidateAntiForgeryToken]
- public async Task SignOut()
- {
- await _signInManager.SignOutAsync();
-
- return RedirectToPage("/Index");
- }
-
- [AllowAnonymous]
- [HttpGet]
- public IActionResult Register()
- {
- return View();
- }
-
- [HttpPost]
- [AllowAnonymous]
- [ValidateAntiForgeryToken]
- public async Task Register(RegisterViewModel model, string returnUrl = null)
- {
- if (ModelState.IsValid)
- {
- var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
- var result = await _userManager.CreateAsync(user, model.Password);
- if (result.Succeeded)
- {
- await _signInManager.SignInAsync(user, isPersistent: false);
- return RedirectToLocal(returnUrl);
- }
- AddErrors(result);
- }
- // If we got this far, something failed, redisplay form
- return View(model);
- }
-
- [HttpGet]
- [AllowAnonymous]
- public async Task ConfirmEmail(string userId, string code)
- {
- if (userId == null || code == null)
- {
- return RedirectToPage("/Index");
- }
- var user = await _userManager.FindByIdAsync(userId);
- if (user == null)
- {
- throw new ApplicationException($"Unable to load user with ID '{userId}'.");
- }
- var result = await _userManager.ConfirmEmailAsync(user, code);
- return View(result.Succeeded ? "ConfirmEmail" : "Error");
- }
-
- [HttpGet]
- [AllowAnonymous]
- public IActionResult ResetPassword(string code = null)
- {
- if (code == null)
- {
- throw new ApplicationException("A code must be supplied for password reset.");
- }
- var model = new ResetPasswordViewModel { Code = code };
- return View(model);
- }
-
- private IActionResult RedirectToLocal(string returnUrl)
- {
- if (Url.IsLocalUrl(returnUrl))
- {
- return Redirect(returnUrl);
- }
- else
- {
- return RedirectToPage("/Index");
- }
- }
-
- private void AddErrors(IdentityResult result)
- {
- foreach (var error in result.Errors)
- {
- ModelState.AddModelError("", error.Description);
- }
- }
- }
-}
diff --git a/src/Web/Extensions/UrlHelperExtensions.cs b/src/Web/Extensions/UrlHelperExtensions.cs
index 95d971a..6edc697 100644
--- a/src/Web/Extensions/UrlHelperExtensions.cs
+++ b/src/Web/Extensions/UrlHelperExtensions.cs
@@ -1,23 +1,12 @@
-using Microsoft.eShopWeb.Web.Controllers;
-
-namespace Microsoft.AspNetCore.Mvc
+namespace Microsoft.AspNetCore.Mvc
{
public static class UrlHelperExtensions
{
public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
{
return urlHelper.Action(
- action: nameof(AccountController.ConfirmEmail),
- controller: "Account",
- values: new { userId, code },
- protocol: scheme);
- }
-
- public static string ResetPasswordCallbackLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
- {
- return urlHelper.Action(
- action: nameof(AccountController.ResetPassword),
- controller: "Account",
+ action: "GET",
+ controller: "ConfirmEmail",
values: new { userId, code },
protocol: scheme);
}
diff --git a/tests/FunctionalTests/Web/Controllers/AccountControllerSignIn.cs b/tests/FunctionalTests/Web/Controllers/AccountControllerSignIn.cs
index 0035a83..c706368 100644
--- a/tests/FunctionalTests/Web/Controllers/AccountControllerSignIn.cs
+++ b/tests/FunctionalTests/Web/Controllers/AccountControllerSignIn.cs
@@ -81,7 +81,7 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers
keyValues.Add(new KeyValuePair("__RequestVerificationToken", token));
var formContent = new FormUrlEncodedContent(keyValues);
- var postResponse = await Client.PostAsync("/account/sign-in", formContent);
+ var postResponse = await Client.PostAsync("/identity/account/login", formContent);
Assert.Equal(HttpStatusCode.Redirect, postResponse.StatusCode);
Assert.Equal(new System.Uri("/", UriKind.Relative), postResponse.Headers.Location);
}