diff --git a/AspnetIdentitySample/AspnetIdentitySample.csproj b/AspnetIdentitySample/AspnetIdentitySample.csproj index 8b1a4fa..5c848d8 100644 --- a/AspnetIdentitySample/AspnetIdentitySample.csproj +++ b/AspnetIdentitySample/AspnetIdentitySample.csproj @@ -252,7 +252,9 @@ - + + Designer + Web.config @@ -276,6 +278,9 @@ + + + diff --git a/AspnetIdentitySample/Controllers/AccountController.cs b/AspnetIdentitySample/Controllers/AccountController.cs index 264f858..664937a 100644 --- a/AspnetIdentitySample/Controllers/AccountController.cs +++ b/AspnetIdentitySample/Controllers/AccountController.cs @@ -10,6 +10,8 @@ using Microsoft.AspNet.Identity.Owin; using Microsoft.Owin.Security; using AspnetIdentitySample.Models; +using System.Text; +using System.Security.Cryptography; namespace AspnetIdentitySample.Controllers { @@ -91,6 +93,7 @@ public async Task Register(RegisterViewModel model) var user = new MyUser(); user.UserName = model.UserName; user.HomeTown = model.HomeTown; + user.Email = model.Email; var result = await IdentityManager.Users.CreateLocalUserAsync(user, model.Password); if (result.Success) { @@ -252,6 +255,7 @@ public async Task ExternalLoginConfirmation(ExternalLoginConfirmat // Get the information about the user from the external login provider var user = new MyUser(); user.UserName = model.UserName; + user.Email = model.Email; IdentityResult result = await IdentityManager.Authentication.CreateAndSignInExternalUserAsync(AuthenticationManager,user); if (result.Success) { @@ -304,6 +308,123 @@ public ActionResult RemoveAccountList() }).Result; } + // + // GET: /Account/BeforePasswordReset + [AllowAnonymous] + public ActionResult ForgotPassword() + { + return View(); + } + + // + // POST: /Account/BeforePasswordReset + + //http://stackoverflow.com/a/698879/208922 + + [AllowAnonymous] + [HttpPost] + [ValidateAntiForgeryToken] + public async Task ForgotPassword(BeforePasswordResetViewModel model) + { + string message = null; + //the token is valid for one day + var until = DateTime.Now.AddDays(1); + //We find the user, as the token can not generate the e-mail address, + //but the name should be. + var db = new MyDbContext(); + var user = db.Users.SingleOrDefault(x => x.Email == model.Email); + + if (null != user) + { + var token = new StringBuilder(); + + //Prepare a 10-character random text + using (RNGCryptoServiceProvider + rngCsp = new RNGCryptoServiceProvider()) + { + var data = new byte[4]; + for (int i = 0; i < 10; i++) + { + //filled with an array of random numbers + rngCsp.GetBytes(data); + //this is converted into a character from A to Z + var randomchar = Convert.ToChar( + //produce a random number + //between 0 and 25 + BitConverter.ToUInt32(data, 0) % 26 + //Convert.ToInt32('A')==65 + + 65 + ); + token.Append(randomchar); + } + } + //This will be the password change identifier + //that the user will be sent out + var tokenid = token.ToString(); + + //Generating a token + var result = await IdentityManager + .Passwords + .GenerateResetPasswordTokenAsync( + tokenid, + user.UserName, + until + ); + + if (result.Success) + { + //send the email + //... + } + } + message = "We have sent a password reset request if the email is verified."; + return RedirectToAction("PasswordReset", new { token = string.Empty, message = message }); + } + + // + // GET: /Account/PasswordReset + [AllowAnonymous] + public ActionResult PasswordReset(string message) + { + ViewBag.StatusMessage = message ?? ""; + return View(); + } + + // + // POST: /Account/PasswordReset + [AllowAnonymous] + [HttpPost] + [ValidateAntiForgeryToken] + public async Task PasswordReset(PasswordResetViewModel model) + { + if (ModelState.IsValid) + { + string message = null; + //reset the password + var result = await IdentityManager.Passwords.ResetPasswordAsync(model.Token, model.Password); + if (result.Success) + { + message = "The password has been reset."; + return RedirectToAction("PasswordResetCompleted", new { message = message }); + } + else + { + AddErrors(result); + } + } + // If we got this far, something failed, redisplay form + return View(model); + } + + // + // GET: /Account/PasswordResetCompleted + [AllowAnonymous] + public ActionResult PasswordResetCompleted(string message) + { + ViewBag.StatusMessage = message ?? ""; + return View(); + } + protected override void Dispose(bool disposing) { if (disposing && IdentityManager != null) @@ -353,4 +474,4 @@ public override void ExecuteResult(ControllerContext context) } #endregion } -} \ No newline at end of file +} diff --git a/AspnetIdentitySample/Models/AccountViewModels.cs b/AspnetIdentitySample/Models/AccountViewModels.cs index bf15041..bb9346a 100644 --- a/AspnetIdentitySample/Models/AccountViewModels.cs +++ b/AspnetIdentitySample/Models/AccountViewModels.cs @@ -11,6 +11,12 @@ public class ExternalLoginConfirmationViewModel [Required] public string LoginProvider { get; set; } + + [Required] + [Display(Name = "E-mail address")] + [EmailAddress] + public string Email { get; set; } + } public class ManageUserViewModel @@ -65,5 +71,38 @@ public class RegisterViewModel public string ConfirmPassword { get; set; } public string HomeTown { get; set; } + + [Required] + [Display(Name = "E-mail address")] + [EmailAddress] + public string Email { get; set; } + } + + public class BeforePasswordResetViewModel + { + [Required] + [Display(Name = "E-mail address")] + [EmailAddress] + public string Email { get; set; } + + } + + public class PasswordResetViewModel + { + [Required] + [Display(Name = "Token")] + public string Token { 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; } + } } diff --git a/AspnetIdentitySample/Models/AppModel.cs b/AspnetIdentitySample/Models/AppModel.cs index d273141..3e6c23d 100644 --- a/AspnetIdentitySample/Models/AppModel.cs +++ b/AspnetIdentitySample/Models/AppModel.cs @@ -10,6 +10,7 @@ namespace AspnetIdentitySample.Models public class MyUser : User { public string HomeTown { get; set; } + public string Email { get; set; } } public class MyDbContext : IdentityDbContextWithCustomUser { diff --git a/AspnetIdentitySample/Views/Account/ExternalLoginConfirmation.cshtml b/AspnetIdentitySample/Views/Account/ExternalLoginConfirmation.cshtml index 60a4bb3..cf1bf71 100644 --- a/AspnetIdentitySample/Views/Account/ExternalLoginConfirmation.cshtml +++ b/AspnetIdentitySample/Views/Account/ExternalLoginConfirmation.cshtml @@ -23,6 +23,10 @@ @Html.TextBoxFor(m => m.UserName) @Html.ValidationMessageFor(m => m.UserName)
+ @Html.LabelFor(m => m.Email) + @Html.TextBoxFor(m => m.Email) + @Html.ValidationMessageFor(m => m.Email) +
@Html.HiddenFor(m => m.LoginProvider) diff --git a/AspnetIdentitySample/Views/Account/ForgotPassword.cshtml b/AspnetIdentitySample/Views/Account/ForgotPassword.cshtml new file mode 100644 index 0000000..eb161d0 --- /dev/null +++ b/AspnetIdentitySample/Views/Account/ForgotPassword.cshtml @@ -0,0 +1,34 @@ +@model AspnetIdentitySample.Models.BeforePasswordResetViewModel + +@{ + ViewBag.Title = "Forgot your password?"; +} + +
+

@ViewBag.Title.

+
+ +@using (Html.BeginForm()) { + @Html.AntiForgeryToken() + @Html.ValidationSummary(true) + +
+ Forgot password + +
+ @Html.LabelFor(model => model.Email, new { @class = "control-label" }) +
+ @Html.EditorFor(model => model.Email) + @Html.ValidationMessageFor(model => model.Email, null, new { @class = "help-inline" }) +
+
+ +
+ +
+
+} + +@section Scripts { + @Scripts.Render("~/bundles/jqueryval") +} diff --git a/AspnetIdentitySample/Views/Account/Login.cshtml b/AspnetIdentitySample/Views/Account/Login.cshtml index f5ec486..f13518b 100644 --- a/AspnetIdentitySample/Views/Account/Login.cshtml +++ b/AspnetIdentitySample/Views/Account/Login.cshtml @@ -45,6 +45,9 @@

@Html.ActionLink("Register", "Register") if you don't have a local account.

+

+ @Html.ActionLink("Forgot password?", "ForgotPassword") +

} diff --git a/AspnetIdentitySample/Views/Account/PasswordReset.cshtml b/AspnetIdentitySample/Views/Account/PasswordReset.cshtml new file mode 100644 index 0000000..87d8701 --- /dev/null +++ b/AspnetIdentitySample/Views/Account/PasswordReset.cshtml @@ -0,0 +1,53 @@ +@model AspnetIdentitySample.Models.PasswordResetViewModel + +@{ + ViewBag.Title = "PasswordReset"; +} + +
+

@ViewBag.Title.

+
+ +

@ViewBag.StatusMessage

+ +@using (Html.BeginForm()) { + @Html.AntiForgeryToken() + @Html.ValidationSummary(true) + +
+ PasswordResetViewModel + +
+ @Html.LabelFor(model => model.Token, new { @class = "control-label" }) +
+ @Html.EditorFor(model => model.Token) + @Html.ValidationMessageFor(model => model.Token, null, new { @class = "help-inline" }) +
+
+ +
+ @Html.LabelFor(model => model.Password, new { @class = "control-label" }) +
+ @Html.EditorFor(model => model.Password) + @Html.ValidationMessageFor(model => model.Password, null, new { @class = "help-inline" }) +
+
+ +
+ @Html.LabelFor(model => model.ConfirmPassword, new { @class = "control-label" }) +
+ @Html.EditorFor(model => model.ConfirmPassword) + @Html.ValidationMessageFor(model => model.ConfirmPassword, null, new { @class = "help-inline" }) +
+
+ +
+ +
+
+} + +@section Scripts { + @Scripts.Render("~/bundles/jqueryval") +} + diff --git a/AspnetIdentitySample/Views/Account/PasswordResetCompleted.cshtml b/AspnetIdentitySample/Views/Account/PasswordResetCompleted.cshtml new file mode 100644 index 0000000..e9b01f6 --- /dev/null +++ b/AspnetIdentitySample/Views/Account/PasswordResetCompleted.cshtml @@ -0,0 +1,7 @@ +@{ + ViewBag.Title = "Register"; +} +
+

@ViewBag.Title.

+
+

@ViewBag.StatusMessage

diff --git a/AspnetIdentitySample/Views/Account/Register.cshtml b/AspnetIdentitySample/Views/Account/Register.cshtml index 40c86c0..36991b6 100644 --- a/AspnetIdentitySample/Views/Account/Register.cshtml +++ b/AspnetIdentitySample/Views/Account/Register.cshtml @@ -32,6 +32,13 @@ @Html.PasswordFor(m => m.ConfirmPassword) + +
+ @Html.LabelFor(m => m.Email, new { @class = "control-label" }) +
+ @Html.TextBoxFor(m => m.Email) +
+
@Html.LabelFor(m => m.HomeTown, new { @class = "control-label" })