11using System ;
2+ using System . Collections . Generic ;
23using System . Linq ;
34using System . Threading . Tasks ;
4- using J2N . Collections . Generic ;
55using Microsoft . AspNetCore . Mvc ;
6+ using Microsoft . AspNetCore . WebUtilities ;
7+ using Microsoft . Extensions . Logging ;
8+ using Microsoft . Extensions . Options ;
69using SGFDevs . Models ;
710using SGFDevs . ViewModels ;
11+ using Umbraco . Cms . Core . Configuration . Models ;
812using Umbraco . Cms . Core ;
913using Umbraco . Cms . Core . Cache ;
1014using Umbraco . Cms . Core . Logging ;
15+ using Umbraco . Cms . Core . Mail ;
16+ using Umbraco . Cms . Core . Models . Email ;
17+ using Umbraco . Cms . Core . Models . PublishedContent ;
1118using Umbraco . Cms . Core . Routing ;
1219using Umbraco . Cms . Core . Security ;
1320using Umbraco . Cms . Core . Services ;
1421using Umbraco . Cms . Core . Web ;
1522using Umbraco . Cms . Infrastructure . Persistence ;
23+ using Umbraco . Cms . Web . Common ;
1624using Umbraco . Cms . Web . Common . Models ;
1725using Umbraco . Cms . Web . Common . Filters ;
1826using Umbraco . Cms . Web . Common . Security ;
1927using Umbraco . Cms . Web . Website . Controllers ;
28+ using Umbraco . Extensions ;
2029
2130namespace SGFDevs . Controllers ;
2231
2332[ AutoValidateAntiforgeryToken ]
2433public class AccountController : SurfaceController
2534{
26- private IMemberSignInManager _memberSignInManager ;
27- private IMemberManager _memberManager ;
28- private IMemberService _memberService ;
35+ private readonly IMemberSignInManager _memberSignInManager ;
36+ private readonly IMemberManager _memberManager ;
37+ private readonly IMemberService _memberService ;
38+ private readonly UmbracoHelper _umbracoHelper ;
39+ private readonly IEmailSender _emailSender ;
40+ private readonly IOptions < GlobalSettings > _globalSettings ;
41+ private readonly ILogger < AccountController > _logger ;
2942
3043 public AccountController (
3144 IUmbracoContextAccessor umbracoContextAccessor ,
@@ -36,12 +49,20 @@ public AccountController(
3649 IPublishedUrlProvider publishedUrlProvider ,
3750 IMemberSignInManager memberSignInManager ,
3851 IMemberManager memberManager ,
39- IMemberService memberService
52+ IMemberService memberService ,
53+ UmbracoHelper umbracoHelper ,
54+ IEmailSender emailSender ,
55+ IOptions < GlobalSettings > globalSettings ,
56+ ILogger < AccountController > logger
4057 ) : base ( umbracoContextAccessor , databaseFactory , services , appCaches , profilingLogger , publishedUrlProvider )
4158 {
4259 _memberSignInManager = memberSignInManager ;
4360 _memberManager = memberManager ;
4461 _memberService = memberService ;
62+ _umbracoHelper = umbracoHelper ;
63+ _emailSender = emailSender ;
64+ _globalSettings = globalSettings ;
65+ _logger = logger ;
4566 }
4667
4768 [ HttpPost ]
@@ -136,6 +157,101 @@ public async Task<IActionResult> Register(RegisterModel model)
136157 return CurrentUmbracoPage ( ) ;
137158 }
138159
160+ [ HttpPost ]
161+ public async Task < IActionResult > ForgotPassword ( ForgotPasswordModel model )
162+ {
163+ if ( ! ModelState . IsValid )
164+ return CurrentUmbracoPage ( ) ;
165+
166+ if ( _emailSender . CanSendRequiredEmail ( ) == false )
167+ {
168+ ModelState . AddModelError ( string . Empty , "Password reset is unavailable right now." ) ;
169+ return CurrentUmbracoPage ( ) ;
170+ }
171+
172+ var fromAddress = _globalSettings . Value . Smtp ? . From ;
173+ if ( string . IsNullOrWhiteSpace ( fromAddress ) )
174+ {
175+ ModelState . AddModelError ( string . Empty , "Password reset is unavailable right now." ) ;
176+ return CurrentUmbracoPage ( ) ;
177+ }
178+
179+ var resetPage = _umbracoHelper
180+ . ContentAtRoot ( )
181+ . SelectMany ( root => root . DescendantsOrSelf ( ) )
182+ . FirstOrDefault ( content => content . ContentType . Alias == "resetPassword" ) ;
183+
184+ if ( resetPage == null )
185+ {
186+ _logger . LogError ( "Unable to locate the reset password page in content." ) ;
187+ ModelState . AddModelError ( string . Empty , "Password reset is unavailable right now." ) ;
188+ return CurrentUmbracoPage ( ) ;
189+ }
190+
191+ var resetPageUrl = resetPage . Url ( mode : UrlMode . Absolute ) ;
192+ if ( Uri . TryCreate ( resetPageUrl , UriKind . Absolute , out _ ) == false )
193+ {
194+ _logger . LogError ( "Unable to build an absolute URL for the reset password page." ) ;
195+ ModelState . AddModelError ( string . Empty , "Password reset is unavailable right now." ) ;
196+ return CurrentUmbracoPage ( ) ;
197+ }
198+
199+ var member = await _memberManager . FindByEmailAsync ( model . Email ) ;
200+ if ( member != null )
201+ {
202+ try
203+ {
204+ var token = await _memberManager . GeneratePasswordResetTokenAsync ( member ) ;
205+ var resetLink = QueryHelpers . AddQueryString ( resetPageUrl , new Dictionary < string , string >
206+ {
207+ [ "memberId" ] = member . Id ,
208+ [ "token" ] = token ,
209+ } ) ;
210+
211+ var subject = "Reset your Springfield Devs password" ;
212+ var body = $ "<p>Hi { member . Name } ,</p><p>Someone requested a password reset for your Springfield Devs account.</p><p>If that was you, use the link below to choose a new password:</p><p><a href=\" { resetLink } \" >Reset your password</a></p><p>If you did not request this, you can ignore this email.</p>";
213+ var emailMessage = new EmailMessage ( fromAddress , member . Email , subject , body , true ) ;
214+
215+ await _emailSender . SendAsync ( emailMessage , "PasswordReset" , true , _globalSettings . Value . Smtp ? . EmailExpiration ) ;
216+ }
217+ catch ( Exception ex )
218+ {
219+ _logger . LogError ( ex , "Failed to send a password reset email for member {MemberId}." , member . Id ) ;
220+ }
221+ }
222+
223+ TempData [ "ForgotPasswordMessage" ] = "If an account exists for that email, we sent a reset link." ;
224+ return Redirect ( "/forgotten-password" ) ;
225+ }
226+
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+
139255 [ HttpPost ]
140256 [ UmbracoMemberAuthorize ]
141257 public async Task < IActionResult > ProfileUpdate ( MemberProfile profile )
0 commit comments