Updating the sample to use VS 2013 Update 3 templates for Identity

1. This contains the new manage controller with a bunch of features like 2 factor auth

Some of the features are not enabled yet. But this is to set a baseline with update3 templates.

Automation to come up in future as more identity extensions are available.
This commit is contained in:
Praburaj 2014-08-28 16:57:43 -07:00
parent 05cd84667a
commit 9f4d46714b
30 changed files with 1429 additions and 159 deletions

View File

@ -1,8 +1,11 @@
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;
using MusicStore.Models;
using System.Linq;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNet.Http.Security;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering;
using MusicStore.Models;
namespace MusicStore.Controllers
{
@ -35,25 +38,78 @@ namespace MusicStore.Controllers
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
if (ModelState.IsValid)
if (!ModelState.IsValid)
{
var signInStatus = await SignInManager.PasswordSignInAsync(model.UserName, model.Password, model.RememberMe, shouldLockout: false);
switch (signInStatus)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
ModelState.AddModelError("", "User is locked out, try again later.");
return View(model);
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid username or password.");
return View(model);
}
return View(model);
}
// If we got this far, something failed, redisplay form
return View(model);
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var signInStatus = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (signInStatus)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
//
// GET: /Account/VerifyCode
//TODO : Some of the identity helpers not implemented
//[AllowAnonymous]
//public async Task<ActionResult> 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 });
//}
//
// POST: /Account/VerifyCode
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// The following code protects for brute force attacks against the two factor codes.
// If a user enters incorrect codes for a specified amount of time then the user account
// will be locked out for a specified amount of time.
// You can configure the account lockout settings in IdentityConfig
// TODO : This helper does not take in the remember browser option yet.
// var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser);
var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(model.ReturnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid code.");
return View(model);
}
}
//
@ -73,17 +129,26 @@ namespace MusicStore.Controllers
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.UserName };
var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
//Bug: Remember browser option missing?
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 <a href=\"" + callbackUrl + "\">here</a>");
// 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");
return RedirectToAction("Index", "Home");
}
else
{
AddErrors(result);
}
AddErrors(result);
}
// If we got this far, something failed, redisplay form
@ -91,42 +156,234 @@ namespace MusicStore.Controllers
}
//
// GET: /Account/Manage
public IActionResult Manage(ManageMessageId? message = null)
// GET: /Account/ConfirmEmail
//TODO: This does not work yet due to some missing identity features
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
if (userId == null || code == null)
{
return View("Error");
}
var user = new ApplicationUser { Id = userId };
//Bug: Throws NullRefException
var result = await UserManager.ConfirmEmailAsync(user, code);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
//
// GET: /Account/ForgotPassword
[AllowAnonymous]
public ActionResult ForgotPassword()
{
ViewBag.StatusMessage =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.Error ? "An error has occurred."
: "";
ViewBag.ReturnUrl = Url.Action("Manage");
return View();
}
//
// POST: /Account/Manage
// POST: /Account/ForgotPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Manage(ManageUserViewModel model)
public async Task<ActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
ViewBag.ReturnUrl = Url.Action("Manage");
if (ModelState.IsValid)
{
var user = await GetCurrentUserAsync();
var result = await UserManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (result.Succeeded)
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null || !(await UserManager.IsEmailConfirmedAsync(user)))
{
return RedirectToAction("Manage", new { Message = ManageMessageId.ChangePasswordSuccess });
}
else
{
AddErrors(result);
// Don't reveal that the user does not exist or is not confirmed
return View("ForgotPasswordConfirmation");
}
// 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.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 <a href=\"" + callbackUrl + "\">here</a>");
// 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");
}
ModelState.AddModelError("", string.Format("We could not locate an account with email : {0}", model.Email));
// If we got this far, something failed, redisplay form
return View(model);
}
//
// GET: /Account/ForgotPasswordConfirmation
[AllowAnonymous]
public ActionResult ForgotPasswordConfirmation()
{
return View();
}
//
// GET: /Account/ResetPassword
[AllowAnonymous]
public ActionResult ResetPassword(string code)
{
var resetPasswordViewModel = new ResetPasswordViewModel() { Code = code };
return code == null ? View("Error") : View(resetPasswordViewModel);
}
//
// POST: /Account/ResetPassword
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ResetPassword(ResetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await UserManager.FindByNameAsync(model.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
var result = await UserManager.ResetPasswordAsync(user, model.Code, model.Password);
if (result.Succeeded)
{
return RedirectToAction("ResetPasswordConfirmation", "Account");
}
AddErrors(result);
return View();
}
//
// GET: /Account/ResetPasswordConfirmation
[AllowAnonymous]
public ActionResult ResetPasswordConfirmation()
{
return View();
}
//
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// Request a redirect to the external login provider
var redirectUri = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
return new ChallengeResult(provider, new AuthenticationProperties() { RedirectUri = redirectUri });
}
//
// GET: /Account/SendCode
[AllowAnonymous]
public async Task<ActionResult> SendCode(bool rememberMe, string returnUrl = null)
{
// TODO: This currently throws
var userId = await GetCurrentUserAsync();
if (userId == null)
{
return View("Error");
}
var userFactors = await UserManager.GetValidTwoFactorProvidersAsync(userId);
var factorOptions = userFactors.Select(purpose => new SelectListItem { Text = purpose, Value = purpose }).ToList();
return View(new SendCodeViewModel { Providers = factorOptions, ReturnUrl = returnUrl, RememberMe = rememberMe });
}
//
// POST: /Account/SendCode
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> SendCode(SendCodeViewModel model)
{
if (!ModelState.IsValid)
{
return View();
}
// Generate the token and send it
if (!await SignInManager.SendTwoFactorCodeAsync(model.SelectedProvider))
{
return View("Error");
}
return RedirectToAction("VerifyCode", new { Provider = model.SelectedProvider, ReturnUrl = model.ReturnUrl, RememberMe = model.RememberMe });
}
//
// GET: /Account/ExternalLoginCallback
// TODO: Some identity helpers on external login does not exist
//[AllowAnonymous]
//public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
//{
// var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();
// 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 });
// }
//}
//
// POST: /Account/ExternalLoginConfirmation
// TODO: Some of the identity helpers not available
//[HttpPost]
//[AllowAnonymous]
//[ValidateAntiForgeryToken]
//public async Task<ActionResult> ExternalLoginConfirmation(ExternalLoginConfirmationViewModel model, string returnUrl)
//{
// 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);
// }
// ViewBag.ReturnUrl = returnUrl;
// return View(model);
//}
//
// POST: /Account/LogOff
[HttpPost]
@ -137,6 +394,14 @@ namespace MusicStore.Controllers
return RedirectToAction("Index", "Home");
}
//
// GET: /Account/ExternalLoginFailure
[AllowAnonymous]
public ActionResult ExternalLoginFailure()
{
return View();
}
#region Helpers
private void AddErrors(IdentityResult result)
@ -152,13 +417,7 @@ namespace MusicStore.Controllers
return await UserManager.FindByIdAsync(Context.User.Identity.GetUserId());
}
public enum ManageMessageId
{
ChangePasswordSuccess,
Error
}
private IActionResult RedirectToLocal(string returnUrl)
private ActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{

View File

@ -0,0 +1,348 @@
using System;
using System.Threading.Tasks;
using System.Security.Principal;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;
using MusicStore.Models;
namespace MusicStore.Controllers
{
/// <summary>
/// Summary description for ManageController
/// </summary>
public class ManageController : Controller
{
public ManageController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
{
UserManager = userManager;
SignInManager = signInManager;
}
public UserManager<ApplicationUser> UserManager { get; private set; }
public SignInManager<ApplicationUser> SignInManager { get; private set; }
//
// GET: /Manage/Index
public async Task<ActionResult> Index(ManageMessageId? message = null)
{
ViewBag.StatusMessage =
message == ManageMessageId.ChangePasswordSuccess ? "Your password has been changed."
: message == ManageMessageId.SetPasswordSuccess ? "Your password has been set."
: message == ManageMessageId.SetTwoFactorSuccess ? "Your two-factor authentication provider has been set."
: message == ManageMessageId.Error ? "An error has occurred."
: message == ManageMessageId.AddPhoneSuccess ? "Your phone number was added."
: message == ManageMessageId.RemovePhoneSuccess ? "Your phone number was removed."
: "";
var user = await GetCurrentUserAsync();
var model = new IndexViewModel
{
HasPassword = await UserManager.HasPasswordAsync(user),
PhoneNumber = await UserManager.GetPhoneNumberAsync(user),
TwoFactor = await UserManager.GetTwoFactorEnabledAsync(user),
Logins = await UserManager.GetLoginsAsync(user),
BrowserRemembered = await SignInManager.IsTwoFactorClientRememberedAsync(user)
};
return View(model);
}
//
// POST: /Manage/RemoveLogin
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RemoveLogin(string loginProvider, string providerKey)
{
ManageMessageId? message = ManageMessageId.Error;
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await UserManager.RemoveLoginAsync(user, loginProvider, providerKey);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false);
message = ManageMessageId.RemoveLoginSuccess;
}
}
return RedirectToAction("ManageLogins", new { Message = message });
}
//
// GET: /Account/AddPhoneNumber
public IActionResult AddPhoneNumber()
{
return View();
}
//
// POST: /Account/AddPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddPhoneNumber(AddPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// Generate the token and send it
var code = await UserManager.GenerateChangePhoneNumberTokenAsync(await GetCurrentUserAsync(), model.Number);
if (UserManager.SmsService != null)
{
var message = new IdentityMessage
{
Destination = model.Number,
Body = "Your security code is: " + code
};
await UserManager.SmsService.SendAsync(message);
}
return RedirectToAction("VerifyPhoneNumber", new { PhoneNumber = model.Number });
}
//
// POST: /Manage/EnableTwoFactorAuthentication
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EnableTwoFactorAuthentication()
{
var user = await GetCurrentUserAsync();
if (user != null)
{
await UserManager.SetTwoFactorEnabledAsync(user, true);
// TODO: flow remember me somehow?
await SignInManager.SignInAsync(user, isPersistent: false);
}
return RedirectToAction("Index", "Manage");
}
//
// POST: /Manage/DisableTwoFactorAuthentication
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DisableTwoFactorAuthentication()
{
var user = await GetCurrentUserAsync();
if (user != null)
{
await UserManager.SetTwoFactorEnabledAsync(user, false);
await SignInManager.SignInAsync(user, isPersistent: false);
}
return RedirectToAction("Index", "Manage");
}
//
// GET: /Account/VerifyPhoneNumber
public async Task<IActionResult> VerifyPhoneNumber(string phoneNumber)
{
// 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;
return phoneNumber == null ? View("Error") : View(new VerifyPhoneNumberViewModel { PhoneNumber = phoneNumber });
}
//
// POST: /Account/VerifyPhoneNumber
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> VerifyPhoneNumber(VerifyPhoneNumberViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await UserManager.ChangePhoneNumberAsync(user, model.PhoneNumber, model.Code);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", new { Message = ManageMessageId.AddPhoneSuccess });
}
}
// If we got this far, something failed, redisplay form
ModelState.AddModelError("", "Failed to verify phone");
return View(model);
}
//
// GET: /Account/RemovePhoneNumber
public async Task<IActionResult> RemovePhoneNumber()
{
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await UserManager.SetPhoneNumberAsync(user, null);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", new { Message = ManageMessageId.RemovePhoneSuccess });
}
}
return RedirectToAction("Index", new { Message = ManageMessageId.Error });
}
//
// GET: /Manage/ChangePassword
public IActionResult ChangePassword()
{
return View();
}
//
// POST: /Account/Manage
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await UserManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", new { Message = ManageMessageId.ChangePasswordSuccess });
}
AddErrors(result);
return View(model);
}
return RedirectToAction("Index", new { Message = ManageMessageId.Error });
}
//
// GET: /Manage/SetPassword
public IActionResult SetPassword()
{
return View();
}
//
// POST: /Manage/SetPassword
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> SetPassword(SetPasswordViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await UserManager.AddPasswordAsync(user, model.NewPassword);
if (result.Succeeded)
{
await SignInManager.SignInAsync(user, isPersistent: false);
return RedirectToAction("Index", new { Message = ManageMessageId.SetPasswordSuccess });
}
AddErrors(result);
return View(model);
}
return RedirectToAction("Index", new { Message = ManageMessageId.Error });
}
//
// POST: /Manage/RememberBrowser
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> RememberBrowser()
{
var user = await GetCurrentUserAsync();
if (user != null)
{
await SignInManager.RememberTwoFactorClient(user);
await SignInManager.SignInAsync(user, isPersistent: false);
}
return RedirectToAction("Index", "Manage");
}
//
// POST: /Manage/ForgetBrowser
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ForgetBrowser()
{
await SignInManager.ForgetTwoFactorClientAsync();
return RedirectToAction("Index", "Manage");
}
//
// GET: /Account/Manage
//public async Task<IActionResult> 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
//});
//}
#region Helpers
private void AddErrors(IdentityResult result)
{
foreach (var error in result.Errors)
{
ModelState.AddModelError("", error);
}
}
private async Task<bool> HasPhoneNumber()
{
var user = await UserManager.FindByIdAsync(User.Identity.GetUserId());
if (user != null)
{
return user.PhoneNumber != null;
}
return false;
}
public enum ManageMessageId
{
AddPhoneSuccess,
ChangePasswordSuccess,
SetTwoFactorSuccess,
SetPasswordSuccess,
RemoveLoginSuccess,
RemovePhoneSuccess,
Error
}
private async Task<ApplicationUser> GetCurrentUserAsync()
{
return await UserManager.FindByIdAsync(Context.User.Identity.GetUserId());
}
private IActionResult RedirectToLocal(string returnUrl)
{
if (Url.IsLocalUrl(returnUrl))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
#endregion
}
}

View File

@ -1,5 +1,5 @@
{
"DefaultAdminUsername": "Administrator",
"DefaultAdminUsername": "Administrator@test.com",
"DefaultAdminPassword": "YouShouldChangeThisPassword1!",
"Data": {
"DefaultConnection": {

View File

@ -1,38 +1,58 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNet.Mvc.Rendering;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace MusicStore.Models
{
public class ExternalLoginConfirmationViewModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Display(Name = "Email")]
public string Email { get; set; }
}
public class ManageUserViewModel
public class ExternalLoginListViewModel
{
public string ReturnUrl { get; set; }
}
public class SendCodeViewModel
{
public string SelectedProvider { get; set; }
public ICollection<SelectListItem> Providers { get; set; }
public string ReturnUrl { get; set; }
public bool RememberMe { get; set; }
}
public class VerifyCodeViewModel
{
[Required]
[DataType(DataType.Password)]
[Display(Name = "Current password")]
public string OldPassword { get; set; }
public string Provider { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
[Display(Name = "Code")]
public string Code { get; set; }
public string ReturnUrl { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
[Display(Name = "Remember this browser?")]
public bool RememberBrowser { get; set; }
public bool RememberMe { get; set; }
}
public class ForgotViewModel
{
[Required]
[Display(Name = "Email")]
public string Email { get; set; }
}
public class LoginViewModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Display(Name = "Email")]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
@ -46,8 +66,9 @@ namespace MusicStore.Models
public class RegisterViewModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
@ -60,4 +81,33 @@ namespace MusicStore.Models
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public class ResetPasswordViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public string Code { get; set; }
}
public class ForgotPasswordViewModel
{
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNet.Http.Security;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc.Rendering;
namespace MusicStore.Models
{
public class IndexViewModel
{
public bool HasPassword { get; set; }
public IList<UserLoginInfo> Logins { get; set; }
public string PhoneNumber { get; set; }
public bool TwoFactor { get; set; }
public bool BrowserRemembered { get; set; }
}
public class ManageLoginsViewModel
{
public IList<UserLoginInfo> CurrentLogins { get; set; }
public IList<AuthenticationDescription> OtherLogins { get; set; }
}
public class FactorViewModel
{
public string Purpose { get; set; }
}
public class SetPasswordViewModel
{
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public class ChangePasswordViewModel
{
[Required]
[DataType(DataType.Password)]
[Display(Name = "Current password")]
public string OldPassword { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "New password")]
public string NewPassword { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm new password")]
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public class AddPhoneNumberViewModel
{
[Required]
[Phone]
[Display(Name = "Phone Number")]
public string Number { get; set; }
}
public class VerifyPhoneNumberViewModel
{
[Required]
[Display(Name = "Code")]
public string Code { get; set; }
[Required]
[Phone]
[Display(Name = "Phone Number")]
public string PhoneNumber { get; set; }
}
public class ConfigureTwoFactorViewModel
{
public string SelectedProvider { get; set; }
public ICollection<SelectListItem> Providers { get; set; }
}
}

View File

@ -53,9 +53,7 @@
<Content Include="Scripts\respond.min.js" />
<Content Include="Scripts\_references.js" />
<Content Include="Views\Account\Login.cshtml" />
<Content Include="Views\Account\Manage.cshtml" />
<Content Include="Views\Account\Register.cshtml" />
<Content Include="Views\Account\_ChangePasswordPartial.cshtml" />
<Content Include="Views\Checkout\AddressAndPayment.cshtml" />
<Content Include="Views\Checkout\Complete.cshtml" />
<Content Include="Views\Home\Index.cshtml" />

View File

@ -24,9 +24,9 @@ namespace MusicStore
configuration.AddJsonFile("LocalConfig.json");
configuration.AddEnvironmentVariables(); //All environment variables in the process's context flow in as configuration values.
/* Error page middleware displays a nice formatted HTML page for any unhandled exceptions in the request pipeline.
* Note: ErrorPageOptions.ShowAll to be used only at development time. Not recommended for production.
*/
/* Error page middleware displays a nice formatted HTML page for any unhandled exceptions in the request pipeline.
* Note: ErrorPageOptions.ShowAll to be used only at development time. Not recommended for production.
*/
app.UseErrorPage(ErrorPageOptions.ShowAll);
app.UseServices(services =>
@ -78,9 +78,11 @@ namespace MusicStore
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = ClaimsIdentityOptions.DefaultAuthenticationType,
LoginPath = new PathString("/Account/Login"),
LoginPath = new PathString("/Account/Login")
});
app.UseTwoFactorSignInCookies();
// Add MVC to the request pipeline
app.UseMvc(routes =>
{

View File

@ -0,0 +1,10 @@
{
ViewBag.Title = "Confirm Email";
}
<h2>@ViewBag.Title.</h2>
<div>
<p>
Thank you for confirming your email. Please @Html.ActionLink("Click here to Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })
</p>
</div>

View File

@ -0,0 +1,39 @@
@model MusicStore.Models.ExternalLoginConfirmationViewModel
@{
ViewBag.Title = "Register";
}
<h2>@ViewBag.Title.</h2>
<h3>Associate your @ViewBag.LoginProvider account.</h3>
@using (Html.BeginForm("ExternalLoginConfirmation", "Account", new { ReturnUrl = ViewBag.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<h4>Association Form</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<p class="text-info">
You've successfully authenticated with <strong>@ViewBag.LoginProvider</strong>.
Please enter a user name for this site below and click the Register button to finish
logging in.
</p>
<div class="form-group">
@Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Register" />
</div>
</div>
}
@section Scripts {
@*TODO : Until script helpers are available, adding script references manually*@
@*@Scripts.Render("~/bundles/jqueryval")*@
<script src="@Url.Content("~/Scripts/jquery.validate.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")"></script>
}

View File

@ -0,0 +1,8 @@
@{
ViewBag.Title = "Login Failure";
}
<hgroup>
<h2>@ViewBag.Title.</h2>
<h3 class="text-danger">Unsuccessful login with service.</h3>
</hgroup>

View File

@ -0,0 +1,32 @@
@model MusicStore.Models.ForgotPasswordViewModel
@{
ViewBag.Title = "Forgot your password?";
}
<h2>@ViewBag.Title.</h2>
@using (Html.BeginForm("ForgotPassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<h4>Enter your email.</h4>
<hr />
@Html.ValidationSummary("", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Email Link" />
</div>
</div>
}
@section Scripts {
@*TODO : Until script helpers are available, adding script references manually*@
@*@Scripts.Render("~/bundles/jqueryval")*@
<script src="@Url.Content("~/Scripts/jquery.validate.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")"></script>
}

View File

@ -0,0 +1,15 @@
@{
ViewBag.Title = "Forgot Password Confirmation";
}
<hgroup class="title">
<h1>@ViewBag.Title.</h1>
</hgroup>
<div>
<p>
Please check your email to reset your password.
</p>
<p>
For demo purpose only: @Html.ActionLink("Click here to reset the password: ", "ResetPassword", new { code = ViewBag.Code });
</p>
</div>

View File

@ -1,4 +1,5 @@
@model MusicStore.Models.LoginViewModel
@using MusicStore.Models
@model LoginViewModel
@{
ViewBag.Title = "Log in";
@ -13,19 +14,19 @@
@Html.AntiForgeryToken()
<h4>Use a local account to log in.</h4>
<hr />
@Html.ValidationSummary(true)
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
@Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.UserName)
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Email, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
@Html.ValidationMessageFor(m => m.Password)
@Html.ValidationMessageFor(m => m.Password, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@ -42,11 +43,20 @@
</div>
</div>
<p>
@Html.ActionLink("Register", "Register") if you don't have a local account.
@Html.ActionLink("Register as a new user", "Register")
</p>
@*Enable this once you have account confirmation enabled for password reset functionality*@
<p>
@Html.ActionLink("Forgot your password?", "ForgotPassword")
</p>
}
</section>
</div>
@*<div class="col-md-4">
<section id="socialLoginForm">
@Html.PartialAsync("_ExternalLoginsListPartial", new ExternalLoginListViewModel { ReturnUrl = ViewBag.ReturnUrl })
</section>
</div>*@
</div>
@section Scripts {
@*TODO : Until script helpers are available, adding script references manually*@

View File

@ -1,19 +0,0 @@
@{
ViewBag.Title = "Manage Account";
}
<h2>@ViewBag.Title.</h2>
<p class="text-success">@ViewBag.StatusMessage</p>
<div class="row">
<div class="col-md-12">
@await Html.PartialAsync("_ChangePasswordPartial")
</div>
</div>
@section Scripts {
@*TODO : Until script helpers are available, adding script references manually*@
@*@Scripts.Render("~/bundles/jqueryval")*@
<script src="@Url.Content("~/Scripts/jquery.validate.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")"></script>
}

View File

@ -10,11 +10,11 @@
@Html.AntiForgeryToken()
<h4>Create a new account.</h4>
<hr />
@Html.ValidationSummary()
@Html.ValidationSummary("", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.UserName, new { @class = "col-md-2 control-label" })
@Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.UserName, new { @class = "form-control" })
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
</div>
</div>
<div class="form-group">

View File

@ -0,0 +1,15 @@
@{
ViewBag.Title = "Register Confirmation";
}
<hgroup class="title">
<h1>@ViewBag.Title.</h1>
</hgroup>
<div>
<p>
Please check your email to activate your account.
</p>
<p>
Demo/testing purposes only: The sample displays the code and user id in the page: @Html.ActionLink("Click here to confirm your email: ", "ConfirmEmail", new { code = ViewBag.Code, userId = ViewBag.UserId })
</p>
</div>

View File

@ -0,0 +1,45 @@
@model MusicStore.Models.ResetPasswordViewModel
@{
ViewBag.Title = "Reset password";
}
<h2>@ViewBag.Title.</h2>
@using (Html.BeginForm("ResetPassword", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<h4>Reset your password.</h4>
<hr />
@Html.ValidationSummary("", new { @class = "text-danger" })
@Html.HiddenFor(model => model.Code)
<div class="form-group">
@Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.Password, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Reset" />
</div>
</div>
}
@section Scripts {
@*TODO : Until script helpers are available, adding script references manually*@
@*@Scripts.Render("~/bundles/jqueryval")*@
<script src="@Url.Content("~/Scripts/jquery.validate.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")"></script>
}

View File

@ -0,0 +1,12 @@
@{
ViewBag.Title = "Reset password confirmation";
}
<hgroup class="title">
<h1>@ViewBag.Title.</h1>
</hgroup>
<div>
<p>
Your password has been reset. Please @Html.ActionLink("click here to log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })
</p>
</div>

View File

@ -0,0 +1,27 @@
@model MusicStore.Models.SendCodeViewModel
@{
ViewBag.Title = "Send";
}
<h2>@ViewBag.Title.</h2>
@using (Html.BeginForm("SendCode", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) {
@Html.AntiForgeryToken()
@Html.Hidden("rememberMe", @Model.RememberMe)
<h4>Send verification code</h4>
<hr />
<div class="row">
<div class="col-md-8">
Select Two-Factor Authentication Provider:
@Html.DropDownListFor(model => model.SelectedProvider, Model.Providers)
<input type="submit" value="Submit" class="btn btn-default" />
</div>
</div>
}
@section Scripts {
@*TODO : Until script helpers are available, adding script references manually*@
@*@Scripts.Render("~/bundles/jqueryval")*@
<script src="@Url.Content("~/Scripts/jquery.validate.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")"></script>
}

View File

@ -0,0 +1,28 @@
@model MusicStore.Models.ExternalLoginListViewModel
@using Microsoft.AspNet.Http.Security;
<h4>Use another service to log in.</h4>
<hr />
@{
var loginProviders = Context.GetAuthenticationTypes();
if (loginProviders.Count() == 0) {
<div>
<p>
There are no external authentication services configured. See <a href="http://go.microsoft.com/fwlink/?LinkId=313242">this article</a>
for details on setting up this ASP.NET application to support logging in via external services.
</p>
</div>
}
else {
using (Html.BeginForm("ExternalLogin", "Account", new { ReturnUrl = Model.ReturnUrl })) {
@Html.AntiForgeryToken()
<div id="socialLoginList">
<p>
@foreach (AuthenticationDescription p in loginProviders) {
<button type="submit" class="btn btn-default" id="@p.AuthenticationType" name="provider" value="@p.AuthenticationType" title="Log in using your @p.Caption account">@p.AuthenticationType</button>
}
</p>
</div>
}
}
}

View File

@ -0,0 +1,32 @@
@model MusicStore.Models.AddPhoneNumberViewModel
@{
ViewBag.Title = "Phone Number";
}
<h2>@ViewBag.Title.</h2>
@using (Html.BeginForm("AddPhoneNumber", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<h4>Add a phone number</h4>
<hr />
@Html.ValidationSummary("", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.Number, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Number, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Submit" />
</div>
</div>
}
@section Scripts {
@*TODO : Until script helpers are available, adding script references manually*@
@*@Scripts.Render("~/bundles/jqueryval")*@
<script src="@Url.Content("~/Scripts/jquery.validate.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")"></script>
}

View File

@ -1,14 +1,16 @@
@using System.Security.Principal
@model MusicStore.Models.ManageUserViewModel
@model MusicStore.Models.ChangePasswordViewModel
@{
ViewBag.Title = "Change Password";
}
<p>You're logged in as <strong>@User.Identity.GetUserName()</strong>.</p>
<h2>@ViewBag.Title.</h2>
@using (Html.BeginForm("Manage", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
@using (Html.BeginForm("ChangePassword", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<h4>Change Password Form</h4>
<hr />
@Html.ValidationSummary()
@Html.ValidationSummary("", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.OldPassword, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@ -27,10 +29,15 @@
@Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Change password" class="btn btn-default" />
</div>
</div>
}
@section Scripts {
@*TODO : Until script helpers are available, adding script references manually*@
@*@Scripts.Render("~/bundles/jqueryval")*@
<script src="@Url.Content("~/Scripts/jquery.validate.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")"></script>
}

View File

@ -0,0 +1,106 @@
@model MusicStore.Models.IndexViewModel
@{
ViewBag.Title = "Manage";
}
<h2>@ViewBag.Title.</h2>
<p class="text-success">@ViewBag.StatusMessage</p>
<div>
<h4>Change your account settings</h4>
<hr />
<dl class="dl-horizontal">
<dt>Password:</dt>
<dd>
[
@if (Model.HasPassword)
{
@Html.ActionLink("Change your password", "ChangePassword")
}
else
{
@Html.ActionLink("Create", "SetPassword")
}
]
</dd>
<dt>External Logins:</dt>
<dd>
@Model.Logins.Count [
@Html.ActionLink("Manage", "ManageLogins") ]
</dd>
@*
Phone Numbers can used as a second factor of verification in a two-factor authentication system.
See <a href="http://go.microsoft.com/fwlink/?LinkId=313242">this article</a>
for details on setting up this ASP.NET application to support two-factor authentication using SMS.
Uncomment the following block after you have set up two-factor authentication
*@
<dt>Phone Number:</dt>
<dd>
@(Model.PhoneNumber ?? "None") [
@if (Model.PhoneNumber != null)
{
@Html.ActionLink("Change", "AddPhoneNumber")
@: &nbsp;|&nbsp;
@Html.ActionLink("Remove", "RemovePhoneNumber")
}
else
{
@Html.ActionLink("Add", "AddPhoneNumber")
}
]
</dd>
<dt>Two-Factor Authentication:</dt>
<dd>
@if (Model.TwoFactor)
{
using (Html.BeginForm("DisableTwoFactorAuthentication", "Manage", FormMethod.Post))
{
@Html.AntiForgeryToken()
<p>
Enabled
<input type="submit" value="Disable" class="btn btn-link" />
</p>
}
}
else
{
using (Html.BeginForm("EnableTwoFactorAuthentication", "Manage", FormMethod.Post))
{
@Html.AntiForgeryToken()
<p>
Disabled
<input type="submit" value="Enable" class="btn btn-link" />
</p>
}
}
</dd>
<dt>Browser remembered:</dt>
<dd>
@if (Model.BrowserRemembered)
{
using (Html.BeginForm("ForgetBrowser", "Manage", FormMethod.Post))
{
@Html.AntiForgeryToken()
<p>
Browser is curently remembered for two factor:
<input type="submit" value="Forget Browser" class="btn btn-default" />
</p>
}
}
else
{
using (Html.BeginForm("RememberBrowser", "Manage", FormMethod.Post))
{
@Html.AntiForgeryToken()
<p>
Browser is curently not remembered for two factor:
<input type="submit" value="Remember Browser" class="btn btn-default" />
</p>
}
}
</dd>
</dl>
</div>

View File

@ -0,0 +1,58 @@
@using Microsoft.AspNet.Http.Security;
@model MusicStore.Models.ManageLoginsViewModel
@{
ViewBag.Title = "Manage your external logins";
}
<h2>@ViewBag.Title.</h2>
<p class="text-success">@ViewBag.StatusMessage</p>
@if (Model.CurrentLogins.Count > 0)
{
<h4>Registered Logins</h4>
<table class="table">
<tbody>
@foreach (var account in Model.CurrentLogins)
{
<tr>
<td>@account.LoginProvider</td>
<td>
@if (ViewBag.ShowRemoveButton)
{
using (Html.BeginForm("RemoveLogin", "Manage"))
{
@Html.AntiForgeryToken()
<div>
@Html.Hidden("loginProvider", account.LoginProvider)
@Html.Hidden("providerKey", account.ProviderKey)
<input type="submit" class="btn btn-default" value="Remove" title="Remove this @account.LoginProvider login from your account" />
</div>
}
}
else
{
@: &nbsp;
}
</td>
</tr>
}
</tbody>
</table>
}
@if (Model.OtherLogins.Count > 0)
{
<h4>Add another service to log in.</h4>
<hr />
using (Html.BeginForm("LinkLogin", "Manage"))
{
@Html.AntiForgeryToken()
<div id="socialLoginList">
<p>
@foreach (AuthenticationDescription p in Model.OtherLogins)
{
<button type="submit" class="btn btn-default" id="@p.AuthenticationType" name="provider" value="@p.AuthenticationType" title="Log in using your @p.Caption account">@p.AuthenticationType</button>
}
</p>
</div>
}
}

View File

@ -0,0 +1,43 @@
@model MusicStore.Models.SetPasswordViewModel
@{
ViewBag.Title = "Create Password";
}
<h2>@ViewBag.Title.</h2>
<p class="text-info">
You do not have a local username/password for this site. Add a local
account so you can log in without an external login.
</p>
@using (Html.BeginForm("SetPassword", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
<h4>Create Local Login</h4>
<hr />
@Html.ValidationSummary("", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.NewPassword, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.NewPassword, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Set password" class="btn btn-default" />
</div>
</div>
}
@section Scripts {
@*TODO : Until script helpers are available, adding script references manually*@
@*@Scripts.Render("~/bundles/jqueryval")*@
<script src="@Url.Content("~/Scripts/jquery.validate.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")"></script>
}

View File

@ -0,0 +1,34 @@
@model MusicStore.Models.VerifyPhoneNumberViewModel
@{
ViewBag.Title = "Verify Phone Number";
}
<h2>@ViewBag.Title.</h2>
@using (Html.BeginForm("VerifyPhoneNumber", "Manage", FormMethod.Post, new { @class = "form-horizontal", role = "form" }))
{
@Html.AntiForgeryToken()
@Html.Hidden("phoneNumber", @Model.PhoneNumber)
<h4>Enter verification code</h4>
<h5>@ViewBag.Status</h5>
<hr />
@Html.ValidationSummary("", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(m => m.Code, new { @class = "col-md-2 control-label" })
<div class="col-md-10">
@Html.TextBoxFor(m => m.Code, new { @class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Submit" />
</div>
</div>
}
@section Scripts {
@*TODO : Until script helpers are available, adding script references manually*@
@*@Scripts.Render("~/bundles/jqueryval")*@
<script src="@Url.Content("~/Scripts/jquery.validate.js")"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")"></script>
}

View File

@ -0,0 +1,15 @@
@{
ViewBag.Title = "Demo code display page - Not for production use";
}
<hgroup class="title">
<h1>@ViewBag.Title.</h1>
</hgroup>
<div>
<p>
Demo code display page - Not for production use.
</p>
<p>
[Demo]: @ViewBag.Status
</p>
</div>

View File

@ -0,0 +1,8 @@
@{
ViewBag.Title = "Locked Out";
}
<hgroup>
<h1 class="text-danger">Locked out.</h1>
<h2 class="text-danger">This account has been locked out, please try again later.</h2>
</hgroup>

View File

@ -8,7 +8,7 @@
<ul class="nav navbar-nav navbar-right">
<li>
@Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Manage", "Account", routeValues: null, htmlAttributes: new { title = "Manage" })
@Html.ActionLink("Hello " + User.Identity.GetUserName() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "Manage" })
</li>
<li><a href="javascript:document.getElementById('logoutForm').submit()">Log off</a></li>
</ul>

View File

@ -97,36 +97,36 @@ namespace E2ETests
RegisterUserWithNonMatchingPasswords();
//Register a valid user
var generatedUserName = RegisterValidUser();
var generatedEmail = RegisterValidUser();
//Register a user - Negative scenario : Trying to register a user name that's already registered.
RegisterExistingUser(generatedUserName);
RegisterExistingUser(generatedEmail);
//Logout from this user session - This should take back to the home page
SignOutUser(generatedUserName);
SignOutUser(generatedEmail);
//Sign in scenarios: Invalid password - Expected an invalid user name password error.
SignInWithInvalidPassword(generatedUserName, "InvalidPassword~1");
SignInWithInvalidPassword(generatedEmail, "InvalidPassword~1");
//Sign in scenarios: Valid user name & password.
SignInWithUser(generatedUserName, "Password~1");
SignInWithUser(generatedEmail, "Password~1");
//Change password scenario
ChangePassword(generatedUserName);
ChangePassword(generatedEmail);
//SignIn with old password and verify old password is not allowed and new password is allowed
SignOutUser(generatedUserName);
SignInWithInvalidPassword(generatedUserName, "Password~1");
SignInWithUser(generatedUserName, "Password~2");
SignOutUser(generatedEmail);
SignInWithInvalidPassword(generatedEmail, "Password~1");
SignInWithUser(generatedEmail, "Password~2");
//Making a request to a protected resource that this user does not have access to - should automatically redirect to login page again
AccessStoreWithoutPermissions(generatedUserName);
AccessStoreWithoutPermissions(generatedEmail);
//Logout from this user session - This should take back to the home page
SignOutUser(generatedUserName);
SignOutUser(generatedEmail);
//Login as an admin user
SignInWithUser("Administrator", "YouShouldChangeThisPassword1!");
SignInWithUser("Administrator@test.com", "YouShouldChangeThisPassword1!");
//Now navigating to the store manager should work fine as this user has the necessary permission to administer the store.
AccessStoreWithPermissions();
@ -222,9 +222,9 @@ namespace E2ETests
Assert.Contains("<li class=\"divider\"></li>", responseContent, StringComparison.OrdinalIgnoreCase);
}
private void AccessStoreWithoutPermissions(string userName = null)
private void AccessStoreWithoutPermissions(string email = null)
{
Console.WriteLine("Trying to access StoreManager that needs ManageStore claim with the current user : {0}", userName ?? "Anonymous");
Console.WriteLine("Trying to access StoreManager that needs ManageStore claim with the current user : {0}", email ?? "Anonymous");
var response = httpClient.GetAsync("StoreManager/").Result;
ThrowIfResponseStatusNotOk(response);
var responseContent = response.Content.ReadAsStringAsync().Result;
@ -259,11 +259,11 @@ namespace E2ETests
var responseContent = response.Content.ReadAsStringAsync().Result;
ValidateLayoutPage(responseContent);
var generatedUserName = Guid.NewGuid().ToString().Replace("-", string.Empty);
Console.WriteLine("Creating a new user with name '{0}'", generatedUserName);
var generatedEmail = Guid.NewGuid().ToString().Replace("-", string.Empty) + "@test.com";
Console.WriteLine("Creating a new user with name '{0}'", generatedEmail);
var formParameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("UserName", generatedUserName),
new KeyValuePair<string, string>("Email", generatedEmail),
new KeyValuePair<string, string>("Password", "Password~1"),
new KeyValuePair<string, string>("ConfirmPassword", "Password~2"),
new KeyValuePair<string, string>("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Register")),
@ -273,8 +273,8 @@ namespace E2ETests
response = httpClient.PostAsync("Account/Register", content).Result;
responseContent = response.Content.ReadAsStringAsync().Result;
Assert.Null(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application"));
Assert.Contains("<div class=\"validation-summary-errors\" data-valmsg-summary=\"true\"><ul><li>The password and confirmation password do not match.</li>", responseContent, StringComparison.OrdinalIgnoreCase);
Console.WriteLine("Server side model validator rejected the user '{0}''s registration as passwords do not match.", generatedUserName);
Assert.Contains("<div class=\"validation-summary-errors text-danger\" data-valmsg-summary=\"true\"><ul><li>The password and confirmation password do not match.</li>", responseContent, StringComparison.OrdinalIgnoreCase);
Console.WriteLine("Server side model validator rejected the user '{0}''s registration as passwords do not match.", generatedEmail);
}
private string RegisterValidUser()
@ -284,11 +284,11 @@ namespace E2ETests
var responseContent = response.Content.ReadAsStringAsync().Result;
ValidateLayoutPage(responseContent);
var generatedUserName = Guid.NewGuid().ToString().Replace("-", string.Empty);
Console.WriteLine("Creating a new user with name '{0}'", generatedUserName);
var generatedEmail = Guid.NewGuid().ToString().Replace("-", string.Empty) + "@test.com";
Console.WriteLine("Creating a new user with name '{0}'", generatedEmail);
var formParameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("UserName", generatedUserName),
new KeyValuePair<string, string>("Email", generatedEmail),
new KeyValuePair<string, string>("Password", "Password~1"),
new KeyValuePair<string, string>("ConfirmPassword", "Password~1"),
new KeyValuePair<string, string>("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Register")),
@ -297,24 +297,24 @@ 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}!", generatedUserName), responseContent, StringComparison.OrdinalIgnoreCase);
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", generatedUserName);
return generatedUserName;
Console.WriteLine("Successfully registered user '{0}' and signed in", generatedEmail);
return generatedEmail;
}
private void RegisterExistingUser(string userName)
private void RegisterExistingUser(string email)
{
Console.WriteLine("Trying to register a user with name '{0}' again", userName);
Console.WriteLine("Trying to register a user with name '{0}' again", email);
var response = httpClient.GetAsync("Account/Register").Result;
ThrowIfResponseStatusNotOk(response);
var responseContent = response.Content.ReadAsStringAsync().Result;
Console.WriteLine("Creating a new user with name '{0}'", userName);
Console.WriteLine("Creating a new user with name '{0}'", email);
var formParameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("UserName", userName),
new KeyValuePair<string, string>("Email", email),
new KeyValuePair<string, string>("Password", "Password~1"),
new KeyValuePair<string, string>("ConfirmPassword", "Password~1"),
new KeyValuePair<string, string>("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Register")),
@ -323,13 +323,13 @@ namespace E2ETests
var content = new FormUrlEncodedContent(formParameters.ToArray());
response = httpClient.PostAsync("Account/Register", content).Result;
responseContent = response.Content.ReadAsStringAsync().Result;
Assert.Contains(string.Format("Name {0} is already taken.", userName), responseContent, StringComparison.OrdinalIgnoreCase);
Console.WriteLine("Identity threw a valid exception that user '{0}' already exists in the system", userName);
Assert.Contains(string.Format("Name {0} is already taken.", email), responseContent, StringComparison.OrdinalIgnoreCase);
Console.WriteLine("Identity threw a valid exception that user '{0}' already exists in the system", email);
}
private void SignOutUser(string userName)
private void SignOutUser(string email)
{
Console.WriteLine("Signing out from '{0}''s session", userName);
Console.WriteLine("Signing out from '{0}''s session", email);
var response = httpClient.GetAsync(string.Empty).Result;
ThrowIfResponseStatusNotOk(response);
var responseContent = response.Content.ReadAsStringAsync().Result;
@ -352,7 +352,7 @@ namespace E2ETests
Assert.Contains("/Images/home-showcase.png", responseContent, StringComparison.OrdinalIgnoreCase);
//Verify cookie cleared on logout
Assert.Null(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application"));
Console.WriteLine("Successfully signed out of '{0}''s session", userName);
Console.WriteLine("Successfully signed out of '{0}''s session", email);
}
else
{
@ -362,15 +362,15 @@ namespace E2ETests
}
}
private void SignInWithInvalidPassword(string userName, string invalidPassword)
private void SignInWithInvalidPassword(string email, string invalidPassword)
{
var response = httpClient.GetAsync("Account/Login").Result;
ThrowIfResponseStatusNotOk(response);
var responseContent = response.Content.ReadAsStringAsync().Result;
Console.WriteLine("Signing in with user '{0}'", userName);
Console.WriteLine("Signing in with user '{0}'", email);
var formParameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("UserName", userName),
new KeyValuePair<string, string>("Email", email),
new KeyValuePair<string, string>("Password", invalidPassword),
new KeyValuePair<string, string>("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Login")),
};
@ -378,21 +378,21 @@ namespace E2ETests
var content = new FormUrlEncodedContent(formParameters.ToArray());
response = httpClient.PostAsync("Account/Login", content).Result;
responseContent = response.Content.ReadAsStringAsync().Result;
Assert.Contains("<div class=\"validation-summary-errors\"><ul><li>Invalid username or password.</li>", responseContent, StringComparison.OrdinalIgnoreCase);
Assert.Contains("<div class=\"validation-summary-errors text-danger\"><ul><li>Invalid login attempt.</li>", responseContent, StringComparison.OrdinalIgnoreCase);
//Verify cookie not sent
Assert.Null(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application"));
Console.WriteLine("Identity successfully prevented an invalid user login.");
}
private void SignInWithUser(string userName, string password)
private void SignInWithUser(string email, string password)
{
var response = httpClient.GetAsync("Account/Login").Result;
ThrowIfResponseStatusNotOk(response);
var responseContent = response.Content.ReadAsStringAsync().Result;
Console.WriteLine("Signing in with user '{0}'", userName);
Console.WriteLine("Signing in with user '{0}'", email);
var formParameters = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("UserName", userName),
new KeyValuePair<string, string>("Email", email),
new KeyValuePair<string, string>("Password", password),
new KeyValuePair<string, string>("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Login")),
};
@ -400,16 +400,16 @@ namespace E2ETests
var content = new FormUrlEncodedContent(formParameters.ToArray());
response = httpClient.PostAsync("Account/Login", content).Result;
responseContent = response.Content.ReadAsStringAsync().Result;
Assert.Contains(string.Format("Hello {0}!", userName), responseContent, StringComparison.OrdinalIgnoreCase);
Assert.Contains(string.Format("Hello {0}!", email), 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 signed in with user '{0}'", userName);
Console.WriteLine("Successfully signed in with user '{0}'", email);
}
private void ChangePassword(string userName)
private void ChangePassword(string email)
{
var response = httpClient.GetAsync("Account/Manage").Result;
var response = httpClient.GetAsync("Manage/ChangePassword").Result;
ThrowIfResponseStatusNotOk(response);
var responseContent = response.Content.ReadAsStringAsync().Result;
var formParameters = new List<KeyValuePair<string, string>>
@ -417,15 +417,15 @@ namespace E2ETests
new KeyValuePair<string, string>("OldPassword", "Password~1"),
new KeyValuePair<string, string>("NewPassword", "Password~2"),
new KeyValuePair<string, string>("ConfirmPassword", "Password~2"),
new KeyValuePair<string, string>("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Account/Manage")),
new KeyValuePair<string, string>("__RequestVerificationToken", HtmlDOMHelper.RetrieveAntiForgeryToken(responseContent, "/Manage/ChangePassword")),
};
var content = new FormUrlEncodedContent(formParameters.ToArray());
response = httpClient.PostAsync("Account/Manage", content).Result;
response = httpClient.PostAsync("Manage/ChangePassword", content).Result;
responseContent = response.Content.ReadAsStringAsync().Result;
Assert.Contains("Your password has been changed.", responseContent, StringComparison.OrdinalIgnoreCase);
Assert.NotNull(httpClientHandler.CookieContainer.GetCookies(new Uri(ApplicationBaseUrl)).GetCookieWithName(".AspNet.Microsoft.AspNet.Identity.Application"));
Console.WriteLine("Successfully changed the password for user '{0}'", userName);
Console.WriteLine("Successfully changed the password for user '{0}'", email);
}
private string CreateAlbum()