Last active
August 14, 2022 21:16
-
-
Save miketrebilcock/5195817 to your computer and use it in GitHub Desktop.
Password Resetting in Servicestack
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using CORE.Kernel.ExtensionMethods; | |
using CORE.Models; | |
using ServiceStack.Common; | |
using ServiceStack.Common.Web; | |
using ServiceStack.FluentValidation; | |
using ServiceStack.ServiceHost; | |
using ServiceStack.ServiceInterface; | |
using ServiceStack.ServiceInterface.Auth; | |
using ServiceStack.ServiceInterface.ServiceModel; | |
using ServiceStack.ServiceInterface.Validation; | |
using ServiceStack.WebHost.Endpoints; | |
using System; | |
using System.Collections.Generic; | |
using System.Runtime.Serialization; | |
using Validation; | |
namespace CORE | |
{ | |
[Route("/passwordreset")] | |
[Route("/api/auth/passwordreset")] | |
public class PasswordResetRequest | |
{ | |
public string Email { get; set; } | |
public string Id { get; set; } | |
public string newpassword { get; set; } | |
} | |
public class passwordResetResponse | |
{ | |
public string id { get; set; } | |
public bool passwordChanged { get; set; } | |
public bool valid { get; set; } | |
} | |
[DefaultRequest(typeof(PasswordResetRequest))] | |
internal class PasswordResetService : AppServiceBase | |
{ | |
public IUserAuthRepository UserAuthRepo { get; set; } | |
//Called when a password reset link is clicked. | |
public object Get(PasswordResetRequest request) | |
{ | |
if (!ValidationLibrary.validate(request.Id, ValidationLibrary.GUID)) | |
return new passwordResetResponse() { valid = false }; | |
//Display Change Password Screen | |
var resetrequest = Cache.Get<PasswordResetRequest>(request.Id); | |
var response = new passwordResetResponse(); | |
response.valid = !(resetrequest == null); | |
response.id = request.Id; | |
return response; | |
} | |
//Called when the password request is initiated. | |
public string Post(PasswordResetRequest request) | |
{ | |
if (request.Email == null) | |
{ | |
this.Response.StatusCode = 400; | |
return "You must provide an email address."; | |
} | |
if (UserAuthRepo.GetUserAuthByUserName(request.Email) == null) | |
{ | |
this.Response.StatusCode = 400; | |
return "Email Address not registered."; | |
} | |
request.Id = Guid.NewGuid().ToString(); | |
Cache.Add<PasswordResetRequest>(request.Id, request, new TimeSpan(1, 0, 0)); | |
var email = new Email(); | |
email.to = new List<EmailAddress>(); | |
email.to.Add(new EmailAddress() { email = request.Email }); | |
email.from = new EmailAddress() { email = "[email protected]" }; | |
email.subject = "Password Reset"; | |
email.body = "<p>Uh Oh! Have you forgotten your password?</p><p>Somebody asked for a password reminder, if it was you click this link to reset it now.</p><p><a href=\""+AppConfig.getAppConfig().appURL+"/passwordreset?id=" + request.Id + "\">"+AppConfig.getAppConfig().appURL+"/passwordreset?id=" + request.Id + "</a></p><p>This link is valid for 1 hour</p><p>If you've remember your password, or you didn't request a reset, you can ignore this email</p><p>Regards</p><p>Cornwall College</p>"; | |
email.TrackingId = request.Id; | |
PublishMessage<Email>(email); | |
return "An email has been sent with a link to reset your password."; | |
} | |
public passwordResetResponse Put(PasswordResetRequest request) | |
{ | |
if (!ValidationLibrary.validate(request.Id, ValidationLibrary.GUID)) | |
return new passwordResetResponse() { valid = false }; | |
if (!ValidationLibrary.validate(request.newpassword, ValidationLibrary.UserPassword)) | |
return new passwordResetResponse() { valid = false }; | |
//Changes the password | |
var resetrequest = Cache.Get<PasswordResetRequest>(request.Id); | |
var response = new passwordResetResponse(); | |
if (resetrequest == null) | |
{ | |
response.valid = false; | |
return response; | |
} | |
else | |
{ | |
response.valid = true; | |
} | |
var existingUser = UserAuthRepo.GetUserAuthByUserName(resetrequest.Email); | |
if (existingUser == null) | |
{ | |
return new passwordResetResponse() { valid = false }; | |
} | |
UserAuthRepo.UpdateUserAuth(existingUser, existingUser, request.newpassword); | |
response.passwordChanged = true; | |
Cache.Remove(resetrequest.Id); | |
return response; | |
} | |
} | |
} |
Just my 2 pence: Further to this, on Line 69-71, I wouldn't return an error if it's an unknown email address as this leaves you open to brute force "find a valid email address" attacks - return should be a success and on the client give a message like "If you're a valid user you'll receive an email..."
Thanks for idea of using cache to store reset request
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There is a security issue here!
Line 103 should be:
if (resetrequest == null || resetrequest.Email != request.Email)
otherwise it is possible to ask a password reset from one user and then reset the password of another user. Nice!