Skip to content

Commit b9a7c7a

Browse files
committed
feat: add reset password flow
1 parent c79055a commit b9a7c7a

8 files changed

Lines changed: 149 additions & 2 deletions

File tree

SgfDevs/Controllers/AccountController.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,34 @@ public async Task<IActionResult> ForgotPassword(ForgotPasswordModel model)
224224
return Redirect("/forgotten-password");
225225
}
226226

227+
[HttpPost]
228+
public async Task<IActionResult> ResetPassword(ResetPasswordModel model)
229+
{
230+
if (!ModelState.IsValid)
231+
return CurrentUmbracoPage();
232+
233+
var member = await _memberManager.FindByIdAsync(model.MemberId);
234+
if (member == null)
235+
{
236+
ModelState.AddModelError(string.Empty, "This reset link is invalid or has expired.");
237+
return CurrentUmbracoPage();
238+
}
239+
240+
var result = await _memberManager.ResetPasswordAsync(member, model.Token, model.Password);
241+
if (!result.Succeeded)
242+
{
243+
foreach (var error in result.Errors)
244+
{
245+
ModelState.AddModelError(string.Empty, error.Description);
246+
}
247+
248+
return CurrentUmbracoPage();
249+
}
250+
251+
TempData["LoginMessage"] = "Your password has been updated. Please log in.";
252+
return Redirect("/login");
253+
}
254+
227255
[HttpPost]
228256
[UmbracoMemberAuthorize]
229257
public async Task<IActionResult> ProfileUpdate(MemberProfile profile)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace SGFDevs.Models;
2+
3+
public static class PasswordValidationRules
4+
{
5+
public const string Pattern = "^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$";
6+
public const string ErrorMessage = "You know the drill.. Password must contain at least one upper and lowercase letter, a numeric digit, a special character, and be at least 8 characters long.";
7+
}

SgfDevs/Models/RegisterModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ public class RegisterModel
2222

2323
[Required]
2424
[DataType(DataType.Password)]
25-
[RegularExpression("^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$", ErrorMessage ="You know the drill.. Password must contain at least one upper and lowercase letter, a numeric digit, a special character, and be at least 8 characters long.")]
25+
[RegularExpression(PasswordValidationRules.Pattern, ErrorMessage = PasswordValidationRules.ErrorMessage)]
2626
public string Password { get; set; }
2727

2828
[Required]
2929
[Display(Name = "Null Check")]
3030
[RegularExpression("SGF|sgf", ErrorMessage = "Better luck next time.")]
3131
public string ChallengeQuestion { get; set; }
32-
}
32+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace SGFDevs.Models;
4+
5+
public class ResetPasswordModel
6+
{
7+
[Required]
8+
public string MemberId { get; set; }
9+
10+
[Required]
11+
public string Token { get; set; }
12+
13+
[Required]
14+
[DataType(DataType.Password)]
15+
[RegularExpression(PasswordValidationRules.Pattern, ErrorMessage = PasswordValidationRules.ErrorMessage)]
16+
public string Password { get; set; }
17+
18+
[Required]
19+
[DataType(DataType.Password)]
20+
[Compare(nameof(Password), ErrorMessage = "Passwords do not match.")]
21+
[Display(Name = "Confirm Password")]
22+
public string ConfirmPassword { get; set; }
23+
}

SgfDevs/Views/Components/Login/Default.cshtml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44

55
<div class="container">
6+
@if (TempData["LoginMessage"] is string loginMessage)
7+
{
8+
<p>@loginMessage</p>
9+
}
610
<p>@ViewData["Login"]</p>
711
<p>@ViewData["invalid"]</p>
812
<p>@ViewData["LoginInvalid"]</p>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<SGFDevs.Models.ResetPasswordModel>
2+
3+
<div class="container">
4+
<div class="form">
5+
<header>
6+
<h1>Reset your password</h1>
7+
<p>Choose a new password for your Springfield Devs account.</p>
8+
</header>
9+
10+
@if (string.IsNullOrWhiteSpace(Model.MemberId) || string.IsNullOrWhiteSpace(Model.Token))
11+
{
12+
<p>This reset link is invalid or has expired.</p>
13+
<p><a href="/forgotten-password">Request a new reset link</a></p>
14+
}
15+
else
16+
{
17+
@using (Html.BeginUmbracoForm("ResetPassword", "Account", FormMethod.Post))
18+
{
19+
@Html.ValidationSummary(true)
20+
<input asp-for="MemberId" type="hidden" />
21+
<input asp-for="Token" type="hidden" />
22+
23+
<div class="field">
24+
<label asp-for="Password"></label>
25+
<input asp-for="Password" class="form-control" />
26+
<span asp-validation-for="Password" class="text-danger"></span>
27+
</div>
28+
29+
<div class="field">
30+
<label asp-for="ConfirmPassword"></label>
31+
<input asp-for="ConfirmPassword" class="form-control" />
32+
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
33+
</div>
34+
35+
<footer>
36+
<button class="button tall wide" type="submit">Update password</button>
37+
<p><a href="/login">Back to login</a></p>
38+
</footer>
39+
}
40+
}
41+
</div>
42+
</div>
43+
44+
<div class="mt_75"></div>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Microsoft.AspNetCore.Mvc;
2+
using SGFDevs.Models;
3+
4+
namespace SGFDevs.Views.Components.ResetPassword;
5+
6+
public class ResetPasswordViewComponent : ViewComponent
7+
{
8+
public IViewComponentResult Invoke(string memberId, string token)
9+
{
10+
return View(new ResetPasswordModel
11+
{
12+
MemberId = memberId,
13+
Token = token,
14+
});
15+
}
16+
}

SgfDevs/Views/ResetPassword.cshtml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
@using Umbraco.Cms.Web.Common.PublishedModels;
2+
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<ContentModels.ResetPassword>
3+
@using ContentModels = Umbraco.Cms.Web.Common.PublishedModels;
4+
5+
@{
6+
var memberId = Context.Request.Query["memberId"].ToString();
7+
var token = Context.Request.Query["token"].ToString();
8+
}
9+
10+
@section Head
11+
{
12+
<style>
13+
.validation-summary-errors {
14+
color: red;
15+
font-weight: bold;
16+
}
17+
</style>
18+
}
19+
20+
@section Scripts {
21+
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js"></script>
22+
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"></script>
23+
}
24+
25+
@await Component.InvokeAsync("ResetPassword", new { memberId, token })

0 commit comments

Comments
 (0)