Skip to content

Instantly share code, notes, and snippets.

@miketrebilcock
Last active August 14, 2022 21:16
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save miketrebilcock/5195817 to your computer and use it in GitHub Desktop.
Save miketrebilcock/5195817 to your computer and use it in GitHub Desktop.
Password Resetting in Servicestack
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 = "core@cornwall.ac.uk" };
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;
}
}
}
@PaulSnijders
Copy link

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!

@glaidler
Copy link

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..."

@GuerrillaCoder
Copy link

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